From 24974b07ba7fabcc9c93fbf5bcb7aa0ac6e69c04 Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 17 Apr 2018 22:03:20 +0200 Subject: Safer to pylint on Python 3 --- .pylintrc | 2 +- linter_plugin.py | 8 +++++--- setup.py | 6 ++---- tox.ini | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.pylintrc b/.pylintrc index 36d8c286f..43c45b04a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -22,7 +22,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins=linter_plugin +load-plugins= [MESSAGES CONTROL] diff --git a/linter_plugin.py b/linter_plugin.py index 4938755cf..85896a36b 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -9,6 +9,8 @@ from astroid import nodes def register(unused_linter): """Register this module as PyLint plugin.""" + pass + def _transform(cls): # fix the "no-member" error on instances of @@ -17,9 +19,9 @@ def _transform(cls): # TODO: this is too broad and applies to any tested class... - if cls.slots() is not None: - for slot in cls.slots(): - cls.locals[slot.value] = [nodes.EmptyNode()] + #if cls.slots() is not None: + # for slot in cls.slots(): + # cls.locals[slot.value] = [nodes.EmptyNode()] if cls.name == 'JSONObjectWithFields': # _fields is magically introduced by JSONObjectWithFieldsMeta diff --git a/setup.py b/setup.py index 3760fd35b..082df7070 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ import codecs import os import re -import sys from setuptools import setup from setuptools import find_packages @@ -52,14 +51,13 @@ install_requires = [ ] dev_extras = [ - # Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289 - 'astroid==1.3.5', + 'astroid', 'coverage', 'ipdb', 'pytest', 'pytest-cov', 'pytest-xdist', - 'pylint==1.4.2', # upstream #248 + 'pylint>=1.8.4', 'tox', 'twine', 'wheel', diff --git a/tox.ini b/tox.ini index 38d4e6ae1..140b7b65d 100644 --- a/tox.ini +++ b/tox.ini @@ -117,6 +117,7 @@ basepython = python2.7 # continue, but tox return code will reflect previous error commands = {[base]install_packages} + pip install --upgrade astroid pylint # required! pylint --reports=n --rcfile=.pylintrc {[base]source_paths} [testenv:mypy] -- cgit v1.2.3 From 5300d7d71f77da2ec5eb82cefc7e5842932dd996 Mon Sep 17 00:00:00 2001 From: James Payne Date: Mon, 14 May 2018 16:33:30 +0000 Subject: Fix Pylint upgrade issues * Remove unsupported pylint disable options * star-args removed in Pylint 1.4.3 * abstract-class-little-used removed in Pylint 1.4.3 * Fixes new lint errors * Copy dummy-variable-rgx expression to new ignored-argument-names expression to ignore unused funtion arguments * Notable changes * Refactor to satisfy Pylint no-else-return warning * Fix Pylint inconsistent-return-statements warning * Refactor to satisfy consider-iterating-dictionary * Remove methods with only super call to satisfy useless-super-delegation * Refactor too-many-nested-statements where possible * Suppress type checked errors where member is dynamically added (notably derived from josepy.JSONObjectWithFields) * Remove None default of func parameter for ExitHandler and ErrorHandler Resolves #5973 --- .pylintrc | 3 +++ acme/acme/challenges.py | 5 ++-- acme/acme/challenges_test.py | 2 +- acme/acme/client.py | 28 ++++++++++------------ acme/acme/client_test.py | 18 +++++++------- acme/acme/crypto_util.py | 4 +--- acme/acme/jws_test.py | 2 +- acme/acme/messages.py | 19 +++++++-------- acme/acme/standalone.py | 4 ++-- acme/acme/test_util.py | 5 ++-- certbot-apache/certbot_apache/configurator.py | 23 +++++++++--------- certbot-apache/certbot_apache/display_ops.py | 7 +++--- certbot-apache/certbot_apache/obj.py | 5 ++-- certbot-apache/certbot_apache/override_centos.py | 2 +- certbot-apache/certbot_apache/override_debian.py | 5 ++-- certbot-apache/certbot_apache/override_gentoo.py | 2 +- certbot-apache/certbot_apache/parser.py | 28 ++++++++++------------ .../certbot_apache/tests/configurator_test.py | 10 ++++---- .../certbot_apache/tests/entrypoint_test.py | 4 ++-- certbot-apache/certbot_apache/tests/gentoo_test.py | 1 + .../certbot_apache/tests/http_01_test.py | 6 ++--- certbot-apache/certbot_apache/tests/parser_test.py | 1 + .../certbot_apache/tests/tls_sni_01_test.py | 4 ++-- certbot-apache/certbot_apache/tests/util.py | 3 +-- certbot-apache/certbot_apache/tls_sni_01.py | 2 +- .../configurators/common.py | 3 +-- .../configurators/nginx/common.py | 27 +++++++++++---------- .../certbot_compatibility_test/interfaces.py | 5 ++-- .../certbot_compatibility_test/test_driver.py | 7 +++--- .../certbot_compatibility_test/util.py | 2 +- .../certbot_compatibility_test/validator_test.py | 2 +- .../certbot_dns_cloudflare/dns_cloudflare.py | 8 +++---- .../certbot_dns_digitalocean/dns_digitalocean.py | 2 +- .../certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py | 2 +- .../certbot_dns_google/dns_google.py | 5 ++-- certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py | 15 ++++++------ .../certbot_dns_rfc2136/dns_rfc2136.py | 1 - .../certbot_dns_rfc2136/dns_rfc2136_test.py | 4 ++-- .../certbot_dns_route53/dns_route53.py | 6 ++--- certbot-nginx/certbot_nginx/configurator.py | 21 +++++++--------- certbot-nginx/certbot_nginx/display_ops.py | 2 +- certbot-nginx/certbot_nginx/http_01.py | 3 ++- certbot-nginx/certbot_nginx/obj.py | 10 ++++---- certbot-nginx/certbot_nginx/parser.py | 23 ++++++++---------- certbot-nginx/certbot_nginx/tests/parser_test.py | 3 --- certbot-nginx/certbot_nginx/tests/util.py | 11 ++++----- certbot/account.py | 6 ++--- certbot/auth_handler.py | 5 ++-- certbot/cert_manager.py | 7 +++--- certbot/cli.py | 10 ++++---- certbot/client.py | 19 +++++++-------- certbot/configuration.py | 2 +- certbot/display/ops.py | 11 ++++----- certbot/display/util.py | 14 ++++------- certbot/eff.py | 2 +- certbot/error_handler.py | 5 ++-- certbot/interfaces.py | 2 +- certbot/log.py | 3 +-- certbot/main.py | 24 +++++++++++++------ certbot/ocsp.py | 6 ++--- certbot/plugins/common.py | 5 ++-- certbot/plugins/disco.py | 13 +++++----- certbot/plugins/dns_common.py | 4 ++-- certbot/plugins/dns_common_lexicon.py | 1 + certbot/plugins/dns_common_test.py | 4 ---- certbot/plugins/manual_test.py | 3 +-- certbot/plugins/selection.py | 3 +-- certbot/plugins/standalone.py | 7 +++--- certbot/plugins/storage_test.py | 2 +- certbot/plugins/util.py | 11 ++++----- certbot/plugins/webroot.py | 2 +- certbot/reverter.py | 3 ++- certbot/storage.py | 13 ++++------ certbot/tests/acme_util.py | 3 +-- certbot/tests/auth_handler_test.py | 3 --- certbot/tests/cert_manager_test.py | 10 ++++---- certbot/tests/client_test.py | 1 - certbot/tests/crypto_util_test.py | 12 ---------- certbot/tests/display/ops_test.py | 4 ++-- certbot/tests/display/util_test.py | 9 ++++--- certbot/tests/eff_test.py | 2 +- certbot/tests/log_test.py | 6 ++--- certbot/tests/main_test.py | 5 ++-- certbot/tests/ocsp_test.py | 2 +- certbot/tests/renewal_test.py | 8 +------ certbot/tests/reporter_test.py | 2 +- certbot/tests/util.py | 15 ++++++------ certbot/tests/util_test.py | 4 ++-- certbot/util.py | 11 ++++----- letshelp-certbot/letshelp_certbot/apache_test.py | 2 +- 90 files changed, 293 insertions(+), 348 deletions(-) diff --git a/.pylintrc b/.pylintrc index 43c45b04a..3d6361dcb 100644 --- a/.pylintrc +++ b/.pylintrc @@ -189,6 +189,9 @@ init-import=no # not used). dummy-variables-rgx=(unused)?_.*|dummy +# A regular expression matching ignored argument variable names +ignored-argument-names=(unused)?_.*|dummy + # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index b2a4882eb..dee1b1765 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -95,6 +95,7 @@ class _TokenChallenge(Challenge): """ # TODO: check that path combined with uri does not go above # URI_ROOT_PATH! + # pylint: disable=unsupported-membership-test return b'..' not in self.token and b'/' not in self.token @@ -142,7 +143,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): @six.add_metaclass(abc.ABCMeta) class KeyAuthorizationChallenge(_TokenChallenge): - # pylint: disable=abstract-class-little-used,too-many-ancestors + # pylint: disable=too-many-ancestors """Challenge based on Key Authorization. :param response_cls: Subclass of `KeyAuthorizationChallengeResponse` @@ -174,7 +175,7 @@ class KeyAuthorizationChallenge(_TokenChallenge): :rtype: KeyAuthorizationChallengeResponse """ - return self.response_cls( + return self.response_cls( # pylint: disable=not-callable key_authorization=self.key_authorization(account_key)) @abc.abstractmethod diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 834d569aa..7e23b917a 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -6,7 +6,7 @@ import mock import OpenSSL import requests -from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error +from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error,relative-import from acme import errors from acme import test_util diff --git a/acme/acme/client.py b/acme/acme/client.py index bdc07fb1c..8b6fce138 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -6,16 +6,16 @@ from email.utils import parsedate_tz import heapq import logging import time +import re +import sys import six from six.moves import http_client # pylint: disable=import-error import josepy as jose import OpenSSL -import re -from requests_toolbelt.adapters.source import SourceAddressAdapter import requests from requests.adapters import HTTPAdapter -import sys +from requests_toolbelt.adapters.source import SourceAddressAdapter from acme import crypto_util from acme import errors @@ -134,7 +134,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), uri=response.headers.get('Location', uri)) - if identifier is not None and authzr.body.identifier != identifier: + if identifier is not None and authzr.body.identifier != identifier: # pylint: disable=no-member raise errors.UnexpectedUpdate(authzr) return authzr @@ -608,7 +608,7 @@ class ClientV2(ClientBase): response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) authorizations = [] - for url in body.authorizations: + for url in body.authorizations: # pylint: disable=not-an-iterable authorizations.append(self._authzr_from_response(self.net.get(url), uri=url)) return messages.OrderResource( body=body, @@ -640,7 +640,7 @@ class ClientV2(ClientBase): for url in orderr.body.authorizations: while datetime.datetime.now() < deadline: authzr = self._authzr_from_response(self.net.get(url), uri=url) - if authzr.body.status != messages.STATUS_PENDING: + if authzr.body.status != messages.STATUS_PENDING: # pylint: disable=no-member responses.append(authzr) break time.sleep(1) @@ -654,7 +654,7 @@ class ClientV2(ClientBase): for chall in authzr.body.challenges: if chall.error != None: failed.append(authzr) - if len(failed) > 0: + if failed: raise errors.ValidationError(failed) return orderr.update(authorizations=responses) @@ -778,8 +778,7 @@ class BackwardsCompatibleClientV2(object): for domain in dnsNames: authorizations.append(self.client.request_domain_challenges(domain)) return messages.OrderResource(authorizations=authorizations, csr_pem=csr_pem) - else: - return self.client.new_order(csr_pem) + return self.client.new_order(csr_pem) def finalize_order(self, orderr, deadline): """Finalize an order and obtain a certificate. @@ -812,12 +811,11 @@ class BackwardsCompatibleClientV2(object): 'certificate, please rerun the command for a new one.') cert = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode() + OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode() # pylint: disable=no-member chain = crypto_util.dump_pyopenssl_chain(chain).decode() return orderr.update(fullchain_pem=(cert + chain)) - else: - return self.client.finalize_order(orderr, deadline) + return self.client.finalize_order(orderr, deadline) def revoke(self, cert, rsn): """Revoke certificate. @@ -835,8 +833,7 @@ class BackwardsCompatibleClientV2(object): def _acme_version_from_directory(self, directory): if hasattr(directory, 'newNonce'): return 2 - else: - return 1 + return 1 class ClientNetwork(object): # pylint: disable=too-many-instance-attributes @@ -913,7 +910,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes if self.account is not None: kwargs["kid"] = self.account["uri"] kwargs["key"] = self.key - # pylint: disable=star-args return jws.JWS.sign(jobj, **kwargs).json_dumps(indent=2) @classmethod @@ -1057,7 +1053,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes if self.REPLAY_NONCE_HEADER in response.headers: nonce = response.headers[self.REPLAY_NONCE_HEADER] try: - decoded_nonce = jws.Header._fields['nonce'].decode(nonce) + decoded_nonce = jws.Header._fields['nonce'].decode(nonce) # pylint: disable=no-member except jose.DeserializationError as error: raise errors.BadNonce(nonce, error) logger.debug('Storing nonce: %s', nonce) diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index f3018ed81..50c792fc4 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -63,7 +63,7 @@ class ClientTestBase(unittest.TestCase): reg = messages.Registration( contact=self.contact, key=KEY.public_key()) the_arg = dict(reg) # type: Dict - self.new_reg = messages.NewRegistration(**the_arg) # pylint: disable=star-args + self.new_reg = messages.NewRegistration(**the_arg) self.regr = messages.RegistrationResource( body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') @@ -403,7 +403,7 @@ class ClientTest(ClientTestBase): def test_answer_challenge(self): self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() + self.response.json.return_value = self.challr.body.to_json() # pylint: disable=no-member chall_response = challenges.DNSResponse(validation=None) @@ -411,7 +411,7 @@ class ClientTest(ClientTestBase): # TODO: split here and separate test self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, - self.challr.body.update(uri='foo'), chall_response) + self.challr.body.update(uri='foo'), chall_response) # pylint: disable=no-member def test_answer_challenge_missing_next(self): self.assertRaises( @@ -465,7 +465,7 @@ class ClientTest(ClientTestBase): self.client.retry_after(response=self.response, default=10)) def test_poll(self): - self.response.json.return_value = self.authzr.body.to_json() + self.response.json.return_value = self.authzr.body.to_json() # pylint: disable=no-member self.assertEqual((self.authzr, self.response), self.client.poll(self.authzr)) @@ -694,7 +694,7 @@ class ClientV2Test(ClientTestBase): def test_new_account(self): self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() + self.response.json.return_value = self.regr.body.to_json() # pylint: disable=no-member self.response.headers['Location'] = self.regr.uri self.assertEqual(self.regr, self.client.new_account(self.new_reg)) @@ -743,7 +743,7 @@ class ClientV2Test(ClientTestBase): def test_poll_authorizations_failure(self): deadline = datetime.datetime(9999, 9, 9) - challb = self.challr.body.update(status=messages.STATUS_INVALID, + challb = self.challr.body.update(status=messages.STATUS_INVALID, # pylint: disable=no-member error=messages.Error.with_code('unauthorized')) authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) self.response.json.return_value = authz.to_json() @@ -799,7 +799,7 @@ class MockJSONDeSerializable(jose.JSONDeSerializable): return {'foo': self.value} @classmethod - def from_json(cls, value): + def from_json(cls, jobj): pass # pragma: no cover @@ -829,7 +829,7 @@ class ClientNetworkTest(unittest.TestCase): MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", acme_version=1) jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) # pylint: disable=no-member self.assertEqual(jws.signature.combined.nonce, b'Tg') def test_wrap_in_jws_v2(self): @@ -839,7 +839,7 @@ class ClientNetworkTest(unittest.TestCase): MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", acme_version=2) jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) # pylint: disable=no-member self.assertEqual(jws.signature.combined.nonce, b'Tg') self.assertEqual(jws.signature.combined.kid, u'acct-uri') self.assertEqual(jws.signature.combined.url, u'url') diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index ad914ca60..0b527b2b2 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -138,7 +138,6 @@ def probe_sni(name, host, port=443, timeout=300, host_protocol_agnostic = None if host == '::' or host == '0' else host try: - # pylint: disable=star-args logger.debug("Attempting to connect to %s:%d%s.", host_protocol_agnostic, port, " from {0}:{1}".format(source_address[0], source_address[1]) if \ socket_kwargs else "") @@ -194,8 +193,7 @@ def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req): if common_name is None: return sans - else: - return [common_name] + [d for d in sans if d != common_name] + return [common_name] + [d for d in sans if d != common_name] def _pyopenssl_cert_or_req_san(cert_or_req): """Get Subject Alternative Names from certificate or CSR using pyOpenSSL. diff --git a/acme/acme/jws_test.py b/acme/acme/jws_test.py index aa3ccb700..62b46b528 100644 --- a/acme/acme/jws_test.py +++ b/acme/acme/jws_test.py @@ -24,7 +24,7 @@ class HeaderTest(unittest.TestCase): def test_nonce_decoder(self): from acme.jws import Header - nonce_field = Header._fields['nonce'] + nonce_field = Header._fields['nonce'] # pylint: disable=no-member self.assertRaises( jose.DeserializationError, nonce_field.decode, self.wrong_nonce) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 03dbc3255..ec0829519 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -40,8 +40,7 @@ def is_acme_error(err): """Check if argument is an ACME error.""" if isinstance(err, Error) and (err.typ is not None): return (ERROR_PREFIX in err.typ) or (OLD_ERROR_PREFIX in err.typ) - else: - return False + return False @six.python_2_unicode_compatible @@ -96,6 +95,7 @@ class Error(jose.JSONObjectWithFields, errors.Error): code = str(self.typ).split(':')[-1] if code in ERROR_CODES: return code + return None def __str__(self): return b' :: '.join( @@ -110,18 +110,19 @@ class _Constant(jose.JSONDeSerializable, collections.Hashable): # type: ignore POSSIBLE_NAMES = NotImplemented def __init__(self, name): - self.POSSIBLE_NAMES[name] = self + super(_Constant, self).__init__() + self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation self.name = name def to_partial_json(self): return self.name @classmethod - def from_json(cls, value): - if value not in cls.POSSIBLE_NAMES: + def from_json(cls, jobj): + if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test raise jose.DeserializationError( '{0} not recognized'.format(cls.__name__)) - return cls.POSSIBLE_NAMES[value] + return cls.POSSIBLE_NAMES[jobj] # pylint: disable=unsubscriptable-object def __repr__(self): return '{0}({1})'.format(self.__class__.__name__, self.name) @@ -179,7 +180,6 @@ class Directory(jose.JSONDeSerializable): def __init__(self, **kwargs): kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items()) - # pylint: disable=star-args super(Directory.Meta, self).__init__(**kwargs) @property @@ -291,7 +291,7 @@ class Registration(ResourceBody): def _filter_contact(self, prefix): return tuple( - detail[len(prefix):] for detail in self.contact + detail[len(prefix):] for detail in self.contact # pylint: disable=not-an-iterable if detail.startswith(prefix)) @property @@ -363,7 +363,6 @@ class ChallengeBody(ResourceBody): def __init__(self, **kwargs): kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items()) - # pylint: disable=star-args super(ChallengeBody, self).__init__(**kwargs) def encode(self, name): @@ -446,7 +445,7 @@ class Authorization(ResourceBody): def resolved_combinations(self): """Combinations with challenges instead of indices.""" return tuple(tuple(self.challenges[idx] for idx in combo) - for combo in self.combinations) + for combo in self.combinations) # pylint: disable=not-an-iterable @Directory.register diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index a370501ee..7441008a0 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -82,7 +82,7 @@ class BaseDualNetworkedServers(object): kwargs["ipv6"] = ip_version new_address = (server_address[0],) + (port,) + server_address[2:] new_args = (new_address,) + remaining_args - server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args + server = ServerClass(*new_args, **kwargs) except socket.error: logger.debug("Failed to bind to %s:%s using %s", new_address[0], new_address[1], "IPv6" if ip_version else "IPv4") @@ -91,7 +91,7 @@ class BaseDualNetworkedServers(object): # If two servers are set up and port 0 was passed in, ensure we always # bind to the same port for both servers. port = server.socket.getsockname()[1] - if len(self.servers) == 0: + if not self.servers: raise socket.error("Could not bind to IPv4 or IPv6.") def serve_forever(self): diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py index 1a0b67056..f04829deb 100644 --- a/acme/acme/test_util.py +++ b/acme/acme/test_util.py @@ -4,8 +4,8 @@ """ import os -import pkg_resources import unittest +import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -92,5 +92,4 @@ def skip_unless(condition, reason): # pragma: no cover return unittest.skipUnless(condition, reason) elif condition: return lambda cls: cls - else: - return lambda cls: None + return lambda cls: None diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index bb82a9d3f..fe1192a39 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -3,12 +3,15 @@ import fnmatch import logging import os -import pkg_resources import re -import six import socket import time +from collections import defaultdict + +import pkg_resources +import six + import zope.component import zope.interface @@ -32,8 +35,6 @@ from certbot_apache import obj from certbot_apache import parser from certbot_apache import tls_sni_01 -from collections import defaultdict - logger = logging.getLogger(__name__) @@ -214,7 +215,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): '.'.join(str(i) for i in self.version)) if self.version < (2, 2): raise errors.NotSupportedError( - "Apache Version %s not supported.", str(self.version)) + "Apache Version {0} not supported.".format(str(self.version))) if not self._check_aug_version(): raise errors.NotSupportedError( @@ -245,7 +246,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): except (OSError, errors.LockError): logger.debug("Encountered error:", exc_info=True) raise errors.PluginError( - "Unable to lock %s", self.conf("server-root")) + "Unable to lock {0}".format(self.conf("server-root"))) def _check_aug_version(self): """ Checks that we have recent enough version of libaugeas. @@ -352,6 +353,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ if len(name.split(".")) == len(domain.split(".")): return fnmatch.fnmatch(name, domain) + return None def _choose_vhosts_wildcard(self, domain, create_ssl=True): @@ -678,7 +680,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if name: all_names.add(name) - if len(vhost_macro) > 0: + if vhost_macro: zope.component.getUtility(interfaces.IDisplay).notification( "Apache mod_macro seems to be in use in file(s):\n{0}" "\n\nUnfortunately mod_macro is not yet supported".format( @@ -1024,6 +1026,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Ugly but takes care of protocol def, eg: 1.1.1.1:443 https if listen.split(":")[-1].split(" ")[0] == port: return True + return None def prepare_https_modules(self, temp): """Helper method for prepare_server_https, taking care of enabling @@ -1170,8 +1173,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if fp.endswith(".conf"): return fp[:-(len(".conf"))] + self.conf("le_vhost_ext") - else: - return fp + self.conf("le_vhost_ext") + return fp + self.conf("le_vhost_ext") def _sift_rewrite_rule(self, line): """Decides whether a line should be copied to a SSL vhost. @@ -1391,8 +1393,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _remove_directives(self, vh_path, directives): for directive in directives: - while len(self.parser.find_dir(directive, None, - vh_path, False)) > 0: + while self.parser.find_dir(directive, None, vh_path, False): directive_path = self.parser.find_dir(directive, None, vh_path, False) self.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) diff --git a/certbot-apache/certbot_apache/display_ops.py b/certbot-apache/certbot_apache/display_ops.py index 097b84b96..be42282e9 100644 --- a/certbot-apache/certbot_apache/display_ops.py +++ b/certbot-apache/certbot_apache/display_ops.py @@ -26,7 +26,7 @@ def select_vhost_multiple(vhosts): return list() tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] # Remove the extra newline from the last entry - if len(tags_list): + if tags_list: tags_list[-1] = tags_list[-1][:-1] code, names = zope.component.getUtility(interfaces.IDisplay).checklist( "Which VirtualHosts would you like to install the wildcard certificate for?", @@ -62,8 +62,7 @@ def select_vhost(domain, vhosts): code, tag = _vhost_menu(domain, vhosts) if code == display_util.OK: return vhosts[tag] - else: - return None + return None def _vhost_menu(domain, vhosts): """Select an appropriate Apache Vhost. @@ -93,7 +92,7 @@ def _vhost_menu(domain, vhosts): for vhost in vhosts: if len(vhost.get_names()) == 1: disp_name = next(iter(vhost.get_names())) - elif len(vhost.get_names()) == 0: + elif not vhost.get_names(): disp_name = "" else: disp_name = "Multiple Names" diff --git a/certbot-apache/certbot_apache/obj.py b/certbot-apache/certbot_apache/obj.py index 290979f27..22abc85cd 100644 --- a/certbot-apache/certbot_apache/obj.py +++ b/certbot-apache/certbot_apache/obj.py @@ -26,7 +26,7 @@ class Addr(common.Addr): def __repr__(self): return "certbot_apache.obj.Addr(" + repr(self.tup) + ")" - def __hash__(self): + def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ if __eq__ or # __cmp__ is overridden. See https://bugs.python.org/issue2235 return super(Addr, self).__hash__() @@ -47,8 +47,7 @@ class Addr(common.Addr): return 0 elif self.get_addr() == "*": return 1 - else: - return 2 + return 2 def conflicts(self, addr): r"""Returns if address could conflict with correct function of self. diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index 0b6b12b96..5477fffad 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -56,5 +56,5 @@ class CentOSParser(parser.ApacheParser): def parse_sysconfig_var(self): """ Parses Apache CLI options from CentOS configuration file """ defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") - for k in defines.keys(): + for k in defines: self.variables[k] = defines[k] diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index 02dffc3f7..3ac596754 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -51,7 +51,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): """ if vhost.enabled: - return + return None enabled_path = ("%s/sites-enabled/%s" % (self.parser.root, @@ -68,7 +68,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): enabled_path) == vhost.filep: # Already in shape vhost.enabled = True - return + return None else: logger.warning( "Could not symlink %s to %s, got error: %s", enabled_path, @@ -81,6 +81,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): vhost.enabled = True logger.info("Enabling available site: %s", vhost.filep) self.save_notes += "Enabled site %s\n" % vhost.filep + return None def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index 165e44c96..437ec9c04 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -56,7 +56,7 @@ class GentooParser(parser.ApacheParser): """ Parses Apache CLI options from Gentoo configuration file """ defines = apache_util.parse_define_file(self.apacheconfig_filep, "APACHE2_OPTS") - for k in defines.keys(): + for k in defines: self.variables[k] = defines[k] def update_modules(self): diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 43878eda2..837e0eaeb 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -82,7 +82,7 @@ class ApacheParser(object): :param str inc_path: path of file to include """ - if len(self.find_dir(case_i("Include"), inc_path)) == 0: + if not self.find_dir(case_i("Include"), inc_path): logger.debug("Adding Include %s to %s", inc_path, get_aug_path(main_config)) self.add_dir( @@ -138,7 +138,7 @@ class ApacheParser(object): mods.add(os.path.basename(mod_filename)[:-2] + "c") else: logger.debug("Could not read LoadModule directive from " + - "Augeas path: {0}".format(match_name[6:])) + "Augeas path: %s", match_name[6:]) self.modules.update(mods) def update_runtime_variables(self): @@ -228,8 +228,8 @@ class ApacheParser(object): "Error running command %s for runtime parameters!%s", command, os.linesep) raise errors.MisconfigurationError( - "Error accessing loaded Apache parameters: %s", - command) + "Error accessing loaded Apache parameters: {0}".format( + command)) # Small errors that do not impede if proc.returncode != 0: logger.warning("Error in checking parameter list: %s", stderr) @@ -255,12 +255,12 @@ class ApacheParser(object): """ filtered = [] if args == 1: - for i in range(len(matches)): - if matches[i].endswith("/arg"): + for i, match in enumerate(matches): + if match.endswith("/arg"): filtered.append(matches[i][:-4]) else: - for i in range(len(matches)): - if matches[i].endswith("/arg[%d]" % args): + for i, match in enumerate(matches): + if match.endswith("/arg[%d]" % args): # Make sure we don't cause an IndexError (end of list) # Check to make sure arg + 1 doesn't exist if (i == (len(matches) - 1) or @@ -305,7 +305,7 @@ class ApacheParser(object): """ if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % (aug_conf_path, mod))) - if len(if_mods) == 0: + if not if_mods: self.aug.set("%s/IfModule[last() + 1]" % aug_conf_path, "") self.aug.set("%s/IfModule[last()]/arg" % aug_conf_path, mod) if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % @@ -569,9 +569,8 @@ class ApacheParser(object): if sys.version_info < (3, 6): # This strips off final /Z(?ms) return fnmatch.translate(clean_fn_match)[:-7] - else: # pragma: no cover - # Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z - return fnmatch.translate(clean_fn_match)[4:-3] + # Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z + return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover def parse_file(self, filepath): """Parse file with Augeas @@ -653,10 +652,7 @@ class ApacheParser(object): use_new = False else: use_new = True - if new_file_match == "*": - remove_old = True - else: - remove_old = False + remove_old = new_file_match == "*" except KeyError: use_new = True remove_old = False diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 23c1ee82b..f15175c9c 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -340,6 +340,7 @@ class MultipleVhostsTest(util.ApacheTest): """Mock method for parser.find_dir""" if directive == "Include" and argument.endswith("options-ssl-apache.conf"): return ["/path/to/whatever"] + return None # pragma: no cover mock_add = mock.MagicMock() self.config.parser.add_dir = mock_add @@ -451,8 +452,7 @@ class MultipleVhostsTest(util.ApacheTest): but an SSLCertificateKeyFile directive is missing.""" if "SSLCertificateFile" in args: return ["example/cert.pem"] - else: - return [] + return [] mock_find_dir = mock.MagicMock(return_value=[]) mock_find_dir.side_effect = side_effect @@ -1025,7 +1025,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access http_vh = self.config._get_http_vhost(ssl_vh) - self.assertTrue(http_vh.ssl == False) + self.assertFalse(http_vh.ssl) @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") @@ -1390,7 +1390,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access cases = {u"*.example.org": True, b"*.x.example.org": True, u"a.example.org": False, b"a.x.example.org": False} - for key in cases.keys(): + for key in cases: self.assertEqual(self.config._wildcard_domain(key), cases[key]) def test_choose_vhosts_wildcard(self): @@ -1514,7 +1514,7 @@ class AugeasVhostsTest(util.ApacheTest): def test_choosevhost_works(self): path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old,default.conf" chosen_vhost = self.config._create_vhost(path) - self.assertTrue(chosen_vhost == None or chosen_vhost.path == path) + self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) @mock.patch("certbot_apache.configurator.ApacheConfigurator._create_vhost") def test_get_vhost_continue(self, mock_vhost): diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/certbot_apache/tests/entrypoint_test.py index c04611465..6d85b0db2 100644 --- a/certbot-apache/certbot_apache/tests/entrypoint_test.py +++ b/certbot-apache/certbot_apache/tests/entrypoint_test.py @@ -14,7 +14,7 @@ class EntryPointTest(unittest.TestCase): def test_get_configurator(self): with mock.patch("certbot.util.get_os_info") as mock_info: - for distro in entrypoint.OVERRIDE_CLASSES.keys(): + for distro in entrypoint.OVERRIDE_CLASSES: mock_info.return_value = (distro, "whatever") self.assertEqual(entrypoint.get_configurator(), entrypoint.OVERRIDE_CLASSES[distro]) @@ -23,7 +23,7 @@ class EntryPointTest(unittest.TestCase): with mock.patch("certbot.util.get_os_info") as mock_info: mock_info.return_value = ("nonexistent", "irrelevant") with mock.patch("certbot.util.get_systemd_os_like") as mock_like: - for like in entrypoint.OVERRIDE_CLASSES.keys(): + for like in entrypoint.OVERRIDE_CLASSES: mock_like.return_value = [like] self.assertEqual(entrypoint.get_configurator(), entrypoint.OVERRIDE_CLASSES[like]) diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py index d32551267..f9950d736 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/certbot_apache/tests/gentoo_test.py @@ -113,6 +113,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): """Mock httpd process stdout""" if command == ['apache2ctl', 'modules']: return mod_val + return None # pragma: no cover mock_get.side_effect = mock_get_cfg self.config.parser.modules = set() diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py index 98bf412ae..489252dfd 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/certbot_apache/tests/http_01_test.py @@ -1,7 +1,7 @@ """Test for certbot_apache.http_01.""" -import mock import os import unittest +import mock from acme import challenges from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module @@ -19,7 +19,7 @@ NUM_ACHALLS = 3 class ApacheHttp01Test(util.ApacheTest): """Test for certbot_apache.http_01.ApacheHttp01.""" - def setUp(self, *args, **kwargs): + def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ super(ApacheHttp01Test, self).setUp(*args, **kwargs) self.account_key = self.rsa512jwk @@ -77,7 +77,7 @@ class ApacheHttp01Test(util.ApacheTest): calls = mock_enmod.call_args_list other_calls = [] for call in calls: - if "rewrite" != call[0][0]: + if call[0][0] != "rewrite": other_calls.append(call) # If these lists are equal, we never enabled mod_rewrite diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index 4496781c9..429baa7dd 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -234,6 +234,7 @@ class BasicParserTest(util.ParserTest): return inc_val elif cmd[-1] == "DUMP_MODULES": return mod_val + return None # pragma: no cover mock_cfg.side_effect = mock_get_vars diff --git a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py b/certbot-apache/certbot_apache/tests/tls_sni_01_test.py index 8cea97f04..536237914 100644 --- a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py +++ b/certbot-apache/certbot_apache/tests/tls_sni_01_test.py @@ -4,14 +4,14 @@ import unittest import mock +from six.moves import xrange # pylint: disable=redefined-builtin, import-error + from certbot import errors from certbot.plugins import common_test from certbot_apache import obj from certbot_apache.tests import util -from six.moves import xrange # pylint: disable=redefined-builtin, import-error - class TlsSniPerformTest(util.ApacheTest): """Test the ApacheTlsSni01 challenge.""" diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 6d3cfa109..57d01470f 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -115,8 +115,7 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc """Mock default vhost path""" if key == "vhost_root": return vhost_path - else: - return orig_os_constant(key) + return orig_os_constant(key) with mock.patch("certbot_apache.configurator.ApacheConfigurator.constant") as mock_cons: mock_cons.side_effect = mock_os_constant diff --git a/certbot-apache/certbot_apache/tls_sni_01.py b/certbot-apache/certbot_apache/tls_sni_01.py index 65230cdcb..432f99d69 100644 --- a/certbot-apache/certbot_apache/tls_sni_01.py +++ b/certbot-apache/certbot_apache/tls_sni_01.py @@ -135,7 +135,7 @@ class ApacheTlsSni01(common.TLSSNI01): return addrs for addr in vhost.addrs: - if "_default_" == addr.get_addr(): + if addr.get_addr() == "_default_": addrs.add(default_addr) else: addrs.add( diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index 2a800c1c2..a6d8eb9cd 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -88,8 +88,7 @@ class Proxy(object): """Returns the set of domain names that can be tested against""" if self._test_names: return self._test_names - else: - return {"example.com"} + return {"example.com"} def deploy_cert(self, domain, cert_path, key_path, chain_path=None, fullchain_path=None): diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index ed5cf750e..9caa8fc7e 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -19,10 +19,6 @@ class Proxy(configurators_common.Proxy): # pylint: disable=too-many-instance-attributes """A common base for Nginx test configurators""" - def __init__(self, args): - """Initializes the plugin with the given command line args""" - super(Proxy, self).__init__(args) - def load_config(self): """Loads the next configuration for the plugin to test""" config = super(Proxy, self).load_config() @@ -48,7 +44,7 @@ class Proxy(configurators_common.Proxy): def _prepare_configurator(self): """Prepares the Nginx plugin for testing""" - for k in constants.CLI_DEFAULTS.keys(): + for k in constants.CLI_DEFAULTS: setattr(self.le_config, "nginx_" + k, constants.os_constant(k)) conf = configuration.NamespaceConfig(self.le_config) @@ -75,12 +71,19 @@ def _get_names(config): all_names = set() for root, _dirs, files in os.walk(config): for this_file in files: - for line in open(os.path.join(root, this_file)): - if line.strip().startswith("server_name"): - names = line.partition("server_name")[2].rpartition(";")[0] - for n in names.split(): - # Filter out wildcards in both all_names and test_names - if not n.startswith("*."): - all_names.add(n) + update_names = _get_server_names(root, this_file) + all_names.update(update_names) non_ip_names = set(n for n in all_names if not util.IP_REGEX.match(n)) return all_names, non_ip_names + +def _get_server_names(root, filename): + """Returns all names in a config file path""" + all_names = set() + for line in open(os.path.join(root, filename)): + if line.strip().startswith("server_name"): + names = line.partition("server_name")[2].rpartition(";")[0] + for n in names.split(): + # Filter out wildcards in both all_names and test_names + if not n.startswith("*."): + all_names.add(n) + return all_names diff --git a/certbot-compatibility-test/certbot_compatibility_test/interfaces.py b/certbot-compatibility-test/certbot_compatibility_test/interfaces.py index 7d3daee09..0249c2aaa 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/interfaces.py +++ b/certbot-compatibility-test/certbot_compatibility_test/interfaces.py @@ -6,8 +6,9 @@ import certbot.interfaces # pylint: disable=no-self-argument,no-method-argument -class IPluginProxy(zope.interface.Interface): +class IPluginProxy(zope.interface.Interface): # pylint: disable=inherit-non-class """Wraps a Certbot plugin""" + http_port = zope.interface.Attribute( "The port to connect to on localhost for HTTP traffic") @@ -17,7 +18,7 @@ class IPluginProxy(zope.interface.Interface): def add_parser_arguments(cls, parser): """Adds command line arguments needed by the parser""" - def __init__(args): + def __init__(args): # pylint: disable=super-init-not-called """Initializes the plugin with the given command line args""" def cleanup_from_tests(): # type: ignore diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 2c6c917b3..088b42d03 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -240,9 +240,8 @@ def test_rollback(plugin, config, backup): if _dirs_are_unequal(config, backup): logger.error("*** Rollback failed for config `%s`", config) return False - else: - logger.info("Rollback succeeded") - return True + logger.info("Rollback succeeded") + return True def _create_backup(config, temp_dir): @@ -257,7 +256,7 @@ def _create_backup(config, temp_dir): def _dirs_are_unequal(dir1, dir2): """Returns True if dir1 and dir2 are unequal""" dircmps = [filecmp.dircmp(dir1, dir2)] - while len(dircmps): + while dircmps: dircmp = dircmps.pop() if dircmp.left_only or dircmp.right_only: logger.error("The following files and directories are only " diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index 4155944bd..07909dc65 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -35,7 +35,7 @@ def create_le_config(parent_dir): config["domains"] = None - return argparse.Namespace(**config) # pylint: disable=star-args + return argparse.Namespace(**config) def extract_configs(configs, parent_dir): diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py index d0552a756..c4a668c5e 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py @@ -1,6 +1,6 @@ """Tests for certbot_compatibility_test.validator.""" -import requests import unittest +import requests import mock import OpenSSL diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py index f1156642f..2877cfaa6 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py @@ -159,7 +159,7 @@ class _CloudflareClient(object): 'you have supplied valid Cloudflare API credentials.{2}' .format(code, e, ' ({0})'.format(hint) if hint else '')) - if len(zones) > 0: + if zones: zone_id = zones[0]['id'] logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name) return zone_id @@ -191,9 +191,9 @@ class _CloudflareClient(object): logger.debug('Encountered CloudFlareAPIError getting TXT record_id: %s', e) records = [] - if len(records) > 0: + if records: # Cleanup is returning the system to the state we found it. If, for some reason, # there are multiple matching records, we only delete one because we only added one. return records[0]['id'] - else: - logger.debug('Unable to find TXT record.') + logger.debug('Unable to find TXT record.') + return None diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py index 4bf279279..134fd6248 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py @@ -154,7 +154,7 @@ class _DigitalOceanClient(object): for guess in domain_name_guesses: matches = [domain for domain in domains if domain.name == guess] - if len(matches) > 0: + if matches: domain = matches[0] logger.debug('Found base domain for %s using name %s', domain_name, guess) return domain diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py index 982edfdd3..fb6bcf3bb 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py @@ -79,7 +79,7 @@ class _DNSMadeEasyLexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and str(e).startswith('404 Client Error: Not Found for url:'): - return + return None hint = None if str(e).startswith('403 Client Error: Forbidden for url:'): diff --git a/certbot-dns-google/certbot_dns_google/dns_google.py b/certbot-dns-google/certbot_dns_google/dns_google.py index c204cb0ca..cf3bd6861 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/dns_google.py @@ -274,7 +274,7 @@ class _GoogleClient(object): raise errors.PluginError('Encountered error finding managed zone: {0}' .format(e)) - if len(zones) > 0: + if zones: zone_id = zones[0]['id'] logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) return zone_id @@ -303,5 +303,4 @@ class _GoogleClient(object): if isinstance(content, bytes): return content.decode() - else: - return content + return content diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py index 28db126c1..7b53a2fa9 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py @@ -73,11 +73,10 @@ class _NS1LexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:') or \ str(e).startswith("400 Client Error: Bad Request for url:")): - return # Expected errors when zone name guess is wrong - else: - hint = None - if str(e).startswith('401 Client Error: Unauthorized for url:'): - hint = 'Is your API key correct?' - - return errors.PluginError('Error determining zone identifier: {0}.{1}' - .format(e, ' ({0})'.format(hint) if hint else '')) + return None # Expected errors when zone name guess is wrong + hint = None + if str(e).startswith('401 Client Error: Unauthorized for url:'): + hint = 'Is your API key correct?' + + return errors.PluginError('Error determining zone identifier: {0}.{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index b8c01cdd3..f985c9bf4 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -220,4 +220,3 @@ class _RFC2136Client(object): except Exception as e: raise errors.PluginError('Encountered error when making query: {0}' .format(e)) - diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index 89ce3d93e..30afdbec9 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -171,7 +171,7 @@ class RFC2136ClientTest(unittest.TestCase): result = self.rfc2136_client._query_soa(DOMAIN) query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue(result == True) + self.assertTrue(result) @mock.patch("dns.query.udp") def test_query_soa_not_found(self, query_mock): @@ -181,7 +181,7 @@ class RFC2136ClientTest(unittest.TestCase): result = self.rfc2136_client._query_soa(DOMAIN) query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue(result == False) + self.assertFalse(result) @mock.patch("dns.query.udp") def test_query_soa_wraps_errors(self, query_mock): diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/dns_route53.py index f71935de2..e32017b34 100644 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/dns_route53.py @@ -44,7 +44,7 @@ class Authenticator(dns_common.DNSAuthenticator): def _setup_credentials(self): pass - def _perform(self, domain, validation_domain_name, validation): # pylint: disable=missing-docstring + def _perform(self, domain, validation_name, validation): # pylint: disable=missing-docstring pass def perform(self, achalls): @@ -65,9 +65,9 @@ class Authenticator(dns_common.DNSAuthenticator): raise errors.PluginError("\n".join([str(e), INSTRUCTIONS])) return [achall.response(achall.account_key) for achall in achalls] - def _cleanup(self, domain, validation_domain_name, validation): + def _cleanup(self, domain, validation_name, validation): try: - self._change_txt_record("DELETE", validation_domain_name, validation) + self._change_txt_record("DELETE", validation_name, validation) except (NoCredentialsError, ClientError) as e: logger.debug('Encountered error during cleanup: %s', e, exc_info=True) diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 118699aa2..c331978ef 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -13,6 +13,7 @@ import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util +from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module from certbot import constants as core_constants from certbot import crypto_util @@ -29,7 +30,6 @@ from certbot_nginx import parser from certbot_nginx import tls_sni_01 from certbot_nginx import http_01 from certbot_nginx import obj # pylint: disable=unused-import -from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module @@ -157,7 +157,7 @@ class NginxConfigurator(common.Installer): except (OSError, errors.LockError): logger.debug('Encountered error:', exc_info=True) raise errors.PluginError( - 'Unable to lock %s', self.conf('server-root')) + 'Unable to lock {0}'.format(self.conf('server-root'))) # Entry point in main.py for installing cert @@ -398,9 +398,8 @@ class NginxConfigurator(common.Installer): rank = matches[0]['rank'] wildcards = [x for x in matches if x['rank'] == rank] return max(wildcards, key=lambda x: len(x['name']))['vhost'] - else: - # Exact or regex match - return matches[0]['vhost'] + # Exact or regex match + return matches[0]['vhost'] def _rank_matches_by_name_and_ssl(self, vhost_list, target_name): @@ -479,25 +478,23 @@ class NginxConfigurator(common.Installer): if matching_port == "" or matching_port is None: # if no port is specified, Nginx defaults to listening on port 80. return test_port == self.DEFAULT_LISTEN_PORT - else: - return test_port == matching_port + return test_port == matching_port def _vhost_listening_on_port_no_ssl(self, vhost, port): found_matching_port = False - if len(vhost.addrs) == 0: + if not vhost.addrs: # if there are no listen directives at all, Nginx defaults to # listening on port 80. found_matching_port = (port == self.DEFAULT_LISTEN_PORT) else: for addr in vhost.addrs: - if self._port_matches(port, addr.get_port()) and addr.ssl == False: + if self._port_matches(port, addr.get_port()) and not addr.ssl: found_matching_port = True if found_matching_port: # make sure we don't have an 'ssl on' directive return not self.parser.has_ssl_on_directive(vhost) - else: - return False + return False def _get_redirect_ranked_matches(self, target_name, port): """Gets a ranked list of plaintextish port-listening vhosts matching target_name @@ -586,7 +583,7 @@ class NginxConfigurator(common.Installer): # If the vhost was implicitly listening on the default Nginx port, # have it continue to do so. - if len(vhost.addrs) == 0: + if not vhost.addrs: listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]] self.parser.add_server_directives(vhost, listen_block) diff --git a/certbot-nginx/certbot_nginx/display_ops.py b/certbot-nginx/certbot_nginx/display_ops.py index 5d6bda6b0..9b973d8d3 100644 --- a/certbot-nginx/certbot_nginx/display_ops.py +++ b/certbot-nginx/certbot_nginx/display_ops.py @@ -22,7 +22,7 @@ def select_vhost_multiple(vhosts): return list() tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] # Remove the extra newline from the last entry - if len(tags_list): + if tags_list: tags_list[-1] = tags_list[-1][:-1] code, names = zope.component.getUtility(interfaces.IDisplay).checklist( "Which server blocks would you like to modify?", diff --git a/certbot-nginx/certbot_nginx/http_01.py b/certbot-nginx/certbot_nginx/http_01.py index 677ce0737..842b12214 100644 --- a/certbot-nginx/certbot_nginx/http_01.py +++ b/certbot-nginx/certbot_nginx/http_01.py @@ -4,13 +4,13 @@ import logging import os from acme import challenges +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.plugins import common from certbot_nginx import obj from certbot_nginx import nginxparser -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) @@ -207,3 +207,4 @@ class NginxHttp01(common.ChallengePerformer): ' ', '$1', ' ', 'break']] self.configurator.parser.add_server_directives(vhost, rewrite_directive, insert_at_top=True) + return None diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/obj.py index 8868fcfad..b14be81ea 100644 --- a/certbot-nginx/certbot_nginx/obj.py +++ b/certbot-nginx/certbot_nginx/obj.py @@ -84,7 +84,7 @@ class Addr(common.Addr): port = tup[2] # The rest of the parts are options; we only care about ssl and default - while len(parts) > 0: + while parts: nextpart = parts.pop() if nextpart == 'ssl': ssl = True @@ -120,7 +120,7 @@ class Addr(common.Addr): def __repr__(self): return "Addr(" + self.__str__() + ")" - def __hash__(self): + def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ # See certbot-apache/certbot_apache/obj.py for more information return super(Addr, self).__hash__() @@ -224,15 +224,17 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods for a in self.addrs: if a.ipv6: return True + return False def ipv4_enabled(self): """Return true if one or more of the listen directives in vhost are IPv4 only""" - if self.addrs is None or len(self.addrs) == 0: + if not self.addrs: return True for a in self.addrs: if not a.ipv6: return True + return False def display_repr(self): """Return a representation of VHost to be used in dialog""" @@ -250,7 +252,7 @@ def _find_directive(directives, directive_name, match_content=None): """Find a directive of type directive_name in directives. If match_content is given, Searches for `match_content` in the directive arguments. """ - if not directives or isinstance(directives, six.string_types) or len(directives) == 0: + if not directives or isinstance(directives, six.string_types): return None # If match_content is None, just match on directive type. Otherwise, match on diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py index 5bc7946dc..1919dc335 100644 --- a/certbot-nginx/certbot_nginx/parser.py +++ b/certbot-nginx/certbot_nginx/parser.py @@ -4,8 +4,8 @@ import functools import glob import logging import os -import pyparsing import re +import pyparsing import six @@ -52,6 +52,7 @@ class NginxParser(object): :param str filepath: The path to the files to parse, as a glob """ + # pylint: disable=too-many-nested-blocks filepath = self.abs_path(filepath) trees = self._parse_files(filepath) for tree in trees: @@ -82,8 +83,7 @@ class NginxParser(object): """ if not os.path.isabs(path): return os.path.join(self.root, path) - else: - return path + return path def _build_addr_to_ssl(self): """Builds a map from address to whether it listens on ssl in any server block @@ -381,7 +381,7 @@ class NginxParser(object): if only_directives is not None: new_directives = nginxparser.UnspacedList([]) for directive in raw_in_parsed[1]: - if len(directive) > 0 and directive[0] in only_directives: + if directive and directive[0] in only_directives: new_directives.append(directive) raw_in_parsed[1] = new_directives @@ -394,7 +394,7 @@ class NginxParser(object): addr.default = False addr.ipv6only = False for directive in enclosing_block[new_vhost.path[-1]][1]: - if len(directive) > 0 and directive[0] == 'listen': + if directive and directive[0] == 'listen': if 'default_server' in directive: del directive[directive.index('default_server')] if 'default' in directive: @@ -460,19 +460,19 @@ def get_best_match(target_name, names): elif _regex_match(target_name, name): regex.append(name) - if len(exact) > 0: + if exact: # There can be more than one exact match; e.g. eff.org, .eff.org match = min(exact, key=len) return ('exact', match) - if len(wildcard_start) > 0: + if wildcard_start: # Return the longest wildcard match = max(wildcard_start, key=len) return ('wildcard_start', match) - if len(wildcard_end) > 0: + if wildcard_end: # Return the longest wildcard match = max(wildcard_end, key=len) return ('wildcard_end', match) - if len(regex) > 0: + if regex: # Just return the first one for now match = regex[0] return ('regex', match) @@ -517,10 +517,7 @@ def _regex_match(target_name, name): # After tilde is a perl-compatible regex try: regex = re.compile(name[1:]) - if re.match(regex, target_name): - return True - else: - return False + return re.match(regex, target_name) except re.error: # pragma: no cover # perl-compatible regexes are sometimes not recognized by python return False diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index 5a37c9565..340202b45 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -17,9 +17,6 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-m class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods """Nginx Parser Test.""" - def setUp(self): - super(NginxParserTest, self).setUp() - def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index ad1af2b96..f018cdece 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -1,9 +1,9 @@ """Common utilities for certbot_nginx.""" import copy import os -import pkg_resources import tempfile import unittest +import pkg_resources import josepy as jose import mock @@ -113,8 +113,7 @@ def contains_at_depth(haystack, needle, n): return False if n == 0: return needle in haystack - else: - for item in haystack: - if contains_at_depth(item, needle, n - 1): - return True - return False + for item in haystack: + if contains_at_depth(item, needle, n - 1): + return True + return False diff --git a/certbot/account.py b/certbot/account.py index 70d9a7fc3..132cca845 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -107,7 +107,7 @@ class AccountMemoryStorage(interfaces.AccountStorage): def find_all(self): return list(six.itervalues(self.accounts)) - def save(self, account, acme): + def save(self, account, client): # pylint: disable=unused-argument if account.id in self.accounts: logger.debug("Overwriting account: %s", account.id) @@ -193,8 +193,8 @@ class AccountFileStorage(interfaces.AccountStorage): account_id, acc.id)) return acc - def save(self, account, acme): - self._save(account, acme, regr_only=False) + def save(self, account, client): + self._save(account, client, regr_only=False) def save_regr(self, account, acme): """Save the registration resource. diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py index caf112c61..f831232af 100644 --- a/certbot/auth_handler.py +++ b/certbot/auth_handler.py @@ -374,7 +374,7 @@ def challb_to_achall(challb, account_key, domain): return achallenges.DNS(challb=challb, domain=domain) else: raise errors.Error( - "Received unsupported challenge of type: %s", chall.typ) + "Received unsupported challenge of type: {0}".format(chall.typ)) def gen_challenge_path(challbs, preferences, combinations): @@ -405,8 +405,7 @@ def gen_challenge_path(challbs, preferences, combinations): """ if combinations: return _find_smart_path(challbs, preferences, combinations) - else: - return _find_dumb_path(challbs, preferences) + return _find_dumb_path(challbs, preferences) def _find_smart_path(challbs, preferences, combinations): diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index d841c1912..c50432231 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -2,9 +2,9 @@ import datetime import logging import os -import pytz import re import traceback +import pytz import zope.component from certbot import crypto_util @@ -181,10 +181,9 @@ def _archive_files(candidate_lineage, filetype): archive_dir = candidate_lineage.archive_dir pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) if re.match("{0}[0-9]*.pem".format(filetype), f)] - if len(pattern) > 0: + if pattern: return pattern - else: - return None + return None def _acceptable_matches(): """ Generates the list that's passed to match_and_check_overlaps. Is its own function to diff --git a/certbot/cli.py b/certbot/cli.py index b71d60055..ccec8baea 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -293,8 +293,7 @@ def config_help(name, hidden=False): """Extract the help message for an `.IConfig` attribute.""" if hidden: return argparse.SUPPRESS - else: - return interfaces.IConfig[name].__doc__ + return interfaces.IConfig[name].__doc__ class HelpfulArgumentGroup(object): @@ -510,7 +509,7 @@ class HelpfulArgumentParser(object): # Help that are synonyms for --help subcommands COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] def _list_subcommands(self): - longest = max(len(v) for v in VERB_HELP_MAP.keys()) + longest = max(len(v) for v in VERB_HELP_MAP) text = "The full list of available SUBCOMMANDS is:\n\n" for verb, props in sorted(VERB_HELP): @@ -538,7 +537,7 @@ class HelpfulArgumentParser(object): apache_doc = "(the certbot apache plugin is not installed)" usage = SHORT_USAGE - if help_arg == True: + if help_arg is True: self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_USAGE) sys.exit(0) elif help_arg in self.COMMANDS_TOPICS: @@ -839,8 +838,7 @@ class HelpfulArgumentParser(object): return dict([(t, True) for t in self.help_topics]) elif not chosen_topic: return dict([(t, False) for t in self.help_topics]) - else: - return dict([(t, t == chosen_topic) for t in self.help_topics]) + return dict([(t, t == chosen_topic) for t in self.help_topics]) def _add_all_groups(helpful): helpful.add_group("automation", description="Flags for automating execution & other tweaks") diff --git a/certbot/client.py b/certbot/client.py index 45dc9c63b..bd420ecd7 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -293,7 +293,7 @@ class Client(object): orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) authzr = orderr.authorizations - auth_domains = set(a.body.identifier.value for a in authzr) + auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable successful_domains = [d for d in domains if d in auth_domains] # allow_subset_of_names is currently disabled for wildcard @@ -362,11 +362,10 @@ class Client(object): logger.debug("Dry run: Skipping creating new lineage for %s", new_name) return None - else: - return storage.RenewableCert.new_lineage( - new_name, cert, - key.pem, chain, - self.config) + return storage.RenewableCert.new_lineage( + new_name, cert, + key.pem, chain, + self.config) def _choose_lineagename(self, domains, certname): """Chooses a name for the new lineage. @@ -385,8 +384,7 @@ class Client(object): elif util.is_wildcard_domain(domains[0]): # Don't make files and directories starting with *. return domains[0][2:] - else: - return domains[0] + return domains[0] def save_certificate(self, cert_pem, chain_pem, cert_path, chain_path, fullchain_path): @@ -670,9 +668,8 @@ def _open_pem_file(cli_arg_path, pem_path): if cli.set_by_cli(cli_arg_path): return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ os.path.abspath(pem_path) - else: - uniq = util.unique_file(pem_path, 0o644, "wb") - return uniq[0], os.path.abspath(uniq[1]) + uniq = util.unique_file(pem_path, 0o644, "wb") + return uniq[0], os.path.abspath(uniq[1]) def _save_chain(chain_pem, chain_file): """Saves chain_pem at a unique path based on chain_path. diff --git a/certbot/configuration.py b/certbot/configuration.py index 297795609..e7f95fd2d 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -2,7 +2,7 @@ import copy import os -from six.moves.urllib import parse # pylint: disable=import-error +from six.moves.urllib import parse # pylint: disable=import-error,relative-import import zope.interface from certbot import constants diff --git a/certbot/display/ops.py b/certbot/display/ops.py index 1e15a8474..109efe187 100644 --- a/certbot/display/ops.py +++ b/certbot/display/ops.py @@ -83,8 +83,7 @@ def choose_account(accounts): "Please choose an account", labels, force_interactive=True) if code == display_util.OK: return accounts[index] - else: - return None + return None def choose_values(values, question=None): """Display screen to let user pick one or multiple values from the provided @@ -99,8 +98,7 @@ def choose_values(values, question=None): question, tags=values, force_interactive=True) if code == display_util.OK and items: return items - else: - return [] + return [] def choose_names(installer, question=None): """Display screen to select domains to validate. @@ -129,8 +127,7 @@ def choose_names(installer, question=None): code, names = _filter_names(names, question) if code == display_util.OK and names: return names - else: - return [] + return [] def get_valid_domains(domains): @@ -214,7 +211,7 @@ def _choose_names_manually(prompt_prefix=""): except errors.ConfigurationError as e: invalid_domains[domain] = str(e) - if len(invalid_domains): + if invalid_domains: retry_message = ( "One or more of the entered domain names was not valid:" "{0}{0}").format(os.linesep) diff --git a/certbot/display/util.py b/certbot/display/util.py index 5e97bca4e..d7308161e 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -183,8 +183,7 @@ class FileDisplay(object): if ans == "c" or ans == "C": return CANCEL, "-1" - else: - return OK, ans + return OK, ans def yesno(self, message, yes_label="Yes", no_label="No", default=None, cli_flag=None, force_interactive=False, **unused_kwargs): @@ -260,7 +259,7 @@ class FileDisplay(object): force_interactive=True) if code == OK: - if len(ans.strip()) == 0: + if not ans.strip(): ans = " ".join(str(x) for x in range(1, len(tags)+1)) indices = separate_list_input(ans) selected_tags = self._scrub_checklist_input(indices, tags) @@ -528,8 +527,7 @@ class NoninteractiveDisplay(object): """ if default is None: self._interaction_fail(message, cli_flag) - else: - return OK, default + return OK, default def yesno(self, message, yes_label=None, no_label=None, default=None, cli_flag=None, **unused_kwargs): @@ -546,8 +544,7 @@ class NoninteractiveDisplay(object): """ if default is None: self._interaction_fail(message, cli_flag) - else: - return default + return default def checklist(self, message, tags, default=None, cli_flag=None, **unused_kwargs): @@ -565,8 +562,7 @@ class NoninteractiveDisplay(object): """ if default is None: self._interaction_fail(message, cli_flag, "? ".join(tags)) - else: - return OK, default + return OK, default def directory_select(self, message, default=None, cli_flag=None, **unused_kwargs): diff --git a/certbot/eff.py b/certbot/eff.py index b047c0b97..6aba4c273 100644 --- a/certbot/eff.py +++ b/certbot/eff.py @@ -73,7 +73,7 @@ def _check_response(response): logger.debug('Received response:\n%s', response.content) try: response.raise_for_status() - if response.json()['status'] == False: + if not response.json()['status']: _report_failure('your e-mail address appears to be invalid') except requests.exceptions.HTTPError: _report_failure() diff --git a/certbot/error_handler.py b/certbot/error_handler.py index e2737711e..eabde766f 100644 --- a/certbot/error_handler.py +++ b/certbot/error_handler.py @@ -53,7 +53,7 @@ class ErrorHandler(object): deferred until they finish. """ - def __init__(self, func=None, *args, **kwargs): + def __init__(self, func, *args, **kwargs): self.call_on_regular_exit = False self.body_executed = False self.funcs = [] @@ -147,7 +147,6 @@ class ExitHandler(ErrorHandler): In addition to cleaning up on all signals, also cleans up on regular exit. """ - def __init__(self, func=None, *args, **kwargs): + def __init__(self, func, *args, **kwargs): ErrorHandler.__init__(self, func, *args, **kwargs) self.call_on_regular_exit = True - diff --git a/certbot/interfaces.py b/certbot/interfaces.py index c96f6bd51..a82ee66eb 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -73,7 +73,7 @@ class IPluginFactory(zope.interface.Interface): description = zope.interface.Attribute("Short plugin description") - def __call__(config, name): + def __call__(config, name): # pylint: disable=signature-differs """Create new `IPlugin`. :param IConfig config: Configuration. diff --git a/certbot/log.py b/certbot/log.py index face93cb3..4fd126f12 100644 --- a/certbot/log.py +++ b/certbot/log.py @@ -180,8 +180,7 @@ class ColoredStreamHandler(logging.StreamHandler): out = super(ColoredStreamHandler, self).format(record) if self.colored and record.levelno >= self.red_level: return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) - else: - return out + return out class MemoryHandler(logging.handlers.MemoryHandler): diff --git a/certbot/main.py b/certbot/main.py index 8a9a37084..e528059b4 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -175,6 +175,7 @@ def _handle_subset_cert_request(config, domains, cert): raise errors.Error(USER_CANCELLED) +# pylint: disable=inconsistent-return-statements def _handle_identical_cert_request(config, lineage): """Figure out what to do if a lineage has the same names as a previously obtained one @@ -222,8 +223,7 @@ def _handle_identical_cert_request(config, lineage): return "reinstall", lineage elif response[1] == 1: return "renew", lineage - else: - assert False, "This is impossible" + assert False, "This is imporssible" def _find_lineage_for_domains(config, domains): """Determine whether there are duplicated names and how to handle @@ -262,6 +262,7 @@ def _find_lineage_for_domains(config, domains): return _handle_identical_cert_request(config, ident_names_cert) elif subset_names_cert is not None: return _handle_subset_cert_request(config, domains, subset_names_cert) + return None, None def _find_cert(config, domains, certname): """Finds an existing certificate object given domains and/or a certificate name. @@ -340,7 +341,7 @@ def _get_added_removed(after, before): def _format_list(character, strings): """Format list with given character """ - if len(strings) == 0: + if not strings: formatted = "{br}(None)" else: formatted = "{br}{ch} " + "{br}{ch} ".join(strings) @@ -515,6 +516,7 @@ def _determine_account(config): raise errors.Error( "Registration cannot proceed without accepting " "Terms of Service.") + return None try: acc, acme = client.register( config, account_storage, tos_cb=_tos_cb) @@ -686,6 +688,7 @@ def unregister(config, unused_plugins): account_files.delete(config.account) reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY) + return None def register(config, unused_plugins): @@ -710,7 +713,7 @@ def register(config, unused_plugins): # registering a new account if not config.update_registration: - if len(accounts) > 0: + if accounts: # 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) @@ -719,10 +722,10 @@ def register(config, unused_plugins): "unsupported.") # _determine_account will register an account _determine_account(config) - return + return None # --update-registration - if len(accounts) == 0: + if not accounts: return "Could not find an existing account to update." if config.email is None: if config.register_unsafely_without_email: @@ -739,6 +742,7 @@ def register(config, unused_plugins): account_storage.save_regr(acc, cb_client.acme) eff.handle_subscription(config) add_msg("Your e-mail address was updated to {0}.".format(config.email)) + return None def _install_cert(config, le_client, domains, lineage=None): """Install a cert @@ -802,6 +806,7 @@ def install(config, plugins): raise errors.ConfigurationError("Path to certificate or key was not defined. " "If your certificate is managed by Certbot, please use --cert-name " "to define which certificate you would like to install.") + return None def _populate_from_certname(config): """Helper function for install to populate missing config values from lineage @@ -911,6 +916,7 @@ def enhance(config, plugins): config.chain_path = lineage.chain_path le_client = _init_le_client(config, authenticator=None, installer=installer) le_client.enhance_config(domains, config.chain_path, ask_redirect=False) + return None def rollback(config, plugins): @@ -1050,6 +1056,7 @@ def revoke(config, unused_plugins): # TODO: coop with renewal config return str(e) display_ops.success_revocation(config.cert_path[0]) + return None def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals @@ -1096,6 +1103,7 @@ def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals display_ops.success_renewal(domains) _suggest_donation_if_appropriate(config) + return None def _csr_get_and_save_cert(config, le_client): @@ -1280,7 +1288,7 @@ def set_displayer(config): zope.component.provideUtility(displayer) -def main(cli_args=sys.argv[1:]): +def main(cli_args=None): """Command line argument parsing and main script execution. :returns: result of requested command @@ -1289,6 +1297,8 @@ def main(cli_args=sys.argv[1:]): :raises errors.Error: error if plugin command is not supported """ + if cli_args is None: + cli_args = sys.argv[1:] log.pre_arg_parse_setup() plugins = plugins_disco.PluginsRegistry.find_all() diff --git a/certbot/ocsp.py b/certbot/ocsp.py index d34110f88..6eb4f9b3f 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -89,9 +89,8 @@ class RevocationChecker(object): host = url.partition("://")[2].rstrip("/") if host: return url, host - else: - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): """Parse openssl's weird output to work out what it means.""" @@ -117,4 +116,3 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): logger.warn("Unable to properly parse OCSP output: %s\nstderr:%s", ocsp_output, ocsp_errors) return False - diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 147d9e21a..1ba3685f8 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -299,9 +299,8 @@ class Addr(object): # too long, truncate addr_list = addr_list[0:len(result)] append_to_end = False - for i in range(0, len(addr_list)): - block = addr_list[i] - if len(block) == 0: + for i, block in enumerate(addr_list): + if not block: # encountered ::, so rest of the blocks should be # appended to the end append_to_end = True diff --git a/certbot/plugins/disco.py b/certbot/plugins/disco.py index 062c11650..758f42984 100644 --- a/certbot/plugins/disco.py +++ b/certbot/plugins/disco.py @@ -2,11 +2,10 @@ import collections import itertools import logging + import pkg_resources import six -from collections import OrderedDict - import zope.interface import zope.interface.verify @@ -184,7 +183,11 @@ class PluginsRegistry(collections.Mapping): # This prevents deadlock caused by plugins acquiring a lock # and ensures at least one concurrent Certbot instance will run # successfully. - self._plugins = OrderedDict(sorted(six.iteritems(plugins))) + + # Pylint checks for super init, but also claims the super + # has no __init__member + # pylint: disable=super-init-not-called + self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) @classmethod def find_all(cls): @@ -233,7 +236,6 @@ class PluginsRegistry(collections.Mapping): def ifaces(self, *ifaces_groups): """Filter plugins based on interfaces.""" - # pylint: disable=star-args return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) def verify(self, ifaces): @@ -269,8 +271,7 @@ class PluginsRegistry(collections.Mapping): assert len(candidates) <= 1 if candidates: return candidates[0] - else: - return None + return None def __repr__(self): return "{0}({1})".format( diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py index ba88b7aef..8498c182a 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/plugins/dns_common.py @@ -83,7 +83,7 @@ class DNSAuthenticator(common.Plugin): raise NotImplementedError() @abc.abstractmethod - def _perform(self, domain, validation_domain_name, validation): # pragma: no cover + def _perform(self, domain, validation_name, validation): # pragma: no cover """ Performs a dns-01 challenge by creating a DNS TXT record. @@ -95,7 +95,7 @@ class DNSAuthenticator(common.Plugin): raise NotImplementedError() @abc.abstractmethod - def _cleanup(self, domain, validation_domain_name, validation): # pragma: no cover + def _cleanup(self, domain, validation_name, validation): # pragma: no cover """ Deletes the DNS TXT record which would have been created by `_perform_achall`. diff --git a/certbot/plugins/dns_common_lexicon.py b/certbot/plugins/dns_common_lexicon.py index 7a97fc950..25489bb80 100644 --- a/certbot/plugins/dns_common_lexicon.py +++ b/certbot/plugins/dns_common_lexicon.py @@ -95,3 +95,4 @@ class LexiconClient(object): if not str(e).startswith('No domain found'): return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' .format(domain_name, e)) + return None diff --git a/certbot/plugins/dns_common_test.py b/certbot/plugins/dns_common_test.py index 9b0f0c875..62c99d579 100644 --- a/certbot/plugins/dns_common_test.py +++ b/certbot/plugins/dns_common_test.py @@ -22,10 +22,6 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat _perform = mock.MagicMock() _cleanup = mock.MagicMock() - def __init__(self, *args, **kwargs): - # pylint: disable=protected-access - super(DNSAuthenticatorTest._FakeDNSAuthenticator, self).__init__(*args, **kwargs) - def more_info(self): # pylint: disable=missing-docstring,no-self-use return 'A fake authenticator for testing.' diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index e5c22b377..66c58599a 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -137,8 +137,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.auth.cleanup([achall]) self.assertEqual(os.environ['CERTBOT_AUTH_OUTPUT'], 'foo') self.assertEqual(os.environ['CERTBOT_DOMAIN'], achall.domain) - if (isinstance(achall.chall, challenges.HTTP01) or - isinstance(achall.chall, challenges.DNS01)): + if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)): self.assertEqual( os.environ['CERTBOT_VALIDATION'], achall.validation(achall.account_key)) diff --git a/certbot/plugins/selection.py b/certbot/plugins/selection.py index 030d5b6db..821fab652 100644 --- a/certbot/plugins/selection.py +++ b/certbot/plugins/selection.py @@ -82,8 +82,7 @@ def pick_plugin(config, default, plugins, question, ifaces): plugin_ep = choose_plugin(list(six.itervalues(prepared)), question) if plugin_ep is None: return None - else: - return plugin_ep.init() + return plugin_ep.init() elif len(prepared) == 1: plugin_ep = list(prepared.values())[0] logger.debug("Single candidate plugin: %s", plugin_ep) diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py index 817403bd3..7183aad42 100644 --- a/certbot/plugins/standalone.py +++ b/certbot/plugins/standalone.py @@ -225,7 +225,8 @@ class Authenticator(common.Plugin): try: return self._perform_single(achall) except errors.StandaloneBindError as error: - _handle_perform_error(error) + if not _handle_perform_error(error): + raise def _perform_single(self, achall): if isinstance(achall.chall, challenges.HTTP01): @@ -282,5 +283,5 @@ def _handle_perform_error(error): "Cancel", default=False) if not should_retry: raise errors.PluginError(msg) - else: - raise + return True + return False diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py index 8d96f400c..4b5af5c22 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/plugins/storage_test.py @@ -1,8 +1,8 @@ """Tests for certbot.plugins.storage.PluginStorage""" import json -import mock import os import unittest +import mock from certbot import errors diff --git a/certbot/plugins/util.py b/certbot/plugins/util.py index ad2257e1d..3ae7aa532 100644 --- a/certbot/plugins/util.py +++ b/certbot/plugins/util.py @@ -17,7 +17,7 @@ def get_prefixes(path): """ prefix = path prefixes = [] - while len(prefix) > 0: + while prefix: prefixes.append(prefix) prefix, _ = os.path.split(prefix) # break once we hit '/' @@ -49,8 +49,7 @@ def path_surgery(cmd): if util.exe_exists(cmd): return True - else: - expanded = " expanded" if any(added) else "" - logger.warning("Failed to find executable %s in%s PATH: %s", cmd, - expanded, path) - return False + expanded = " expanded" if any(added) else "" + logger.warning("Failed to find executable %s in%s PATH: %s", cmd, + expanded, path) + return False diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index 6328b16ef..3999ffedb 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -220,7 +220,7 @@ to serve all files under specified web root ({0}).""" self.performed[root_path].remove(achall) not_removed = [] - while len(self._created_dirs) > 0: + while self._created_dirs: path = self._created_dirs.pop() try: os.rmdir(path) diff --git a/certbot/reverter.py b/certbot/reverter.py index 15ad1a987..b51c0798f 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -146,7 +146,7 @@ class Reverter(object): if not backups: logger.info("Certbot has not saved backups of your configuration") - return + return None # Make sure there isn't anything unexpected in the backup folder # There should only be timestamped (float) directories try: @@ -182,6 +182,7 @@ class Reverter(object): return os.linesep.join(output) zope.component.getUtility(interfaces.IDisplay).notification( os.linesep.join(output), force_interactive=True, pause=False) + return None def add_to_temp_checkpoint(self, save_files, save_notes): """Add files to temporary checkpoint. diff --git a/certbot/storage.py b/certbot/storage.py index c453e55b0..965db462b 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -5,11 +5,11 @@ import logging import os import re import stat +import shutil import configobj import parsedatetime import pytz -import shutil import six import certbot @@ -272,8 +272,7 @@ def full_archive_path(config_obj, cli_config, lineagename): """ if config_obj and "archive_dir" in config_obj: return config_obj["archive_dir"] - else: - return os.path.join(cli_config.default_archive_dir, lineagename) + return os.path.join(cli_config.default_archive_dir, lineagename) def _full_live_path(cli_config, lineagename): """Returns the full default live path for a lineagename""" @@ -481,8 +480,7 @@ class RenewableCert(object): server = self.configuration["renewalparams"].get("server", None) if server: return util.is_staging(server) - else: - return False + return False def _check_symlinks(self): """Raises an exception if a symlink doesn't exist""" @@ -671,9 +669,8 @@ class RenewableCert(object): matches = pattern.match(os.path.basename(target)) if matches: return int(matches.groups()[0]) - else: - logger.debug("No matches for target %s.", kind) - return None + logger.debug("No matches for target %s.", kind) + return None def version(self, kind, version): """The filename that corresponds to the specified version and kind. diff --git a/certbot/tests/acme_util.py b/certbot/tests/acme_util.py index 53a2f214a..8ae2348d1 100644 --- a/certbot/tests/acme_util.py +++ b/certbot/tests/acme_util.py @@ -42,7 +42,7 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name if status == messages.STATUS_VALID: kwargs.update({"validated": datetime.datetime.now()}) - return messages.ChallengeBody(**kwargs) # pylint: disable=star-args + return messages.ChallengeBody(**kwargs) # Pending ChallengeBody objects @@ -93,7 +93,6 @@ def gen_authzr(authz_status, domain, challs, statuses, combos=True): "status": authz_status, }) - # pylint: disable=star-args return messages.AuthorizationResource( uri="https://trusted.ca/new-authz-resource", body=messages.Authorization(**authz_kwargs) diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 9a8a13498..a5d714bad 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -518,21 +518,18 @@ class ReportFailedChallsTest(unittest.TestCase): self.assertTrue(kwargs["error"].description is not None) self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args challb=messages.ChallengeBody(**kwargs), domain="example.com", account_key="key") kwargs["chall"] = acme_util.TLSSNI01 self.tls_sni_same = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args challb=messages.ChallengeBody(**kwargs), domain="example.com", account_key="key") kwargs["error"] = messages.Error(typ="dnssec", detail="detail") self.tls_sni_diff = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args challb=messages.ChallengeBody(**kwargs), domain="foo.bar", account_key="key") diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 6ec1d4f5c..fd87991b7 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -205,9 +205,11 @@ class CertificatesTest(BaseCertManagerTest): @mock.patch('certbot.cert_manager.ocsp.RevocationChecker.ocsp_revoked') def test_report_human_readable(self, mock_revoked): + # pylint: disable=too-many-statements mock_revoked.return_value = None from certbot import cert_manager - import datetime, pytz + import datetime + import pytz expiry = pytz.UTC.fromutc(datetime.datetime.utcnow()) cert = mock.MagicMock(lineagename="nameone") @@ -229,20 +231,20 @@ class CertificatesTest(BaseCertManagerTest): # pylint: disable=protected-access out = get_report() self.assertTrue('1 hour(s)' in out) - self.assertTrue('VALID' in out and not 'INVALID' in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.target_expiry += datetime.timedelta(days=1) # pylint: disable=protected-access out = get_report() self.assertTrue('1 day' in out) self.assertFalse('under' in out) - self.assertTrue('VALID' in out and not 'INVALID' in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.target_expiry += datetime.timedelta(days=2) # pylint: disable=protected-access out = get_report() self.assertTrue('3 days' in out) - self.assertTrue('VALID' in out and not 'INVALID' in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.is_test_cert = True mock_revoked.return_value = True diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 6add141d4..baa8ce0c9 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -110,7 +110,6 @@ class ClientTestCommon(test_util.ConfigTestCase): self.config.no_verify_ssl = False self.config.allow_subset_of_names = False - # pylint: disable=star-args self.account = mock.MagicMock(**{"key.pem": KEY}) from certbot.client import Client diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 2fe0e3d30..af70ecdc9 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -189,9 +189,6 @@ class VerifyCertSetup(unittest.TestCase): class VerifyRenewableCertTest(VerifyCertSetup): """Tests for certbot.crypto_util.verify_renewable_cert.""" - def setUp(self): - super(VerifyRenewableCertTest, self).setUp() - def _call(self, renewable_cert): from certbot.crypto_util import verify_renewable_cert return verify_renewable_cert(renewable_cert) @@ -207,9 +204,6 @@ class VerifyRenewableCertTest(VerifyCertSetup): class VerifyRenewableCertSigTest(VerifyCertSetup): """Tests for certbot.crypto_util.verify_renewable_cert.""" - def setUp(self): - super(VerifyRenewableCertSigTest, self).setUp() - def _call(self, renewable_cert): from certbot.crypto_util import verify_renewable_cert_sig return verify_renewable_cert_sig(renewable_cert) @@ -225,9 +219,6 @@ class VerifyRenewableCertSigTest(VerifyCertSetup): class VerifyFullchainTest(VerifyCertSetup): """Tests for certbot.crypto_util.verify_fullchain.""" - def setUp(self): - super(VerifyFullchainTest, self).setUp() - def _call(self, renewable_cert): from certbot.crypto_util import verify_fullchain return verify_fullchain(renewable_cert) @@ -246,9 +237,6 @@ class VerifyFullchainTest(VerifyCertSetup): class VerifyCertMatchesPrivKeyTest(VerifyCertSetup): """Tests for certbot.crypto_util.verify_cert_matches_priv_key.""" - def setUp(self): - super(VerifyCertMatchesPrivKeyTest, self).setUp() - def _call(self, renewable_cert): from certbot.crypto_util import verify_cert_matches_priv_key return verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey) diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index 9de8c5e9a..caac85d59 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -43,7 +43,7 @@ class GetEmailTest(unittest.TestCase): mock_input.return_value = (display_util.OK, "foo@bar.baz") with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.return_value = True - self.assertTrue(self._call() is "foo@bar.baz") + self.assertTrue(self._call() == "foo@bar.baz") @test_util.patch_get_utility("certbot.display.ops.z_util") def test_ok_not_safe(self, mock_get_utility): @@ -51,7 +51,7 @@ class GetEmailTest(unittest.TestCase): mock_input.return_value = (display_util.OK, "foo@bar.baz") with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email: mock_safe_email.side_effect = [False, True] - self.assertTrue(self._call() is "foo@bar.baz") + self.assertTrue(self._call() == "foo@bar.baz") @test_util.patch_get_utility("certbot.display.ops.z_util") def test_invalid_flag(self, mock_get_utility): diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 1dfc21c30..80308eb97 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -226,7 +226,6 @@ class FileOutputDisplayTest(unittest.TestCase): @mock.patch("certbot.display.util.input_with_timeout") def test_directory_select(self, mock_input): - # pylint: disable=star-args args = ["msg", "/var/www/html", "--flag", True] user_input = "/var/www/html" mock_input.return_value = user_input @@ -315,7 +314,9 @@ class FileOutputDisplayTest(unittest.TestCase): def test_methods_take_force_interactive(self): # Every IDisplay method implemented by FileDisplay must take # force_interactive to prevent workflow regressions. - for name in interfaces.IDisplay.names(): # pylint: disable=no-member + + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 arg_spec = inspect.getargspec(getattr(self.displayer, name)) self.assertTrue("force_interactive" in arg_spec.args) @@ -374,7 +375,9 @@ class NoninteractiveDisplayTest(unittest.TestCase): # should take **kwargs because every method of FileDisplay must # take force_interactive which doesn't apply to # NoninteractiveDisplay. - for name in interfaces.IDisplay.names(): # pylint: disable=no-member + + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 method = getattr(self.displayer, name) # asserts method accepts arbitrary keyword arguments self.assertFalse(inspect.getargspec(method).keywords is None) diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index 8d0d5778c..cf79ffc70 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -1,8 +1,8 @@ """Tests for certbot.eff.""" -import requests import unittest import mock +import requests from certbot import constants import certbot.tests.util as test_util diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index 549d2c5e1..e0212aed6 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -280,7 +280,6 @@ class PreArgParseExceptHookTest(unittest.TestCase): @mock.patch('certbot.log.post_arg_parse_except_hook') def test_it(self, mock_post_arg_parse_except_hook): - # pylint: disable=star-args memory_handler = mock.MagicMock() args = ('some', 'args',) kwargs = {'some': 'kwargs'} @@ -356,7 +355,6 @@ class PostArgParseExceptHookTest(unittest.TestCase): mock_logger.error.side_effect = write_err with mock.patch('certbot.log.sys.stderr', mock_err): try: - # pylint: disable=star-args self._call( *exc_info, debug=debug, log_path=self.log_path) except SystemExit as exit_err: @@ -406,13 +404,13 @@ class ExitWithLogPathTest(test_util.TempDirTestCase): self.assertTrue('logfiles' in err_str) self.assertTrue(self.tempdir in err_str) + # pylint: disable=inconsistent-return-statements def _test_common(self, *args, **kwargs): try: self._call(*args, **kwargs) except SystemExit as err: return str(err) - else: # pragma: no cover - self.fail('SystemExit was not raised.') + self.fail('SystemExit was not raised.') # pragma: no cover if __name__ == "__main__": diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 0986ff060..991179bf2 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -4,12 +4,12 @@ from __future__ import print_function import itertools -import mock import os import shutil import traceback import unittest import datetime +import mock import pytz import josepy as jose @@ -606,8 +606,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met fn.endswith("chain") or fn.endswith("privkey")): return True - else: - return orig_open(fn, *args, **kwargs) + return orig_open(fn, *args, **kwargs) with mock.patch("os.path.isfile") as mock_if: mock_if.side_effect = mock_isfile diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 2d54274f0..963e845ba 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -90,7 +90,7 @@ class OCSPTest(unittest.TestCase): @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') def test_translate_ocsp(self, mock_run, mock_log): - # pylint: disable=protected-access,star-args + # pylint: disable=protected-access mock_run.return_value = openssl_confused from certbot import ocsp self.assertEqual(ocsp._translate_ocsp_query(*openssl_happy), False) diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index d292d909a..ec5c20f28 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -1,6 +1,6 @@ """Tests for certbot.renewal""" -import mock import unittest +import mock from acme import challenges @@ -12,9 +12,6 @@ import certbot.tests.util as test_util class RenewalTest(test_util.ConfigTestCase): - def setUp(self): - super(RenewalTest, self).setUp() - @mock.patch('certbot.cli.set_by_cli') def test_ancient_webroot_renewal_conf(self, mock_set_by_cli): mock_set_by_cli.return_value = False @@ -34,9 +31,6 @@ class RenewalTest(test_util.ConfigTestCase): class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): """Tests for certbot.renewal.restore_required_config_elements.""" - def setUp(self): - super(RestoreRequiredConfigElementsTest, self).setUp() - @classmethod def _call(cls, *args, **kwargs): from certbot.renewal import restore_required_config_elements diff --git a/certbot/tests/reporter_test.py b/certbot/tests/reporter_test.py index 9ec8dca28..0eafd2540 100644 --- a/certbot/tests/reporter_test.py +++ b/certbot/tests/reporter_test.py @@ -1,7 +1,7 @@ """Tests for certbot.reporter.""" -import mock import sys import unittest +import mock import six diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 8434d11de..95a962290 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -5,10 +5,10 @@ """ import multiprocessing import os -import pkg_resources import shutil import tempfile import unittest +import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -101,8 +101,7 @@ def skip_unless(condition, reason): # pragma: no cover return unittest.skipUnless(condition, reason) elif condition: return lambda cls: cls - else: - return lambda cls: None + return lambda cls: None def make_lineage(config_dir, testfile): @@ -254,13 +253,13 @@ class FreezableMock(object): if name in ('return_value', 'side_effect'): return setattr(self._mock, name, value) - else: - return object.__setattr__(self, name, value) + return object.__setattr__(self, name, value) def _create_get_utility_mock(): display = FreezableMock() - for name in interfaces.IDisplay.names(): # pylint: disable=no-member + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 if name != 'notification': frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call) setattr(display, name, frozen_mock) @@ -284,7 +283,8 @@ def _create_get_utility_mock_with_stdout(stdout): display = FreezableMock() - for name in interfaces.IDisplay.names(): # pylint: disable=no-member + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 if name == 'notification': frozen_mock = FreezableMock(frozen=True, func=_write_msg) @@ -306,7 +306,6 @@ def _assert_valid_call(*args, **kwargs): assert_kwargs['cli_flag'] = kwargs.get('cli_flag', None) assert_kwargs['force_interactive'] = kwargs.get('force_interactive', False) - # pylint: disable=star-args display_util.assert_valid_call(*assert_args, **assert_kwargs) diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 0e280f3ab..08bf50dc2 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -551,7 +551,7 @@ class OsInfoTest(unittest.TestCase): comm_mock = mock.Mock() comm_attrs = {'communicate.return_value': ('42.42.42', 'error')} - comm_mock.configure_mock(**comm_attrs) # pylint: disable=star-args + comm_mock.configure_mock(**comm_attrs) popen_mock.return_value = comm_mock self.assertEqual(get_os_info()[0], 'darwin') self.assertEqual(get_os_info()[1], '42.42.42') @@ -607,7 +607,7 @@ class AtexitRegisterTest(unittest.TestCase): self.assertTrue(mock_atexit.register.called) args, kwargs = mock_atexit.register.call_args atexit_func = args[0] - atexit_func(*args[1:], **kwargs) # pylint: disable=star-args + atexit_func(*args[1:], **kwargs) if __name__ == "__main__": diff --git a/certbot/util.py b/certbot/util.py index 55acd624f..7ce06f74a 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -2,6 +2,7 @@ import argparse import atexit import collections +from collections import OrderedDict # distutils.version under virtualenv confuses pylint # For more info, see: https://github.com/PyCQA/pylint/issues/73 import distutils.version # pylint: disable=import-error,no-name-in-module @@ -10,14 +11,12 @@ import logging import os import platform import re -import six import socket import stat import subprocess import sys -from collections import OrderedDict - +import six import configargparse from certbot import constants @@ -217,7 +216,6 @@ def safe_open(path, mode="w", chmod=None, buffering=None): defaults if ``None``. """ - # pylint: disable=star-args open_args = () if chmod is None else (chmod,) fdopen_args = () if buffering is None else (buffering,) return os.fdopen( @@ -465,9 +463,8 @@ def safe_email(email): """Scrub email address before using it.""" if EMAIL_REGEX.match(email) is not None: return not email.startswith(".") and ".." not in email - else: - logger.warning("Invalid email address: %s.", email) - return False + logger.warning("Invalid email address: %s.", email) + return False class _ShowWarning(argparse.Action): diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py index e0656ae05..0ad9771a4 100644 --- a/letshelp-certbot/letshelp_certbot/apache_test.py +++ b/letshelp-certbot/letshelp_certbot/apache_test.py @@ -2,11 +2,11 @@ import argparse import functools import os -import pkg_resources import subprocess import tarfile import tempfile import unittest +import pkg_resources import mock # six is used in mock.patch() -- cgit v1.2.3 From 2ddaf3db043ea8526ae3f9ab2ef120b194b2e506 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 5 Feb 2019 19:45:15 +0100 Subject: Use built-in support for OCSP in cryptography >= 2.5 (#6603) In response to #6594. [Fixes #6594.] To execute OCSP requests, certbot relies currently on a openssl binary execution. If openssl is not present in the PATH, the OCSP check will be silently ignored. Since version 2.4, cryptography has support for OCSP requests, without the need to have openssl binary available locally. This PR takes advantage of it, and will use the built-in support of OCSP in cryptography for versions >= 2.4. Otherwise, fallback is done do a direct call to openssl binary, allowing oldest requirements to still work with legacy cryptography versions. Update: requirement is now cryptography >= 2.5, to avoid to rely on a private method from cryptography. * Implement logic using cryptography * Working OSCP using pure cryptography * Fix openssl usage in unit tests * Reduce verbosity * Add tests * Improve naive skipIf * Test resiliency * Update ocsp.py * Validate OCSP response. Unify OCSP URL get * Improve resiliency checks, correct lint/mypy * Improve hash selection * Fix warnings when calling openssl bin * Load OCSP tests assets as vectors. * Update ocsp.py * Protect against invalid ocsp response. * Add checks to OCSP response * Add more control on ocsp response * Be lenient about assertion that next_update must be in the future, similarly to openssl. * Construct a more advanced OCSP response mock to trigger more logic in ocsp module. * Add test * Refactor signature process to use crypto_util * Fallback for cryptography 2.4 * Avoid a collision with a meteor. * Correct method signature documentation * Relax OCSP update interval * Trigger built-in ocsp logic from cryptography with 2.5+ * Update pinned version of cryptography * Update certbot/ocsp.py Co-Authored-By: adferrand * Update ocsp.py * Update ocsp_test.py * Update CHANGELOG.md * Update CHANGELOG.md --- CHANGELOG.md | 5 +- certbot/crypto_util.py | 53 ++++-- certbot/ocsp.py | 201 ++++++++++++++++----- certbot/tests/ocsp_test.py | 172 +++++++++++++++--- certbot/tests/testdata/google_certificate.pem | 41 +++++ .../tests/testdata/google_issuer_certificate.pem | 26 +++ certbot/util.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 40 ++-- .../pieces/dependency-requirements.txt | 40 ++-- 9 files changed, 453 insertions(+), 127 deletions(-) create mode 100644 certbot/tests/testdata/google_certificate.pem create mode 100644 certbot/tests/testdata/google_issuer_certificate.pem diff --git a/CHANGELOG.md b/CHANGELOG.md index cae93c5b7..204b7b328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* Avoid to process again challenges that are already validated +* Avoid reprocessing challenges that are already validated when a certificate is issued. +* If possible, Certbot uses built-in support for OCSP from recent cryptography + versions instead of the OpenSSL binary: as a consequence Certbot does not need + the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. * Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges with the `acme` module. diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index c4a389cd5..66c68eb38 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -19,7 +19,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey # https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography import x509 # type: ignore +from cryptography import x509 # type: ignore from OpenSSL import crypto from OpenSSL import SSL # type: ignore @@ -226,7 +226,7 @@ def verify_renewable_cert(renewable_cert): def verify_renewable_cert_sig(renewable_cert): - """ Verifies the signature of a `.storage.RenewableCert` object. + """Verifies the signature of a `.storage.RenewableCert` object. :param `.storage.RenewableCert` renewable_cert: cert to verify @@ -239,22 +239,8 @@ def verify_renewable_cert_sig(renewable_cert): cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() with warnings.catch_warnings(): - warnings.simplefilter("ignore") - if isinstance(pk, RSAPublicKey): - # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi - verifier = pk.verifier( # type: ignore - cert.signature, PKCS1v15(), cert.signature_hash_algorithm - ) - verifier.update(cert.tbs_certificate_bytes) - verifier.verify() - elif isinstance(pk, EllipticCurvePublicKey): - verifier = pk.verifier( - cert.signature, ECDSA(cert.signature_hash_algorithm) - ) - verifier.update(cert.tbs_certificate_bytes) - verifier.verify() - else: - raise errors.Error("Unsupported public key type") + verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, + cert.signature_hash_algorithm) except (IOError, ValueError, InvalidSignature) as e: error_str = "verifying the signature of the cert located at {0} has failed. \ Details: {1}".format(renewable_cert.cert, e) @@ -262,6 +248,37 @@ def verify_renewable_cert_sig(renewable_cert): raise errors.Error(error_str) +def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm): + """Check the signature of a payload. + + :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature + :param bytes signature: the signature bytes + :param bytes payload: the payload bytes + :param cryptography.hazmat.primitives.hashes.HashAlgorithm + signature_hash_algorithm: algorithm used to hash the payload + + :raises InvalidSignature: If signature verification fails. + :raises errors.Error: If public key type is not supported + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + if isinstance(public_key, RSAPublicKey): + # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi + verifier = public_key.verifier( # type: ignore + signature, PKCS1v15(), signature_hash_algorithm + ) + verifier.update(payload) + verifier.verify() + elif isinstance(public_key, EllipticCurvePublicKey): + verifier = public_key.verifier( + signature, ECDSA(signature_hash_algorithm) + ) + verifier.update(payload) + verifier.verify() + else: + raise errors.Error("Unsupported public key type") + + def verify_cert_matches_priv_key(cert_path, key_path): """ Verifies that the private key and cert match. diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 049e14827..0abfd3c23 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -1,53 +1,79 @@ """Tools for checking certificate revocation.""" import logging import re - +from datetime import datetime, timedelta from subprocess import Popen, PIPE +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp # pylint: disable=import-error + getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp = None # type: ignore +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +import requests + +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot import crypto_util from certbot import errors from certbot import util logger = logging.getLogger(__name__) + class RevocationChecker(object): - "This class figures out OCSP checking on this system, and performs it." + """This class figures out OCSP checking on this system, and performs it.""" - def __init__(self): + def __init__(self, enforce_openssl_binary_usage=False): self.broken = False - - if not util.exe_exists("openssl"): - logger.info("openssl not installed, can't check revocation") - self.broken = True - return - - # New versions of openssl want -header var=val, old ones want -header var val - test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) - _out, err = test_host_format.communicate() - if "Missing =" in err: - self.host_args = lambda host: ["Host=" + host] - else: - self.host_args = lambda host: ["Host", host] - + self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp + + if self.use_openssl_binary: + if not util.exe_exists("openssl"): + logger.info("openssl not installed, can't check revocation") + self.broken = True + return + + # New versions of openssl want -header var=val, old ones want -header var val + test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], + stdout=PIPE, stderr=PIPE, universal_newlines=True) + _out, err = test_host_format.communicate() + if "Missing =" in err: + self.host_args = lambda host: ["Host=" + host] + else: + self.host_args = lambda host: ["Host", host] def ocsp_revoked(self, cert_path, chain_path): + # type: (str, str) -> bool """Get revoked status for a particular cert version. .. todo:: Make this a non-blocking call :param str cert_path: Path to certificate :param str chain_path: Path to intermediate cert - :rtype bool or None: :returns: True if revoked; False if valid or the check failed + :rtype: bool """ if self.broken: return False - - url, host = self.determine_ocsp_server(cert_path) - if not host: + url, host = _determine_ocsp_server(cert_path) + if not host or not url: return False + + if self.use_openssl_binary: + return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) + else: + return _check_ocsp_cryptography(cert_path, chain_path, url) + + def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): + # type: (str, str, str, str) -> bool # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! cmd = ["openssl", "ocsp", "-no_nonce", @@ -65,33 +91,117 @@ class RevocationChecker(object): except errors.SubprocessError: logger.info("OCSP check failed for %s (are we offline?)", cert_path) return False - return _translate_ocsp_query(cert_path, output, err) - def determine_ocsp_server(self, cert_path): - """Extract the OCSP server host from a certificate. +def _determine_ocsp_server(cert_path): + # type: (str) -> Tuple[Optional[str], Optional[str]] + """Extract the OCSP server host from a certificate. - :param str cert_path: Path to the cert we're checking OCSP for - :rtype tuple: - :returns: (OCSP server URL or None, OCSP server host or None) + :param str cert_path: Path to the cert we're checking OCSP for + :rtype tuple: + :returns: (OCSP server URL or None, OCSP server host or None) - """ - try: - url, _err = util.run_script( - ["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"], - log=logger.debug) - except errors.SubprocessError: - logger.info("Cannot extract OCSP URI from %s", cert_path) - return None, None + """ + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + try: + extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + ocsp_oid = x509.AuthorityInformationAccessOID.OCSP + descriptions = [description for description in extension.value + if description.access_method == ocsp_oid] + + url = descriptions[0].access_location.value + except (x509.ExtensionNotFound, IndexError): + logger.info("Cannot extract OCSP URI from %s", cert_path) + return None, None + + url = url.rstrip() + host = url.partition("://")[2].rstrip("/") + + if host: + return url, host + else: + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None + + +def _check_ocsp_cryptography(cert_path, chain_path, url): + # type: (str, str, str) -> bool + # Retrieve OCSP response + with open(chain_path, 'rb') as file_handler: + issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + request_binary = request.public_bytes(serialization.Encoding.DER) + response = requests.post(url, data=request_binary, + headers={'Content-Type': 'application/ocsp-request'}) + if response.status_code != 200: + logger.info("OCSP check failed for %s (are we offline?)", cert_path) + return False + response_ocsp = ocsp.load_der_ocsp_response(response.content) + + # Check OCSP response validity + if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: + logger.error("Invalid OCSP response status for %s: %s", + cert_path, response_ocsp.response_status) + return False + + # Check OCSP signature + try: + _check_ocsp_response(response_ocsp, request, issuer) + except UnsupportedAlgorithm as e: + logger.error(str(e)) + except errors.Error as e: + logger.error(str(e)) + except InvalidSignature: + logger.error('Invalid signature on OCSP response for %s', cert_path) + except AssertionError as error: + logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) + else: + # Check OCSP certificate status + logger.debug("OCSP certificate status for %s is: %s", + cert_path, response_ocsp.certificate_status) + return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED + + return False + + +def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): + """Verify that the OCSP is valid for serveral criterias""" + # Assert OCSP response corresponds to the certificate we are talking about + if response_ocsp.serial_number != request_ocsp.serial_number: + raise AssertionError('the certificate in response does not correspond ' + 'to the certificate in request') + + # Assert signature is valid + _check_ocsp_response_signature(response_ocsp, issuer_cert) + + # Assert issuer in response is the expected one + if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) + or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash + or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): + raise AssertionError('the issuer does not correspond to issuer of the certificate.') + + # Assert nextUpdate is in the future, and that thisUpdate is not too old + if response_ocsp.next_update: + if response_ocsp.next_update < datetime.now() - timedelta(minutes=5): + raise AssertionError('next update is in the past.') + interval = response_ocsp.next_update - response_ocsp.this_update + if datetime.now() - response_ocsp.this_update > interval + timedelta(minutes=5): + raise AssertionError('this update is too old.') + + +def _check_ocsp_response_signature(response_ocsp, issuer_cert): + """Verify an OCSP response signature against certificate issuer""" + # Following line may raise UnsupportedAlgorithm + chosen_hash = response_ocsp.signature_hash_algorithm + crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature, + response_ocsp.tbs_response_bytes, chosen_hash) - url = url.rstrip() - host = url.partition("://")[2].rstrip("/") - if host: - return url, host - else: - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): """Parse openssl's weird output to work out what it means.""" @@ -102,7 +212,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): warning = good.group(1) if good else None - if (not "Response verify OK" in ocsp_errors) or (good and warning) or unknown: + if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: logger.info("Revocation status for %s is unknown", cert_path) logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) return False @@ -115,6 +225,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False - diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 55cd24adb..ad3467e5a 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,18 +1,33 @@ """Tests for ocsp.py""" # pylint: disable=protected-access - import unittest +from datetime import datetime, timedelta +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +from cryptography import x509 +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp as ocsp_lib # pylint: disable=import-error + getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp_lib = None # type: ignore import mock from certbot import errors +from certbot.tests import util as test_util out = """Missing = in header key=value ocsp: Use -help for summary. """ -class OCSPTest(unittest.TestCase): +class OCSPTestOpenSSL(unittest.TestCase): + """ + OCSP revokation tests using OpenSSL binary. + """ def setUp(self): from certbot import ocsp @@ -22,7 +37,7 @@ class OCSPTest(unittest.TestCase): mock_communicate.communicate.return_value = (None, out) mock_popen.return_value = mock_communicate mock_exists.return_value = True - self.checker = ocsp.RevocationChecker() + self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) def tearDown(self): pass @@ -37,23 +52,23 @@ class OCSPTest(unittest.TestCase): mock_exists.return_value = True from certbot import ocsp - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 1) self.assertEqual(checker.host_args("x"), ["Host=x"]) mock_communicate.communicate.return_value = (None, out.partition("\n")[2]) - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(checker.host_args("x"), ["Host", "x"]) self.assertEqual(checker.broken, False) mock_exists.return_value = False mock_popen.call_count = 0 - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 0) self.assertEqual(mock_log.call_count, 1) self.assertEqual(checker.broken, True) - @mock.patch('certbot.ocsp.RevocationChecker.determine_ocsp_server') + @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.util.run_script') def test_ocsp_revoked(self, mock_run, mock_determine): self.checker.broken = True @@ -71,21 +86,12 @@ class OCSPTest(unittest.TestCase): self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) self.assertEqual(mock_run.call_count, 2) + def test_determine_ocsp_server(self): + cert_path = test_util.vector_path('google_certificate.pem') - @mock.patch('certbot.ocsp.logger.info') - @mock.patch('certbot.util.run_script') - def test_determine_ocsp_server(self, mock_run, mock_info): - uri = "http://ocsp.stg-int-x1.letsencrypt.org/" - host = "ocsp.stg-int-x1.letsencrypt.org" - mock_run.return_value = uri, "" - self.assertEqual(self.checker.determine_ocsp_server("beep"), (uri, host)) - mock_run.return_value = "ftp:/" + host + "/", "" - self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) - self.assertEqual(mock_info.call_count, 1) - - c = "confusion" - mock_run.side_effect = errors.SubprocessError(c) - self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) + from certbot import ocsp + result = ocsp._determine_ocsp_server(cert_path) + self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result) @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') @@ -112,6 +118,129 @@ class OCSPTest(unittest.TestCase): self.assertEqual(mock_log.info.call_count, 1) +@unittest.skipIf(not ocsp_lib, + reason='This class tests functionalities available only on cryptography>=2.5.0') +class OSCPTestCryptography(unittest.TestCase): + """ + OCSP revokation tests using Cryptography >= 2.4.0 + """ + + def setUp(self): + from certbot import ocsp + self.checker = ocsp.RevocationChecker() + self.cert_path = test_util.vector_path('google_certificate.pem') + self.chain_path = test_util.vector_path('google_issuer_certificate.pem') + + @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp._check_ocsp_cryptography') + def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): + mock_determine.return_value = ('http://example.com', 'example.com') + self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') + + @mock.patch('certbot.ocsp.requests.post') + @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') + def test_revoke(self, mock_ocsp_response, mock_post): + with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload'): + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertTrue(revoked) + + @mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') + @mock.patch('certbot.ocsp.requests.post') + @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') + def test_revoke_resiliency(self, mock_ocsp_response, mock_post, mock_check): + # Server return an invalid HTTP response + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=400) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # OCSP response in invalid + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # OCSP response is valid, but certificate status is unknown + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # The OCSP response says that the certificate is revoked, but certificate + # does not contain the OCSP extension. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + with mock.patch('cryptography.x509.Extensions.get_extension_for_class', + side_effect=x509.ExtensionNotFound( + 'Not found', x509.AuthorityInformationAccessOID.OCSP)): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # Valid response, OCSP extension is present, + # but OCSP response uses an unsupported signature. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = UnsupportedAlgorithm('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # And now, the signature itself is invalid. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = InvalidSignature('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # Finally, assertion error on OCSP response validity + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = AssertionError('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + +def _construct_mock_ocsp_response(certificate_status, response_status): + cert = x509.load_pem_x509_certificate( + test_util.load_vector('google_certificate.pem'), default_backend()) + issuer = x509.load_pem_x509_certificate( + test_util.load_vector('google_issuer_certificate.pem'), default_backend()) + builder = ocsp_lib.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + + return mock.Mock( + response_status=response_status, + certificate_status=certificate_status, + serial_number=request.serial_number, + issuer_key_hash=request.issuer_key_hash, + issuer_name_hash=request.issuer_name_hash, + hash_algorithm=hashes.SHA1(), + next_update=datetime.now() + timedelta(days=1), + this_update=datetime.now() - timedelta(days=1), + signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1, + ) + + # pylint: disable=line-too-long openssl_confused = ("", """ /etc/letsencrypt/live/example.org/cert.pem: good @@ -165,5 +294,6 @@ revoked """, """Response verify OK""") + if __name__ == '__main__': unittest.main() # pragma: no cover diff --git a/certbot/tests/testdata/google_certificate.pem b/certbot/tests/testdata/google_certificate.pem new file mode 100644 index 000000000..c26fea0b1 --- /dev/null +++ b/certbot/tests/testdata/google_certificate.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1 +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk +IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy +MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB +BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF +Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG +A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD +VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ +w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj +/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE +sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ +xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM +fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag +re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG +A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE +AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0 +oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy +LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy +dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB +FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF +BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS +BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 +U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA +MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY +BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT +Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg +U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE +AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51 +vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV +CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN +8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG +eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v +ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN +HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB +Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk +myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq +WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik= +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/google_issuer_certificate.pem b/certbot/tests/testdata/google_issuer_certificate.pem new file mode 100644 index 000000000..50db47bc4 --- /dev/null +++ b/certbot/tests/testdata/google_issuer_certificate.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw +HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs +U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy +MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg +U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW +XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK +71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9 +RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z +ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT +kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz +AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa +Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu +MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv +b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz +cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc +aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA +HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e +ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq +wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu +FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy +7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV +c7o835DLAFshEWfC7TIe3g== +-----END CERTIFICATE----- diff --git a/certbot/util.py b/certbot/util.py index 416075ce8..097593e9d 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -62,7 +62,7 @@ def run_script(params, log=logger.error): """Run the script with the given params. :param list params: List of parameters to pass to Popen - :param logging.Logger log: Logger to use for errors + :param callable log: Logger method to use for errors """ try: diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 48ddfb570..8aaa1e3fb 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1034,26 +1034,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 1fac78836..dff57dfd5 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -60,26 +60,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 -- cgit v1.2.3 From 7e6a1f248866df8b581f372f3e57355ca4ab4b1c Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 6 Feb 2019 20:02:35 +0200 Subject: Apache plugin: configure all matching domain names to be able to answer HTTP challenge. (#6729) Attempts to configure all of the following VirtualHosts for answering the HTTP challenge: * VirtualHosts that have the requested domain name in either `ServerName` or `ServerAlias` directive. * VirtualHosts that have a wildcard name that would match the requested domain name. This also applies to HTTPS VirtualHosts, making Apache plugin able to handle cases where HTTP redirection takes place in reverse proxy or similar, before reaching the Apache HTTPD. Even though also HTTPS VirtualHosts are selected, Apache plugin tries to ensure that at least one of the selected VirtualHosts listens to HTTP-01 port (configured with `--http-01-port` CLI option). So in a case where only HTTPS VirtualHosts exist, but user wants to configure those, `--http-01-port` parameter needs to be set for the port configured to the HTTPS VirtualHost(s). Fixes: #6730 * Select all matching VirtualHosts for HTTP-01 challenges instead of just one * Finalize PR and add tests * Changelog entry --- CHANGELOG.md | 2 + certbot-apache/certbot_apache/configurator.py | 9 +++-- certbot-apache/certbot_apache/http_01.py | 44 +++++++++++++++++----- .../certbot_apache/tests/configurator_test.py | 22 +++++------ .../certbot_apache/tests/http_01_test.py | 34 +++++++++++------ certbot-apache/certbot_apache/tests/parser_test.py | 2 +- .../apache2/sites-available/duplicatehttp.conf | 9 +++++ .../apache2/sites-available/duplicatehttps.conf | 14 +++++++ .../apache2/sites-enabled/duplicatehttp.conf | 1 + .../apache2/sites-enabled/duplicatehttps.conf | 1 + certbot-apache/certbot_apache/tests/util.py | 12 +++++- 11 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf create mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf create mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf create mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 204b7b328..c529f3bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support on 2.x branch is maintained). +* Apache plugin now attempts to configure all VirtualHosts matching requested + domain name instead of only a single one when answering the HTTP-01 challenge. ### Fixed diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 16de3a3d8..efd766e63 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -577,8 +577,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.assoc[target_name] = vhost return vhost - def included_in_wildcard(self, names, target_name): - """Is target_name covered by a wildcard? + def domain_in_names(self, names, target_name): + """Checks if target domain is covered by one or more of the provided + names. The target name is matched by wildcard as well as exact match. :param names: server aliases :type names: `collections.Iterable` of `str` @@ -649,7 +650,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): names = vhost.get_names() if target_name in names: points = 3 - elif self.included_in_wildcard(names, target_name): + elif self.domain_in_names(names, target_name): points = 2 elif any(addr.get_addr() == target_name for addr in vhost.addrs): points = 1 @@ -1463,7 +1464,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): matches = self.parser.find_dir( "ServerAlias", start=vh_path, exclude=False) aliases = (self.aug.get(match) for match in matches) - return self.included_in_wildcard(aliases, target_name) + return self.domain_in_names(aliases, target_name) def _add_name_vhost_if_necessary(self, vhost): """Add NameVirtualHost Directives if necessary for new vhost. diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py index 22598baca..962433415 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/http_01.py @@ -2,7 +2,7 @@ import logging import os -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.plugins import common from certbot_apache.obj import VirtualHost # pylint: disable=unused-import @@ -89,15 +89,27 @@ class ApacheHttp01(common.TLSSNI01): self.configurator.enable_mod(mod, temp=True) def _mod_config(self): + selected_vhosts = [] # type: List[VirtualHost] + http_port = str(self.configurator.config.http01_port) for chall in self.achalls: - vh = self.configurator.find_best_http_vhost( - chall.domain, filter_defaults=False, - port=str(self.configurator.config.http01_port)) - if vh: - self._set_up_include_directives(vh) - else: - for vh in self._relevant_vhosts(): - self._set_up_include_directives(vh) + # Search for matching VirtualHosts + for vh in self._matching_vhosts(chall.domain): + selected_vhosts.append(vh) + + # Ensure that we have one or more VirtualHosts that we can continue + # with. (one that listens to port configured with --http-01-port) + found = False + for vhost in selected_vhosts: + if any(a.is_wildcard() or a.get_port() == http_port for a in vhost.addrs): + found = True + + if not found: + for vh in self._relevant_vhosts(): + selected_vhosts.append(vh) + + # Add the challenge configuration + for vh in selected_vhosts: + self._set_up_include_directives(vh) self.configurator.reverter.register_file_creation( True, self.challenge_conf_pre) @@ -121,6 +133,20 @@ class ApacheHttp01(common.TLSSNI01): with open(self.challenge_conf_post, "w") as new_conf: new_conf.write(config_text_post) + def _matching_vhosts(self, domain): + """Return all VirtualHost objects that have the requested domain name or + a wildcard name that would match the domain in ServerName or ServerAlias + directive. + """ + matching_vhosts = [] + for vhost in self.configurator.vhosts: + if self.configurator.domain_in_names(vhost.get_names(), domain): + # domain_in_names also matches the exact names, so no need + # to check "domain in vhost.get_names()" explicitly here + matching_vhosts.append(vhost) + + return matching_vhosts + def _relevant_vhosts(self): http01_port = str(self.configurator.config.http01_port) relevant_vhosts = [] diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 4aaa23ea4..ff93682b6 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -139,7 +139,8 @@ class MultipleVhostsTest(util.ApacheTest): names = self.config.get_all_names() self.assertEqual(names, set( ["certbot.demo", "ocspvhost.com", "encryption-example.demo", - "nonsym.link", "vhost.in.rootconf", "www.certbot.demo"] + "nonsym.link", "vhost.in.rootconf", "www.certbot.demo", + "duplicate.example.com"] )) @certbot_util.patch_get_utility() @@ -158,8 +159,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.vhosts.append(vhost) names = self.config.get_all_names() - # Names get filtered, only 5 are returned - self.assertEqual(len(names), 8) + self.assertEqual(len(names), 9) self.assertTrue("zombo.com" in names) self.assertTrue("google.com" in names) self.assertTrue("certbot.demo" in names) @@ -200,7 +200,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_get_virtual_hosts(self): """Make sure all vhosts are being properly found.""" vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 10) + self.assertEqual(len(vhs), 12) found = 0 for vhost in vhs: @@ -211,7 +211,7 @@ class MultipleVhostsTest(util.ApacheTest): else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 10) + self.assertEqual(found, 12) # Handle case of non-debian layout get_virtual_hosts with mock.patch( @@ -219,7 +219,7 @@ class MultipleVhostsTest(util.ApacheTest): ) as mock_conf: mock_conf.return_value = False vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 10) + self.assertEqual(len(vhs), 12) @mock.patch("certbot_apache.display_ops.select_vhost") def test_choose_vhost_none_avail(self, mock_select): @@ -322,7 +322,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.vhosts = [ vh for vh in self.config.vhosts if vh.name not in ["certbot.demo", "nonsym.link", - "encryption-example.demo", + "encryption-example.demo", "duplicate.example.com", "ocspvhost.com", "vhost.in.rootconf"] and "*.blue.purple.com" not in vh.aliases ] @@ -333,7 +333,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_non_default_vhosts(self): # pylint: disable=protected-access vhosts = self.config._non_default_vhosts(self.config.vhosts) - self.assertEqual(len(vhosts), 8) + self.assertEqual(len(vhosts), 10) def test_deploy_cert_enable_new_vhost(self): # Create @@ -688,7 +688,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]), self.config.is_name_vhost(ssl_vhost)) - self.assertEqual(len(self.config.vhosts), 11) + self.assertEqual(len(self.config.vhosts), 13) def test_clean_vhost_ssl(self): # pylint: disable=protected-access @@ -1269,7 +1269,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 11) + self.assertEqual(len(self.config.vhosts), 13) def test_create_own_redirect_for_old_apache_version(self): self.config.parser.modules.add("rewrite_module") @@ -1280,7 +1280,7 @@ class MultipleVhostsTest(util.ApacheTest): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 11) + self.assertEqual(len(self.config.vhosts), 13) def test_sift_rewrite_rule(self): # pylint: disable=protected-access diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py index 9c729b08c..bf7a3719a 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/certbot_apache/tests/http_01_test.py @@ -27,8 +27,8 @@ class ApacheHttp01Test(util.ApacheTest): self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge] vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/multiple_vhosts") - # Takes the vhosts for encryption-example.demo, certbot.demo, and - # vhost.in.rootconf + # Takes the vhosts for encryption-example.demo, certbot.demo + # and vhost.in.rootconf self.vhosts = [vh_truth[0], vh_truth[3], vh_truth[10]] for i in range(NUM_ACHALLS): @@ -39,7 +39,7 @@ class ApacheHttp01Test(util.ApacheTest): "pending"), domain=self.vhosts[i].name, account_key=self.account_key)) - modules = ["rewrite", "authz_core", "authz_host"] + modules = ["ssl", "rewrite", "authz_core", "authz_host"] for mod in modules: self.config.parser.modules.add("mod_{0}.c".format(mod)) self.config.parser.modules.add(mod + "_module") @@ -111,6 +111,17 @@ class ApacheHttp01Test(util.ApacheTest): domain="something.nonexistent", account_key=self.account_key)] self.common_perform_test(achalls, vhosts) + def test_configure_multiple_vhosts(self): + vhosts = [v for v in self.config.vhosts if "duplicate.example.com" in v.get_names()] + self.assertEqual(len(vhosts), 2) + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'a' * 16))), + "pending"), + domain="duplicate.example.com", account_key=self.account_key)] + self.common_perform_test(achalls, vhosts) + def test_no_vhost(self): for achall in self.achalls: self.http.add_chall(achall) @@ -176,15 +187,14 @@ class ApacheHttp01Test(util.ApacheTest): self._test_challenge_file(achall) for vhost in vhosts: - if not vhost.ssl: - matches = self.config.parser.find_dir("Include", - self.http.challenge_conf_pre, - vhost.path) - self.assertEqual(len(matches), 1) - matches = self.config.parser.find_dir("Include", - self.http.challenge_conf_post, - vhost.path) - self.assertEqual(len(matches), 1) + matches = self.config.parser.find_dir("Include", + self.http.challenge_conf_pre, + vhost.path) + self.assertEqual(len(matches), 1) + matches = self.config.parser.find_dir("Include", + self.http.challenge_conf_post, + vhost.path) + self.assertEqual(len(matches), 1) self.assertTrue(os.path.exists(challenge_dir)) diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index a089ec471..5d692eeac 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -52,7 +52,7 @@ class BasicParserTest(util.ParserTest): test2 = self.parser.find_dir("documentroot") self.assertEqual(len(test), 1) - self.assertEqual(len(test2), 7) + self.assertEqual(len(test2), 8) def test_add_dir(self): aug_default = "/files" + self.parser.loc["default"] diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf new file mode 100644 index 000000000..5684651fb --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf @@ -0,0 +1,9 @@ + + ServerName duplicate.example.com + + ServerAdmin webmaster@certbot.demo + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf new file mode 100644 index 000000000..e3ac21fac --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf @@ -0,0 +1,14 @@ + + + ServerName duplicate.example.com + + ServerAdmin webmaster@certbot.demo + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + +SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem +SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem + + diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf new file mode 120000 index 000000000..a69ee3c1d --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf @@ -0,0 +1 @@ +../sites-available/duplicatehttp.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf new file mode 120000 index 000000000..a52ee1ccb --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf @@ -0,0 +1 @@ +../sites-available/duplicatehttps.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 9329ccb20..02de6ada4 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -196,7 +196,17 @@ def get_vh_truth(temp_dir, config_name): "/files" + os.path.join(temp_dir, config_name, "apache2/apache2.conf/VirtualHost"), set([obj.Addr.fromstring("*:80")]), False, True, - "vhost.in.rootconf")] + "vhost.in.rootconf"), + obj.VirtualHost( + os.path.join(prefix, "duplicatehttp.conf"), + os.path.join(aug_pre, "duplicatehttp.conf/VirtualHost"), + set([obj.Addr.fromstring("10.2.3.4:80")]), False, True, + "duplicate.example.com"), + obj.VirtualHost( + os.path.join(prefix, "duplicatehttps.conf"), + os.path.join(aug_pre, "duplicatehttps.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("10.2.3.4:443")]), True, True, + "duplicate.example.com")] return vh_truth if config_name == "debian_apache_2_4/multi_vhosts": prefix = os.path.join( -- cgit v1.2.3 From 2560ef0ffa5aebf5ef54c9a543dfc3edb28314d0 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Feb 2019 12:47:56 -0800 Subject: Test all on push events to tested non-master branches. (#6741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We always run a full set of CI tests before beginning the release process. The way this would work previously is we would either trigger tests on the `test-everything` branch to run through Travis' web UI or if it was a point release, create a new branch based on `test-everything` but modify `.travis.yml` so the branch that was pulled in to be tested was the point release branch instead of `master`. This no longer works because the former `test-everything` tests are now only run when Travis automatically runs our tests nightly. We could create and maintain a separate branch for the purpose of manually running all tests or remove the conditionals from the latest `.travis.yml` file every time before we want to run these tests, but there must be A Better Wayâ„¢. This PR makes the change that in addition to running all tests nightly, they would also run on pushes to tested branches other than master. These changes do not affect the tests run on PRs or on commits to `master`. What is affected is commits to point release branches and branches named `test-*`. (See [.travis.yml](https://github.com/certbot/certbot/blob/2ddaf3db043ea8526ae3f9ab2ef120b194b2e506/.travis.yml#L177) for what branches we run tests on.) Running all tests on point release branches automates the step of running our full test suite before doing a point release. The changes to `test-*` could be a mixed bag, however, since we switched to travis-ci.com over 3 weeks ago, I'm the only one who has used this functionality and I personally prefer things this way. At the very least, since these branches don't seem to be widely used, I think we can make this change and reevaluate if it becomes a problem. * Test all on push events to non-master branches. * Move branches section up. * expand comment --- .travis.yml | 64 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 16cb6f23f..0d59ec9e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,16 @@ before_script: - 'if [ $TRAVIS_OS_NAME = osx ] ; then ulimit -n 1024 ; fi' - export TOX_TESTENV_PASSENV=TRAVIS +# Only build pushes to the master branch, PRs, and branches beginning with +# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of +# simultaneous Travis runs, which speeds turnaround time on review since there +# is a cap of on the number of simultaneous runs. +branches: + only: + - master + - /^\d+\.\d+\.x$/ + - /^test-.*$/ + matrix: include: # These environments are always executed @@ -62,85 +72,87 @@ matrix: - python: "2.7" env: TOXENV=nginxroundtrip - # These environments are executed on cron events only + # These environments are executed on cron events and commits to tested + # branches other than master. Which branches are tested is controlled by + # the "branches" section earlier in this file. - python: "3.7" dist: xenial env: TOXENV=py37 CERTBOT_NO_PIN=1 - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "2.7" env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "2.7" env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "2.7" env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "2.7" env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.4" env: TOXENV=py34 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.4" env: TOXENV=py34 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.5" env: TOXENV=py35 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.5" env: TOXENV=py35 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.6" env: TOXENV=py36 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.6" env: TOXENV=py36 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.7" dist: xenial env: TOXENV=py37 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - python: "3.7" dist: xenial env: TOXENV=py37 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - sudo: required env: TOXENV=le_auto_xenial services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - sudo: required env: TOXENV=le_auto_jessie services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - sudo: required env: TOXENV=le_auto_centos6 services: docker - if: type = cron + if: type = cron OR (type = push AND branch != master) - sudo: required env: TOXENV=docker_dev services: docker @@ -148,7 +160,7 @@ matrix: apt: packages: # don't install nginx and apache - libaugeas0 - if: type = cron + if: type = cron OR (type = push AND branch != master) - language: generic env: TOXENV=py27 os: osx @@ -157,7 +169,7 @@ matrix: packages: - augeas - python2 - if: type = cron + if: type = cron OR (type = push AND branch != master) - language: generic env: TOXENV=py3 os: osx @@ -166,19 +178,7 @@ matrix: packages: - augeas - python3 - if: type = cron - - - -# Only build pushes to the master branch, PRs, and branches beginning with -# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of -# simultaneous Travis runs, which speeds turnaround time on review since there -# is a cap of on the number of simultaneous runs. -branches: - only: - - master - - /^\d+\.\d+\.x$/ - - /^test-.*$/ + if: type = cron OR (type = push AND branch != master) # container-based infrastructure sudo: false -- cgit v1.2.3 From c5baf035df064c854e0c16be3479b3bbaa778b41 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 6 Feb 2019 14:51:52 -0800 Subject: Update CHANGELOG.md (#6745) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c529f3bb4..c119323cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ package with changes other than its version number was: * acme * certbot +* certbot-apache * certbot-dns-cloudxns * certbot-dns-dnsimple * certbot-dns-dnsmadeeasy -- cgit v1.2.3 From ab79d1d44a8fe44c1ec90b71355b550480dbd042 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Feb 2019 16:36:32 -0800 Subject: Revert "Use built-in support for OCSP in cryptography >= 2.5 (#6603) (#6747) I think this is causing failures in some of our tests so this PR reverts the change until we can fix the problem. The 2nd commit is to keep the change using more idiomatic wording in the changelog for another change that got included in this PR. * Revert "Use built-in support for OCSP in cryptography >= 2.5 (#6603)" This reverts commit 2ddaf3db043ea8526ae3f9ab2ef120b194b2e506. * keep changelog correction --- CHANGELOG.md | 3 - certbot/crypto_util.py | 53 ++---- certbot/ocsp.py | 201 +++++---------------- certbot/tests/ocsp_test.py | 172 +++--------------- certbot/tests/testdata/google_certificate.pem | 41 ----- .../tests/testdata/google_issuer_certificate.pem | 26 --- certbot/util.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 40 ++-- .../pieces/dependency-requirements.txt | 40 ++-- 9 files changed, 126 insertions(+), 452 deletions(-) delete mode 100644 certbot/tests/testdata/google_certificate.pem delete mode 100644 certbot/tests/testdata/google_issuer_certificate.pem diff --git a/CHANGELOG.md b/CHANGELOG.md index c119323cc..140d5df40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Avoid reprocessing challenges that are already validated when a certificate is issued. -* If possible, Certbot uses built-in support for OCSP from recent cryptography - versions instead of the OpenSSL binary: as a consequence Certbot does not need - the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. * Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges with the `acme` module. diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 66c68eb38..c4a389cd5 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -19,7 +19,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey # https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography import x509 # type: ignore +from cryptography import x509 # type: ignore from OpenSSL import crypto from OpenSSL import SSL # type: ignore @@ -226,7 +226,7 @@ def verify_renewable_cert(renewable_cert): def verify_renewable_cert_sig(renewable_cert): - """Verifies the signature of a `.storage.RenewableCert` object. + """ Verifies the signature of a `.storage.RenewableCert` object. :param `.storage.RenewableCert` renewable_cert: cert to verify @@ -239,8 +239,22 @@ def verify_renewable_cert_sig(renewable_cert): cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() with warnings.catch_warnings(): - verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, - cert.signature_hash_algorithm) + warnings.simplefilter("ignore") + if isinstance(pk, RSAPublicKey): + # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi + verifier = pk.verifier( # type: ignore + cert.signature, PKCS1v15(), cert.signature_hash_algorithm + ) + verifier.update(cert.tbs_certificate_bytes) + verifier.verify() + elif isinstance(pk, EllipticCurvePublicKey): + verifier = pk.verifier( + cert.signature, ECDSA(cert.signature_hash_algorithm) + ) + verifier.update(cert.tbs_certificate_bytes) + verifier.verify() + else: + raise errors.Error("Unsupported public key type") except (IOError, ValueError, InvalidSignature) as e: error_str = "verifying the signature of the cert located at {0} has failed. \ Details: {1}".format(renewable_cert.cert, e) @@ -248,37 +262,6 @@ def verify_renewable_cert_sig(renewable_cert): raise errors.Error(error_str) -def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm): - """Check the signature of a payload. - - :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature - :param bytes signature: the signature bytes - :param bytes payload: the payload bytes - :param cryptography.hazmat.primitives.hashes.HashAlgorithm - signature_hash_algorithm: algorithm used to hash the payload - - :raises InvalidSignature: If signature verification fails. - :raises errors.Error: If public key type is not supported - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - if isinstance(public_key, RSAPublicKey): - # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi - verifier = public_key.verifier( # type: ignore - signature, PKCS1v15(), signature_hash_algorithm - ) - verifier.update(payload) - verifier.verify() - elif isinstance(public_key, EllipticCurvePublicKey): - verifier = public_key.verifier( - signature, ECDSA(signature_hash_algorithm) - ) - verifier.update(payload) - verifier.verify() - else: - raise errors.Error("Unsupported public key type") - - def verify_cert_matches_priv_key(cert_path, key_path): """ Verifies that the private key and cert match. diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 0abfd3c23..049e14827 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -1,79 +1,53 @@ """Tools for checking certificate revocation.""" import logging import re -from datetime import datetime, timedelta + from subprocess import Popen, PIPE -try: - # Only cryptography>=2.5 has ocsp module - # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error - getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') -except (ImportError, AttributeError): # pragma: no cover - ocsp = None # type: ignore -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -import requests - -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module -from certbot import crypto_util from certbot import errors from certbot import util logger = logging.getLogger(__name__) - class RevocationChecker(object): - """This class figures out OCSP checking on this system, and performs it.""" + "This class figures out OCSP checking on this system, and performs it." - def __init__(self, enforce_openssl_binary_usage=False): + def __init__(self): self.broken = False - self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp - - if self.use_openssl_binary: - if not util.exe_exists("openssl"): - logger.info("openssl not installed, can't check revocation") - self.broken = True - return - - # New versions of openssl want -header var=val, old ones want -header var val - test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) - _out, err = test_host_format.communicate() - if "Missing =" in err: - self.host_args = lambda host: ["Host=" + host] - else: - self.host_args = lambda host: ["Host", host] + + if not util.exe_exists("openssl"): + logger.info("openssl not installed, can't check revocation") + self.broken = True + return + + # New versions of openssl want -header var=val, old ones want -header var val + test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], + stdout=PIPE, stderr=PIPE, universal_newlines=True) + _out, err = test_host_format.communicate() + if "Missing =" in err: + self.host_args = lambda host: ["Host=" + host] + else: + self.host_args = lambda host: ["Host", host] + def ocsp_revoked(self, cert_path, chain_path): - # type: (str, str) -> bool """Get revoked status for a particular cert version. .. todo:: Make this a non-blocking call :param str cert_path: Path to certificate :param str chain_path: Path to intermediate cert + :rtype bool or None: :returns: True if revoked; False if valid or the check failed - :rtype: bool """ if self.broken: return False - url, host = _determine_ocsp_server(cert_path) - if not host or not url: - return False - - if self.use_openssl_binary: - return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) - else: - return _check_ocsp_cryptography(cert_path, chain_path, url) - def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): - # type: (str, str, str, str) -> bool + url, host = self.determine_ocsp_server(cert_path) + if not host: + return False # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! cmd = ["openssl", "ocsp", "-no_nonce", @@ -91,117 +65,33 @@ class RevocationChecker(object): except errors.SubprocessError: logger.info("OCSP check failed for %s (are we offline?)", cert_path) return False - return _translate_ocsp_query(cert_path, output, err) - - -def _determine_ocsp_server(cert_path): - # type: (str) -> Tuple[Optional[str], Optional[str]] - """Extract the OCSP server host from a certificate. - - :param str cert_path: Path to the cert we're checking OCSP for - :rtype tuple: - :returns: (OCSP server URL or None, OCSP server host or None) - - """ - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - try: - extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) - ocsp_oid = x509.AuthorityInformationAccessOID.OCSP - descriptions = [description for description in extension.value - if description.access_method == ocsp_oid] - - url = descriptions[0].access_location.value - except (x509.ExtensionNotFound, IndexError): - logger.info("Cannot extract OCSP URI from %s", cert_path) - return None, None - - url = url.rstrip() - host = url.partition("://")[2].rstrip("/") - if host: - return url, host - else: - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None - - -def _check_ocsp_cryptography(cert_path, chain_path, url): - # type: (str, str, str) -> bool - # Retrieve OCSP response - with open(chain_path, 'rb') as file_handler: - issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - builder = ocsp.OCSPRequestBuilder() - builder = builder.add_certificate(cert, issuer, hashes.SHA1()) - request = builder.build() - request_binary = request.public_bytes(serialization.Encoding.DER) - response = requests.post(url, data=request_binary, - headers={'Content-Type': 'application/ocsp-request'}) - if response.status_code != 200: - logger.info("OCSP check failed for %s (are we offline?)", cert_path) - return False - response_ocsp = ocsp.load_der_ocsp_response(response.content) - - # Check OCSP response validity - if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: - logger.error("Invalid OCSP response status for %s: %s", - cert_path, response_ocsp.response_status) - return False - - # Check OCSP signature - try: - _check_ocsp_response(response_ocsp, request, issuer) - except UnsupportedAlgorithm as e: - logger.error(str(e)) - except errors.Error as e: - logger.error(str(e)) - except InvalidSignature: - logger.error('Invalid signature on OCSP response for %s', cert_path) - except AssertionError as error: - logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) - else: - # Check OCSP certificate status - logger.debug("OCSP certificate status for %s is: %s", - cert_path, response_ocsp.certificate_status) - return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED - - return False - - -def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): - """Verify that the OCSP is valid for serveral criterias""" - # Assert OCSP response corresponds to the certificate we are talking about - if response_ocsp.serial_number != request_ocsp.serial_number: - raise AssertionError('the certificate in response does not correspond ' - 'to the certificate in request') - - # Assert signature is valid - _check_ocsp_response_signature(response_ocsp, issuer_cert) + return _translate_ocsp_query(cert_path, output, err) - # Assert issuer in response is the expected one - if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) - or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash - or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): - raise AssertionError('the issuer does not correspond to issuer of the certificate.') - # Assert nextUpdate is in the future, and that thisUpdate is not too old - if response_ocsp.next_update: - if response_ocsp.next_update < datetime.now() - timedelta(minutes=5): - raise AssertionError('next update is in the past.') - interval = response_ocsp.next_update - response_ocsp.this_update - if datetime.now() - response_ocsp.this_update > interval + timedelta(minutes=5): - raise AssertionError('this update is too old.') + def determine_ocsp_server(self, cert_path): + """Extract the OCSP server host from a certificate. + :param str cert_path: Path to the cert we're checking OCSP for + :rtype tuple: + :returns: (OCSP server URL or None, OCSP server host or None) -def _check_ocsp_response_signature(response_ocsp, issuer_cert): - """Verify an OCSP response signature against certificate issuer""" - # Following line may raise UnsupportedAlgorithm - chosen_hash = response_ocsp.signature_hash_algorithm - crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature, - response_ocsp.tbs_response_bytes, chosen_hash) + """ + try: + url, _err = util.run_script( + ["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"], + log=logger.debug) + except errors.SubprocessError: + logger.info("Cannot extract OCSP URI from %s", cert_path) + return None, None + url = url.rstrip() + host = url.partition("://")[2].rstrip("/") + if host: + return url, host + else: + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): """Parse openssl's weird output to work out what it means.""" @@ -212,7 +102,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): warning = good.group(1) if good else None - if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: + if (not "Response verify OK" in ocsp_errors) or (good and warning) or unknown: logger.info("Revocation status for %s is unknown", cert_path) logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) return False @@ -225,5 +115,6 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False + diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index ad3467e5a..55cd24adb 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,33 +1,18 @@ """Tests for ocsp.py""" # pylint: disable=protected-access + import unittest -from datetime import datetime, timedelta -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -from cryptography import x509 -try: - # Only cryptography>=2.5 has ocsp module - # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp as ocsp_lib # pylint: disable=import-error - getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm') -except (ImportError, AttributeError): # pragma: no cover - ocsp_lib = None # type: ignore import mock from certbot import errors -from certbot.tests import util as test_util out = """Missing = in header key=value ocsp: Use -help for summary. """ +class OCSPTest(unittest.TestCase): -class OCSPTestOpenSSL(unittest.TestCase): - """ - OCSP revokation tests using OpenSSL binary. - """ def setUp(self): from certbot import ocsp @@ -37,7 +22,7 @@ class OCSPTestOpenSSL(unittest.TestCase): mock_communicate.communicate.return_value = (None, out) mock_popen.return_value = mock_communicate mock_exists.return_value = True - self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) + self.checker = ocsp.RevocationChecker() def tearDown(self): pass @@ -52,23 +37,23 @@ class OCSPTestOpenSSL(unittest.TestCase): mock_exists.return_value = True from certbot import ocsp - checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) + checker = ocsp.RevocationChecker() self.assertEqual(mock_popen.call_count, 1) self.assertEqual(checker.host_args("x"), ["Host=x"]) mock_communicate.communicate.return_value = (None, out.partition("\n")[2]) - checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) + checker = ocsp.RevocationChecker() self.assertEqual(checker.host_args("x"), ["Host", "x"]) self.assertEqual(checker.broken, False) mock_exists.return_value = False mock_popen.call_count = 0 - checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) + checker = ocsp.RevocationChecker() self.assertEqual(mock_popen.call_count, 0) self.assertEqual(mock_log.call_count, 1) self.assertEqual(checker.broken, True) - @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp.RevocationChecker.determine_ocsp_server') @mock.patch('certbot.util.run_script') def test_ocsp_revoked(self, mock_run, mock_determine): self.checker.broken = True @@ -86,12 +71,21 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) self.assertEqual(mock_run.call_count, 2) - def test_determine_ocsp_server(self): - cert_path = test_util.vector_path('google_certificate.pem') - from certbot import ocsp - result = ocsp._determine_ocsp_server(cert_path) - self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result) + @mock.patch('certbot.ocsp.logger.info') + @mock.patch('certbot.util.run_script') + def test_determine_ocsp_server(self, mock_run, mock_info): + uri = "http://ocsp.stg-int-x1.letsencrypt.org/" + host = "ocsp.stg-int-x1.letsencrypt.org" + mock_run.return_value = uri, "" + self.assertEqual(self.checker.determine_ocsp_server("beep"), (uri, host)) + mock_run.return_value = "ftp:/" + host + "/", "" + self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) + self.assertEqual(mock_info.call_count, 1) + + c = "confusion" + mock_run.side_effect = errors.SubprocessError(c) + self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') @@ -118,129 +112,6 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(mock_log.info.call_count, 1) -@unittest.skipIf(not ocsp_lib, - reason='This class tests functionalities available only on cryptography>=2.5.0') -class OSCPTestCryptography(unittest.TestCase): - """ - OCSP revokation tests using Cryptography >= 2.4.0 - """ - - def setUp(self): - from certbot import ocsp - self.checker = ocsp.RevocationChecker() - self.cert_path = test_util.vector_path('google_certificate.pem') - self.chain_path = test_util.vector_path('google_issuer_certificate.pem') - - @mock.patch('certbot.ocsp._determine_ocsp_server') - @mock.patch('certbot.ocsp._check_ocsp_cryptography') - def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): - mock_determine.return_value = ('http://example.com', 'example.com') - self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') - - @mock.patch('certbot.ocsp.requests.post') - @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') - def test_revoke(self, mock_ocsp_response, mock_post): - with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload'): - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertTrue(revoked) - - @mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') - @mock.patch('certbot.ocsp.requests.post') - @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') - def test_revoke_resiliency(self, mock_ocsp_response, mock_post, mock_check): - # Server return an invalid HTTP response - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=400) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # OCSP response in invalid - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED) - mock_post.return_value = mock.Mock(status_code=200) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # OCSP response is valid, but certificate status is unknown - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # The OCSP response says that the certificate is revoked, but certificate - # does not contain the OCSP extension. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - with mock.patch('cryptography.x509.Extensions.get_extension_for_class', - side_effect=x509.ExtensionNotFound( - 'Not found', x509.AuthorityInformationAccessOID.OCSP)): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # Valid response, OCSP extension is present, - # but OCSP response uses an unsupported signature. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = UnsupportedAlgorithm('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # And now, the signature itself is invalid. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = InvalidSignature('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - # Finally, assertion error on OCSP response validity - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = AssertionError('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - - self.assertFalse(revoked) - - -def _construct_mock_ocsp_response(certificate_status, response_status): - cert = x509.load_pem_x509_certificate( - test_util.load_vector('google_certificate.pem'), default_backend()) - issuer = x509.load_pem_x509_certificate( - test_util.load_vector('google_issuer_certificate.pem'), default_backend()) - builder = ocsp_lib.OCSPRequestBuilder() - builder = builder.add_certificate(cert, issuer, hashes.SHA1()) - request = builder.build() - - return mock.Mock( - response_status=response_status, - certificate_status=certificate_status, - serial_number=request.serial_number, - issuer_key_hash=request.issuer_key_hash, - issuer_name_hash=request.issuer_name_hash, - hash_algorithm=hashes.SHA1(), - next_update=datetime.now() + timedelta(days=1), - this_update=datetime.now() - timedelta(days=1), - signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1, - ) - - # pylint: disable=line-too-long openssl_confused = ("", """ /etc/letsencrypt/live/example.org/cert.pem: good @@ -294,6 +165,5 @@ revoked """, """Response verify OK""") - if __name__ == '__main__': unittest.main() # pragma: no cover diff --git a/certbot/tests/testdata/google_certificate.pem b/certbot/tests/testdata/google_certificate.pem deleted file mode 100644 index c26fea0b1..000000000 --- a/certbot/tests/testdata/google_certificate.pem +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1 -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk -IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy -MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB -BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF -Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG -A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD -VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ -w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj -/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE -sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ -xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM -fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag -re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG -A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE -AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0 -oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy -LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy -dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB -FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF -BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS -BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 -U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA -MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY -BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT -Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg -U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE -AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51 -vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV -CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN -8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG -eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v -ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN -HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB -Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk -myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq -WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik= ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/google_issuer_certificate.pem b/certbot/tests/testdata/google_issuer_certificate.pem deleted file mode 100644 index 50db47bc4..000000000 --- a/certbot/tests/testdata/google_issuer_certificate.pem +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw -HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs -U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy -MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg -U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW -XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK -71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9 -RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z -ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT -kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz -AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH -AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa -Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu -MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv -b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz -cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc -aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA -HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e -ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq -wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu -FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy -7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV -c7o835DLAFshEWfC7TIe3g== ------END CERTIFICATE----- diff --git a/certbot/util.py b/certbot/util.py index 097593e9d..416075ce8 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -62,7 +62,7 @@ def run_script(params, log=logger.error): """Run the script with the given params. :param list params: List of parameters to pass to Popen - :param callable log: Logger method to use for errors + :param logging.Logger log: Logger to use for errors """ try: diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 8aaa1e3fb..48ddfb570 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1034,26 +1034,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 +cryptography==2.2.2 \ + --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ + --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ + --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ + --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ + --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ + --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ + --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ + --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ + --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ + --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ + --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ + --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ + --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ + --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ + --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ + --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ + --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ + --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ + --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index dff57dfd5..1fac78836 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -60,26 +60,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 +cryptography==2.2.2 \ + --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ + --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ + --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ + --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ + --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ + --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ + --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ + --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ + --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ + --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ + --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ + --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ + --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ + --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ + --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ + --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ + --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ + --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ + --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 -- cgit v1.2.3 From 67828562a0349421488c4fce1e1953c34f4e1b53 Mon Sep 17 00:00:00 2001 From: J0WI Date: Thu, 7 Feb 2019 18:06:04 +0100 Subject: Upgrade to Alpine 3.9 (#6743) Alpine 3.9 comes with OpenSSL 1.1.1. --- CHANGELOG.md | 2 ++ Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 140d5df40..8022e90cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* Certbot's official Docker images are now based on Alpine Linux 3.9 rather + than 3.7. The new version comes with OpenSSL 1.1.1. * Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support on 2.x branch is maintained). * Apache plugin now attempts to configure all VirtualHosts matching requested diff --git a/Dockerfile b/Dockerfile index f3626dc8d..8f434e89c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:2-alpine3.7 +FROM python:2-alpine3.9 ENTRYPOINT [ "certbot" ] EXPOSE 80 443 @@ -12,7 +12,7 @@ COPY certbot src/certbot RUN apk add --no-cache --virtual .certbot-deps \ libffi \ - libssl1.0 \ + libssl1.1 \ openssl \ ca-certificates \ binutils -- cgit v1.2.3 From 432e18d943782b308f77eb8225ba03617cb12c6d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Feb 2019 12:40:45 -0800 Subject: Revert "Call atexit handlers before test tearDown to remove errors on Windows (#6667)" (#6752) This reverts commit ca25d1b66a24e351739a6b4dbfadd8f471857721. --- certbot/tests/lock_test.py | 7 ++----- certbot/tests/util.py | 31 +++++++++++++++---------------- certbot/tests/util_test.py | 24 +++++++++++++++--------- certbot/util.py | 6 +++--- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index 2ade47827..aa82701f3 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -10,6 +10,7 @@ from certbot import errors from certbot.tests import util as test_util +@test_util.broken_on_windows class LockDirTest(test_util.TempDirTestCase): """Tests for certbot.lock.lock_dir.""" @classmethod @@ -24,6 +25,7 @@ class LockDirTest(test_util.TempDirTestCase): test_util.lock_and_call(assert_raises, lock_path) +@test_util.broken_on_windows class LockFileTest(test_util.TempDirTestCase): """Tests for certbot.lock.LockFile.""" @classmethod @@ -35,7 +37,6 @@ class LockFileTest(test_util.TempDirTestCase): super(LockFileTest, self).setUp() self.lock_path = os.path.join(self.tempdir, 'test.lock') - @test_util.broken_on_windows def test_acquire_without_deletion(self): # acquire the lock in another process but don't delete the file child = multiprocessing.Process(target=self._call, @@ -53,7 +54,6 @@ class LockFileTest(test_util.TempDirTestCase): self.assertRaises, errors.LockError, self._call, self.lock_path) test_util.lock_and_call(assert_raises, self.lock_path) - @test_util.broken_on_windows def test_locked_repr(self): lock_file = self._call(self.lock_path) locked_repr = repr(lock_file) @@ -71,7 +71,6 @@ class LockFileTest(test_util.TempDirTestCase): self.assertTrue(lock_file.__class__.__name__ in lock_repr) self.assertTrue(self.lock_path in lock_repr) - @test_util.broken_on_windows def test_race(self): should_delete = [True, False] stat = os.stat @@ -87,13 +86,11 @@ class LockFileTest(test_util.TempDirTestCase): self._call(self.lock_path) self.assertFalse(should_delete) - @test_util.broken_on_windows def test_removed(self): lock_file = self._call(self.lock_path) lock_file.release() self.assertFalse(os.path.exists(self.lock_path)) - @test_util.broken_on_windows @mock.patch('certbot.compat.fcntl.lockf') def test_unexpected_lockf_err(self, mock_lockf): msg = 'hi there' diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 38a9075c1..8c5db2c2f 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -3,15 +3,14 @@ .. warning:: This module is not part of the public API. """ -import logging import multiprocessing import os import pkg_resources import shutil -import stat import tempfile import unittest import sys +import warnings from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -329,22 +328,22 @@ class TempDirTestCase(unittest.TestCase): def tearDown(self): """Execute after test""" - # Cleanup opened resources after a test. This is usually done through atexit handlers in - # Certbot, but during tests, atexit will not run registered functions before tearDown is - # called and instead will run them right before the entire test process exits. - # It is a problem on Windows, that does not accept to clean resources before closing them. - logging.shutdown() - util._release_locks() # pylint: disable=protected-access - - def handle_rw_files(_, path, __): - """Handle read-only files, that will fail to be removed on Windows.""" - os.chmod(path, stat.S_IWRITE) - os.remove(path) - shutil.rmtree(self.tempdir, onerror=handle_rw_files) - + # On Windows we have various files which are not correctly closed at the time of tearDown. + # For know, we log them until a proper file close handling is written. + # Useful for development only, so no warning when we are on a CI process. + def onerror_handler(_, path, excinfo): + """On error handler""" + if not os.environ.get('APPVEYOR'): # pragma: no cover + message = ('Following error occurred when deleting the tempdir {0}' + ' for path {1} during tearDown process: {2}' + .format(self.tempdir, path, str(excinfo))) + warnings.warn(message) + shutil.rmtree(self.tempdir, onerror=onerror_handler) class ConfigTestCase(TempDirTestCase): - """Test class which sets up a NamespaceConfig object.""" + """Test class which sets up a NamespaceConfig object. + + """ def setUp(self): super(ConfigTestCase, self).setUp() self.config = configuration.NamespaceConfig( diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 81d0629c8..6685b88c6 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -191,12 +191,7 @@ class CheckPermissionsTest(test_util.TempDirTestCase): def test_wrong_mode(self): os.chmod(self.tempdir, 0o400) - try: - self.assertFalse(self._call(0o600)) - finally: - # Without proper write permissions, Windows is unable to delete a folder, - # even with admin permissions. Write access must be explicitly set first. - os.chmod(self.tempdir, 0o700) + self.assertFalse(self._call(0o600)) class UniqueFileTest(test_util.TempDirTestCase): @@ -282,9 +277,20 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): for f, _ in items: f.close() - def test_failure(self): - with mock.patch("certbot.util.os.open", side_effect=OSError(errno.EIO)): - self.assertRaises(OSError, self._call, "wow") + @mock.patch("certbot.util.os.fdopen") + def test_failure(self, mock_fdopen): + err = OSError("whoops") + err.errno = errno.EIO + mock_fdopen.side_effect = err + self.assertRaises(OSError, self._call, "wow") + + @mock.patch("certbot.util.os.fdopen") + def test_subsequent_failure(self, mock_fdopen): + self._call("wow") + err = OSError("whoops") + err.errno = errno.EIO + mock_fdopen.side_effect = err + self.assertRaises(OSError, self._call, "wow") class SafelyRemoveTest(test_util.TempDirTestCase): diff --git a/certbot/util.py b/certbot/util.py index 416075ce8..d7c542465 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -142,7 +142,6 @@ def _release_locks(): except: # pylint: disable=bare-except msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock) logger.debug(msg, exc_info=True) - _LOCKS.clear() def set_up_core_dir(directory, mode, uid, strict): @@ -226,8 +225,9 @@ def safe_open(path, mode="w", chmod=None, buffering=None): fdopen_args = () # type: Union[Tuple[()], Tuple[int]] if buffering is not None: fdopen_args = (buffering,) - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) - return os.fdopen(fd, mode, *fdopen_args) + return os.fdopen( + os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args), + mode, *fdopen_args) def _unique_file(path, filename_pat, count, chmod, mode): -- cgit v1.2.3 From ee3c14cbab9ed2df7ef69aca9171936918f95438 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Feb 2019 13:20:30 -0800 Subject: Update changelog for 0.31.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8022e90cc..30f813ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.31.0 - master +## 0.31.0 - 2019-02-07 ### Added -- cgit v1.2.3 From 75499277be6699fd5a9b884837546391950a3ec9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Feb 2019 13:27:10 -0800 Subject: Release 0.31.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 84 ++++----------------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 6 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 6 +- certbot-dns-dnsmadeeasy/setup.py | 6 +- certbot-dns-gehirn/setup.py | 6 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 6 +- certbot-dns-luadns/setup.py | 6 +- certbot-dns-nsone/setup.py | 6 +- certbot-dns-ovh/setup.py | 6 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 6 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 20 ++--- letsencrypt-auto | 84 ++++----------------- letsencrypt-auto-source/certbot-auto.asc | 16 ++-- letsencrypt-auto-source/letsencrypt-auto | 26 +++---- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++--- 26 files changed, 112 insertions(+), 216 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index eac3974fa..1a7d54704 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.31.0.dev0' +version = '0.31.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 14d6cacb6..e7270cb34 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index a79a9c5ae..03c2994e3 100755 --- a/certbot-auto +++ b/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.30.2" +LE_AUTO_VERSION="0.31.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -333,63 +333,11 @@ BootstrapDebCommon() { fi augeas_pkg="libaugeas0 augeas-lenses" - AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` if [ "$ASSUME_YES" = 1 ]; then YES_FLAG="-y" fi - AddBackportRepo() { - # ARGS: - BACKPORT_NAME="$1" - BACKPORT_SOURCELINE="$2" - say "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME." - if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then - # This can theoretically error if sources.list.d is empty, but in that case we don't care. - if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..." - sleep 1s - add_backports=1 - else - read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response - case $response in - [yY][eE][sS]|[yY]|"") - add_backports=1;; - *) - add_backports=0;; - esac - fi - if [ "$add_backports" = 1 ]; then - sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list" - apt-get $QUIET_FLAG update - fi - fi - fi - if [ "$add_backports" != 0 ]; then - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg - augeas_pkg= - fi - } - - - if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then - if lsb_release -a | grep -q wheezy ; then - AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" - elif lsb_release -a | grep -q precise ; then - # XXX add ARM case - AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" - else - echo "No libaugeas0 version is available that's new enough to run the" - echo "Certbot apache plugin..." - fi - # XXX add a case for ubuntu PPAs - fi - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \ python \ python-dev \ @@ -1140,9 +1088,9 @@ parsedatetime==2.1 \ pbr==1.8.1 \ --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==16.2.0 \ - --hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \ - --hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e +pyOpenSSL==18.0.0 \ + --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ + --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 pyparsing==2.1.8 \ --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ @@ -1232,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f519ed422..649054661 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index ff33293fe..ab98e04e1 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 1a6f900d8..e52d9ac06 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 2f7fa37d6..f918bf622 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1a2ce5d92..a4225aec0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 0a99f452d..732ec768e 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f4a75379c..f6356e2a0 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index c99ad38aa..e2d335cbd 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 31c2c20bc..52af79c4f 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,12 +1,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 31472e8cf..cee34eb9f 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 41b99cc73..ed4dbedfd 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 5b3329568..cd0214429 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index edf7b6ba6..0faa2d4a0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 69c2c7ed3..3f5dfe76a 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 4ebfc6e1d..098dd9dad 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 70e11f62b..2b4941147 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.31.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index bf68034c8..51c53a9dd 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.31.0.dev0' +__version__ = '0.31.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index cd6d431b3..e95b2fcd5 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.30.2 + "". (default: CertbotACMEClient/0.31.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -351,8 +351,9 @@ revoke: Specify reason for revoking certificate. (default: unspecified) --delete-after-revoke - Delete certificates after revoking them. (default: - None) + Delete certificates after revoking them, along with + all previous and later versions of those certificates. + (default: None) --no-delete-after-revoke Do not delete certificates after revoking them. This option should be used with caution because the 'renew' @@ -479,10 +480,9 @@ apache: Apache Web Server plugin --apache-enmod APACHE_ENMOD - Path to the Apache 'a2enmod' binary (default: a2enmod) + Path to the Apache 'a2enmod' binary (default: None) --apache-dismod APACHE_DISMOD - Path to the Apache 'a2dismod' binary (default: - a2dismod) + Path to the Apache 'a2dismod' binary (default: None) --apache-le-vhost-ext APACHE_LE_VHOST_EXT SSL vhost configuration extension (default: -le- ssl.conf) @@ -496,16 +496,16 @@ apache: /var/log/apache2) --apache-challenge-location APACHE_CHALLENGE_LOCATION Directory path for challenge configuration (default: - /etc/apache2) + /etc/apache2/other) --apache-handle-modules APACHE_HANDLE_MODULES Let installer handle enabling required modules for you - (Only Ubuntu/Debian currently) (default: True) + (Only Ubuntu/Debian currently) (default: False) --apache-handle-sites APACHE_HANDLE_SITES Let installer handle enabling sites for you (Only - Ubuntu/Debian currently) (default: True) + Ubuntu/Debian currently) (default: False) --apache-ctl APACHE_CTL Full path to Apache control script (default: - apache2ctl) + apachectl) dns-cloudflare: Obtain certificates using a DNS TXT record (if you are using Cloudflare diff --git a/letsencrypt-auto b/letsencrypt-auto index a79a9c5ae..03c2994e3 100755 --- a/letsencrypt-auto +++ b/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.30.2" +LE_AUTO_VERSION="0.31.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -333,63 +333,11 @@ BootstrapDebCommon() { fi augeas_pkg="libaugeas0 augeas-lenses" - AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` if [ "$ASSUME_YES" = 1 ]; then YES_FLAG="-y" fi - AddBackportRepo() { - # ARGS: - BACKPORT_NAME="$1" - BACKPORT_SOURCELINE="$2" - say "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME." - if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then - # This can theoretically error if sources.list.d is empty, but in that case we don't care. - if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..." - sleep 1s - add_backports=1 - else - read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response - case $response in - [yY][eE][sS]|[yY]|"") - add_backports=1;; - *) - add_backports=0;; - esac - fi - if [ "$add_backports" = 1 ]; then - sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list" - apt-get $QUIET_FLAG update - fi - fi - fi - if [ "$add_backports" != 0 ]; then - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg - augeas_pkg= - fi - } - - - if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then - if lsb_release -a | grep -q wheezy ; then - AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" - elif lsb_release -a | grep -q precise ; then - # XXX add ARM case - AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" - else - echo "No libaugeas0 version is available that's new enough to run the" - echo "Certbot apache plugin..." - fi - # XXX add a case for ubuntu PPAs - fi - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \ python \ python-dev \ @@ -1140,9 +1088,9 @@ parsedatetime==2.1 \ pbr==1.8.1 \ --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==16.2.0 \ - --hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \ - --hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e +pyOpenSSL==18.0.0 \ + --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ + --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 pyparsing==2.1.8 \ --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ @@ -1232,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index a60ccd8bb..d95f9abcb 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlxLcw8ACgkQTRfJlc2X -dfK35gf+PoxtrJJIjvybNqd3lb8HOg2ntIVmXcYJGuuUo6m09fzai+XI6cOm5Dpu -l2D5OrbLqmez8tYkCkEWHV0OfwyVWw+m8T3sXlcrv14eA1RfgMnZ+cmmlpDskzHU -EOtaXo1/IkLDwBRrsl8IUbwD2XxbjuLsA2Sevoa59NlfTXJUApfAzohl3epRiJjB -gugdqcsfjRRAqQqOz+iJCKBCWSTIrr/g6Y9aZu9V93t/WDSLRFjehxO1GQrLnCnX -17JGlr0/AXd67jOKS1OWmORPPAFfLIXezUMtgrz5hE7T5UviaUu9ySV8UCxq1N79 -cfSBb/HIUxZ0wf1CkTUMRFQpA7cGtw== -=cNcT +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlxcop8ACgkQTRfJlc2X +dfIbZwf/faKu7IjLi0qFQ+kw8zaAnV47JDgfWqbR5GSdwWPqld+QyHlcRfPgwYma +fKj9+g/FvPNPSfjHRRCoFrYvpZ4lZ+f4HPN9+OjydfM77rdDhVDwzs8dbKIk02yU +0IEJhXj5Q9hF3TSDZcyXAJdBU1lz51ohtVIXelMBPmzhYPCZF47iE9/k9pApQi86 +RTji7hxPcF/n7mzXrbyTvk+kDxSdDlE0Eg9syK7XaFDBTa2lqgG8wTnMPVqhc/hm +WM/uwkzbYarjy05ffV1kM683nP0rECnHlYT38pYcT2puw2kn/QthwR5j/jB/DWSc +94Kw7BeMH651V8EaNwYIiouylnVH3A== +=U+Qh -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 48ddfb570..03c2994e3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.31.0.dev0" +LE_AUTO_VERSION="0.31.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1180,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index f5175187a..3ecca1510 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 80249cd9a..514d3271b 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 -- cgit v1.2.3 From 917dc16b302d7bac421c19472f1554c8e6991cab Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Feb 2019 13:27:12 -0800 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f813ac6..8f0b2d04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.32.0 - 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. + ## 0.31.0 - 2019-02-07 ### Added -- cgit v1.2.3 From 381d097895182b2bfd3ef5a9ecd17d7c7bea6ea4 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Feb 2019 13:27:13 -0800 Subject: Bump version to 0.32.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 4 ++-- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 4 ++-- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/local-oldest-requirements.txt | 4 ++-- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/local-oldest-requirements.txt | 4 ++-- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/local-oldest-requirements.txt | 4 ++-- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/local-oldest-requirements.txt | 4 ++-- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/local-oldest-requirements.txt | 4 ++-- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/local-oldest-requirements.txt | 4 ++-- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/local-oldest-requirements.txt | 4 ++-- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 29 files changed, 38 insertions(+), 38 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 1a7d54704..6ec226d26 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.31.0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e7270cb34..52528a536 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 649054661..e1c82b063 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index ab98e04e1..d6a16f468 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e52d9ac06..f0f2446e1 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index f918bf622..c61f05339 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index a4225aec0..7d39a10d0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 732ec768e..8a5f234b5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f6356e2a0..67ae1d4eb 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index e2d335cbd..429702d75 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 52af79c4f..6797501b9 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index cee34eb9f..07d9decdf 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index ed4dbedfd..314b0a16c 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 01cbcb317..b7e6072af 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,3 +1,3 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index cd0214429..a878eac47 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0faa2d4a0..39f35e953 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 3f5dfe76a..e915c6b86 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 098dd9dad..429f960f7 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 2b4941147..f78473ada 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 51c53a9dd..767448a12 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.31.0' +__version__ = '0.32.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 03c2994e3..02656521f 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.31.0" +LE_AUTO_VERSION="0.32.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 66c9767623a97c44974b0aafbd234915e8822002 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 12 Feb 2019 14:59:34 -0800 Subject: Fix #6501 (#6761) --- CHANGELOG.md | 6 ++++-- acme/setup.py | 4 +++- setup.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f0b2d04d..ac0f60eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the + warnings described at https://github.com/certbot/josepy/issues/13. ### Fixed @@ -20,7 +21,8 @@ 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 More details about these changes can be found on our GitHub repo. diff --git a/acme/setup.py b/acme/setup.py index 6ec226d26..79d6d3389 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -11,7 +11,9 @@ install_requires = [ # rsa_recover_prime_factors (>=0.8) 'cryptography>=1.2.3', # formerly known as acme.jose: - 'josepy>=1.0.0', + # 1.1.0+ is required to avoid the warnings described at + # https://github.com/certbot/josepy/issues/13. + 'josepy>=1.1.0', # Connection.set_tlsext_host_name (>=0.13) 'mock', 'PyOpenSSL>=0.13.1', diff --git a/setup.py b/setup.py index 9e6af2d4f..14fef37f3 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,9 @@ install_requires = [ 'ConfigArgParse>=0.9.3', 'configobj', 'cryptography>=1.2.3', # load_pem_x509_certificate - 'josepy', + # 1.1.0+ is required to avoid the warnings described at + # https://github.com/certbot/josepy/issues/13. + 'josepy>=1.1.0', 'mock', 'parsedatetime>=1.3', # Calendar.parseDT 'pyrfc3339', -- cgit v1.2.3 From a0a8292ff26a2d062e75b865d9b9b10977dc1f80 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 13 Feb 2019 00:36:27 +0100 Subject: Correct the Content-Type used in the POST-as-GET request to retrieve a cert (#6757) --- acme/acme/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acme/acme/client.py b/acme/acme/client.py index 41338e17e..76b6a7788 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -739,8 +739,7 @@ class ClientV2(ClientBase): if body.error is not None: raise errors.IssuanceError(body.error) if body.certificate is not None: - certificate_response = self._post_as_get(body.certificate, - content_type=DER_CONTENT_TYPE).text + certificate_response = self._post_as_get(body.certificate).text return orderr.update(body=body, fullchain_pem=certificate_response) raise errors.TimeoutError() -- cgit v1.2.3 From f10f98fec536caca74017af2c51a6b4868ddc50c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 12 Feb 2019 16:54:04 -0800 Subject: More carefully check for certbot --version output. (#6762) --- tests/letstest/scripts/test_leauto_upgrades.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 0c2b374f2..e08a0710c 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -23,7 +23,7 @@ fi # started failing on newer distros with newer versions of OpenSSL. INITIAL_VERSION="0.17.0" git checkout -f "v$INITIAL_VERSION" letsencrypt-auto -if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | grep "$INITIAL_VERSION" ; then +if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail exit 1 fi @@ -85,7 +85,7 @@ if [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; fi # Create a 2nd venv at the new path to ensure we properly handle this case export VENV_PATH="/opt/eff.org/certbot/venv" - if ! sudo -E ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | grep "$INITIAL_VERSION" ; then + if ! sudo -E ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo second installation appeared to fail exit 1 fi @@ -98,7 +98,7 @@ if ./letsencrypt-auto -v --debug --version | grep "WARNING: couldn't find Python fi EXPECTED_VERSION=$(grep -m1 LE_AUTO_VERSION certbot-auto | cut -d\" -f2) -if ! /opt/eff.org/certbot/venv/bin/letsencrypt --version 2>&1 | grep "$EXPECTED_VERSION" ; then +if ! /opt/eff.org/certbot/venv/bin/letsencrypt --version 2>&1 | tail -n1 | grep "^certbot $EXPECTED_VERSION$" ; then echo upgrade appeared to fail exit 1 fi -- cgit v1.2.3 From cff8769db737f19a93fe26cd1d5247904c4b188a Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 13 Feb 2019 18:37:01 +0200 Subject: Apache: respect CERTBOT_DOCS environment variable (#6598) Apache plugin will now use command line default values from `ApacheConfingurator.OS_DEFAULTS` instead of respective distribution override when `CERTBOT_DOCS=1` environment variable is present. Fixes: #6234 * Apache: respect CERTBOT_DOCS environment variable * Move the tests to apache plugin --- CHANGELOG.md | 3 +++ certbot-apache/certbot_apache/configurator.py | 31 +++++++++++++++------- .../certbot_apache/tests/configurator_test.py | 31 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac0f60eb8..57a895e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the warnings described at https://github.com/certbot/josepy/issues/13. +* Apache plugin now respects CERTBOT_DOCS environment variable when adding + command line defaults. ### Fixed @@ -23,6 +25,7 @@ package with changes other than its version number was: * acme * certbot +* certbot-apache More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index efd766e63..f7f4ff925 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -92,6 +92,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ description = "Apache Web Server plugin" + if os.environ.get("CERTBOT_DOCS") == "1": + description += ( # pragma: no cover + " (Please note that the default values of the Apache plugin options" + " change depending on the operating system Certbot is run on.)" + ) OS_DEFAULTS = dict( server_root="/etc/apache2", @@ -141,28 +146,36 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # When adding, modifying or deleting command line arguments, be sure to # include the changes in the list used in method _prepare_options() to # ensure consistent behavior. - add("enmod", default=cls.OS_DEFAULTS["enmod"], + + # Respect CERTBOT_DOCS environment variable and use default values from + # base class regardless of the underlying distribution (overrides). + if os.environ.get("CERTBOT_DOCS") == "1": + DEFAULTS = ApacheConfigurator.OS_DEFAULTS + else: + # cls.OS_DEFAULTS can be distribution specific, see override classes + DEFAULTS = cls.OS_DEFAULTS + add("enmod", default=DEFAULTS["enmod"], help="Path to the Apache 'a2enmod' binary") - add("dismod", default=cls.OS_DEFAULTS["dismod"], + add("dismod", default=DEFAULTS["dismod"], help="Path to the Apache 'a2dismod' binary") - add("le-vhost-ext", default=cls.OS_DEFAULTS["le_vhost_ext"], + add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"], help="SSL vhost configuration extension") - add("server-root", default=cls.OS_DEFAULTS["server_root"], + add("server-root", default=DEFAULTS["server_root"], help="Apache server root directory") add("vhost-root", default=None, help="Apache server VirtualHost configuration root") - add("logs-root", default=cls.OS_DEFAULTS["logs_root"], + add("logs-root", default=DEFAULTS["logs_root"], help="Apache server logs directory") add("challenge-location", - default=cls.OS_DEFAULTS["challenge_location"], + default=DEFAULTS["challenge_location"], help="Directory path for challenge configuration") - add("handle-modules", default=cls.OS_DEFAULTS["handle_modules"], + add("handle-modules", default=DEFAULTS["handle_modules"], help="Let installer handle enabling required modules for you " + "(Only Ubuntu/Debian currently)") - add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"], + add("handle-sites", default=DEFAULTS["handle_sites"], help="Let installer handle enabling sites for you " + "(Only Ubuntu/Debian currently)") - add("ctl", default=cls.OS_DEFAULTS["ctl"], + add("ctl", default=DEFAULTS["ctl"], help="Full path to Apache control script") util.add_deprecated_argument( add, argument_name="init-script", nargs=1) diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index ff93682b6..7c281d707 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -115,6 +115,37 @@ class MultipleVhostsTest(util.ApacheTest): # Weak test.. ApacheConfigurator.add_parser_arguments(mock.MagicMock()) + def test_docs_parser_arguments(self): + os.environ["CERTBOT_DOCS"] = "1" + from certbot_apache.configurator import ApacheConfigurator + mock_add = mock.MagicMock() + ApacheConfigurator.add_parser_arguments(mock_add) + parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", + "vhost_root", "logs_root", "challenge_location", + "handle_modules", "handle_sites", "ctl"] + exp = dict() + + for k in ApacheConfigurator.OS_DEFAULTS: + if k in parserargs: + exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] + # Special cases + exp["vhost-root"] = None + exp["init-script"] = None + + found = set() + for call in mock_add.call_args_list: + # init-script is a special case: deprecated argument + if call[0][0] != "init-script": + self.assertEqual(exp[call[0][0]], call[1]['default']) + found.add(call[0][0]) + + # Make sure that all (and only) the expected values exist + self.assertEqual(len(mock_add.call_args_list), len(found)) + for e in exp: + self.assertTrue(e in found) + + del os.environ["CERTBOT_DOCS"] + def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use from certbot_apache.entrypoint import OVERRIDE_CLASSES for cls in OVERRIDE_CLASSES.values(): -- cgit v1.2.3 From acc0b1e773de076e71f628b0b64e69f85fc9a39b Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 14 Feb 2019 19:43:27 +0100 Subject: Fix the pebble fetch script (#6765) This PR updates and fixes `pebble-fetch.sh` considering latest improvements done on Pebble, to start a working instance. * Fix the pebble fetch script * Update pebble-fetch.sh * Update tox.ini --- tests/pebble-fetch.sh | 33 +++++++++++++++++++++++++-------- tox.ini | 1 - 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/tests/pebble-fetch.sh b/tests/pebble-fetch.sh index b0ba08961..6b562eec2 100755 --- a/tests/pebble-fetch.sh +++ b/tests/pebble-fetch.sh @@ -2,7 +2,7 @@ # Download and run Pebble instance for integration testing set -xe -PEBBLE_VERSION=2018-11-02 +PEBBLE_VERSION=v1.0.1 # 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. @@ -13,15 +13,32 @@ 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: + image: letsencrypt/pebble:${PEBBLE_VERSION} + command: pebble -dnsserver 10.30.50.3:8053 + environment: - PEBBLE_VA_NOSLEEP=1 + ports: + - 14000:14000 + networks: + acmenet: + ipv4_address: 10.30.50.2 + challtestsrv: + image: letsencrypt/pebble-challtestsrv:${PEBBLE_VERSION} + command: pebble-challtestsrv -defaultIPv6 "" -defaultIPv4 10.30.50.1 + ports: + - 8055:8055 + networks: + acmenet: + ipv4_address: 10.30.50.3 +networks: + acmenet: + driver: bridge + ipam: + driver: default + config: + - subnet: 10.30.50.0/24 UNLIKELY_EOF docker-compose -f "$PEBBLEPATH/docker-compose.yml" up -d pebble diff --git a/tox.ini b/tox.ini index b66e330da..b386ebc86 100644 --- a/tox.ini +++ b/tox.ini @@ -166,7 +166,6 @@ passenv = HOME GOPATH PEBBLEPATH - PEBBLE_STRICT setenv = SERVER=https://localhost:14000/dir -- cgit v1.2.3 From 583d40f5cf5bc22799eadb99b9900e4cff17b4a9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 14 Feb 2019 15:26:44 -0800 Subject: Pin pytest in test_sdists.sh. (#6764) * pin pytest in test_sdists.sh. * Use pip_install.py in test_tests.sh. --- tests/letstest/scripts/test_sdists.sh | 2 ++ tests/letstest/scripts/test_tests.sh | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index 0b9a91ffd..260a0acfb 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -12,6 +12,8 @@ export VENV_ARGS="-p $PYTHON" # setup venv tools/_venv_common.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt . ./venv/bin/activate +# pytest is needed to run tests on some of our packages so we install a pinned version here. +tools/pip_install.py pytest # build sdists for pkg_dir in acme . $PLUGINS; do diff --git a/tests/letstest/scripts/test_tests.sh b/tests/letstest/scripts/test_tests.sh index e6ab836b8..d5fd6e14a 100755 --- a/tests/letstest/scripts/test_tests.sh +++ b/tests/letstest/scripts/test_tests.sh @@ -1,18 +1,20 @@ #!/bin/sh -xe -LE_AUTO="letsencrypt/letsencrypt-auto-source/letsencrypt-auto" +REPO_ROOT="letsencrypt" +LE_AUTO="$REPO_ROOT/letsencrypt-auto-source/letsencrypt-auto" LE_AUTO="$LE_AUTO --debug --no-self-upgrade --non-interactive" MODULES="acme certbot certbot_apache certbot_nginx" +PIP_INSTALL="$REPO_ROOT/tools/pip_install.py" VENV_NAME=venv # *-auto respects VENV_PATH $LE_AUTO --os-packages-only LE_AUTO_SUDO="" VENV_PATH="$VENV_NAME" $LE_AUTO --no-bootstrap --version . $VENV_NAME/bin/activate +"$PIP_INSTALL" pytest # change to an empty directory to ensure CWD doesn't affect tests cd $(mktemp -d) -pip install pytest==3.2.5 for module in $MODULES ; do echo testing $module -- cgit v1.2.3 From e40d929e804137566c0e04f87f9d5c62a7966a4a Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 15 Feb 2019 01:55:27 +0100 Subject: [Windows|Unix] New platform independent locking mechanism - the revenge (#6663) First PR about this issue, #6440, involved to much refactoring to ensure a correct behavior on Linux and installer plugins. This PR proposes a new implementation of a lock mechanism for Linux and Windows, without implying a refactoring. It takes strictly the existing behavior for Linux, and add the appropriate logic for Windows. The `lock` module formalizes two independant mechanism dedicated to each platform, to improve maintainability. Tests related to locking are re-activated for Windows, or definitively skipped because of irrelevancy for this platform. 6 more tests are enabled overall. * Reimplement lock file on basic level * Remove unused code * Re-activate some tests * Update doc * Reactivate tests relevant to locks in Windows. Correct a test that was not testing what is supposed to test. * Clean compat. * Move close sooner in Windows lock implementation * Add strong mypy types * Use os.name * Refactor lock mechanism logic * Enable more tests * Update lock.py * Update lock_test.py --- certbot/compat.py | 56 ------------ certbot/lock.py | 207 ++++++++++++++++++++++++++++++++++++--------- certbot/tests/lock_test.py | 59 ++++++++----- certbot/tests/util_test.py | 16 ++-- 4 files changed, 214 insertions(+), 124 deletions(-) diff --git a/certbot/compat.py b/certbot/compat.py index 3b5d068a6..f533f5954 100644 --- a/certbot/compat.py +++ b/certbot/compat.py @@ -13,13 +13,6 @@ import stat from certbot import errors -try: - # Linux specific - import fcntl # pylint: disable=import-error -except ImportError: - # Windows specific - import msvcrt # pylint: disable=import-error - UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ 'certificates', 'enhance', 'revoke', 'delete', 'register', 'unregister', 'config_changes', 'plugins'] @@ -118,55 +111,6 @@ def readline_with_timeout(timeout, prompt): return sys.stdin.readline() -def lock_file(fd): - """ - Lock the file linked to the specified file descriptor. - - :param int fd: The file descriptor of the file to lock. - - """ - if 'fcntl' in sys.modules: - # Linux specific - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - else: - # Windows specific - msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) - - -def release_locked_file(fd, path): - """ - Remove, close, and release a lock file specified by its file descriptor and its path. - - :param int fd: The file descriptor of the lock file. - :param str path: The path of the lock file. - - """ - # Linux specific - # - # It is important the lock file is removed before it's released, - # otherwise: - # - # process A: open lock file - # process B: release lock file - # process A: lock file - # process A: check device and inode - # process B: delete file - # process C: open and lock a different file at the same path - try: - os.remove(path) - except OSError as err: - if err.errno == errno.EACCES: - # Windows specific - # We will not be able to remove a file before closing it. - # To avoid race conditions described for Linux, we will not delete the lockfile, - # just close it to be reused on the next Certbot call. - pass - else: - raise - finally: - os.close(fd) - - def compare_file_modes(mode1, mode2): """Return true if the two modes can be considered as equals for this platform""" if os.name != 'nt': diff --git a/certbot/lock.py b/certbot/lock.py index 3ff46518d..760a12b8f 100644 --- a/certbot/lock.py +++ b/certbot/lock.py @@ -1,15 +1,23 @@ -"""Implements file locks for locking files and directories in UNIX.""" +"""Implements file locks compatible with Linux and Windows for locking files and directories.""" import errno import logging import os +try: + import fcntl # pylint: disable=import-error +except ImportError: + import msvcrt # pylint: disable=import-error + POSIX_MODE = False +else: + POSIX_MODE = True -from certbot import compat from certbot import errors +from acme.magic_typing import Optional, Callable # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) def lock_dir(dir_path): + # type: (str) -> LockFile """Place a lock file on the directory at dir_path. The lock file is placed in the root of dir_path with the name @@ -27,34 +35,99 @@ def lock_dir(dir_path): class LockFile(object): - """A UNIX lock file. - - This lock file is released when the locked file is closed or the - process exits. It cannot be used to provide synchronization between - threads. It is based on the lock_file package by Martin Horcicka. - + """ + Platform independent file lock system. + LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, + instance is created, the associated file is 'locked from the point of view of the OS, + meaning that if another instance of Certbot try at the same time to acquire the same lock, + it will raise an Exception. Calling release method will release the lock, and make it + available to every other instance. + Upon exit, Certbot will also release all the locks. + This allows us to protect a file or directory from being concurrently accessed + or modified by two Certbot instances. + LockFile is platform independent: it will proceed to the appropriate OS lock mechanism + depending on Linux or Windows. """ def __init__(self, path): - """Initialize and acquire the lock file. + # type: (str) -> None + """ + Create a LockFile instance on the given file path, and acquire lock. + :param str path: the path to the file that will hold a lock + """ + self._path = path + mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism + self._lock_mechanism = mechanism(path) - :param str path: path to the file to lock + self.acquire() + def __repr__(self): + # type: () -> str + repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) + if self.is_locked(): + repr_str += 'acquired>' + else: + repr_str += 'released>' + return repr_str + + def acquire(self): + # type: () -> None + """ + Acquire the lock on the file, forbidding any other Certbot instance to acquire it. :raises errors.LockError: if unable to acquire the lock + """ + self._lock_mechanism.acquire() + def release(self): + # type: () -> None """ - super(LockFile, self).__init__() - self._path = path - self._fd = None + Release the lock on the file, allowing any other Certbot instance to acquire it. + """ + self._lock_mechanism.release() - self.acquire() + def is_locked(self): + # type: () -> bool + """ + Check if the file is currently locked. + :return: True if the file is locked, False otherwise + """ + return self._lock_mechanism.is_locked() - def acquire(self): - """Acquire the lock file. - :raises errors.LockError: if lock is already held - :raises OSError: if unable to open or stat the lock file +class _BaseLockMechanism(object): + def __init__(self, path): + # type: (str) -> None + """ + Create a lock file mechanism for Unix. + :param str path: the path to the lock file + """ + self._path = path + self._fd = None # type: Optional[int] + def is_locked(self): + # type: () -> bool + """Check if lock file is currently locked. + :return: True if the lock file is locked + :rtype: bool """ + return self._fd is not None + + def acquire(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + def release(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + +class _UnixLockMechanism(_BaseLockMechanism): + """ + A UNIX lock file mechanism. + This lock file is released when the locked file is closed or the + process exits. It cannot be used to provide synchronization between + threads. It is based on the lock_file package by Martin Horcicka. + """ + def acquire(self): + # type: () -> None + """Acquire the lock.""" while self._fd is None: # Open the file fd = os.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) @@ -68,33 +141,29 @@ class LockFile(object): os.close(fd) def _try_lock(self, fd): - """Try to acquire the lock file without blocking. - + # type: (int) -> None + """ + Try to acquire the lock file without blocking. :param int fd: file descriptor of the opened file to lock - """ try: - compat.lock_file(fd) + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as err: if err.errno in (errno.EACCES, errno.EAGAIN): - logger.debug( - "A lock on %s is held by another process.", self._path) - raise errors.LockError( - "Another instance of Certbot is already running.") + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') raise def _lock_success(self, fd): - """Did we successfully grab the lock? - + # type: (int) -> bool + """ + Did we successfully grab the lock? Because this class deletes the locked file when the lock is released, it is possible another process removed and recreated the file between us opening the file and acquiring the lock. - :param int fd: file descriptor of the opened file to lock - :returns: True if the lock was successfully acquired :rtype: bool - """ try: stat1 = os.stat(self._path) @@ -108,17 +177,75 @@ class LockFile(object): # the same device and inode, they're the same file. return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino - def __repr__(self): - repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) - if self._fd is None: - repr_str += 'released>' - else: - repr_str += 'acquired>' - return repr_str - def release(self): + # type: () -> None """Remove, close, and release the lock file.""" + # It is important the lock file is removed before it's released, + # otherwise: + # + # process A: open lock file + # process B: release lock file + # process A: lock file + # process A: check device and inode + # process B: delete file + # process C: open and lock a different file at the same path + try: + os.remove(self._path) + finally: + # Following check is done to make mypy happy: it ensure that self._fd, marked + # as Optional[int] is effectively int to make it compatible with os.close signature. + if self._fd is None: # pragma: no cover + raise TypeError('Error, self._fd is None.') + try: + os.close(self._fd) + finally: + self._fd = None + + +class _WindowsLockMechanism(_BaseLockMechanism): + """ + A Windows lock file mechanism. + By default on Windows, acquiring a file handler gives exclusive access to the process + and results in an effective lock. However, it is possible to explicitly acquire the + file handler in shared access in terms of read and write, and this is done by os.open + and io.open in Python. So an explicit lock needs to be done through the call of + msvcrt.locking, that will lock the first byte of the file. In theory, it is also + possible to access a file in shared delete access, allowing other processes to delete an + opened file. But this needs also to be done explicitly by all processes using the Windows + low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers + state that deleting a file opened by a process from another process is not possible with + os.open and io.open. + Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race + condition encountered on Linux is not possible on Windows, leading to a simpler workflow. + """ + def acquire(self): + """Acquire the lock""" + open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC + + fd = os.open(self._path, open_mode, 0o600) try: - compat.release_locked_file(self._fd, self._path) + msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) + except (IOError, OSError) as err: + os.close(fd) + # Anything except EACCES is unexpected. Raise directly the error in that case. + if err.errno != errno.EACCES: + raise + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + + self._fd = fd + + def release(self): + """Release the lock.""" + try: + msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) + os.close(self._fd) + + try: + os.remove(self._path) + except OSError as e: + # If the lock file cannot be removed, it is not a big deal. + # Likely another instance is acquiring the lock we just released. + logger.debug(str(e)) finally: self._fd = None diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index aa82701f3..aa1de299b 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -3,6 +3,12 @@ import functools import multiprocessing import os import unittest +try: + import fcntl # pylint: disable=import-error,unused-import +except ImportError: + POSIX_MODE = False +else: + POSIX_MODE = True import mock @@ -10,7 +16,6 @@ from certbot import errors from certbot.tests import util as test_util -@test_util.broken_on_windows class LockDirTest(test_util.TempDirTestCase): """Tests for certbot.lock.lock_dir.""" @classmethod @@ -18,6 +23,7 @@ class LockDirTest(test_util.TempDirTestCase): from certbot.lock import lock_dir return lock_dir(*args, **kwargs) + @test_util.broken_on_windows def test_it(self): assert_raises = functools.partial( self.assertRaises, errors.LockError, self._call, self.tempdir) @@ -25,7 +31,6 @@ class LockDirTest(test_util.TempDirTestCase): test_util.lock_and_call(assert_raises, lock_path) -@test_util.broken_on_windows class LockFileTest(test_util.TempDirTestCase): """Tests for certbot.lock.LockFile.""" @classmethod @@ -49,6 +54,7 @@ class LockFileTest(test_util.TempDirTestCase): # Test we're still able to properly acquire and release the lock self.test_removed() + @test_util.broken_on_windows def test_contention(self): assert_raises = functools.partial( self.assertRaises, errors.LockError, self._call, self.lock_path) @@ -71,6 +77,8 @@ class LockFileTest(test_util.TempDirTestCase): self.assertTrue(lock_file.__class__.__name__ in lock_repr) self.assertTrue(self.lock_path in lock_repr) + @test_util.skip_on_windows( + 'Race conditions on lock are specific to the non-blocking file access approach on Linux.') def test_race(self): should_delete = [True, False] stat = os.stat @@ -91,27 +99,36 @@ class LockFileTest(test_util.TempDirTestCase): lock_file.release() self.assertFalse(os.path.exists(self.lock_path)) - @mock.patch('certbot.compat.fcntl.lockf') - def test_unexpected_lockf_err(self, mock_lockf): + def test_unexpected_lockf_or_locking_err(self): + if POSIX_MODE: + mocked_function = 'certbot.lock.fcntl.lockf' + else: + mocked_function = 'certbot.lock.msvcrt.locking' msg = 'hi there' - mock_lockf.side_effect = IOError(msg) - try: - self._call(self.lock_path) - except IOError as err: - self.assertTrue(msg in str(err)) - else: # pragma: no cover - self.fail('IOError not raised') - - @mock.patch('certbot.lock.os.stat') - def test_unexpected_stat_err(self, mock_stat): + with mock.patch(mocked_function) as mock_lock: + mock_lock.side_effect = IOError(msg) + try: + self._call(self.lock_path) + except IOError as err: + self.assertTrue(msg in str(err)) + else: # pragma: no cover + self.fail('IOError not raised') + + def test_unexpected_os_err(self): + if POSIX_MODE: + mock_function = 'certbot.lock.os.stat' + else: + mock_function = 'certbot.lock.msvcrt.locking' + # The only expected errno are ENOENT and EACCES in lock module. msg = 'hi there' - mock_stat.side_effect = OSError(msg) - try: - self._call(self.lock_path) - except OSError as err: - self.assertTrue(msg in str(err)) - else: # pragma: no cover - self.fail('OSError not raised') + with mock.patch(mock_function) as mock_os: + mock_os.side_effect = OSError(msg) + try: + self._call(self.lock_path) + except OSError as err: + self.assertTrue(msg in str(err)) + else: # pragma: no cover + self.fail('OSError not raised') if __name__ == "__main__": diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 6685b88c6..c85009c62 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -2,7 +2,6 @@ import argparse import errno import os -import shutil import unittest import mock @@ -88,7 +87,6 @@ class LockDirUntilExit(test_util.TempDirTestCase): import certbot.util reload_module(certbot.util) - @test_util.broken_on_windows @mock.patch('certbot.util.logger') @mock.patch('certbot.util.atexit_register') def test_it(self, mock_register, mock_logger): @@ -100,11 +98,15 @@ class LockDirUntilExit(test_util.TempDirTestCase): self.assertEqual(mock_register.call_count, 1) registered_func = mock_register.call_args[0][0] - shutil.rmtree(subdir) - registered_func() # exception not raised - # logger.debug is only called once because the second call - # to lock subdir was ignored because it was already locked - self.assertEqual(mock_logger.debug.call_count, 1) + + from certbot import util + # Despite lock_dir_until_exit has been called twice to subdir, its lock should have been + # added only once. So we expect to have two lock references: for self.tempdir and subdir + self.assertTrue(len(util._LOCKS) == 2) # pylint: disable=protected-access + registered_func() # Exception should not be raised + # Logically, logger.debug, that would be invoked in case of unlock failure, + # should never been called. + self.assertEqual(mock_logger.debug.call_count, 0) class SetUpCoreDirTest(test_util.TempDirTestCase): -- cgit v1.2.3 From 0489ca588851430d03c45fecec56fc1ca8223c83 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 16 Feb 2019 03:51:22 +0100 Subject: [Windows] Fixes lock_and_call test method (#6772) The method `lock_and_call`, in `certbot.tests.util` is designed to acquire a lock on a foreign process, then execute a callable in the current process. This is done to closely reproduce the lock mechanism involved between two certbot instances that are running in parallel. This method uses the `multiprocessing` module. But its implementation in `lock_and_call` is broken for Windows: the two processes fail to communicate, leading to a deadlock. In fact, `multiprocessing` module is using the fork mechanism on Linux, and the spawn mechanism on Windows, leading to behavior inconsistencies between the two platforms. As this method is for tests, and not for production code, I did not try to make two implementations "by the book", one suitable for Windows, the other for Linux, like for the `certbot.lock` module. Instead, I use a `subprocess` approach with a trigger file allowing to coordinate the current process and the subprocess. With this, `lock_and_call` is running from the same code both on Linux and Windows. Relevant tests in the `certbot.tests.lock_test` test module are now enabled for Windows. * Implement new lock_and_call method * Reactivate tests for Windows --- certbot/tests/lock_test.py | 2 - certbot/tests/util.py | 95 +++++++++++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index aa1de299b..8658443d0 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -23,7 +23,6 @@ class LockDirTest(test_util.TempDirTestCase): from certbot.lock import lock_dir return lock_dir(*args, **kwargs) - @test_util.broken_on_windows def test_it(self): assert_raises = functools.partial( self.assertRaises, errors.LockError, self._call, self.tempdir) @@ -54,7 +53,6 @@ class LockFileTest(test_util.TempDirTestCase): # Test we're still able to properly acquire and release the lock self.test_removed() - @test_util.broken_on_windows def test_contention(self): assert_raises = functools.partial( self.assertRaises, errors.LockError, self._call, self.lock_path) diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 8c5db2c2f..953d36536 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -3,7 +3,6 @@ .. warning:: This module is not part of the public API. """ -import multiprocessing import os import pkg_resources import shutil @@ -11,6 +10,8 @@ import tempfile import unittest import sys import warnings +import subprocess +import time from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -23,8 +24,8 @@ from six.moves import reload_module # pylint: disable=import-error from certbot import constants from certbot import interfaces from certbot import storage -from certbot import util from certbot import configuration +from certbot import util from certbot.display import util as display_util @@ -211,7 +212,7 @@ class FreezableMock(object): """ def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT): - self._frozen_set = set() if frozen else set(('freeze',)) + self._frozen_set = set() if frozen else {'freeze', } self._func = func self._mock = mock.MagicMock() if return_value != mock.sentinel.DEFAULT: @@ -340,6 +341,7 @@ class TempDirTestCase(unittest.TestCase): warnings.warn(message) shutil.rmtree(self.tempdir, onerror=onerror_handler) + class ConfigTestCase(TempDirTestCase): """Test class which sets up a NamespaceConfig object. @@ -358,47 +360,58 @@ class ConfigTestCase(TempDirTestCase): self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path'] self.config.server = "https://example.com" -def lock_and_call(func, lock_path): - """Grab a lock for lock_path and call func. - - :param callable func: object to call after acquiring the lock - :param str lock_path: path to file or directory to lock +def lock_and_call(callback, path_to_lock): + """Grab a lock on path_to_lock from a foreign process and call the callback. + :param callable callback: object to call after acquiring the lock + :param str path_to_lock: path to file or directory to lock """ - # Reload module to reset internal _LOCKS dictionary + script = """\ +import os +import sys +import time +from certbot import lock + +path_to_lock = sys.argv[1] +trigger = sys.argv[2] + +if os.path.isdir(path_to_lock): + my_lock = lock.lock_dir(path_to_lock) +else: + my_lock = lock.LockFile(path_to_lock) +try: + open(trigger, 'w').close() + while os.path.exists(trigger): + time.sleep(1) +finally: + my_lock.release() +""" + # Reload certbot.util module to reset internal _LOCKS dictionary. reload_module(util) - # start child and wait for it to grab the lock - cv = multiprocessing.Condition() - cv.acquire() - child_args = (cv, lock_path,) - child = multiprocessing.Process(target=hold_lock, args=child_args) - child.start() - cv.wait() - - # call func and terminate the child - func() - cv.notify() - cv.release() - child.join() - assert child.exitcode == 0 - -def hold_lock(cv, lock_path): # pragma: no cover - """Acquire a file lock at lock_path and wait to release it. - - :param multiprocessing.Condition cv: condition for synchronization - :param str lock_path: path to the file lock + workspace = tempfile.mkdtemp() + try: + tmp_script = os.path.join(workspace, 'test_script.py') + with open(tmp_script, 'w') as file_handle: + file_handle.write(script) + + # Trigger file is used to coordinate current process and its subprocess. + trigger = os.path.join(workspace, 'trigger') + process = subprocess.Popen([sys.executable, tmp_script, path_to_lock, trigger]) + try: + # Poll and wait for the lock to be acquired, spotted by the trigger file creation. + while not os.path.exists(trigger): + time.sleep(1) + # Then execute the callback. + callback() + finally: + # This will trigger the lock release in subprocess. + os.remove(trigger) + process.communicate() + assert process.returncode == 0 + finally: + shutil.rmtree(workspace) - """ - from certbot import lock - if os.path.isdir(lock_path): - my_lock = lock.lock_dir(lock_path) - else: - my_lock = lock.LockFile(lock_path) - cv.acquire() - cv.notify() - cv.wait() - my_lock.release() def skip_on_windows(reason): """Decorator to skip permanently a test on Windows. A reason is required.""" @@ -407,6 +420,7 @@ def skip_on_windows(reason): return unittest.skipIf(sys.platform == 'win32', reason)(function) return wrapper + def broken_on_windows(function): """Decorator to skip temporarily a broken test on Windows.""" reason = 'Test is broken and ignored on windows but should be fixed.' @@ -415,9 +429,10 @@ def broken_on_windows(function): and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', reason)(function) + def temp_join(path): """ Return the given path joined to the tempdir path for the current platform Eg.: 'cert' => /tmp/cert (Linux) or 'C:\\Users\\currentuser\\AppData\\Temp\\cert' (Windows) """ - return os.path.join(tempfile.gettempdir(), path) + return os.path.join(tempfile.gettempdir(), path) -- cgit v1.2.3 From 209a0c4d2ce6ddd553326e10e71a012b05027bfe Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 20 Feb 2019 00:15:06 +0100 Subject: [Windows] Refactor lock_and_call using queues (#6778) * Refactor lock_and_call using queues * Update util.py * Replace queue by event * Add comments * Update certbot/tests/util.py Co-Authored-By: adferrand * Update certbot/tests/util.py Co-Authored-By: adferrand * Add control on timeout --- certbot/tests/util.py | 83 +++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 953d36536..289f2dd9b 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -10,8 +10,7 @@ import tempfile import unittest import sys import warnings -import subprocess -import time +from multiprocessing import Process, Event from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -25,6 +24,7 @@ from certbot import constants from certbot import interfaces from certbot import storage from certbot import configuration +from certbot import lock from certbot import util from certbot.display import util as display_util @@ -361,56 +361,49 @@ class ConfigTestCase(TempDirTestCase): self.config.server = "https://example.com" +def _handle_lock(event_in, event_out, path): + """ + Acquire a file lock on given path, then wait to release it. This worker is coordinated + using events to signal when the lock should be acquired and released. + :param multiprocessing.Event event_in: event object to signal when to release the lock + :param multiprocessing.Event event_out: event object to signal when the lock is acquired + :param path: the path to lock + """ + if os.path.isdir(path): + my_lock = lock.lock_dir(path) + else: + my_lock = lock.LockFile(path) + try: + event_out.set() + assert event_in.wait(timeout=20), 'Timeout while waiting to release the lock.' + finally: + my_lock.release() + + def lock_and_call(callback, path_to_lock): - """Grab a lock on path_to_lock from a foreign process and call the callback. + """ + Grab a lock on path_to_lock from a foreign process then execute the callback. :param callable callback: object to call after acquiring the lock :param str path_to_lock: path to file or directory to lock """ - script = """\ -import os -import sys -import time -from certbot import lock - -path_to_lock = sys.argv[1] -trigger = sys.argv[2] - -if os.path.isdir(path_to_lock): - my_lock = lock.lock_dir(path_to_lock) -else: - my_lock = lock.LockFile(path_to_lock) -try: - open(trigger, 'w').close() - while os.path.exists(trigger): - time.sleep(1) -finally: - my_lock.release() -""" # Reload certbot.util module to reset internal _LOCKS dictionary. reload_module(util) - workspace = tempfile.mkdtemp() - try: - tmp_script = os.path.join(workspace, 'test_script.py') - with open(tmp_script, 'w') as file_handle: - file_handle.write(script) - - # Trigger file is used to coordinate current process and its subprocess. - trigger = os.path.join(workspace, 'trigger') - process = subprocess.Popen([sys.executable, tmp_script, path_to_lock, trigger]) - try: - # Poll and wait for the lock to be acquired, spotted by the trigger file creation. - while not os.path.exists(trigger): - time.sleep(1) - # Then execute the callback. - callback() - finally: - # This will trigger the lock release in subprocess. - os.remove(trigger) - process.communicate() - assert process.returncode == 0 - finally: - shutil.rmtree(workspace) + emit_event = Event() + receive_event = Event() + process = Process(target=_handle_lock, args=(emit_event, receive_event, path_to_lock)) + process.start() + + # Wait confirmation that lock is acquired + assert receive_event.wait(timeout=10), 'Timeout while waiting to acquire the lock.' + # Execute the callback + callback() + # Trigger unlock from foreign process + emit_event.set() + + # Wait for process termination + process.join(timeout=10) + assert process.exitcode == 0 def skip_on_windows(reason): -- cgit v1.2.3 From bda840b3eec938829306956a0b3e7d4f691ddac2 Mon Sep 17 00:00:00 2001 From: sblondon Date: Wed, 20 Feb 2019 01:08:20 +0100 Subject: add version parameter when the help message is displayed (#6780) --- certbot/cli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/certbot/cli.py b/certbot/cli.py index 31f55711f..398124510 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -108,7 +108,7 @@ manage your account with Let's Encrypt: # This is the short help for certbot --help, where we disable argparse # altogether -HELP_USAGE = """ +HELP_AND_VERSION_USAGE = """ More detailed help: -h, --help [TOPIC] print this message, or detailed help on a topic; @@ -117,6 +117,8 @@ More detailed help: all, automation, commands, paths, security, testing, or any of the subcommands or plugins (certonly, renew, install, register, nginx, apache, standalone, webroot, etc.) + + --version print the version number """ @@ -566,7 +568,7 @@ class HelpfulArgumentParser(object): usage = SHORT_USAGE if help_arg == True: - self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_USAGE) + self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) sys.exit(0) elif help_arg in self.COMMANDS_TOPICS: self.notify(usage + self._list_subcommands()) -- cgit v1.2.3 From 7c731599a0140868acdb609bc20345f0bd0bd3aa Mon Sep 17 00:00:00 2001 From: sydneyli Date: Fri, 18 Jan 2019 17:09:19 -0800 Subject: Generate constraints file to pin deps in Docker images Dockerfiles pin versions using constraints file Pulling out strip_hashes and add --no-deps flag --- Dockerfile | 8 +++++++- certbot-dns-cloudflare/Dockerfile | 2 +- certbot-dns-cloudxns/Dockerfile | 2 +- certbot-dns-digitalocean/Dockerfile | 2 +- certbot-dns-dnsimple/Dockerfile | 2 +- certbot-dns-dnsmadeeasy/Dockerfile | 2 +- certbot-dns-gehirn/Dockerfile | 2 +- certbot-dns-google/Dockerfile | 2 +- certbot-dns-linode/Dockerfile | 2 +- certbot-dns-luadns/Dockerfile | 2 +- certbot-dns-nsone/Dockerfile | 2 +- certbot-dns-ovh/Dockerfile | 2 +- certbot-dns-rfc2136/Dockerfile | 2 +- certbot-dns-route53/Dockerfile | 2 +- certbot-dns-sakuracloud/Dockerfile | 2 +- tools/dev_constraints.txt | 2 +- tools/pip_install.py | 6 ++---- tools/strip_hashes.py | 16 ++++++++++++++++ 18 files changed, 40 insertions(+), 20 deletions(-) create mode 100755 tools/strip_hashes.py diff --git a/Dockerfile b/Dockerfile index 8f434e89c..828f5ec94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,13 @@ VOLUME /etc/letsencrypt /var/lib/letsencrypt WORKDIR /opt/certbot COPY CHANGELOG.md README.rst setup.py src/ + +# Generate constraints file to pin dependency versions COPY letsencrypt-auto-source/pieces/dependency-requirements.txt . +COPY tools /opt/certbot/tools +RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt' +RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt' + COPY acme src/acme COPY certbot src/certbot @@ -23,7 +29,7 @@ RUN apk add --no-cache --virtual .build-deps \ musl-dev \ libffi-dev \ && pip install -r /opt/certbot/dependency-requirements.txt \ - && pip install --no-cache-dir \ + && pip install --no-cache-dir --no-deps \ --editable /opt/certbot/src/acme \ --editable /opt/certbot/src \ && apk del .build-deps diff --git a/certbot-dns-cloudflare/Dockerfile b/certbot-dns-cloudflare/Dockerfile index 27dcc8751..adbf715fa 100644 --- a/certbot-dns-cloudflare/Dockerfile +++ b/certbot-dns-cloudflare/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-cloudflare -RUN pip install --no-cache-dir --editable src/certbot-dns-cloudflare +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudflare diff --git a/certbot-dns-cloudxns/Dockerfile b/certbot-dns-cloudxns/Dockerfile index cc84ea65b..48c88c35c 100644 --- a/certbot-dns-cloudxns/Dockerfile +++ b/certbot-dns-cloudxns/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-cloudxns -RUN pip install --no-cache-dir --editable src/certbot-dns-cloudxns +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudxns diff --git a/certbot-dns-digitalocean/Dockerfile b/certbot-dns-digitalocean/Dockerfile index 8bdd0619f..342e0e876 100644 --- a/certbot-dns-digitalocean/Dockerfile +++ b/certbot-dns-digitalocean/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-digitalocean -RUN pip install --no-cache-dir --editable src/certbot-dns-digitalocean +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-digitalocean diff --git a/certbot-dns-dnsimple/Dockerfile b/certbot-dns-dnsimple/Dockerfile index 38d2be80e..724675339 100644 --- a/certbot-dns-dnsimple/Dockerfile +++ b/certbot-dns-dnsimple/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-dnsimple -RUN pip install --no-cache-dir --editable src/certbot-dns-dnsimple +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsimple diff --git a/certbot-dns-dnsmadeeasy/Dockerfile b/certbot-dns-dnsmadeeasy/Dockerfile index ff7936925..1480baf4f 100644 --- a/certbot-dns-dnsmadeeasy/Dockerfile +++ b/certbot-dns-dnsmadeeasy/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-dnsmadeeasy -RUN pip install --no-cache-dir --editable src/certbot-dns-dnsmadeeasy +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsmadeeasy diff --git a/certbot-dns-gehirn/Dockerfile b/certbot-dns-gehirn/Dockerfile index 48ad902b5..7dce0e521 100644 --- a/certbot-dns-gehirn/Dockerfile +++ b/certbot-dns-gehirn/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-gehirn -RUN pip install --no-cache-dir --editable src/certbot-dns-gehirn +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-gehirn diff --git a/certbot-dns-google/Dockerfile b/certbot-dns-google/Dockerfile index 4a258d0ee..5750b31d9 100644 --- a/certbot-dns-google/Dockerfile +++ b/certbot-dns-google/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-google -RUN pip install --no-cache-dir --editable src/certbot-dns-google +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-google diff --git a/certbot-dns-linode/Dockerfile b/certbot-dns-linode/Dockerfile index 2e237b521..6db8b59fb 100644 --- a/certbot-dns-linode/Dockerfile +++ b/certbot-dns-linode/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-linode -RUN pip install --no-cache-dir --editable src/certbot-dns-linode +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-linode diff --git a/certbot-dns-luadns/Dockerfile b/certbot-dns-luadns/Dockerfile index 6efb4d777..efc9f36d6 100644 --- a/certbot-dns-luadns/Dockerfile +++ b/certbot-dns-luadns/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-luadns -RUN pip install --no-cache-dir --editable src/certbot-dns-luadns +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-luadns diff --git a/certbot-dns-nsone/Dockerfile b/certbot-dns-nsone/Dockerfile index 88fc13c57..de541e850 100644 --- a/certbot-dns-nsone/Dockerfile +++ b/certbot-dns-nsone/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-nsone -RUN pip install --no-cache-dir --editable src/certbot-dns-nsone +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-nsone diff --git a/certbot-dns-ovh/Dockerfile b/certbot-dns-ovh/Dockerfile index e8da96d95..37e488dc4 100644 --- a/certbot-dns-ovh/Dockerfile +++ b/certbot-dns-ovh/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-ovh -RUN pip install --no-cache-dir --editable src/certbot-dns-ovh +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-ovh diff --git a/certbot-dns-rfc2136/Dockerfile b/certbot-dns-rfc2136/Dockerfile index 1b8feb2f8..3ebb6a72e 100644 --- a/certbot-dns-rfc2136/Dockerfile +++ b/certbot-dns-rfc2136/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-rfc2136 -RUN pip install --no-cache-dir --editable src/certbot-dns-rfc2136 +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-rfc2136 diff --git a/certbot-dns-route53/Dockerfile b/certbot-dns-route53/Dockerfile index a1b8d6caf..e1825c11d 100644 --- a/certbot-dns-route53/Dockerfile +++ b/certbot-dns-route53/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-route53 -RUN pip install --no-cache-dir --editable src/certbot-dns-route53 +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-route53 diff --git a/certbot-dns-sakuracloud/Dockerfile b/certbot-dns-sakuracloud/Dockerfile index 694773f61..9fa9b3c22 100644 --- a/certbot-dns-sakuracloud/Dockerfile +++ b/certbot-dns-sakuracloud/Dockerfile @@ -2,4 +2,4 @@ FROM certbot/certbot COPY . src/certbot-dns-sakuracloud -RUN pip install --no-cache-dir --editable src/certbot-dns-sakuracloud +RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-sakuracloud diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 88340cb00..f9ad7329b 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -1,4 +1,4 @@ -# Specifies Python package versions for development. +# Specifies Python package versions for development and building Docker images. # It includes in particular packages not specified in letsencrypt-auto's requirements file. # Some dev package versions specified here may be overridden by higher level constraints # files during tests (eg. letsencrypt-auto-source/pieces/dependency-requirements.txt). diff --git a/tools/pip_install.py b/tools/pip_install.py index dd6302b48..475799eb1 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -19,6 +19,7 @@ import tempfile import merge_requirements as merge_module import readlink +import strip_hashes def find_tools_path(): @@ -47,10 +48,7 @@ def certbot_normal_processing(tools_path, test_constraints): with open(certbot_requirements, 'r') as fd: data = fd.readlines() with open(test_constraints, 'w') as fd: - for line in data: - search = re.search(r'^(\S*==\S*).*$', line) - if search: - fd.write('{0}{1}'.format(search.group(1), os.linesep)) + fd.write(strip_hashes.main(data)) def merge_requirements(tools_path, requirements, test_constraints, all_constraints): diff --git a/tools/strip_hashes.py b/tools/strip_hashes.py new file mode 100755 index 000000000..c5591e2f4 --- /dev/null +++ b/tools/strip_hashes.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +import os +import re +import sys + +def main(args): + out_lines = [] + for line in args: + search = re.search(r'^(\S*==\S*).*$', line) + if search: + out_lines.append(search.group(1)) + return os.linesep.join(out_lines) + +if __name__ == '__main__': + print(main(sys.argv[1:])) -- cgit v1.2.3 From 8bda10541a67fc66c0b4da0ccc5bc8bacf741f25 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 20 Feb 2019 17:00:59 +0200 Subject: Add stdin option for merge_requirements Add stdin and file path support to strip_hashes --- tools/merge_requirements.py | 43 ++++++++++++++++++++++++++++++------------- tools/pip_install.py | 3 ++- tools/strip_hashes.py | 39 ++++++++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/tools/merge_requirements.py b/tools/merge_requirements.py index 4205e6bcf..521d28b8f 100755 --- a/tools/merge_requirements.py +++ b/tools/merge_requirements.py @@ -10,27 +10,36 @@ from __future__ import print_function import sys -def read_file(file_path): - """Reads in a Python requirements file. +def process_entries(entries): + """ Ignore empty lines, comments and editable requirements - :param str file_path: path to requirements file + :param list entries: List of entries :returns: mapping from a project to its pinned version :rtype: dict - """ data = {} - with open(file_path) as file_h: - for line in file_h: - line = line.strip() - if line and not line.startswith('#') and not line.startswith('-e'): - project, version = line.split('==') - if not version: - raise ValueError("Unexpected syntax '{0}'".format(line)) - data[project] = version + for e in entries: + e = e.strip() + if e and not e.startswith('#') and not e.startswith('-e'): + project, version = e.split('==') + if not version: + raise ValueError("Unexpected syntax '{0}'".format(e)) + data[project] = version return data +def read_file(file_path): + """Reads in a Python requirements file. + + :param str file_path: path to requirements file + + :returns: list of entries in the file + :rtype: list + + """ + with open(file_path) as file_h: + return file_h.readlines() def output_requirements(requirements): """Prepare print requirements to stdout. @@ -53,7 +62,15 @@ def main(*paths): """ data = {} for path in paths: - data.update(read_file(path)) + data.update(process_entries(read_file(path))) + + # Need to check if interactive to avoid blocking if nothing is piped + if not sys.stdin.isatty(): + stdin_data = [] + for line in sys.stdin: + stdin_data.append(line) + data.update(process_entries(stdin_data)) + return output_requirements(data) diff --git a/tools/pip_install.py b/tools/pip_install.py index 475799eb1..8f0437d9c 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -48,7 +48,8 @@ def certbot_normal_processing(tools_path, test_constraints): with open(certbot_requirements, 'r') as fd: data = fd.readlines() with open(test_constraints, 'w') as fd: - fd.write(strip_hashes.main(data)) + data = os.linesep.join(strip_hashes.process_entries(data)) + fd.write(data) def merge_requirements(tools_path, requirements, test_constraints, all_constraints): diff --git a/tools/strip_hashes.py b/tools/strip_hashes.py index c5591e2f4..7e7809458 100755 --- a/tools/strip_hashes.py +++ b/tools/strip_hashes.py @@ -1,16 +1,45 @@ #!/usr/bin/env python +"""Removes hash information from requirement files passed to it as file path +arguments or simply piped to stdin.""" import os import re import sys -def main(args): + +def process_entries(entries): + """Strips off hash strings from dependencies. + + :param list entries: List of entries + + :returns: list of dependencies without hashes + :rtype: list + """ out_lines = [] - for line in args: - search = re.search(r'^(\S*==\S*).*$', line) + for e in entries: + e = e.strip() + search = re.search(r'^(\S*==\S*).*$', e) if search: out_lines.append(search.group(1)) - return os.linesep.join(out_lines) + return out_lines + +def main(*paths): + """Reads dependency definitions from a (list of) file(s) or stdin and + removes hashes from returned entries""" + + deps = [] + for path in paths: + with open(path) as file_h: + deps += process_entries(file_h.readlines()) + + # Need to check if interactive to avoid blocking if nothing is piped + if not sys.stdin.isatty(): + stdin_data = [] + for line in sys.stdin: + stdin_data.append(line) + deps += process_entries(stdin_data) + + return os.linesep.join(deps) if __name__ == '__main__': - print(main(sys.argv[1:])) + print(main(*sys.argv[1:])) # pylint: disable=star-args -- cgit v1.2.3 From eef4c476335a9338dc71509d3d7c21de7d81b486 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 20 Feb 2019 15:20:44 -0800 Subject: Add failure message if test farm tests do not run the correct number of tests. (#6771) Fixes #6748. --- tests/letstest/multitester.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 8babc67b3..b8ae937ad 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -564,6 +564,11 @@ try: ii, target, status = outq print('%d %s %s'%(ii, target['name'], status)) results_file.write('%d %s %s\n'%(ii, target['name'], status)) + if len(outputs) != num_processes: + failure_message = 'FAILURE: Some target machines failed to run and were not tested. ' +\ + 'Tests should be rerun.' + print(failure_message) + results_file.write(failure_message + '\n') results_file.close() finally: -- cgit v1.2.3 From eb5c4eca877baf52c0c39778002fce1e9482cff7 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 21 Feb 2019 01:20:16 +0100 Subject: [Windows] Working unit tests for certbot-nginx (#6782) This PR fixes certbot-nginx and relevant tests to make them succeed on Windows. Next step will be to enable integration tests through certbot-ci in a future PR. * Fix tests and incompabilities in certbot-nginx for Windows * Fix lint, fix oldest local dependencies --- certbot-nginx/certbot_nginx/configurator.py | 7 +++---- certbot-nginx/certbot_nginx/parser.py | 4 ++-- .../certbot_nginx/tests/configurator_test.py | 19 ++++++++----------- certbot-nginx/certbot_nginx/tests/http_01_test.py | 6 ------ certbot-nginx/certbot_nginx/tests/parser_test.py | 12 +++++++++--- certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py | 6 ------ certbot-nginx/certbot_nginx/tests/util.py | 18 ++++++++++++++++++ certbot-nginx/local-oldest-requirements.txt | 4 ++-- tools/install_and_test.py | 2 +- tox.cover.py | 5 ++++- 10 files changed, 47 insertions(+), 36 deletions(-) diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index dd0bf9e8b..ffe1ddac7 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -13,6 +13,7 @@ import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util +from certbot import compat from certbot import constants as core_constants from certbot import crypto_util from certbot import errors @@ -164,9 +165,7 @@ class NginxConfigurator(common.Installer): util.lock_dir_until_exit(self.conf('server-root')) except (OSError, errors.LockError): logger.debug('Encountered error:', exc_info=True) - raise errors.PluginError( - 'Unable to lock %s', self.conf('server-root')) - + raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root'))) # Entry point in main.py for installing cert def deploy_cert(self, domain, cert_path, key_path, @@ -899,7 +898,7 @@ class NginxConfigurator(common.Installer): have permissions of root. """ - uid = os.geteuid() + uid = compat.os_geteuid() util.make_or_verify_dir( self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) util.make_or_verify_dir( diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py index 622eb8d55..c5f780d94 100644 --- a/certbot-nginx/certbot_nginx/parser.py +++ b/certbot-nginx/certbot_nginx/parser.py @@ -81,9 +81,9 @@ class NginxParser(object): """ if not os.path.isabs(path): - return os.path.join(self.root, path) + return os.path.normpath(os.path.join(self.root, path)) else: - return path + return os.path.normpath(path) def _build_addr_to_ssl(self): """Builds a map from address to whether it listens on ssl in any server block diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 957588e2a..706e2637a 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -1,7 +1,6 @@ # pylint: disable=too-many-public-methods """Test for certbot_nginx.configurator.""" import os -import shutil import unittest import mock @@ -33,12 +32,6 @@ class NginxConfiguratorTest(util.NginxTest): self.config = util.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - shutil.rmtree(self.logs_dir) - @mock.patch("certbot_nginx.configurator.util.exe_exists") def test_prepare_no_install(self, mock_exe_exists): mock_exe_exists.return_value = False @@ -69,8 +62,11 @@ class NginxConfiguratorTest(util.NginxTest): def test_prepare_locked(self): server_root = self.config.conf("server-root") + + from certbot import util as certbot_util + certbot_util._LOCKS[server_root].release() # pylint: disable=protected-access + self.config.config_test = mock.Mock() - os.remove(os.path.join(server_root, ".certbot.lock")) certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) @mock.patch("certbot_nginx.configurator.util.exe_exists") @@ -88,11 +84,11 @@ class NginxConfiguratorTest(util.NginxTest): def test_get_all_names(self, mock_gethostbyaddr): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) names = self.config.get_all_names() - self.assertEqual(names, set( - ["155.225.50.69.nephoscale.net", "www.example.org", "another.alias", + self.assertEqual(names, { + "155.225.50.69.nephoscale.net", "www.example.org", "another.alias", "migration.com", "summer.com", "geese.com", "sslon.com", "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com", - "headers.com"])) + "headers.com"}) def test_supported_enhancements(self): self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], @@ -171,6 +167,7 @@ class NginxConfiguratorTest(util.NginxTest): 'abc.www.foo.com': "etc_nginx/foo.conf", 'www.bar.co.uk': "etc_nginx/nginx.conf", 'ipv6.com': "etc_nginx/sites-enabled/ipv6.com"} + conf_path = {key: os.path.normpath(value) for key, value in conf_path.items()} vhost = self.config.choose_vhosts(name)[0] path = os.path.relpath(vhost.filep, self.temp_dir) diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py index ed3c257ee..41c4b95fc 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/http_01_test.py @@ -1,6 +1,5 @@ """Tests for certbot_nginx.http_01""" import unittest -import shutil import mock import six @@ -54,11 +53,6 @@ class HttpPerformTest(util.NginxTest): from certbot_nginx import http_01 self.http01 = http_01.NginxHttp01(config) - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - def test_perform0(self): responses = self.http01.perform() self.assertEqual([], responses) diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index f6f28e42b..3a68f7f24 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -67,9 +67,15 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods def test_abs_path(self): nparser = parser.NginxParser(self.config_path) - self.assertEqual('/etc/nginx/*', nparser.abs_path('/etc/nginx/*')) - self.assertEqual(os.path.join(self.config_path, 'foo/bar/'), - nparser.abs_path('foo/bar/')) + if os.name != 'nt': + self.assertEqual('/etc/nginx/*', nparser.abs_path('/etc/nginx/*')) + self.assertEqual(os.path.join(self.config_path, 'foo/bar'), + nparser.abs_path('foo/bar')) + else: + self.assertEqual('C:\\etc\\nginx\\*', nparser.abs_path('C:\\etc\\nginx\\*')) + self.assertEqual(os.path.join(self.config_path, 'foo\\bar'), + nparser.abs_path('foo\\bar')) + def test_filedump(self): nparser = parser.NginxParser(self.config_path) diff --git a/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py b/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py index 72b65911c..62ca085ef 100644 --- a/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py @@ -1,6 +1,5 @@ """Tests for certbot_nginx.tls_sni_01""" import unittest -import shutil import mock import six @@ -55,11 +54,6 @@ class TlsSniPerformTest(util.NginxTest): from certbot_nginx import tls_sni_01 self.sni = tls_sni_01.NginxTlsSni01(config) - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - @mock.patch("certbot_nginx.configurator" ".NginxConfigurator.choose_vhosts") def test_perform(self, mock_choose): diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index ad1af2b96..ef669dac0 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -4,6 +4,8 @@ import os import pkg_resources import tempfile import unittest +import shutil +import warnings import josepy as jose import mock @@ -33,6 +35,22 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( "rsa512_key.pem")) + def tearDown(self): + # On Windows we have various files which are not correctly closed at the time of tearDown. + # For know, we log them until a proper file close handling is written. + # Useful for development only, so no warning when we are on a CI process. + def onerror_handler(_, path, excinfo): + """On error handler""" + if not os.environ.get('APPVEYOR'): # pragma: no cover + message = ('Following error occurred when deleting path {0}' + 'during tearDown process: {1}'.format(path, str(excinfo))) + warnings.warn(message) + + shutil.rmtree(self.temp_dir, onerror=onerror_handler) + shutil.rmtree(self.config_dir, onerror=onerror_handler) + shutil.rmtree(self.work_dir, onerror=onerror_handler) + shutil.rmtree(self.logs_dir, onerror=onerror_handler) + def get_data_filename(filename): """Gets the filename of a test data file.""" diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index bcd02d197..db6b261f0 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,2 @@ -acme[dev]==0.26.0 -certbot[dev]==0.22.0 +acme[dev]==0.29.0 +-e .[dev] diff --git a/tools/install_and_test.py b/tools/install_and_test.py index b15c8eca5..288226527 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -15,7 +15,7 @@ import subprocess import re SKIP_PROJECTS_ON_WINDOWS = [ - 'certbot-apache', 'certbot-nginx', 'certbot-postfix', 'letshelp-certbot'] + 'certbot-apache', 'certbot-postfix', 'letshelp-certbot'] def call_with_print(command, cwd=None): diff --git a/tox.cover.py b/tox.cover.py index 008424641..e323ba255 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -35,7 +35,8 @@ COVER_THRESHOLDS = { } SKIP_PROJECTS_ON_WINDOWS = [ - 'certbot-apache', 'certbot-nginx', 'certbot-postfix', 'letshelp-certbot'] + 'certbot-apache', 'certbot-postfix', 'letshelp-certbot'] + def cover(package): threshold = COVER_THRESHOLDS.get(package)['windows' if os.name == 'nt' else 'linux'] @@ -54,6 +55,7 @@ def cover(package): sys.executable, '-m', 'coverage', 'report', '--fail-under', str(threshold), '--include', '{0}/*'.format(pkg_dir), '--show-missing']) + def main(): description = """ This script is used by tox.ini (and thus by Travis CI and AppVeyor) in order @@ -77,5 +79,6 @@ Option -e makes sure we fail fast and don't submit to codecov.""" for package in packages: cover(package) + if __name__ == '__main__': main() -- cgit v1.2.3 From b10ceb7d907cdcc36ec2d745e7802127919ec043 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 22 Feb 2019 01:55:08 +0100 Subject: Fix test sdists with atexit handlers (#6769) So merging the study from @bmw and me, here is what happened. Each invocation of `certbot.logger.post_arg_parse_setup` create a file handler on `letsencrypt.log`. This function also set an atexit handler invoking `logger.shutdown()`, that have the effect to close all logger file handler not already closed at this point. This method is supposed to be called when a python process is close to exit, because it makes all logger unable to write new logs on any handler. Before #6667 and this PR, for tests, the atexit handle would be triggered only at the end of the pytest process. It means that each test that launches `certbot.logger.post_arg_parse_setup` add a new file handler. These tests were typically connecting the file handler on a `letsencrypt.log` located in a temporary directory, and this directory and content was wipped out at each test tearDown. As a consequence, the file handles, not cleared from the logger, were accumulating in the logger, with all of them connected to a deleted file log, except the last one that was just created by the current test. Considering the number of tests concerned, there were ~300 file handler at the end of pytest execution. One can see that, on prior #6667, by calling `print(logger.getLogger().handlers` on the `tearDown` of these tests, and see the array growing at each test execution. Even if this represent a memory leak, this situation was not really a problem on Linux: because a file can be deleted before it is closed, it was only meaning that a given invocation of `logger.debug` for instance, during the tests, was written in 300 log files. The overhead is negligeable. On Windows however, the file handlers were failing because you cannot delete a file before it is closed. It was one of the reason for #6667, that added a call to `logging.shutdown()` at each test tearDown, with the consequence to close all file handlers. At this point, Linux is not happy anymore. Any call to `logger.warn` will generate an error for each closed file handler. As a file handler is added for each test, the number of errors grows on each test, following an arithmetical suite divergence. On `test_sdists.py`, that is using the bare setuptools test suite without output capturing, we can see the damages. The total output takes 216000 lines, and 23000 errors are generated. A decent machine can support this load, but a not a small AWS instance, that is crashing during the execution. Even with pytest, the captured output and the memory leak become so large that segfaults are generated. On the current PR, the problem is solved, by resetting the file handlers array on the logging system on each test tearDown. So each fileHandler is properly closed, and removed from the stack. They do not participate anymore in the logging system, and can be garbage collected. Then we stay on always one file handler opened at any time, and tests can succeed on AWS instances. For the record, here is all the places where the logging system is called and fail if there is still file handlers closed but not cleaned (extracted from the original huge output before correction): ``` Logged from file account.py, line 116 Logged from file account.py, line 178 Logged from file client.py, line 166 Logged from file client.py, line 295 Logged from file client.py, line 415 Logged from file client.py, line 422 Logged from file client.py, line 480 Logged from file client.py, line 503 Logged from file client.py, line 540 Logged from file client.py, line 601 Logged from file client.py, line 622 Logged from file client.py, line 750 Logged from file cli.py, line 220 Logged from file cli.py, line 226 Logged from file crypto_util.py, line 101 Logged from file crypto_util.py, line 127 Logged from file crypto_util.py, line 147 Logged from file crypto_util.py, line 261 Logged from file crypto_util.py, line 283 Logged from file crypto_util.py, line 307 Logged from file crypto_util.py, line 336 Logged from file disco.py, line 116 Logged from file disco.py, line 124 Logged from file disco.py, line 134 Logged from file disco.py, line 138 Logged from file disco.py, line 141 Logged from file dns_common_lexicon.py, line 45 Logged from file dns_common_lexicon.py, line 61 Logged from file dns_common_lexicon.py, line 67 Logged from file dns_common.py, line 316 Logged from file dns_common.py, line 64 Logged from file eff.py, line 60 Logged from file eff.py, line 73 Logged from file error_handler.py, line 105 Logged from file error_handler.py, line 110 Logged from file error_handler.py, line 87 Logged from file hooks.py, line 248 Logged from file main.py, line 1071 Logged from file main.py, line 1075 Logged from file main.py, line 1189 Logged from file ops.py, line 122 Logged from file ops.py, line 325 Logged from file ops.py, line 338 Logged from file reporter.py, line 55 Logged from file selection.py, line 110 Logged from file selection.py, line 118 Logged from file selection.py, line 123 Logged from file selection.py, line 176 Logged from file selection.py, line 231 Logged from file selection.py, line 310 Logged from file selection.py, line 66 Logged from file standalone.py, line 101 Logged from file standalone.py, line 88 Logged from file standalone.py, line 97 Logged from file standalone.py, line 98 Logged from file storage.py, line 52 Logged from file storage.py, line 59 Logged from file storage.py, line 75 Logged from file util.py, line 56 Logged from file webroot.py, line 165 Logged from file webroot.py, line 186 Logged from file webroot.py, line 187 Logged from file webroot.py, line 204 Logged from file webroot.py, line 223 Logged from file webroot.py, line 234 Logged from file webroot.py, line 235 Logged from file webroot.py, line 237 Logged from file webroot.py, line 91 ``` * Reapply #6667 * Make setuptools delegates tests execution to pytest, like in acme module. * Clean handlers at each tearDown to avoid memory leaks. * Update changelog --- CHANGELOG.md | 2 ++ acme/setup.py | 4 +++- certbot-apache/setup.py | 20 ++++++++++++++++++++ certbot-nginx/setup.py | 20 ++++++++++++++++++++ certbot/tests/lock_test.py | 3 +++ certbot/tests/util.py | 33 ++++++++++++++++++--------------- certbot/tests/util_test.py | 24 +++++++++--------------- certbot/util.py | 6 +++--- setup.py | 20 ++++++++++++++++++++ 9 files changed, 98 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a895e07..7744e3745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). warnings described at https://github.com/certbot/josepy/issues/13. * Apache plugin now respects CERTBOT_DOCS environment variable when adding command line defaults. +* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. ### Fixed @@ -26,6 +27,7 @@ package with changes other than its version number was: * acme * certbot * certbot-apache +* certbot-nginx More details about these changes can be found on our GitHub repo. diff --git a/acme/setup.py b/acme/setup.py index 79d6d3389..6cb5c3f92 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -36,6 +36,7 @@ docs_extras = [ 'sphinx_rtd_theme', ] + class PyTest(TestCommand): user_options = [] @@ -50,6 +51,7 @@ class PyTest(TestCommand): errno = pytest.main(shlex.split(self.pytest_args)) sys.exit(errno) + setup( name='acme', version=version, @@ -82,7 +84,7 @@ setup( 'dev': dev_extras, 'docs': docs_extras, }, - tests_require=["pytest"], test_suite='acme', + tests_require=["pytest"], cmdclass={"test": PyTest}, ) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 52528a536..5d15611dd 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '0.32.0.dev0' @@ -21,6 +23,22 @@ docs_extras = [ 'sphinx_rtd_theme', ] + +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + + setup( name='certbot-apache', version=version, @@ -64,4 +82,6 @@ setup( ], }, test_suite='certbot_apache', + tests_require=["pytest"], + cmdclass={"test": PyTest}, ) diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index f78473ada..8984704a6 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '0.32.0.dev0' @@ -21,6 +23,22 @@ docs_extras = [ 'sphinx_rtd_theme', ] + +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + + setup( name='certbot-nginx', version=version, @@ -64,4 +82,6 @@ setup( ], }, test_suite='certbot_nginx', + tests_require=["pytest"], + cmdclass={"test": PyTest}, ) diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index 8658443d0..6379693ae 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -41,6 +41,7 @@ class LockFileTest(test_util.TempDirTestCase): super(LockFileTest, self).setUp() self.lock_path = os.path.join(self.tempdir, 'test.lock') + @test_util.broken_on_windows def test_acquire_without_deletion(self): # acquire the lock in another process but don't delete the file child = multiprocessing.Process(target=self._call, @@ -58,6 +59,7 @@ class LockFileTest(test_util.TempDirTestCase): self.assertRaises, errors.LockError, self._call, self.lock_path) test_util.lock_and_call(assert_raises, self.lock_path) + @test_util.broken_on_windows def test_locked_repr(self): lock_file = self._call(self.lock_path) locked_repr = repr(lock_file) @@ -92,6 +94,7 @@ class LockFileTest(test_util.TempDirTestCase): self._call(self.lock_path) self.assertFalse(should_delete) + @test_util.broken_on_windows def test_removed(self): lock_file = self._call(self.lock_path) lock_file.release() diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 289f2dd9b..4c0c66a42 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -3,13 +3,14 @@ .. warning:: This module is not part of the public API. """ +import logging import os import pkg_resources import shutil +import stat import tempfile import unittest import sys -import warnings from multiprocessing import Process, Event from cryptography.hazmat.backends import default_backend @@ -329,23 +330,25 @@ class TempDirTestCase(unittest.TestCase): def tearDown(self): """Execute after test""" - # On Windows we have various files which are not correctly closed at the time of tearDown. - # For know, we log them until a proper file close handling is written. - # Useful for development only, so no warning when we are on a CI process. - def onerror_handler(_, path, excinfo): - """On error handler""" - if not os.environ.get('APPVEYOR'): # pragma: no cover - message = ('Following error occurred when deleting the tempdir {0}' - ' for path {1} during tearDown process: {2}' - .format(self.tempdir, path, str(excinfo))) - warnings.warn(message) - shutil.rmtree(self.tempdir, onerror=onerror_handler) + # Cleanup opened resources after a test. This is usually done through atexit handlers in + # Certbot, but during tests, atexit will not run registered functions before tearDown is + # called and instead will run them right before the entire test process exits. + # It is a problem on Windows, that does not accept to clean resources before closing them. + logging.shutdown() + # Remove logging handlers that have been closed so they won't be + # accidentally used in future tests. + logging.getLogger().handlers = [] + util._release_locks() # pylint: disable=protected-access + + def handle_rw_files(_, path, __): + """Handle read-only files, that will fail to be removed on Windows.""" + os.chmod(path, stat.S_IWRITE) + os.remove(path) + shutil.rmtree(self.tempdir, onerror=handle_rw_files) class ConfigTestCase(TempDirTestCase): - """Test class which sets up a NamespaceConfig object. - - """ + """Test class which sets up a NamespaceConfig object.""" def setUp(self): super(ConfigTestCase, self).setUp() self.config = configuration.NamespaceConfig( diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index c85009c62..cac587995 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -193,7 +193,12 @@ class CheckPermissionsTest(test_util.TempDirTestCase): def test_wrong_mode(self): os.chmod(self.tempdir, 0o400) - self.assertFalse(self._call(0o600)) + try: + self.assertFalse(self._call(0o600)) + finally: + # Without proper write permissions, Windows is unable to delete a folder, + # even with admin permissions. Write access must be explicitly set first. + os.chmod(self.tempdir, 0o700) class UniqueFileTest(test_util.TempDirTestCase): @@ -279,20 +284,9 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): for f, _ in items: f.close() - @mock.patch("certbot.util.os.fdopen") - def test_failure(self, mock_fdopen): - err = OSError("whoops") - err.errno = errno.EIO - mock_fdopen.side_effect = err - self.assertRaises(OSError, self._call, "wow") - - @mock.patch("certbot.util.os.fdopen") - def test_subsequent_failure(self, mock_fdopen): - self._call("wow") - err = OSError("whoops") - err.errno = errno.EIO - mock_fdopen.side_effect = err - self.assertRaises(OSError, self._call, "wow") + def test_failure(self): + with mock.patch("certbot.util.os.open", side_effect=OSError(errno.EIO)): + self.assertRaises(OSError, self._call, "wow") class SafelyRemoveTest(test_util.TempDirTestCase): diff --git a/certbot/util.py b/certbot/util.py index d7c542465..416075ce8 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -142,6 +142,7 @@ def _release_locks(): except: # pylint: disable=bare-except msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock) logger.debug(msg, exc_info=True) + _LOCKS.clear() def set_up_core_dir(directory, mode, uid, strict): @@ -225,9 +226,8 @@ def safe_open(path, mode="w", chmod=None, buffering=None): fdopen_args = () # type: Union[Tuple[()], Tuple[int]] if buffering is not None: fdopen_args = (buffering,) - return os.fdopen( - os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args), - mode, *fdopen_args) + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) + return os.fdopen(fd, mode, *fdopen_args) def _unique_file(path, filename_pat, count, chmod, mode): diff --git a/setup.py b/setup.py index 14fef37f3..26a1c4293 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,10 @@ import codecs import os import re +import sys from setuptools import find_packages, setup +from setuptools.command.test import test as TestCommand # Workaround for http://bugs.python.org/issue8876, see # http://bugs.python.org/issue8876#msg208792 @@ -77,6 +79,22 @@ docs_extras = [ 'sphinx_rtd_theme', ] + +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + + setup( name='certbot', version=version, @@ -123,6 +141,8 @@ setup( # to test all packages run "python setup.py test -s # {acme,certbot_apache,certbot_nginx}" test_suite='certbot', + tests_require=["pytest"], + cmdclass={"test": PyTest}, entry_points={ 'console_scripts': [ -- cgit v1.2.3 From 31b4b8e57c542577d35a20f87eaad60d5ce37193 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 22 Feb 2019 06:42:01 -0800 Subject: Log the execution of manual hooks (#6788) * Move logging to execute and fix tests. * update changelog --- CHANGELOG.md | 2 ++ certbot/hooks.py | 36 ++++++++++++++++++++---------------- certbot/plugins/manual.py | 7 +++++-- certbot/tests/hook_test.py | 34 +++++++++++++++++++--------------- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7744e3745..ce7eac2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). warnings described at https://github.com/certbot/josepy/issues/13. * Apache plugin now respects CERTBOT_DOCS environment variable when adding command line defaults. +* The running of manual plugin hooks is now always included in Certbot's log + output. * Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. ### Fixed diff --git a/certbot/hooks.py b/certbot/hooks.py index d5239a437..7d2e42fcd 100644 --- a/certbot/hooks.py +++ b/certbot/hooks.py @@ -93,8 +93,7 @@ def _run_pre_hook_if_necessary(command): if command in executed_pre_hooks: logger.info("Pre-hook command already run, skipping: %s", command) else: - logger.info("Running pre-hook command: %s", command) - _run_hook(command) + _run_hook("pre-hook", command) executed_pre_hooks.add(command) @@ -126,8 +125,7 @@ def post_hook(config): _run_eventually(cmd) # certonly / run elif cmd: - logger.info("Running post-hook command: %s", cmd) - _run_hook(cmd) + _run_hook("post-hook", cmd) post_hooks = [] # type: List[str] @@ -149,8 +147,7 @@ def _run_eventually(command): def run_saved_post_hooks(): """Run any post hooks that were saved up in the course of the 'renew' verb""" for cmd in post_hooks: - logger.info("Running post-hook command: %s", cmd) - _run_hook(cmd) + _run_hook("post-hook", cmd) def deploy_hook(config, domains, lineage_path): @@ -220,23 +217,30 @@ def _run_deploy_hook(command, domains, lineage_path, dry_run): os.environ["RENEWED_DOMAINS"] = " ".join(domains) os.environ["RENEWED_LINEAGE"] = lineage_path - logger.info("Running deploy-hook command: %s", command) - _run_hook(command) + _run_hook("deploy-hook", command) -def _run_hook(shell_cmd): +def _run_hook(cmd_name, shell_cmd): """Run a hook command. - :returns: stderr if there was any""" + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` - err, _ = execute(shell_cmd) + :returns: stderr if there was any""" + err, _ = execute(cmd_name, shell_cmd) return err -def execute(shell_cmd): +def execute(cmd_name, shell_cmd): """Run a command. + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` + :returns: `tuple` (`str` stderr, `str` stdout)""" + logger.info("Running %s command: %s", cmd_name, shell_cmd) # universal_newlines causes Popen.communicate() # to return str objects instead of bytes in Python 3 @@ -245,12 +249,12 @@ def execute(shell_cmd): out, err = cmd.communicate() base_cmd = os.path.basename(shell_cmd.split(None, 1)[0]) if out: - logger.info('Output from %s:\n%s', base_cmd, out) + logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out) if cmd.returncode != 0: - logger.error('Hook command "%s" returned error code %d', - shell_cmd, cmd.returncode) + logger.error('%s command "%s" returned error code %d', + cmd_name, shell_cmd, cmd.returncode) if err: - logger.error('Error output from %s:\n%s', base_cmd, err) + logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) return (err, out) diff --git a/certbot/plugins/manual.py b/certbot/plugins/manual.py index 8723a1c62..123a5bfea 100644 --- a/certbot/plugins/manual.py +++ b/certbot/plugins/manual.py @@ -202,7 +202,7 @@ permitted by DNS standards.) os.environ.pop('CERTBOT_KEY_PATH', None) os.environ.pop('CERTBOT_SNI_DOMAIN', None) os.environ.update(env) - _, out = hooks.execute(self.conf('auth-hook')) + _, out = self._execute_hook('auth-hook') env['CERTBOT_AUTH_OUTPUT'] = out.strip() self.env[achall] = env @@ -243,5 +243,8 @@ permitted by DNS standards.) if 'CERTBOT_TOKEN' not in env: os.environ.pop('CERTBOT_TOKEN', None) os.environ.update(env) - hooks.execute(self.conf('cleanup-hook')) + self._execute_hook('cleanup-hook') self.reverter.recovery_routine() + + def _execute_hook(self, hook_name): + return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index f5bb0c8b5..90f639958 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -121,7 +121,7 @@ class PreHookTest(HookTest): def _test_nonrenew_common(self): mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with(self.config.pre_hook) + mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook) self._test_no_executions_common() def test_no_hooks(self): @@ -137,21 +137,21 @@ class PreHookTest(HookTest): def test_renew_disabled_dir_hooks(self): self.config.directory_hooks = False mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with(self.config.pre_hook) + mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook) self._test_no_executions_common() def test_renew_no_overlap(self): self.config.verb = "renew" mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_any_call(self.dir_hook) - mock_execute.assert_called_with(self.config.pre_hook) + mock_execute.assert_any_call("pre-hook", self.dir_hook) + mock_execute.assert_called_with("pre-hook", self.config.pre_hook) self._test_no_executions_common() def test_renew_with_overlap(self): self.config.pre_hook = self.dir_hook self.config.verb = "renew" mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with(self.dir_hook) + mock_execute.assert_called_once_with("pre-hook", self.dir_hook) self._test_no_executions_common() def _test_no_executions_common(self): @@ -193,7 +193,7 @@ class PostHookTest(HookTest): for verb in ("certonly", "run",): self.config.verb = verb mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with(self.config.post_hook) + mock_execute.assert_called_once_with("post-hook", self.config.post_hook) self.assertFalse(self._get_eventually()) def test_cert_only_and_run_without_hook(self): @@ -277,12 +277,12 @@ class RunSavedPostHooksTest(HookTest): calls = mock_execute.call_args_list for actual_call, expected_arg in zip(calls, self.eventually): - self.assertEqual(actual_call[0][0], expected_arg) + self.assertEqual(actual_call[0][1], expected_arg) def test_single(self): self.eventually = ["foo"] mock_execute = self._call_with_mock_execute_and_eventually() - mock_execute.assert_called_once_with(self.eventually[0]) + mock_execute.assert_called_once_with("post-hook", self.eventually[0]) class RenewalHookTest(HookTest): @@ -360,7 +360,7 @@ class DeployHookTest(RenewalHookTest): self.config.deploy_hook = "foo" mock_execute = self._call_with_mock_execute( self.config, domains, lineage) - mock_execute.assert_called_once_with(self.config.deploy_hook) + mock_execute.assert_called_once_with("deploy-hook", self.config.deploy_hook) class RenewHookTest(RenewalHookTest): @@ -384,7 +384,7 @@ class RenewHookTest(RenewalHookTest): self.config.directory_hooks = False mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - mock_execute.assert_called_once_with(self.config.renew_hook) + mock_execute.assert_called_once_with("deploy-hook", self.config.renew_hook) @mock.patch("certbot.hooks.logger") def test_dry_run(self, mock_logger): @@ -408,13 +408,13 @@ class RenewHookTest(RenewalHookTest): self.config.renew_hook = self.dir_hook mock_execute = self._call_with_mock_execute( self.config, ["example.net", "example.org"], "/foo/bar") - mock_execute.assert_called_once_with(self.dir_hook) + mock_execute.assert_called_once_with("deploy-hook", self.dir_hook) def test_no_overlap(self): mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - mock_execute.assert_any_call(self.dir_hook) - mock_execute.assert_called_with(self.config.renew_hook) + mock_execute.assert_any_call("deploy-hook", self.dir_hook) + mock_execute.assert_called_with("deploy-hook", self.config.renew_hook) class ExecuteTest(unittest.TestCase): @@ -433,18 +433,22 @@ class ExecuteTest(unittest.TestCase): def _test_common(self, returncode, stdout, stderr): given_command = "foo" + given_name = "foo-hook" with mock.patch("certbot.hooks.Popen") as mock_popen: mock_popen.return_value.communicate.return_value = (stdout, stderr) mock_popen.return_value.returncode = returncode with mock.patch("certbot.hooks.logger") as mock_logger: - self.assertEqual(self._call(given_command), (stderr, stdout)) + self.assertEqual(self._call(given_name, given_command), (stderr, stdout)) executed_command = mock_popen.call_args[1].get( "args", mock_popen.call_args[0][0]) self.assertEqual(executed_command, given_command) + mock_logger.info.assert_any_call("Running %s command: %s", + given_name, given_command) if stdout: - self.assertTrue(mock_logger.info.called) + mock_logger.info.assert_any_call(mock.ANY, mock.ANY, + mock.ANY, stdout) if stderr or returncode: self.assertTrue(mock_logger.error.called) -- cgit v1.2.3 From f105aedc9258d7a1de55b7de56802535d3b1b4dc Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 22 Feb 2019 16:55:50 -0800 Subject: Remove display.py. --- tests/display.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 tests/display.py diff --git a/tests/display.py b/tests/display.py deleted file mode 100644 index 1f548e33d..000000000 --- a/tests/display.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Manual test of display functions.""" -import sys - -from certbot.display import util -from certbot.tests.display import util_test - - -def test_visual(displayer, choices): - """Visually test all of the display functions.""" - displayer.notification("Random notification!") - displayer.menu("Question?", choices, - ok_label="O", cancel_label="Can", help_label="??") - displayer.menu("Question?", [choice[1] for choice in choices], - ok_label="O", cancel_label="Can", help_label="??") - displayer.input("Input Message") - displayer.yesno("YesNo Message", yes_label="Yessir", no_label="Nosir") - displayer.checklist("Checklist Message", [choice[0] for choice in choices]) - - -if __name__ == "__main__": - displayer = util.FileDisplay(sys.stdout, False) - test_visual(displayer, util_test.CHOICES) -- cgit v1.2.3 From 401045be89d241bad01ebb8e933bdb83f836bc20 Mon Sep 17 00:00:00 2001 From: Julie R Date: Sat, 23 Feb 2019 03:02:43 +0100 Subject: Add acme library usage example (http-01) (#5494) * Add acme library usage example Create, edit and deactivate account. Setup and perform http-01 challenge. Issue, renew and revoke certificate. * Adapt example to ACME-v2 and exclude data persistence The code to persist/load data would length this example and distract from what is actually important. * Fix domain names and e-mail addresses * Remove unnecessary license header This usage example is under the license for the acme package. * Remove logging information The code will be mostly read by developers, so simplify the logging info into comments. * Revert abstraction of simple methods All methods that are used only once in this example were expanded into the main code in order to make the process more explicit. * Fix missing URL suffix * Improve aesthetics and reorganize workflow Also make words capitalization consistent and improve comments. No complaints from pep8. --- acme/examples/http01_example.py | 240 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 acme/examples/http01_example.py diff --git a/acme/examples/http01_example.py b/acme/examples/http01_example.py new file mode 100644 index 000000000..79508f1b4 --- /dev/null +++ b/acme/examples/http01_example.py @@ -0,0 +1,240 @@ +"""Example ACME-V2 API for HTTP-01 challenge. + +Brief: + +This a complete usage example of the python-acme API. + +Limitations of this example: + - Works for only one Domain name + - Performs only HTTP-01 challenge + - Uses ACME-v2 + +Workflow: + (Account creation) + - Create account key + - Register account and accept TOS + (Certificate actions) + - Select HTTP-01 within offered challenges by the CA server + - Set up http challenge resource + - Set up standalone web server + - Create domain private key and CSR + - Issue certificate + - Renew certificate + - Revoke certificate + (Account update actions) + - Change contact information + - Deactivate Account +""" +from contextlib import contextmanager +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import rsa +import OpenSSL + +from acme import challenges +from acme import client +from acme import crypto_util +from acme import errors +from acme import messages +from acme import standalone +import josepy as jose + +# Constants: + +# This is the staging point for ACME-V2 within Let's Encrypt. +DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory' + +USER_AGENT = 'python-acme-example' + +# Account key size +ACC_KEY_BITS = 2048 + +# Certificate private key size +CERT_PKEY_BITS = 2048 + +# Domain name for the certificate. +DOMAIN = 'client.example.com' + +# If you are running Boulder locally, it is possible to configure any port +# number to execute the challenge, but real CA servers will always use port +# 80, as described in the ACME specification. +PORT = 80 + + +# Useful methods and classes: + + +def new_csr_comp(domain_name, pkey_pem=None): + """Create certificate signing request.""" + if pkey_pem is None: + # Create private key. + pkey = OpenSSL.crypto.PKey() + pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS) + pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, + pkey) + csr_pem = crypto_util.make_csr(pkey_pem, [domain_name]) + return pkey_pem, csr_pem + + +def select_http01_chall(orderr): + """Extract authorization resource from within order resource.""" + # Authorization Resource: authz. + # This object holds the offered challenges by the server and their status. + authz_list = orderr.authorizations + + for authz in authz_list: + # Choosing challenge. + # authz.body.challenges is a set of ChallengeBody objects. + for i in authz.body.challenges: + # Find the supported challenge. + if isinstance(i.chall, challenges.HTTP01): + return i + + raise Exception('HTTP-01 challenge was not offered by the CA server.') + + +@contextmanager +def challenge_server(http_01_resources): + """Manage standalone server set up and shutdown.""" + + # Setting up a fake server that binds at PORT and any address. + address = ('', PORT) + try: + servers = standalone.HTTP01DualNetworkedServers(address, + http_01_resources) + # Start client standalone web server. + servers.serve_forever() + yield servers + finally: + # Shutdown client web server and unbind from PORT + servers.shutdown_and_server_close() + + +def perform_http01(client_acme, challb, orderr): + """Set up standalone webserver and perform HTTP-01 challenge.""" + + response, validation = challb.response_and_validation(client_acme.net.key) + + resource = standalone.HTTP01RequestHandler.HTTP01Resource( + chall=challb.chall, response=response, validation=validation) + + with challenge_server({resource}): + # Let the CA server know that we are ready for the challenge. + client_acme.answer_challenge(challb, response) + + # Wait for challenge status and then issue a certificate. + # It is possible to set a deadline time. + finalized_orderr = client_acme.poll_and_finalize(orderr) + + return finalized_orderr.fullchain_pem + + +# Main examples: + + +def example_http(): + """This example executes the whole process of fulfilling a HTTP-01 + challenge for one specific domain. + + The workflow consists of: + (Account creation) + - Create account key + - Register account and accept TOS + (Certificate actions) + - Select HTTP-01 within offered challenges by the CA server + - Set up http challenge resource + - Set up standalone web server + - Create domain private key and CSR + - Issue certificate + - Renew certificate + - Revoke certificate + (Account update actions) + - Change contact information + - Deactivate Account + + """ + # Create account key + + acc_key = jose.JWKRSA( + key=rsa.generate_private_key(public_exponent=65537, + key_size=ACC_KEY_BITS, + backend=default_backend())) + + # Register account and accept TOS + + net = client.ClientNetwork(acc_key, user_agent=USER_AGENT) + directory = messages.Directory.from_json(net.get(DIRECTORY_URL).json()) + client_acme = client.ClientV2(directory, net=net) + + # Terms of Service URL is in client_acme.directory.meta.terms_of_service + # Registration Resource: regr + # Creates account with contact information. + email = ('fake@example.com') + regr = client_acme.new_account( + messages.NewRegistration.from_data( + email=email, terms_of_service_agreed=True)) + + # Create domain private key and CSR + pkey_pem, csr_pem = new_csr_comp(DOMAIN) + + # Issue certificate + + orderr = client_acme.new_order(csr_pem) + + # Select HTTP-01 within offered challenges by the CA server + challb = select_http01_chall(orderr) + + # The certificate is ready to be used in the variable "fullchain_pem". + fullchain_pem = perform_http01(client_acme, challb, orderr) + + # Renew certificate + + _, csr_pem = new_csr_comp(DOMAIN, pkey_pem) + + orderr = client_acme.new_order(csr_pem) + + challb = select_http01_chall(orderr) + + # Performing challenge + fullchain_pem = perform_http01(client_acme, challb, orderr) + + # Revoke certificate + + fullchain_com = jose.ComparableX509( + OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, fullchain_pem)) + + try: + client_acme.revoke(fullchain_com, 0) # revocation reason = 0 + except errors.ConflictError: + # Certificate already revoked. + pass + + # Query registration status. + client_acme.net.account = regr + try: + regr = client_acme.query_registration(regr) + except errors.Error as err: + if err.typ == messages.OLD_ERROR_PREFIX + 'unauthorized' \ + or err.typ == messages.ERROR_PREFIX + 'unauthorized': + # Status is deactivated. + pass + raise + + # Change contact information + + email = 'newfake@example.com' + regr = client_acme.update_registration( + regr.update( + body=regr.body.update( + contact=('mailto:' + email,) + ) + ) + ) + + # Deactivate account/registration + + regr = client_acme.deactivate_registration(regr) + + +if __name__ == "__main__": + example_http() -- cgit v1.2.3 From 339d034d6a5a57d296607795a4706203f81d7059 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 27 Feb 2019 18:21:47 +0100 Subject: Remove keyAuthorization field from the challenge response JWS token (#6758) Fixes #6755. POSTing the `keyAuthorization` in a JWS token when answering an ACME challenge, has been deprecated for some time now. Indeed, this is superfluous as the request is already authentified by the JWS signature. Boulder still accepts to see this field in the JWS token, and ignore it. Pebble in non strict mode also. But Pebble in strict mode refuses the request, to prepare complete removal of this field in ACME v2. Certbot still sends the `keyAuthorization` field. This PR removes it, and makes Certbot compliant with current ACME v2 protocol, and so Pebble in strict mode. See also [letsencrypt/pebble#192](https://github.com/letsencrypt/pebble/issues/192) for implementation details server side. * New implementation, with a fallback. * Add deprecation on changelog * Update acme/acme/client.py Co-Authored-By: adferrand * Fix an instance parameter * Update changelog, extend coverage * Update comment * Add unit tests on keyAuthorization dump * Update acme/acme/client.py Co-Authored-By: adferrand * Restrict the magic of setting a variable in immutable object in one place. Make a soon to be removed method private. --- CHANGELOG.md | 7 +++++++ acme/acme/challenges.py | 20 ++++++++++++++++++++ acme/acme/challenges_test.py | 12 ++++++++++++ acme/acme/client.py | 26 ++++++++++++++++++++------ acme/acme/client_test.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7eac2e9..53c158f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,13 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * The running of manual plugin hooks is now always included in Certbot's log output. * Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. +* The `acme` module avoids sending the `keyAuthorization` field in the JWS + payload when responding to a challenge as the field is not included in the + current ACME protocol. To ease the migration path for ACME CA servers, + Certbot and its `acme` module will first try the request without the + `keyAuthorization` field but will temporarily retry the request with the + field included if a `malformed` error is received. This fallback will be + removed in version 0.34.0. ### Fixed diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 501f74881..6f2b3757b 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -108,6 +108,10 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): key_authorization = jose.Field("keyAuthorization") thumbprint_hash_function = hashes.SHA256 + def __init__(self, *args, **kwargs): + super(KeyAuthorizationChallengeResponse, self).__init__(*args, **kwargs) + self._dump_authorization_key(False) + def verify(self, chall, account_public_key): """Verify the key authorization. @@ -140,6 +144,22 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): return True + def _dump_authorization_key(self, dump): + # type: (bool) -> None + """ + Set if keyAuthorization is dumped in the JSON representation of this ChallengeResponse. + NB: This method is declared as private because it will eventually be removed. + :param bool dump: True to dump the keyAuthorization, False otherwise + """ + object.__setattr__(self, '_dump_auth_key', dump) + + def to_partial_json(self): + jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json() + if not self._dump_auth_key: # pylint: disable=no-member + jobj.pop('keyAuthorization', None) + + return jobj + @six.add_metaclass(abc.ABCMeta) class KeyAuthorizationChallenge(_TokenChallenge): diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 81d39058e..4b905c1e5 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -94,6 +94,9 @@ class DNS01ResponseTest(unittest.TestCase): self.response = self.chall.response(KEY) def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + self.msg._dump_authorization_key(True) # pylint: disable=protected-access self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): @@ -165,6 +168,9 @@ class HTTP01ResponseTest(unittest.TestCase): self.response = self.chall.response(KEY) def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + self.msg._dump_authorization_key(True) # pylint: disable=protected-access self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): @@ -285,6 +291,9 @@ class TLSSNI01ResponseTest(unittest.TestCase): self.assertEqual(self.z_domain, self.response.z_domain) def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.response.to_partial_json()) + self.response._dump_authorization_key(True) # pylint: disable=protected-access self.assertEqual(self.jmsg, self.response.to_partial_json()) def test_from_json(self): @@ -419,6 +428,9 @@ class TLSALPN01ResponseTest(unittest.TestCase): self.response = self.chall.response(KEY) def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + self.msg._dump_authorization_key(True) # pylint: disable=protected-access self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): diff --git a/acme/acme/client.py b/acme/acme/client.py index 76b6a7788..d74713a7e 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -17,6 +17,7 @@ import requests from requests.adapters import HTTPAdapter import sys +from acme import challenges from acme import crypto_util from acme import errors from acme import jws @@ -155,7 +156,23 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes :raises .UnexpectedUpdate: """ - response = self._post(challb.uri, response) + # Because sending keyAuthorization in a response challenge has been removed from the ACME + # spec, it is not included in the KeyAuthorizationResponseChallenge JSON by default. + # However as a migration path, we temporarily expect a malformed error from the server, + # and fallback by resending the challenge response with the keyAuthorization field. + # TODO: Remove this fallback for Certbot 0.34.0 + try: + response = self._post(challb.uri, response) + except messages.Error as error: + if (error.code == 'malformed' + and isinstance(response, challenges.KeyAuthorizationChallengeResponse)): + logger.debug('Error while responding to a challenge without keyAuthorization ' + 'in the JWS, your ACME CA server may not support it:\n%s', error) + logger.debug('Retrying request with keyAuthorization set.') + response._dump_authorization_key(True) # pylint: disable=protected-access + response = self._post(challb.uri, response) + else: + raise try: authzr_uri = response.links['up']['url'] except KeyError: @@ -781,7 +798,7 @@ class ClientV2(ClientBase): 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) + 'your ACME CA server may not support it:\n%s', error) logger.debug('Retrying request with GET.') else: # pragma: no cover raise @@ -1191,10 +1208,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, acme_version=1, **kwargs): - try: - new_nonce_url = kwargs.pop('new_nonce_url') - except KeyError: - new_nonce_url = None + new_nonce_url = kwargs.pop('new_nonce_url', None) data = self._wrap_in_jws(obj, self._get_nonce(url, new_nonce_url), url, acme_version) kwargs.setdefault('headers', {'Content-Type': content_type}) response = self._send_request('POST', url, data=data, **kwargs) diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index b3d0f1921..dc8cc0c93 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -463,6 +463,34 @@ class ClientTest(ClientTestBase): errors.ClientError, self.client.answer_challenge, self.challr.body, challenges.DNSResponse(validation=None)) + def test_answer_challenge_key_authorization_fallback(self): + self.response.links['up'] = {'url': self.challr.authzr_uri} + self.response.json.return_value = self.challr.body.to_json() + + def _wrapper_post(url, obj, *args, **kwargs): # pylint: disable=unused-argument + """ + Simulate an old ACME CA server, that would respond a 'malformed' + error if keyAuthorization is missing. + """ + jobj = obj.to_partial_json() + if 'keyAuthorization' not in jobj: + raise messages.Error.with_code('malformed') + return self.response + self.net.post.side_effect = _wrapper_post + + # This challenge response is of type KeyAuthorizationChallengeResponse, so the fallback + # should be triggered, and avoid an exception. + http_chall_response = challenges.HTTP01Response(key_authorization='test', + resource=mock.MagicMock()) + self.client.answer_challenge(self.challr.body, http_chall_response) + + # This challenge response is not of type KeyAuthorizationChallengeResponse, so the fallback + # should not be triggered, leading to an exception. + dns_chall_response = challenges.DNSResponse(validation=None) + self.assertRaises( + errors.Error, self.client.answer_challenge, + self.challr.body, dns_chall_response) + def test_retry_after_date(self): self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' self.assertEqual( -- cgit v1.2.3 From 9c405a3cd12588ba27cdd8f79c8a101831bd6ad3 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 28 Feb 2019 00:16:52 +0100 Subject: Fix cryptography OCSP support (#6751) * Reenabling OCSP cryptography support * Refactor the validation logic of OCSP response to match the OpenSSL one * Prepare runtime for OCSP response test * Move unrelated test to another relevant place * Reimplement OCSP status checks in integration tests * Clean script * Protect OCSP check against connection errors * Update tests/certbot-boulder-integration.sh Co-Authored-By: adferrand * Cleaning * Add a specific script for letsencrypt-auto install+help * Remove inconsistent assertion * Add executable permissions * Remove unused variable * Move testdata * Corrected cleanup code * Empty commit --- CHANGELOG.md | 3 + certbot/crypto_util.py | 53 +++-- certbot/ocsp.py | 215 ++++++++++++++++----- certbot/tests/ocsp_test.py | 172 +++++++++++++++-- certbot/tests/testdata/google_certificate.pem | 41 ++++ .../tests/testdata/google_issuer_certificate.pem | 26 +++ certbot/util.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 40 ++-- .../pieces/dependency-requirements.txt | 40 ++-- tests/certbot-boulder-integration.sh | 55 ++++++ .../48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json | 1 + .../private_key.json | 1 + .../48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json | 1 + .../archive/a.encryption-example.com/cert1.pem | 29 +++ .../archive/a.encryption-example.com/chain1.pem | 27 +++ .../a.encryption-example.com/fullchain1.pem | 56 ++++++ .../archive/a.encryption-example.com/privkey1.pem | 28 +++ .../archive/b.encryption-example.com/cert1.pem | 29 +++ .../archive/b.encryption-example.com/chain1.pem | 27 +++ .../b.encryption-example.com/fullchain1.pem | 56 ++++++ .../archive/b.encryption-example.com/privkey1.pem | 28 +++ .../sample-config/csr/0000_csr-certbot.pem | 16 ++ .../sample-config/csr/0001_csr-certbot.pem | 16 ++ .../sample-config/csr/0002_csr-certbot.pem | 17 ++ .../sample-config/csr/0003_csr-certbot.pem | 17 ++ .../sample-config/keys/0000_key-certbot.pem | 28 +++ .../sample-config/keys/0001_key-certbot.pem | 28 +++ .../sample-config/keys/0002_key-certbot.pem | 28 +++ .../sample-config/keys/0003_key-certbot.pem | 28 +++ .../live/a.encryption-example.com/README | 10 + .../live/a.encryption-example.com/cert.pem | 1 + .../live/a.encryption-example.com/chain.pem | 1 + .../live/a.encryption-example.com/fullchain.pem | 1 + .../live/a.encryption-example.com/privkey.pem | 1 + .../live/b.encryption-example.com/README | 10 + .../live/b.encryption-example.com/cert.pem | 1 + .../live/b.encryption-example.com/chain.pem | 1 + .../live/b.encryption-example.com/fullchain.pem | 1 + .../live/b.encryption-example.com/privkey.pem | 1 + .../sample-config/options-ssl-apache.conf | 22 +++ .../renewal/a.encryption-example.com.conf | 15 ++ .../renewal/b.encryption-example.com.conf | 15 ++ .../test_letsencrypt_auto_certonly_standalone.sh | 21 -- .../48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json | 1 - .../private_key.json | 1 - .../48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json | 1 - .../archive/a.encryption-example.com/cert1.pem | 29 --- .../archive/a.encryption-example.com/chain1.pem | 27 --- .../a.encryption-example.com/fullchain1.pem | 56 ------ .../archive/a.encryption-example.com/privkey1.pem | 28 --- .../archive/b.encryption-example.com/cert1.pem | 29 --- .../archive/b.encryption-example.com/chain1.pem | 27 --- .../b.encryption-example.com/fullchain1.pem | 56 ------ .../archive/b.encryption-example.com/privkey1.pem | 28 --- .../sample-config/csr/0000_csr-certbot.pem | 16 -- .../sample-config/csr/0001_csr-certbot.pem | 16 -- .../sample-config/csr/0002_csr-certbot.pem | 17 -- .../sample-config/csr/0003_csr-certbot.pem | 17 -- .../sample-config/keys/0000_key-certbot.pem | 28 --- .../sample-config/keys/0001_key-certbot.pem | 28 --- .../sample-config/keys/0002_key-certbot.pem | 28 --- .../sample-config/keys/0003_key-certbot.pem | 28 --- .../live/a.encryption-example.com/README | 10 - .../live/a.encryption-example.com/cert.pem | 1 - .../live/a.encryption-example.com/chain.pem | 1 - .../live/a.encryption-example.com/fullchain.pem | 1 - .../live/a.encryption-example.com/privkey.pem | 1 - .../live/b.encryption-example.com/README | 10 - .../live/b.encryption-example.com/cert.pem | 1 - .../live/b.encryption-example.com/chain.pem | 1 - .../live/b.encryption-example.com/fullchain.pem | 1 - .../live/b.encryption-example.com/privkey.pem | 1 - .../testdata/sample-config/options-ssl-apache.conf | 22 --- .../renewal/a.encryption-example.com.conf | 15 -- .../renewal/b.encryption-example.com.conf | 15 -- 75 files changed, 1062 insertions(+), 688 deletions(-) create mode 100644 certbot/tests/testdata/google_certificate.pem create mode 100644 certbot/tests/testdata/google_issuer_certificate.pem create mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json create mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json create mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json create mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem create mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem create mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem create mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem create mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem create mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem create mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem create mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem create mode 100644 tests/integration/sample-config/csr/0000_csr-certbot.pem create mode 100644 tests/integration/sample-config/csr/0001_csr-certbot.pem create mode 100644 tests/integration/sample-config/csr/0002_csr-certbot.pem create mode 100644 tests/integration/sample-config/csr/0003_csr-certbot.pem create mode 100644 tests/integration/sample-config/keys/0000_key-certbot.pem create mode 100644 tests/integration/sample-config/keys/0001_key-certbot.pem create mode 100644 tests/integration/sample-config/keys/0002_key-certbot.pem create mode 100644 tests/integration/sample-config/keys/0003_key-certbot.pem create mode 100644 tests/integration/sample-config/live/a.encryption-example.com/README create mode 120000 tests/integration/sample-config/live/a.encryption-example.com/cert.pem create mode 120000 tests/integration/sample-config/live/a.encryption-example.com/chain.pem create mode 120000 tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem create mode 120000 tests/integration/sample-config/live/a.encryption-example.com/privkey.pem create mode 100644 tests/integration/sample-config/live/b.encryption-example.com/README create mode 120000 tests/integration/sample-config/live/b.encryption-example.com/cert.pem create mode 120000 tests/integration/sample-config/live/b.encryption-example.com/chain.pem create mode 120000 tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem create mode 120000 tests/integration/sample-config/live/b.encryption-example.com/privkey.pem create mode 100644 tests/integration/sample-config/options-ssl-apache.conf create mode 100644 tests/integration/sample-config/renewal/a.encryption-example.com.conf create mode 100644 tests/integration/sample-config/renewal/b.encryption-example.com.conf delete mode 100644 tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json delete mode 100644 tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json delete mode 100644 tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json delete mode 100644 tests/letstest/testdata/sample-config/archive/a.encryption-example.com/cert1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/a.encryption-example.com/chain1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/a.encryption-example.com/fullchain1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/a.encryption-example.com/privkey1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/b.encryption-example.com/cert1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/b.encryption-example.com/chain1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/b.encryption-example.com/fullchain1.pem delete mode 100644 tests/letstest/testdata/sample-config/archive/b.encryption-example.com/privkey1.pem delete mode 100644 tests/letstest/testdata/sample-config/csr/0000_csr-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/csr/0001_csr-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/csr/0002_csr-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/csr/0003_csr-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/keys/0000_key-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/keys/0001_key-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/keys/0002_key-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/keys/0003_key-certbot.pem delete mode 100644 tests/letstest/testdata/sample-config/live/a.encryption-example.com/README delete mode 120000 tests/letstest/testdata/sample-config/live/a.encryption-example.com/cert.pem delete mode 120000 tests/letstest/testdata/sample-config/live/a.encryption-example.com/chain.pem delete mode 120000 tests/letstest/testdata/sample-config/live/a.encryption-example.com/fullchain.pem delete mode 120000 tests/letstest/testdata/sample-config/live/a.encryption-example.com/privkey.pem delete mode 100644 tests/letstest/testdata/sample-config/live/b.encryption-example.com/README delete mode 120000 tests/letstest/testdata/sample-config/live/b.encryption-example.com/cert.pem delete mode 120000 tests/letstest/testdata/sample-config/live/b.encryption-example.com/chain.pem delete mode 120000 tests/letstest/testdata/sample-config/live/b.encryption-example.com/fullchain.pem delete mode 120000 tests/letstest/testdata/sample-config/live/b.encryption-example.com/privkey.pem delete mode 100644 tests/letstest/testdata/sample-config/options-ssl-apache.conf delete mode 100644 tests/letstest/testdata/sample-config/renewal/a.encryption-example.com.conf delete mode 100644 tests/letstest/testdata/sample-config/renewal/b.encryption-example.com.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c158f8f..3eea1923d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ More details about these changes can be found on our GitHub repo. * Avoid reprocessing challenges that are already validated when a certificate is issued. +* If possible, Certbot uses built-in support for OCSP from recent cryptography + versions instead of the OpenSSL binary: as a consequence Certbot does not need + the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. * Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges with the `acme` module. diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index c4a389cd5..66c68eb38 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -19,7 +19,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey # https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography import x509 # type: ignore +from cryptography import x509 # type: ignore from OpenSSL import crypto from OpenSSL import SSL # type: ignore @@ -226,7 +226,7 @@ def verify_renewable_cert(renewable_cert): def verify_renewable_cert_sig(renewable_cert): - """ Verifies the signature of a `.storage.RenewableCert` object. + """Verifies the signature of a `.storage.RenewableCert` object. :param `.storage.RenewableCert` renewable_cert: cert to verify @@ -239,22 +239,8 @@ def verify_renewable_cert_sig(renewable_cert): cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() with warnings.catch_warnings(): - warnings.simplefilter("ignore") - if isinstance(pk, RSAPublicKey): - # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi - verifier = pk.verifier( # type: ignore - cert.signature, PKCS1v15(), cert.signature_hash_algorithm - ) - verifier.update(cert.tbs_certificate_bytes) - verifier.verify() - elif isinstance(pk, EllipticCurvePublicKey): - verifier = pk.verifier( - cert.signature, ECDSA(cert.signature_hash_algorithm) - ) - verifier.update(cert.tbs_certificate_bytes) - verifier.verify() - else: - raise errors.Error("Unsupported public key type") + verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, + cert.signature_hash_algorithm) except (IOError, ValueError, InvalidSignature) as e: error_str = "verifying the signature of the cert located at {0} has failed. \ Details: {1}".format(renewable_cert.cert, e) @@ -262,6 +248,37 @@ def verify_renewable_cert_sig(renewable_cert): raise errors.Error(error_str) +def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm): + """Check the signature of a payload. + + :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature + :param bytes signature: the signature bytes + :param bytes payload: the payload bytes + :param cryptography.hazmat.primitives.hashes.HashAlgorithm + signature_hash_algorithm: algorithm used to hash the payload + + :raises InvalidSignature: If signature verification fails. + :raises errors.Error: If public key type is not supported + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + if isinstance(public_key, RSAPublicKey): + # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi + verifier = public_key.verifier( # type: ignore + signature, PKCS1v15(), signature_hash_algorithm + ) + verifier.update(payload) + verifier.verify() + elif isinstance(public_key, EllipticCurvePublicKey): + verifier = public_key.verifier( + signature, ECDSA(signature_hash_algorithm) + ) + verifier.update(payload) + verifier.verify() + else: + raise errors.Error("Unsupported public key type") + + def verify_cert_matches_priv_key(cert_path, key_path): """ Verifies that the private key and cert match. diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 049e14827..6f4c0b0fb 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -1,53 +1,79 @@ """Tools for checking certificate revocation.""" import logging import re - +from datetime import datetime, timedelta from subprocess import Popen, PIPE +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp # pylint: disable=import-error + getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp = None # type: ignore +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +import requests + +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot import crypto_util from certbot import errors from certbot import util logger = logging.getLogger(__name__) + class RevocationChecker(object): - "This class figures out OCSP checking on this system, and performs it." + """This class figures out OCSP checking on this system, and performs it.""" - def __init__(self): + def __init__(self, enforce_openssl_binary_usage=False): self.broken = False - - if not util.exe_exists("openssl"): - logger.info("openssl not installed, can't check revocation") - self.broken = True - return - - # New versions of openssl want -header var=val, old ones want -header var val - test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) - _out, err = test_host_format.communicate() - if "Missing =" in err: - self.host_args = lambda host: ["Host=" + host] - else: - self.host_args = lambda host: ["Host", host] - + self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp + + if self.use_openssl_binary: + if not util.exe_exists("openssl"): + logger.info("openssl not installed, can't check revocation") + self.broken = True + return + + # New versions of openssl want -header var=val, old ones want -header var val + test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], + stdout=PIPE, stderr=PIPE, universal_newlines=True) + _out, err = test_host_format.communicate() + if "Missing =" in err: + self.host_args = lambda host: ["Host=" + host] + else: + self.host_args = lambda host: ["Host", host] def ocsp_revoked(self, cert_path, chain_path): + # type: (str, str) -> bool """Get revoked status for a particular cert version. .. todo:: Make this a non-blocking call :param str cert_path: Path to certificate :param str chain_path: Path to intermediate cert - :rtype bool or None: :returns: True if revoked; False if valid or the check failed + :rtype: bool """ if self.broken: return False - - url, host = self.determine_ocsp_server(cert_path) - if not host: + url, host = _determine_ocsp_server(cert_path) + if not host or not url: return False + + if self.use_openssl_binary: + return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) + else: + return _check_ocsp_cryptography(cert_path, chain_path, url) + + def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): + # type: (str, str, str, str) -> bool # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! cmd = ["openssl", "ocsp", "-no_nonce", @@ -65,33 +91,131 @@ class RevocationChecker(object): except errors.SubprocessError: logger.info("OCSP check failed for %s (are we offline?)", cert_path) return False - return _translate_ocsp_query(cert_path, output, err) - def determine_ocsp_server(self, cert_path): - """Extract the OCSP server host from a certificate. +def _determine_ocsp_server(cert_path): + # type: (str) -> Tuple[Optional[str], Optional[str]] + """Extract the OCSP server host from a certificate. - :param str cert_path: Path to the cert we're checking OCSP for - :rtype tuple: - :returns: (OCSP server URL or None, OCSP server host or None) + :param str cert_path: Path to the cert we're checking OCSP for + :rtype tuple: + :returns: (OCSP server URL or None, OCSP server host or None) - """ - try: - url, _err = util.run_script( - ["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"], - log=logger.debug) - except errors.SubprocessError: - logger.info("Cannot extract OCSP URI from %s", cert_path) - return None, None + """ + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + try: + extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + ocsp_oid = x509.AuthorityInformationAccessOID.OCSP + descriptions = [description for description in extension.value + if description.access_method == ocsp_oid] + + url = descriptions[0].access_location.value + except (x509.ExtensionNotFound, IndexError): + logger.info("Cannot extract OCSP URI from %s", cert_path) + return None, None + + url = url.rstrip() + host = url.partition("://")[2].rstrip("/") + + if host: + return url, host + else: + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None + + +def _check_ocsp_cryptography(cert_path, chain_path, url): + # type: (str, str, str) -> bool + # Retrieve OCSP response + with open(chain_path, 'rb') as file_handler: + issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + request_binary = request.public_bytes(serialization.Encoding.DER) + try: + response = requests.post(url, data=request_binary, + headers={'Content-Type': 'application/ocsp-request'}) + except requests.exceptions.RequestException: + logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True) + return False + if response.status_code != 200: + logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code) + return False + + response_ocsp = ocsp.load_der_ocsp_response(response.content) + + # Check OCSP response validity + if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: + logger.error("Invalid OCSP response status for %s: %s", + cert_path, response_ocsp.response_status) + return False + + # Check OCSP signature + try: + _check_ocsp_response(response_ocsp, request, issuer) + except UnsupportedAlgorithm as e: + logger.error(str(e)) + except errors.Error as e: + logger.error(str(e)) + except InvalidSignature: + logger.error('Invalid signature on OCSP response for %s', cert_path) + except AssertionError as error: + logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) + else: + # Check OCSP certificate status + logger.debug("OCSP certificate status for %s is: %s", + cert_path, response_ocsp.certificate_status) + return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED + + return False + + +def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): + """Verify that the OCSP is valid for serveral criterias""" + # Assert OCSP response corresponds to the certificate we are talking about + if response_ocsp.serial_number != request_ocsp.serial_number: + raise AssertionError('the certificate in response does not correspond ' + 'to the certificate in request') + + # Assert signature is valid + _check_ocsp_response_signature(response_ocsp, issuer_cert) + + # Assert issuer in response is the expected one + if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) + or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash + or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): + raise AssertionError('the issuer does not correspond to issuer of the certificate.') + + # In following checks, two situations can occur: + # * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate + # * nextUpdate is not set, and requirement is thisUpdate < now + # NB1: We add a validity period tolerance to handle clock time inconsistencies, + # value is 5 min like for OpenSSL. + # NB2: Another check is to verify that thisUpdate is not too old, it is optional + # for OpenSSL, so we do not do it here. + # See OpenSSL implementation as a reference: + # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 + now = datetime.now() + if not response_ocsp.this_update: + raise AssertionError('param thisUpdate is not set.') + if response_ocsp.this_update > now + timedelta(minutes=5): + raise AssertionError('param thisUpdate is in the future.') + if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5): + raise AssertionError('param nextUpdate is in the past.') + + +def _check_ocsp_response_signature(response_ocsp, issuer_cert): + """Verify an OCSP response signature against certificate issuer""" + # Following line may raise UnsupportedAlgorithm + chosen_hash = response_ocsp.signature_hash_algorithm + crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature, + response_ocsp.tbs_response_bytes, chosen_hash) - url = url.rstrip() - host = url.partition("://")[2].rstrip("/") - if host: - return url, host - else: - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): """Parse openssl's weird output to work out what it means.""" @@ -102,7 +226,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): warning = good.group(1) if good else None - if (not "Response verify OK" in ocsp_errors) or (good and warning) or unknown: + if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: logger.info("Revocation status for %s is unknown", cert_path) logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) return False @@ -115,6 +239,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False - diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 55cd24adb..ad3467e5a 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,18 +1,33 @@ """Tests for ocsp.py""" # pylint: disable=protected-access - import unittest +from datetime import datetime, timedelta +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +from cryptography import x509 +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp as ocsp_lib # pylint: disable=import-error + getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp_lib = None # type: ignore import mock from certbot import errors +from certbot.tests import util as test_util out = """Missing = in header key=value ocsp: Use -help for summary. """ -class OCSPTest(unittest.TestCase): +class OCSPTestOpenSSL(unittest.TestCase): + """ + OCSP revokation tests using OpenSSL binary. + """ def setUp(self): from certbot import ocsp @@ -22,7 +37,7 @@ class OCSPTest(unittest.TestCase): mock_communicate.communicate.return_value = (None, out) mock_popen.return_value = mock_communicate mock_exists.return_value = True - self.checker = ocsp.RevocationChecker() + self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) def tearDown(self): pass @@ -37,23 +52,23 @@ class OCSPTest(unittest.TestCase): mock_exists.return_value = True from certbot import ocsp - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 1) self.assertEqual(checker.host_args("x"), ["Host=x"]) mock_communicate.communicate.return_value = (None, out.partition("\n")[2]) - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(checker.host_args("x"), ["Host", "x"]) self.assertEqual(checker.broken, False) mock_exists.return_value = False mock_popen.call_count = 0 - checker = ocsp.RevocationChecker() + checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 0) self.assertEqual(mock_log.call_count, 1) self.assertEqual(checker.broken, True) - @mock.patch('certbot.ocsp.RevocationChecker.determine_ocsp_server') + @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.util.run_script') def test_ocsp_revoked(self, mock_run, mock_determine): self.checker.broken = True @@ -71,21 +86,12 @@ class OCSPTest(unittest.TestCase): self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) self.assertEqual(mock_run.call_count, 2) + def test_determine_ocsp_server(self): + cert_path = test_util.vector_path('google_certificate.pem') - @mock.patch('certbot.ocsp.logger.info') - @mock.patch('certbot.util.run_script') - def test_determine_ocsp_server(self, mock_run, mock_info): - uri = "http://ocsp.stg-int-x1.letsencrypt.org/" - host = "ocsp.stg-int-x1.letsencrypt.org" - mock_run.return_value = uri, "" - self.assertEqual(self.checker.determine_ocsp_server("beep"), (uri, host)) - mock_run.return_value = "ftp:/" + host + "/", "" - self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) - self.assertEqual(mock_info.call_count, 1) - - c = "confusion" - mock_run.side_effect = errors.SubprocessError(c) - self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None)) + from certbot import ocsp + result = ocsp._determine_ocsp_server(cert_path) + self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result) @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') @@ -112,6 +118,129 @@ class OCSPTest(unittest.TestCase): self.assertEqual(mock_log.info.call_count, 1) +@unittest.skipIf(not ocsp_lib, + reason='This class tests functionalities available only on cryptography>=2.5.0') +class OSCPTestCryptography(unittest.TestCase): + """ + OCSP revokation tests using Cryptography >= 2.4.0 + """ + + def setUp(self): + from certbot import ocsp + self.checker = ocsp.RevocationChecker() + self.cert_path = test_util.vector_path('google_certificate.pem') + self.chain_path = test_util.vector_path('google_issuer_certificate.pem') + + @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot.ocsp._check_ocsp_cryptography') + def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): + mock_determine.return_value = ('http://example.com', 'example.com') + self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') + + @mock.patch('certbot.ocsp.requests.post') + @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') + def test_revoke(self, mock_ocsp_response, mock_post): + with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload'): + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertTrue(revoked) + + @mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') + @mock.patch('certbot.ocsp.requests.post') + @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') + def test_revoke_resiliency(self, mock_ocsp_response, mock_post, mock_check): + # Server return an invalid HTTP response + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=400) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # OCSP response in invalid + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # OCSP response is valid, but certificate status is unknown + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # The OCSP response says that the certificate is revoked, but certificate + # does not contain the OCSP extension. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + with mock.patch('cryptography.x509.Extensions.get_extension_for_class', + side_effect=x509.ExtensionNotFound( + 'Not found', x509.AuthorityInformationAccessOID.OCSP)): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # Valid response, OCSP extension is present, + # but OCSP response uses an unsupported signature. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = UnsupportedAlgorithm('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # And now, the signature itself is invalid. + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = InvalidSignature('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + # Finally, assertion error on OCSP response validity + mock_ocsp_response.return_value = _construct_mock_ocsp_response( + ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) + mock_post.return_value = mock.Mock(status_code=200) + mock_check.side_effect = AssertionError('foo') + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + + self.assertFalse(revoked) + + +def _construct_mock_ocsp_response(certificate_status, response_status): + cert = x509.load_pem_x509_certificate( + test_util.load_vector('google_certificate.pem'), default_backend()) + issuer = x509.load_pem_x509_certificate( + test_util.load_vector('google_issuer_certificate.pem'), default_backend()) + builder = ocsp_lib.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + + return mock.Mock( + response_status=response_status, + certificate_status=certificate_status, + serial_number=request.serial_number, + issuer_key_hash=request.issuer_key_hash, + issuer_name_hash=request.issuer_name_hash, + hash_algorithm=hashes.SHA1(), + next_update=datetime.now() + timedelta(days=1), + this_update=datetime.now() - timedelta(days=1), + signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1, + ) + + # pylint: disable=line-too-long openssl_confused = ("", """ /etc/letsencrypt/live/example.org/cert.pem: good @@ -165,5 +294,6 @@ revoked """, """Response verify OK""") + if __name__ == '__main__': unittest.main() # pragma: no cover diff --git a/certbot/tests/testdata/google_certificate.pem b/certbot/tests/testdata/google_certificate.pem new file mode 100644 index 000000000..c26fea0b1 --- /dev/null +++ b/certbot/tests/testdata/google_certificate.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1 +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk +IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy +MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB +BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF +Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG +A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD +VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ +w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj +/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE +sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ +xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM +fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag +re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG +A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE +AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0 +oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy +LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy +dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB +FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF +BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS +BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 +U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA +MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY +BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT +Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg +U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE +AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51 +vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV +CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN +8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG +eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v +ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN +HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB +Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk +myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq +WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik= +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/google_issuer_certificate.pem b/certbot/tests/testdata/google_issuer_certificate.pem new file mode 100644 index 000000000..50db47bc4 --- /dev/null +++ b/certbot/tests/testdata/google_issuer_certificate.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw +HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs +U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy +MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg +U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW +XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK +71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9 +RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z +ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT +kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz +AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa +Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu +MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv +b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz +cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc +aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA +HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e +ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq +wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu +FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy +7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV +c7o835DLAFshEWfC7TIe3g== +-----END CERTIFICATE----- diff --git a/certbot/util.py b/certbot/util.py index 416075ce8..097593e9d 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -62,7 +62,7 @@ def run_script(params, log=logger.error): """Run the script with the given params. :param list params: List of parameters to pass to Popen - :param logging.Logger log: Logger to use for errors + :param callable log: Logger method to use for errors """ try: diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 02656521f..19aaa4eeb 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1034,26 +1034,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 1fac78836..dff57dfd5 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -60,26 +60,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 diff --git a/tests/certbot-boulder-integration.sh b/tests/certbot-boulder-integration.sh index 630571148..9d1ae9ec2 100755 --- a/tests/certbot-boulder-integration.sh +++ b/tests/certbot-boulder-integration.sh @@ -15,9 +15,11 @@ command -v python > /dev/null || (echo "Error, python executable is not in the P . ./tests/integration/_common.sh export PATH="$PATH:/usr/sbin" # /usr/sbin/nginx +CURRENT_DIR="$(pwd)" cleanup_and_exit() { EXIT_STATUS=$? + cd $CURRENT_DIR if SERVER_STILL_RUNNING=`ps -p $python_server_pid -o pid=` then echo Kill server subprocess, left running by abnormal exit @@ -527,3 +529,56 @@ if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then fi coverage report --fail-under 64 --include 'certbot/*' --show-missing + +# Test OCSP status + +## OCSP 1: Check stale OCSP status +pushd ./tests/integration + +OUT=`common certificates --config-dir sample-config` +TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l` +EXPIRED=`echo "$OUT" | grep EXPIRED | wc -l` + +if [ "$TEST_CERTS" != 2 ] ; then + echo "Did not find two test certs as expected ($TEST_CERTS)" + exit 1 +fi + +if [ "$EXPIRED" != 2 ] ; then + echo "Did not find two test certs as expected ($EXPIRED)" + exit 1 +fi + +popd + +## OSCP 2: Check live certificate OCSP status (VALID) +common --domains le-ocsp-check.wtf +OUT=`common certificates` +VALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep VALID | wc -l` +EXPIRED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep EXPIRED | wc -l` + +if [ "$VALID" != 1 ] ; then + echo "Expected le-ocsp-check.wtf to be VALID" + exit 1 +fi + +if [ "$EXPIRED" != 0 ] ; then + echo "Did not expect le-ocsp-check.wtf to be EXPIRED" + exit 1 +fi + +## OSCP 3: Check live certificate OCSP status (REVOKED) +common revoke --cert-name le-ocsp-check.wtf --no-delete-after-revoke +OUT=`common certificates` +INVALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep INVALID | wc -l` +REVOKED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep REVOKED | wc -l` + +if [ "$INVALID" != 1 ] ; then + echo "Expected le-ocsp-check.wtf to be INVALID" + exit 1 +fi + +if [ "$REVOKED" != 1 ] ; then + echo "Expected le-ocsp-check.wtf to be REVOKED" + exit 1 +fi diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json new file mode 100644 index 000000000..6fe0b47f3 --- /dev/null +++ b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json @@ -0,0 +1 @@ +{"creation_host": "ec2-52-91-193-99.compute-1.amazonaws.com", "creation_dt": "2016-12-23T02:08:32Z"} \ No newline at end of file diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json new file mode 100644 index 000000000..0affb573d --- /dev/null +++ b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json @@ -0,0 +1 @@ +{"e": "AQAB", "d": "W410Wny96RO4qJ207KGQ3RSn0KAwqb93JBMHWU1yS9H3fN_2eCpFYdMLNFI9t1__nW1okeUioEfvMN_YW-G9krw97kVdZ63MfbeJCf35Onc8VZhAnk_3V8MtS26Of8ml0tTYhlQ65nuzhvHbY7aP-Uk260oDN-AbCCVhu5G4CQiMY6sdtCc8YkB6gK7SK874oWU7ogvAIPtNtEI-AXDUBYNAfoh34s1r2fE6mJSX4UYtzWB2hTUisvZdVL5JUInvxpCQFttk1cwWLFwwb6d2ERCbseeudvGJ6fkYiJ-EYxfHKOQK2kxPeOlLFMwGYQ0khDxTNajxQ1Asl43r7wgAeQ", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw", "q": "0xdfHMMKYWHPE1UoQ10niDI7rnCM9vmPo4JpCOCYZf51KPNJgNaPCw62Q0Y-ZQfCBifypQyf291d0_2C_Rif0WMg07Y-Ypv8SpPK77vLV12GoAoAX2Xy3AJAz1gDBcyUzDtRlrzgCZja9YqIDVzMatkdPJXaBrBu5B-sXv4wGa0", "p": "7pl5xe_400Sn6PdN_F6KLWHFROVd7379WPWGHYmnvOvXx7DmrMjDsTOmhNRlrv7jPemVqMzp1FGsubGBizEMFGyCET30bUgH6ZU7Cmgv-2JKKN1FZnm1QTepZ7kjAT_qRCI6nvN6J0SIX197QOSz3hMmP7UYQXQ32QcVKdCksps", "kty": "RSA", "qi": "zG60VpLZjgR0o7dTeEP-HjbtxHUedyZLGe4FIPyWrPRl28anebkMUGzibpB8z5ohRsqHU2i4tmDq2NMvshISqkpk8t5PLiIcQgU46HQ24SCv7lunkVPKYU1n2uXVVfttrBP4c3UkjYzda1bcIVp6cJHanm_JuWI5nxy9ebVQJiw", "dp": "kRIBx0aj7Jh22x_aa9JzgypKDhzDY4W7tmX5-GWk9ioTVZgKeQ3MZiZ4XZTiimbxdchbNXn5xh0uvuzdTesxZA2he6hGwFcmcHBKqIY2fksBuhznQGpJuXCFcMpRLUZWQrzpFZIGOG_j1tEwGIG1lxXfkKakK8_k0PEMfhMcwHc", "dq": "AsoSRa0GHBdQxy6e45T9ir0vMLToB_NwRHbasHVXTjG4lpvwYrVzGnBNVEI_XNJna_FnMWsjSaJ5NO3qpzGGGxw2ONX1qRPql4mwas6Od08TElZPfvM37FRTSuoc0BzN8ozuHRHN3BKbAheciKCrStYnnr9ULDZ0oKsSegbd19k"} \ No newline at end of file diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json new file mode 100644 index 000000000..fdd2df7da --- /dev/null +++ b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json @@ -0,0 +1 @@ +{"body": {"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", "key": {"e": "AQAB", "kty": "RSA", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw"}}, "uri": "https://acme-staging.api.letsencrypt.org/acme/reg/566631", "new_authzr_uri": "https://acme-staging.api.letsencrypt.org/acme/new-authz", "terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"} \ No newline at end of file diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem new file mode 100644 index 000000000..80739dd3f --- /dev/null +++ b/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco +hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR +WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM +FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca +nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny +Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q +lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR +omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J +/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH +FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY +Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC +4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem new file mode 100644 index 000000000..29a54e2a1 --- /dev/null +++ b/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem new file mode 100644 index 000000000..ba245d213 --- /dev/null +++ b/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco +hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR +WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM +FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca +nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny +Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q +lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR +omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J +/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH +FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY +Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC +4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem new file mode 100644 index 000000000..b3059cb47 --- /dev/null +++ b/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj +8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 +FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH +g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 +/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN +fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg +PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY +JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo +fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc +xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj +bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D +WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 +mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu +wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 +enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO +iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x +nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL +W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 +q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R +zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz +h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 +Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS +xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy +YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY +o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl +wkqKc3PndJHrR40mswUOHbs= +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem new file mode 100644 index 000000000..0c1c6b5ef --- /dev/null +++ b/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR +FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f +s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim ++144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi +Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl +eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 +XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u +SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw +U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT +0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ +omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV +ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem new file mode 100644 index 000000000..29a54e2a1 --- /dev/null +++ b/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem new file mode 100644 index 000000000..705cca6c3 --- /dev/null +++ b/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR +FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f +s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim ++144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi +Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl +eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 +XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u +SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw +U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT +0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ +omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV +ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem new file mode 100644 index 000000000..c43af4f50 --- /dev/null +++ b/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh +DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 +Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt +YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv +Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 +npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 +HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX +Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l +/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r +m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK +tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj +xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n +9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf +Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN +cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT ++kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P +JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F +NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ +IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv +JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc +Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY +EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT +xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe +vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj +t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar +Bwhs4S84KEJFT0LAC3YeRQ== +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/csr/0000_csr-certbot.pem b/tests/integration/sample-config/csr/0000_csr-certbot.pem new file mode 100644 index 000000000..16d73ffde --- /dev/null +++ b/tests/integration/sample-config/csr/0000_csr-certbot.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIChjCCAW4CAQIwFzEVMBMGA1UEAwwMaXMuaXNub3Qub3JnMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7nsHOCTvvQlRYXpI5xE7AggqTVmM8lGi18Y2 +gVlr3WYAS7higHRJjWroAmZ2Bx9IRfHOxwhVWm/hlc/u4w0IYlRnArg6suXrgtn+ +6Ea0WDUCiKEiKvQqD0kaI936hpydU/dY70UZnpKSyi0kiCrLzCkIaXS8HJdLOIXB +Q4FMVqjppYjUejMgrabthq1QTqU0S4MxwS1oj67VqaAkedGWxFgFQ2kIFV0/WL13 +Xs0SCTYyN96KK1Q2CF63HoN79zc+TVslg32DDU5UF7sVVvlkoHcl0OgR9l4jfou5 +HwmatMjXPI+0bWVxmw6iC6tbK7Dx+ytYIodhEOL52Youzy/lLwIDAQABoCowKAYJ +KoZIhvcNAQkOMRswGTAXBgNVHREEEDAOggxpcy5pc25vdC5vcmcwDQYJKoZIhvcN +AQELBQADggEBAAJsLiylvGq64wxVt8EBeXRB4ycBzC5J/pyOWMP9oexW1o3XPhCC ++0tIQVGk7wJMe3+WiPMVsn4pGOUGDaPvfC7ijlvipzaYyLEfnr+J7pukhYbzNHmu +XL5lbTJ0hTCfqUjmi1yE4M/v2eX5yNaEHsZExZ1NbtwutE/Tx5iSqt7kxbIoFqmF +7Tne2JHjt945+/l9yvqaIcEFOmblS0OxY9EjxgJdhKCKbhD/ZoYaVVisc52h/2/M +jtzvzZr1rZCvFnuQxGDco5vYe3u7uJ9tQHLCMpoIorT3kX3yTdgnWxst6XBVUY/P +Q6O18obG4ALoP/ESzvTauQIwFVGfal/jqyI= +-----END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0001_csr-certbot.pem b/tests/integration/sample-config/csr/0001_csr-certbot.pem new file mode 100644 index 000000000..452bc45cd --- /dev/null +++ b/tests/integration/sample-config/csr/0001_csr-certbot.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICgDCCAWgCAQIwFDESMBAGA1UEAwwJaXNub3Qub3JnMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAsEAy7rdPsYFFt9VsK9NZy+W9nbsYGmvIaMSyJkEg +Xe2P0MmnWG/hn6F1bLPm85uS5oQsOWDpwVz31tKhoWhUDbRzPWP5Ur2NnHY92Whz +5tP4ir4vEEDuB9etQ8+wZ7+3z9q1VhPcgDdYyouQVB0QejJ1yUBiVPr289bW//ln +kj9DFxn4oufoJ4ELSZSZgWFM92EGKMMy1zD2bJH87mI0Gs0pIOEo+QMJ8TvVEbau ++aFaTANslqRAF5LaWcrPgvHor7cK5w/4bVBZCmY2QYKqlYwZiRPpwg3Ii6B9Q8kz +rDkGSDjwsazca4api57cza13XkRl7KvyZbwTwlFBud+ydwIDAQABoCcwJQYJKoZI +hvcNAQkOMRgwFjAUBgNVHREEDTALgglpc25vdC5vcmcwDQYJKoZIhvcNAQELBQAD +ggEBAB3vniZw2ML6E9jrMY8DtQjPDDNr1BqOGzyOaJipqpGZSRvhTA44DAAjdFpS +5BLrnXniPIZGG4/6WorLTEDBnlFcLinUg7GDT2DpauQa+4PLxFi13hE1TuSVOp9A +08YXhzALvZxMIjQ/tVhAp0+PkGEWU2wI0SmDvUUTJqMwSJYgXkf/vBS34/koKywV +gPDod5AbLuhYgKiQYwDZ0dd69leT0REmizuaHtA6tW3mBgewSKotwqY3fHmhHV8o +YLSVhImz4jJjK3LjmcdXuBxqE0z+p6n/+lSGG8RR/E8pix4OAkVAP6nyt/loW1BX +ZzWOuSHozGN5UJSL248vLFWrsV8= +-----END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0002_csr-certbot.pem b/tests/integration/sample-config/csr/0002_csr-certbot.pem new file mode 100644 index 000000000..2ee44b3fd --- /dev/null +++ b/tests/integration/sample-config/csr/0002_csr-certbot.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYS5lbmNyeXB0aW9uLWV4YW1wbGUuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrPRxyiGwKrJY/Aavv1r +yP4k7cepumEtPOGg0dgX59byCXIzIGY4zJmNzy/wTdTcpgRe65FbNH5u9RcFws6F +KiAiJ9kM2IwoQXIM1wOUbTzN70ZOPaFNDKN63Zm49Y264ni7lQwW+htpx4OohO6o +SpSuMIGLkKYRezCtXeXYFVKI5w9/6X5lHzXEbupyMZ8sbphBZxqexhiivP+RJG3B +iMBPg1p5Rg1dV4fWW5ZNaFt53ymYp8FV+/44SbBTICFITV49+fIKPfvCjX6epDPf +sgk0SkjRE1vGsTtkKADjRCsVWpz1d69zKkWL0N+1VbMM0G133tCWYe4ioD0pvcL9 +7wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhhLmVuY3J5cHRp +b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJyKJHdUwR9BOKYJarUy +P8mqu6UBUt8faSu6o3EUeDHbnUgxGAVwB5TJV0+JwIjPFQFRofHE8CFhUvi0W0YJ +BsGVqblnJzz80NkUX9uwjBAGKaDxXqXDOctkQSAOJxM/rvD2uJLmlokibDDm7mnS +DX8SUVAPgORDGlVTGATjvmA3YeH05gHRFgRDWFP5DOZs99fx4957HrXhsIxew98s +Felupgswnouyq3crrgcjY0qo3Pc5gjUcuwaT2cjtvzi93f/ImDt6f1sdSSJB00wk +34lbs/Z+0G8bH1dqYIZzkwNgq7rolhDYh3WRgTlfkgkV7FlkQGm8qn5uoQvaXaaS +ShM= +-----END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0003_csr-certbot.pem b/tests/integration/sample-config/csr/0003_csr-certbot.pem new file mode 100644 index 000000000..2a50dc33d --- /dev/null +++ b/tests/integration/sample-config/csr/0003_csr-certbot.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYi5lbmNyeXB0aW9uLWV4YW1wbGUuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYDq1ZEUB9jI4Qz0e0lc +ri0J3W0YXZBmuUkcQvc8nMUeie/tlo+jpWStqvW4uKczgBW7H9+z29yxt15u7ngN +emPUiWXOMqze2NK0crjqW4yqPyB/7fItkghWc3hjGQi13VYG2Kb7XjiS7WBQuwae +cea4J59fPqqqQvMVmwSWo7s1j5MvgXib8vSUONixT75eBsdP2CJPDSMCLxLIfwhr +ghiLjKecy4e5LXV77I62rXqQtbVF2fGtimV673PQNT7peWvW1uV5KTLNtZ6UOB2y +Jx87FnPmluYAtcStvjvZOJ1wccrpuwxWFH8/XNAr1h1z60Lfd3RcJFGA+h243xdF +9wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhiLmVuY3J5cHRp +b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACDw8/zjFaIdp4aqyrzT +fzaqAnoXZt3+0JDPLANy3DLCJmK2TQMyItg/Oid5NEQ45UluXv811IMCcONyVmrD +19W3XErhTJOJMgpjg4GLBRRFhLm+uTIcbv/xEeUgOYbslsqwi2gHECe1Vsj/Ahbo +QXXqcDg1cXe6VTQhX+Nw5q30t/oCmkJWcUVHBON2nbOujRz1+z6AjVl1dM+CYDRq +bsKn7m3biYS7lx7/ApIuhJQsghcmccCtWrH5GsOUsJUgiANv5u+QZgGaajkCRKYV +fD/u8qTPfKb/+lTxtDrfFOGH+mbZKbKf2/ibneYcql8fFQWiapbudI2cMk8yDxA9 +2Tw= +-----END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/keys/0000_key-certbot.pem b/tests/integration/sample-config/keys/0000_key-certbot.pem new file mode 100644 index 000000000..9a018c41e --- /dev/null +++ b/tests/integration/sample-config/keys/0000_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDuewc4JO+9CVFh +ekjnETsCCCpNWYzyUaLXxjaBWWvdZgBLuGKAdEmNaugCZnYHH0hF8c7HCFVab+GV +z+7jDQhiVGcCuDqy5euC2f7oRrRYNQKIoSIq9CoPSRoj3fqGnJ1T91jvRRmekpLK +LSSIKsvMKQhpdLwcl0s4hcFDgUxWqOmliNR6MyCtpu2GrVBOpTRLgzHBLWiPrtWp +oCR50ZbEWAVDaQgVXT9YvXdezRIJNjI33oorVDYIXrceg3v3Nz5NWyWDfYMNTlQX +uxVW+WSgdyXQ6BH2XiN+i7kfCZq0yNc8j7RtZXGbDqILq1srsPH7K1gih2EQ4vnZ +ii7PL+UvAgMBAAECggEBAIX9jeLXrfNSRu0z3b4mCjdsCwiGphCIGayOa5VlfptY +chYZNQ7jR2gzhsPCedIqm1rhL8LYRcyYS/D2cUwUyH8m2PHIPQLC9/3/KZ+sCiv9 +LL1De4USxobsFcnNMLNtT2Ab+1YERw63X85EauAu226MJ3PI6OBPiS3qyNl6zj9p +do9SyzsNFEGtDk+ndWf3keoHBKLge4DP1lA3Jt42wSUxVv9U5SLvFpMQm8PqbqrK +4ofXcgxMFIJHDDGXsoDI7LOOsV6ncBVlui0ELM/QWBb5x1605VxqEDRL+h/wMp5Y +JIc6HbgcERmtHmyFlHHNtjAXxeulJVDJQDekd/irJ5ECgYEA/WQJ4LwkkA/Yhf2W +WYJtD8LuwzRnvGs3R+rgx3+hOeO4TFZD5fzObZVRSwWQO2jbOtBJOaRLUsUngcJQ +DXr/FGf1rnGhLmNeLE+jN9FS73wBhEXViFZ/fzhVibGbc7u45Y5REykZj8HtUHP5 +hBKR2Nx94WDiv1MBgcKrRk6yI50CgYEA8O+vWcMzEdPtonHl8UgTa8/c5g/RBBvS +plB8mVsmM/E5CNwnetZM32cg7dC7yNaZzn3qF6w+LdE2vw3j5VbqvuVUvsRgvYcJ +3kMbHsbsxkRw+HVWZGgEtWNzuYQUL0xN+xzIZDWkbtuaihqYAy4voYNAM08BTNcE +POQEMIGxcDsCgYEAg+TLo3grS/WDjhM2bHcQT9D2uRMRIClqx/uBbzaG9HwNFWcd +xpv102KSwwstTU9CNfXu95sGPhozez5qrumj1rpaTqgE7wF4JnZ5jfdeRRv2KiSz +hlkH2m+3TontUauYDZ0rpF6TWJnn7iW/7jhARHJY77SfslkBgsqSnnEeFp0CgYEA +7FsFVvZRzCRt01UOsPL28mWYmyxa7D/rFvKQONUdFgmG3PUz2aIPCX2e5Q1GmlBD +1Djbg1uaJ9I8dZJHxbzNTnWk+/ujt2mYuax1F20n65xKgsKA/MC6FcM5TH2QW5Hs +UfI7d2rUI1hVMzPBeiU93qDmQy825E1uP9mjbn5cNe8CgYAsBpJgS1LkDruyWmjG +ZTzdHGciA1O3gUArLQmyUfJlPS3Hgwn7wnBBihtGZDHmjJ7734+PQ9ioCnO9Pb+K +8Cp29vJ85lka7o7I48OeScLmczgEUYOPCrbkkKJdKaG6gn5CKpRBVYDlhbWjVZ51 +4uda/BQ1hqHh8WmxK6x21qC9JQ== +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0001_key-certbot.pem b/tests/integration/sample-config/keys/0001_key-certbot.pem new file mode 100644 index 000000000..a3a7faf55 --- /dev/null +++ b/tests/integration/sample-config/keys/0001_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwQDLut0+xgUW3 +1Wwr01nL5b2duxgaa8hoxLImQSBd7Y/QyadYb+GfoXVss+bzm5LmhCw5YOnBXPfW +0qGhaFQNtHM9Y/lSvY2cdj3ZaHPm0/iKvi8QQO4H161Dz7Bnv7fP2rVWE9yAN1jK +i5BUHRB6MnXJQGJU+vbz1tb/+WeSP0MXGfii5+gngQtJlJmBYUz3YQYowzLXMPZs +kfzuYjQazSkg4Sj5AwnxO9URtq75oVpMA2yWpEAXktpZys+C8eivtwrnD/htUFkK +ZjZBgqqVjBmJE+nCDciLoH1DyTOsOQZIOPCxrNxrhqmLntzNrXdeRGXsq/JlvBPC +UUG537J3AgMBAAECggEBAJoZR27X72GvORmmDFG1FInlcIf8EPLo0exoLaqsvnPh +RSCzbxEvoQFE1boZARB1MVdCsLfqN/bMJhU5TAAni3YAE9HVGyRwfuQRrbnsTYnA +Q0prRhLb8kIBHIhxijbrtPaSroF4FA42VfehVqt0TffJLpqrJE5QrqI7cPeVRCzk +laLyi2rjZBhN6l1OxFSIOrEDlcowlPUMORbmNDMbq/dLu5riVO/kP2x70K1IiANI +NZzVhMwkktYj3Ku2altRLcyRrC3Bs46w2QF6wiC88/LMapt79um65P/SgcCgyOYE +oxJywZwMnyw8ut1Y+KS8B7AdzqWmj7Q9wr0xbW6+4eECgYEA6sNrMGZVRUFRPAcr +m3y5fkM/WJ8tAkT3hI2/noljv3k8iameTy/B/y3p+aM8/6Oa/gdO/SWtfKPednkf +CIh/3J5tJ1yvK7wHEEU6r6qxVKr2FLCMfSXoGx+E+r9qPF8WdV+55beVgO86UqA5 +y9a6DhNA+Xt4jDJc+rbpga0pj60CgYEAwDHDV0lR7jVT6iiU6VhAu1gM/SBVqXE/ +VSfmGihgaO4pJ9OgfqusKbraNONc+oBub7B4T3sSnF/I0mSUclD6brmG99OWLIg8 +L6/ed+bLPRO0iTvKRLbyBLom1Totfh/X6iQ2Zci40vLIS7kbYDban16ca+iSm+0B +41RV4q6+vzMCgYBLoxiW6HGStZ+xonHHT+EHsCzppac/su64c18IeiV8HFiH1fFe +e/mZ+LYIqzJM/u5B6CLn5srFfJqBOzbnbescLqLmarM5eQQhltx4mps1tzs/oT4y +WBM3IembTC6zMsOun1/qhkKR3wHAe0UDyrP5MvTdLI3DRbq1QFdtY1gfpQKBgEgg +pNGWJ5RBGSvwbOohf7GPOtioEN3VLVJ09crtSjk23+Uda8b+AE9s20Ur6pHsLwXl +cVFKu9JJtCEZNAiu0T1KjRdmpZ4yxnuTAed3iuByC7fQ43jkO3GAtuAgxD/oDWzG +iE+sg4hPKtIYNujlzSgwJn3su1CfIq1A0jaPI/C3AoGAHGTBtsXdR1goFvcxwA+n +l2bAs/InoED5nj26a//JuONgtGlm//QKCxIgjjktpeZm8sfsaYeR+rwIUODWRX/e +LUF85a70SaH+FZRXBRS2d/zaNxO4F37nE5fwO+VAurSb7El7yOyCepK22iSHMYdl +xak78KZKv3HXW5yrfA+dc2Y= +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0002_key-certbot.pem b/tests/integration/sample-config/keys/0002_key-certbot.pem new file mode 100644 index 000000000..b3059cb47 --- /dev/null +++ b/tests/integration/sample-config/keys/0002_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj +8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 +FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH +g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 +/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN +fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg +PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY +JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo +fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc +xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj +bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D +WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 +mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu +wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 +enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO +iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x +nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL +W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 +q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R +zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz +h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 +Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS +xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy +YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY +o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl +wkqKc3PndJHrR40mswUOHbs= +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0003_key-certbot.pem b/tests/integration/sample-config/keys/0003_key-certbot.pem new file mode 100644 index 000000000..c43af4f50 --- /dev/null +++ b/tests/integration/sample-config/keys/0003_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh +DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 +Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt +YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv +Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 +npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 +HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX +Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l +/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r +m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK +tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj +xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n +9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf +Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN +cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT ++kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P +JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F +NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ +IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv +JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc +Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY +EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT +xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe +vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj +t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar +Bwhs4S84KEJFT0LAC3YeRQ== +-----END PRIVATE KEY----- diff --git a/tests/integration/sample-config/live/a.encryption-example.com/README b/tests/integration/sample-config/live/a.encryption-example.com/README new file mode 100644 index 000000000..15194ae3a --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/README @@ -0,0 +1,10 @@ +This directory contains your keys and certificates. + +`privkey.pem` : the private key for your certificate. +`fullchain.pem`: the certificate file used in most server software. +`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. +`cert.pem` : will break many server configurations, and should not be used + without reading further documentation (see link below). + +We recommend not moving these files. For more information, see the Certbot +User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem new file mode 120000 index 000000000..79b6abdf9 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d6b30420 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..b801ef735 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem new file mode 120000 index 000000000..74e20c5ff --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/README b/tests/integration/sample-config/live/b.encryption-example.com/README new file mode 100644 index 000000000..15194ae3a --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/README @@ -0,0 +1,10 @@ +This directory contains your keys and certificates. + +`privkey.pem` : the private key for your certificate. +`fullchain.pem`: the certificate file used in most server software. +`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. +`cert.pem` : will break many server configurations, and should not be used + without reading further documentation (see link below). + +We recommend not moving these files. For more information, see the Certbot +User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem new file mode 120000 index 000000000..41b06370e --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d3e18bec --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..3a08c1432 --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem new file mode 120000 index 000000000..182aa6d78 --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/options-ssl-apache.conf b/tests/integration/sample-config/options-ssl-apache.conf new file mode 100644 index 000000000..ec07a4ba3 --- /dev/null +++ b/tests/integration/sample-config/options-ssl-apache.conf @@ -0,0 +1,22 @@ +# Baseline setting to Include for SSL sites + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/tests/integration/sample-config/renewal/a.encryption-example.com.conf b/tests/integration/sample-config/renewal/a.encryption-example.com.conf new file mode 100644 index 000000000..4455137b4 --- /dev/null +++ b/tests/integration/sample-config/renewal/a.encryption-example.com.conf @@ -0,0 +1,15 @@ +# renew_before_expiry = 30 days +version = 0.10.0.dev0 +archive_dir = sample-config/archive/a.encryption-example.com +cert = sample-config/live/a.encryption-example.com/cert.pem +privkey = sample-config/live/a.encryption-example.com/privkey.pem +chain = sample-config/live/a.encryption-example.com/chain.pem +fullchain = sample-config/live/a.encryption-example.com/fullchain.pem + +# Options used in the renewal process +[renewalparams] +authenticator = apache +installer = apache +account = 48d6b9e8d767eccf7e4d877d6ffa81e3 +config_dir = sample-config +server = https://acme-staging.api.letsencrypt.org/directory diff --git a/tests/integration/sample-config/renewal/b.encryption-example.com.conf b/tests/integration/sample-config/renewal/b.encryption-example.com.conf new file mode 100644 index 000000000..58d8a13d9 --- /dev/null +++ b/tests/integration/sample-config/renewal/b.encryption-example.com.conf @@ -0,0 +1,15 @@ +# renew_before_expiry = 30 days +version = 0.10.0.dev0 +archive_dir = sample-config/archive/b.encryption-example.com +cert = sample-config/live/b.encryption-example.com/cert.pem +privkey = sample-config/live/b.encryption-example.com/privkey.pem +chain = sample-config/live/b.encryption-example.com/chain.pem +fullchain = sample-config/live/b.encryption-example.com/fullchain.pem + +# Options used in the renewal process +[renewalparams] +authenticator = apache +installer = apache +account = 48d6b9e8d767eccf7e4d877d6ffa81e3 +config_dir = sample-config +server = https://acme-staging.api.letsencrypt.org/directory diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 2cbe66a83..081ff3829 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -17,27 +17,6 @@ letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \ --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL -# we have to jump through some hoops to cope with relative paths in renewal -# conf files ... -# 1. be in the right directory -cd tests/letstest/testdata/ - -# 2. refer to the config with the same level of relativity that it itself -# contains :/ -OUT=`letsencrypt-auto certificates --config-dir sample-config -v --no-self-upgrade` -TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l` -REVOKED=`echo "$OUT" | grep REVOKED | wc -l` - -if [ "$TEST_CERTS" != 2 ] ; then - echo "Did not find two test certs as expected ($TEST_CERTS)" - exit 1 -fi - -if [ "$REVOKED" != 1 ] ; then - echo "Did not find one revoked cert as expected ($REVOKED)" - exit 1 -fi - if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBCOMMAND]"; then echo "letsencrypt-auto not included in help output!" exit 1 diff --git a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json b/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json deleted file mode 100644 index 6fe0b47f3..000000000 --- a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json +++ /dev/null @@ -1 +0,0 @@ -{"creation_host": "ec2-52-91-193-99.compute-1.amazonaws.com", "creation_dt": "2016-12-23T02:08:32Z"} \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json b/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json deleted file mode 100644 index 0affb573d..000000000 --- a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json +++ /dev/null @@ -1 +0,0 @@ -{"e": "AQAB", "d": "W410Wny96RO4qJ207KGQ3RSn0KAwqb93JBMHWU1yS9H3fN_2eCpFYdMLNFI9t1__nW1okeUioEfvMN_YW-G9krw97kVdZ63MfbeJCf35Onc8VZhAnk_3V8MtS26Of8ml0tTYhlQ65nuzhvHbY7aP-Uk260oDN-AbCCVhu5G4CQiMY6sdtCc8YkB6gK7SK874oWU7ogvAIPtNtEI-AXDUBYNAfoh34s1r2fE6mJSX4UYtzWB2hTUisvZdVL5JUInvxpCQFttk1cwWLFwwb6d2ERCbseeudvGJ6fkYiJ-EYxfHKOQK2kxPeOlLFMwGYQ0khDxTNajxQ1Asl43r7wgAeQ", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw", "q": "0xdfHMMKYWHPE1UoQ10niDI7rnCM9vmPo4JpCOCYZf51KPNJgNaPCw62Q0Y-ZQfCBifypQyf291d0_2C_Rif0WMg07Y-Ypv8SpPK77vLV12GoAoAX2Xy3AJAz1gDBcyUzDtRlrzgCZja9YqIDVzMatkdPJXaBrBu5B-sXv4wGa0", "p": "7pl5xe_400Sn6PdN_F6KLWHFROVd7379WPWGHYmnvOvXx7DmrMjDsTOmhNRlrv7jPemVqMzp1FGsubGBizEMFGyCET30bUgH6ZU7Cmgv-2JKKN1FZnm1QTepZ7kjAT_qRCI6nvN6J0SIX197QOSz3hMmP7UYQXQ32QcVKdCksps", "kty": "RSA", "qi": "zG60VpLZjgR0o7dTeEP-HjbtxHUedyZLGe4FIPyWrPRl28anebkMUGzibpB8z5ohRsqHU2i4tmDq2NMvshISqkpk8t5PLiIcQgU46HQ24SCv7lunkVPKYU1n2uXVVfttrBP4c3UkjYzda1bcIVp6cJHanm_JuWI5nxy9ebVQJiw", "dp": "kRIBx0aj7Jh22x_aa9JzgypKDhzDY4W7tmX5-GWk9ioTVZgKeQ3MZiZ4XZTiimbxdchbNXn5xh0uvuzdTesxZA2he6hGwFcmcHBKqIY2fksBuhznQGpJuXCFcMpRLUZWQrzpFZIGOG_j1tEwGIG1lxXfkKakK8_k0PEMfhMcwHc", "dq": "AsoSRa0GHBdQxy6e45T9ir0vMLToB_NwRHbasHVXTjG4lpvwYrVzGnBNVEI_XNJna_FnMWsjSaJ5NO3qpzGGGxw2ONX1qRPql4mwas6Od08TElZPfvM37FRTSuoc0BzN8ozuHRHN3BKbAheciKCrStYnnr9ULDZ0oKsSegbd19k"} \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json b/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json deleted file mode 100644 index fdd2df7da..000000000 --- a/tests/letstest/testdata/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json +++ /dev/null @@ -1 +0,0 @@ -{"body": {"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", "key": {"e": "AQAB", "kty": "RSA", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw"}}, "uri": "https://acme-staging.api.letsencrypt.org/acme/reg/566631", "new_authzr_uri": "https://acme-staging.api.letsencrypt.org/acme/new-authz", "terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"} \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/cert1.pem b/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/cert1.pem deleted file mode 100644 index 80739dd3f..000000000 --- a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/cert1.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco -hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR -WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM -FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca -nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny -Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q -lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR -omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J -/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH -FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY -Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC -4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/chain1.pem b/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/chain1.pem deleted file mode 100644 index 29a54e2a1..000000000 --- a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/chain1.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/fullchain1.pem b/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/fullchain1.pem deleted file mode 100644 index ba245d213..000000000 --- a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/fullchain1.pem +++ /dev/null @@ -1,56 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco -hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR -WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM -FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca -nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny -Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q -lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR -omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J -/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH -FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY -Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC -4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/privkey1.pem b/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/privkey1.pem deleted file mode 100644 index b3059cb47..000000000 --- a/tests/letstest/testdata/sample-config/archive/a.encryption-example.com/privkey1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj -8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 -FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH -g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 -/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN -fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg -PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY -JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo -fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc -xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj -bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D -WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 -mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu -wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 -enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO -iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x -nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL -W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 -q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R -zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz -h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 -Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS -xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy -YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY -o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl -wkqKc3PndJHrR40mswUOHbs= ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/cert1.pem b/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/cert1.pem deleted file mode 100644 index 0c1c6b5ef..000000000 --- a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/cert1.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR -FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f -s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim -+144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi -Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl -eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 -XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u -SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw -U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT -0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ -omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV -ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/chain1.pem b/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/chain1.pem deleted file mode 100644 index 29a54e2a1..000000000 --- a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/chain1.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/fullchain1.pem b/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/fullchain1.pem deleted file mode 100644 index 705cca6c3..000000000 --- a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/fullchain1.pem +++ /dev/null @@ -1,56 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR -FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f -s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim -+144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi -Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl -eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 -XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u -SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw -U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT -0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ -omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV -ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/privkey1.pem b/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/privkey1.pem deleted file mode 100644 index c43af4f50..000000000 --- a/tests/letstest/testdata/sample-config/archive/b.encryption-example.com/privkey1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh -DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 -Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt -YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv -Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 -npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 -HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX -Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l -/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r -m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK -tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj -xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n -9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf -Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN -cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT -+kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P -JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F -NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ -IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv -JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc -Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY -EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT -xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe -vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj -t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar -Bwhs4S84KEJFT0LAC3YeRQ== ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/csr/0000_csr-certbot.pem b/tests/letstest/testdata/sample-config/csr/0000_csr-certbot.pem deleted file mode 100644 index 16d73ffde..000000000 --- a/tests/letstest/testdata/sample-config/csr/0000_csr-certbot.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChjCCAW4CAQIwFzEVMBMGA1UEAwwMaXMuaXNub3Qub3JnMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7nsHOCTvvQlRYXpI5xE7AggqTVmM8lGi18Y2 -gVlr3WYAS7higHRJjWroAmZ2Bx9IRfHOxwhVWm/hlc/u4w0IYlRnArg6suXrgtn+ -6Ea0WDUCiKEiKvQqD0kaI936hpydU/dY70UZnpKSyi0kiCrLzCkIaXS8HJdLOIXB -Q4FMVqjppYjUejMgrabthq1QTqU0S4MxwS1oj67VqaAkedGWxFgFQ2kIFV0/WL13 -Xs0SCTYyN96KK1Q2CF63HoN79zc+TVslg32DDU5UF7sVVvlkoHcl0OgR9l4jfou5 -HwmatMjXPI+0bWVxmw6iC6tbK7Dx+ytYIodhEOL52Youzy/lLwIDAQABoCowKAYJ -KoZIhvcNAQkOMRswGTAXBgNVHREEEDAOggxpcy5pc25vdC5vcmcwDQYJKoZIhvcN -AQELBQADggEBAAJsLiylvGq64wxVt8EBeXRB4ycBzC5J/pyOWMP9oexW1o3XPhCC -+0tIQVGk7wJMe3+WiPMVsn4pGOUGDaPvfC7ijlvipzaYyLEfnr+J7pukhYbzNHmu -XL5lbTJ0hTCfqUjmi1yE4M/v2eX5yNaEHsZExZ1NbtwutE/Tx5iSqt7kxbIoFqmF -7Tne2JHjt945+/l9yvqaIcEFOmblS0OxY9EjxgJdhKCKbhD/ZoYaVVisc52h/2/M -jtzvzZr1rZCvFnuQxGDco5vYe3u7uJ9tQHLCMpoIorT3kX3yTdgnWxst6XBVUY/P -Q6O18obG4ALoP/ESzvTauQIwFVGfal/jqyI= ------END CERTIFICATE REQUEST----- diff --git a/tests/letstest/testdata/sample-config/csr/0001_csr-certbot.pem b/tests/letstest/testdata/sample-config/csr/0001_csr-certbot.pem deleted file mode 100644 index 452bc45cd..000000000 --- a/tests/letstest/testdata/sample-config/csr/0001_csr-certbot.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICgDCCAWgCAQIwFDESMBAGA1UEAwwJaXNub3Qub3JnMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAsEAy7rdPsYFFt9VsK9NZy+W9nbsYGmvIaMSyJkEg -Xe2P0MmnWG/hn6F1bLPm85uS5oQsOWDpwVz31tKhoWhUDbRzPWP5Ur2NnHY92Whz -5tP4ir4vEEDuB9etQ8+wZ7+3z9q1VhPcgDdYyouQVB0QejJ1yUBiVPr289bW//ln -kj9DFxn4oufoJ4ELSZSZgWFM92EGKMMy1zD2bJH87mI0Gs0pIOEo+QMJ8TvVEbau -+aFaTANslqRAF5LaWcrPgvHor7cK5w/4bVBZCmY2QYKqlYwZiRPpwg3Ii6B9Q8kz -rDkGSDjwsazca4api57cza13XkRl7KvyZbwTwlFBud+ydwIDAQABoCcwJQYJKoZI -hvcNAQkOMRgwFjAUBgNVHREEDTALgglpc25vdC5vcmcwDQYJKoZIhvcNAQELBQAD -ggEBAB3vniZw2ML6E9jrMY8DtQjPDDNr1BqOGzyOaJipqpGZSRvhTA44DAAjdFpS -5BLrnXniPIZGG4/6WorLTEDBnlFcLinUg7GDT2DpauQa+4PLxFi13hE1TuSVOp9A -08YXhzALvZxMIjQ/tVhAp0+PkGEWU2wI0SmDvUUTJqMwSJYgXkf/vBS34/koKywV -gPDod5AbLuhYgKiQYwDZ0dd69leT0REmizuaHtA6tW3mBgewSKotwqY3fHmhHV8o -YLSVhImz4jJjK3LjmcdXuBxqE0z+p6n/+lSGG8RR/E8pix4OAkVAP6nyt/loW1BX -ZzWOuSHozGN5UJSL248vLFWrsV8= ------END CERTIFICATE REQUEST----- diff --git a/tests/letstest/testdata/sample-config/csr/0002_csr-certbot.pem b/tests/letstest/testdata/sample-config/csr/0002_csr-certbot.pem deleted file mode 100644 index 2ee44b3fd..000000000 --- a/tests/letstest/testdata/sample-config/csr/0002_csr-certbot.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYS5lbmNyeXB0aW9uLWV4YW1wbGUuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrPRxyiGwKrJY/Aavv1r -yP4k7cepumEtPOGg0dgX59byCXIzIGY4zJmNzy/wTdTcpgRe65FbNH5u9RcFws6F -KiAiJ9kM2IwoQXIM1wOUbTzN70ZOPaFNDKN63Zm49Y264ni7lQwW+htpx4OohO6o -SpSuMIGLkKYRezCtXeXYFVKI5w9/6X5lHzXEbupyMZ8sbphBZxqexhiivP+RJG3B -iMBPg1p5Rg1dV4fWW5ZNaFt53ymYp8FV+/44SbBTICFITV49+fIKPfvCjX6epDPf -sgk0SkjRE1vGsTtkKADjRCsVWpz1d69zKkWL0N+1VbMM0G133tCWYe4ioD0pvcL9 -7wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhhLmVuY3J5cHRp -b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJyKJHdUwR9BOKYJarUy -P8mqu6UBUt8faSu6o3EUeDHbnUgxGAVwB5TJV0+JwIjPFQFRofHE8CFhUvi0W0YJ -BsGVqblnJzz80NkUX9uwjBAGKaDxXqXDOctkQSAOJxM/rvD2uJLmlokibDDm7mnS -DX8SUVAPgORDGlVTGATjvmA3YeH05gHRFgRDWFP5DOZs99fx4957HrXhsIxew98s -Felupgswnouyq3crrgcjY0qo3Pc5gjUcuwaT2cjtvzi93f/ImDt6f1sdSSJB00wk -34lbs/Z+0G8bH1dqYIZzkwNgq7rolhDYh3WRgTlfkgkV7FlkQGm8qn5uoQvaXaaS -ShM= ------END CERTIFICATE REQUEST----- diff --git a/tests/letstest/testdata/sample-config/csr/0003_csr-certbot.pem b/tests/letstest/testdata/sample-config/csr/0003_csr-certbot.pem deleted file mode 100644 index 2a50dc33d..000000000 --- a/tests/letstest/testdata/sample-config/csr/0003_csr-certbot.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYi5lbmNyeXB0aW9uLWV4YW1wbGUuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYDq1ZEUB9jI4Qz0e0lc -ri0J3W0YXZBmuUkcQvc8nMUeie/tlo+jpWStqvW4uKczgBW7H9+z29yxt15u7ngN -emPUiWXOMqze2NK0crjqW4yqPyB/7fItkghWc3hjGQi13VYG2Kb7XjiS7WBQuwae -cea4J59fPqqqQvMVmwSWo7s1j5MvgXib8vSUONixT75eBsdP2CJPDSMCLxLIfwhr -ghiLjKecy4e5LXV77I62rXqQtbVF2fGtimV673PQNT7peWvW1uV5KTLNtZ6UOB2y -Jx87FnPmluYAtcStvjvZOJ1wccrpuwxWFH8/XNAr1h1z60Lfd3RcJFGA+h243xdF -9wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhiLmVuY3J5cHRp -b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACDw8/zjFaIdp4aqyrzT -fzaqAnoXZt3+0JDPLANy3DLCJmK2TQMyItg/Oid5NEQ45UluXv811IMCcONyVmrD -19W3XErhTJOJMgpjg4GLBRRFhLm+uTIcbv/xEeUgOYbslsqwi2gHECe1Vsj/Ahbo -QXXqcDg1cXe6VTQhX+Nw5q30t/oCmkJWcUVHBON2nbOujRz1+z6AjVl1dM+CYDRq -bsKn7m3biYS7lx7/ApIuhJQsghcmccCtWrH5GsOUsJUgiANv5u+QZgGaajkCRKYV -fD/u8qTPfKb/+lTxtDrfFOGH+mbZKbKf2/ibneYcql8fFQWiapbudI2cMk8yDxA9 -2Tw= ------END CERTIFICATE REQUEST----- diff --git a/tests/letstest/testdata/sample-config/keys/0000_key-certbot.pem b/tests/letstest/testdata/sample-config/keys/0000_key-certbot.pem deleted file mode 100644 index 9a018c41e..000000000 --- a/tests/letstest/testdata/sample-config/keys/0000_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDuewc4JO+9CVFh -ekjnETsCCCpNWYzyUaLXxjaBWWvdZgBLuGKAdEmNaugCZnYHH0hF8c7HCFVab+GV -z+7jDQhiVGcCuDqy5euC2f7oRrRYNQKIoSIq9CoPSRoj3fqGnJ1T91jvRRmekpLK -LSSIKsvMKQhpdLwcl0s4hcFDgUxWqOmliNR6MyCtpu2GrVBOpTRLgzHBLWiPrtWp -oCR50ZbEWAVDaQgVXT9YvXdezRIJNjI33oorVDYIXrceg3v3Nz5NWyWDfYMNTlQX -uxVW+WSgdyXQ6BH2XiN+i7kfCZq0yNc8j7RtZXGbDqILq1srsPH7K1gih2EQ4vnZ -ii7PL+UvAgMBAAECggEBAIX9jeLXrfNSRu0z3b4mCjdsCwiGphCIGayOa5VlfptY -chYZNQ7jR2gzhsPCedIqm1rhL8LYRcyYS/D2cUwUyH8m2PHIPQLC9/3/KZ+sCiv9 -LL1De4USxobsFcnNMLNtT2Ab+1YERw63X85EauAu226MJ3PI6OBPiS3qyNl6zj9p -do9SyzsNFEGtDk+ndWf3keoHBKLge4DP1lA3Jt42wSUxVv9U5SLvFpMQm8PqbqrK -4ofXcgxMFIJHDDGXsoDI7LOOsV6ncBVlui0ELM/QWBb5x1605VxqEDRL+h/wMp5Y -JIc6HbgcERmtHmyFlHHNtjAXxeulJVDJQDekd/irJ5ECgYEA/WQJ4LwkkA/Yhf2W -WYJtD8LuwzRnvGs3R+rgx3+hOeO4TFZD5fzObZVRSwWQO2jbOtBJOaRLUsUngcJQ -DXr/FGf1rnGhLmNeLE+jN9FS73wBhEXViFZ/fzhVibGbc7u45Y5REykZj8HtUHP5 -hBKR2Nx94WDiv1MBgcKrRk6yI50CgYEA8O+vWcMzEdPtonHl8UgTa8/c5g/RBBvS -plB8mVsmM/E5CNwnetZM32cg7dC7yNaZzn3qF6w+LdE2vw3j5VbqvuVUvsRgvYcJ -3kMbHsbsxkRw+HVWZGgEtWNzuYQUL0xN+xzIZDWkbtuaihqYAy4voYNAM08BTNcE -POQEMIGxcDsCgYEAg+TLo3grS/WDjhM2bHcQT9D2uRMRIClqx/uBbzaG9HwNFWcd -xpv102KSwwstTU9CNfXu95sGPhozez5qrumj1rpaTqgE7wF4JnZ5jfdeRRv2KiSz -hlkH2m+3TontUauYDZ0rpF6TWJnn7iW/7jhARHJY77SfslkBgsqSnnEeFp0CgYEA -7FsFVvZRzCRt01UOsPL28mWYmyxa7D/rFvKQONUdFgmG3PUz2aIPCX2e5Q1GmlBD -1Djbg1uaJ9I8dZJHxbzNTnWk+/ujt2mYuax1F20n65xKgsKA/MC6FcM5TH2QW5Hs -UfI7d2rUI1hVMzPBeiU93qDmQy825E1uP9mjbn5cNe8CgYAsBpJgS1LkDruyWmjG -ZTzdHGciA1O3gUArLQmyUfJlPS3Hgwn7wnBBihtGZDHmjJ7734+PQ9ioCnO9Pb+K -8Cp29vJ85lka7o7I48OeScLmczgEUYOPCrbkkKJdKaG6gn5CKpRBVYDlhbWjVZ51 -4uda/BQ1hqHh8WmxK6x21qC9JQ== ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/keys/0001_key-certbot.pem b/tests/letstest/testdata/sample-config/keys/0001_key-certbot.pem deleted file mode 100644 index a3a7faf55..000000000 --- a/tests/letstest/testdata/sample-config/keys/0001_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwQDLut0+xgUW3 -1Wwr01nL5b2duxgaa8hoxLImQSBd7Y/QyadYb+GfoXVss+bzm5LmhCw5YOnBXPfW -0qGhaFQNtHM9Y/lSvY2cdj3ZaHPm0/iKvi8QQO4H161Dz7Bnv7fP2rVWE9yAN1jK -i5BUHRB6MnXJQGJU+vbz1tb/+WeSP0MXGfii5+gngQtJlJmBYUz3YQYowzLXMPZs -kfzuYjQazSkg4Sj5AwnxO9URtq75oVpMA2yWpEAXktpZys+C8eivtwrnD/htUFkK -ZjZBgqqVjBmJE+nCDciLoH1DyTOsOQZIOPCxrNxrhqmLntzNrXdeRGXsq/JlvBPC -UUG537J3AgMBAAECggEBAJoZR27X72GvORmmDFG1FInlcIf8EPLo0exoLaqsvnPh -RSCzbxEvoQFE1boZARB1MVdCsLfqN/bMJhU5TAAni3YAE9HVGyRwfuQRrbnsTYnA -Q0prRhLb8kIBHIhxijbrtPaSroF4FA42VfehVqt0TffJLpqrJE5QrqI7cPeVRCzk -laLyi2rjZBhN6l1OxFSIOrEDlcowlPUMORbmNDMbq/dLu5riVO/kP2x70K1IiANI -NZzVhMwkktYj3Ku2altRLcyRrC3Bs46w2QF6wiC88/LMapt79um65P/SgcCgyOYE -oxJywZwMnyw8ut1Y+KS8B7AdzqWmj7Q9wr0xbW6+4eECgYEA6sNrMGZVRUFRPAcr -m3y5fkM/WJ8tAkT3hI2/noljv3k8iameTy/B/y3p+aM8/6Oa/gdO/SWtfKPednkf -CIh/3J5tJ1yvK7wHEEU6r6qxVKr2FLCMfSXoGx+E+r9qPF8WdV+55beVgO86UqA5 -y9a6DhNA+Xt4jDJc+rbpga0pj60CgYEAwDHDV0lR7jVT6iiU6VhAu1gM/SBVqXE/ -VSfmGihgaO4pJ9OgfqusKbraNONc+oBub7B4T3sSnF/I0mSUclD6brmG99OWLIg8 -L6/ed+bLPRO0iTvKRLbyBLom1Totfh/X6iQ2Zci40vLIS7kbYDban16ca+iSm+0B -41RV4q6+vzMCgYBLoxiW6HGStZ+xonHHT+EHsCzppac/su64c18IeiV8HFiH1fFe -e/mZ+LYIqzJM/u5B6CLn5srFfJqBOzbnbescLqLmarM5eQQhltx4mps1tzs/oT4y -WBM3IembTC6zMsOun1/qhkKR3wHAe0UDyrP5MvTdLI3DRbq1QFdtY1gfpQKBgEgg -pNGWJ5RBGSvwbOohf7GPOtioEN3VLVJ09crtSjk23+Uda8b+AE9s20Ur6pHsLwXl -cVFKu9JJtCEZNAiu0T1KjRdmpZ4yxnuTAed3iuByC7fQ43jkO3GAtuAgxD/oDWzG -iE+sg4hPKtIYNujlzSgwJn3su1CfIq1A0jaPI/C3AoGAHGTBtsXdR1goFvcxwA+n -l2bAs/InoED5nj26a//JuONgtGlm//QKCxIgjjktpeZm8sfsaYeR+rwIUODWRX/e -LUF85a70SaH+FZRXBRS2d/zaNxO4F37nE5fwO+VAurSb7El7yOyCepK22iSHMYdl -xak78KZKv3HXW5yrfA+dc2Y= ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/keys/0002_key-certbot.pem b/tests/letstest/testdata/sample-config/keys/0002_key-certbot.pem deleted file mode 100644 index b3059cb47..000000000 --- a/tests/letstest/testdata/sample-config/keys/0002_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj -8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 -FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH -g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 -/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN -fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg -PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY -JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo -fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc -xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj -bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D -WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 -mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu -wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 -enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO -iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x -nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL -W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 -q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R -zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz -h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 -Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS -xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy -YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY -o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl -wkqKc3PndJHrR40mswUOHbs= ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/keys/0003_key-certbot.pem b/tests/letstest/testdata/sample-config/keys/0003_key-certbot.pem deleted file mode 100644 index c43af4f50..000000000 --- a/tests/letstest/testdata/sample-config/keys/0003_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh -DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 -Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt -YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv -Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 -npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 -HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX -Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l -/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r -m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK -tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj -xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n -9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf -Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN -cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT -+kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P -JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F -NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ -IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv -JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc -Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY -EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT -xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe -vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj -t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar -Bwhs4S84KEJFT0LAC3YeRQ== ------END PRIVATE KEY----- diff --git a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/README b/tests/letstest/testdata/sample-config/live/a.encryption-example.com/README deleted file mode 100644 index 15194ae3a..000000000 --- a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/README +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains your keys and certificates. - -`privkey.pem` : the private key for your certificate. -`fullchain.pem`: the certificate file used in most server software. -`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. -`cert.pem` : will break many server configurations, and should not be used - without reading further documentation (see link below). - -We recommend not moving these files. For more information, see the Certbot -User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/cert.pem b/tests/letstest/testdata/sample-config/live/a.encryption-example.com/cert.pem deleted file mode 120000 index 79b6abdf9..000000000 --- a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/chain.pem b/tests/letstest/testdata/sample-config/live/a.encryption-example.com/chain.pem deleted file mode 120000 index 2d6b30420..000000000 --- a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/fullchain.pem b/tests/letstest/testdata/sample-config/live/a.encryption-example.com/fullchain.pem deleted file mode 120000 index b801ef735..000000000 --- a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/privkey.pem b/tests/letstest/testdata/sample-config/live/a.encryption-example.com/privkey.pem deleted file mode 120000 index 74e20c5ff..000000000 --- a/tests/letstest/testdata/sample-config/live/a.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/README b/tests/letstest/testdata/sample-config/live/b.encryption-example.com/README deleted file mode 100644 index 15194ae3a..000000000 --- a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/README +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains your keys and certificates. - -`privkey.pem` : the private key for your certificate. -`fullchain.pem`: the certificate file used in most server software. -`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. -`cert.pem` : will break many server configurations, and should not be used - without reading further documentation (see link below). - -We recommend not moving these files. For more information, see the Certbot -User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/cert.pem b/tests/letstest/testdata/sample-config/live/b.encryption-example.com/cert.pem deleted file mode 120000 index 41b06370e..000000000 --- a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/chain.pem b/tests/letstest/testdata/sample-config/live/b.encryption-example.com/chain.pem deleted file mode 120000 index 2d3e18bec..000000000 --- a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/fullchain.pem b/tests/letstest/testdata/sample-config/live/b.encryption-example.com/fullchain.pem deleted file mode 120000 index 3a08c1432..000000000 --- a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/privkey.pem b/tests/letstest/testdata/sample-config/live/b.encryption-example.com/privkey.pem deleted file mode 120000 index 182aa6d78..000000000 --- a/tests/letstest/testdata/sample-config/live/b.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/letstest/testdata/sample-config/options-ssl-apache.conf b/tests/letstest/testdata/sample-config/options-ssl-apache.conf deleted file mode 100644 index ec07a4ba3..000000000 --- a/tests/letstest/testdata/sample-config/options-ssl-apache.conf +++ /dev/null @@ -1,22 +0,0 @@ -# Baseline setting to Include for SSL sites - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/tests/letstest/testdata/sample-config/renewal/a.encryption-example.com.conf b/tests/letstest/testdata/sample-config/renewal/a.encryption-example.com.conf deleted file mode 100644 index 4455137b4..000000000 --- a/tests/letstest/testdata/sample-config/renewal/a.encryption-example.com.conf +++ /dev/null @@ -1,15 +0,0 @@ -# renew_before_expiry = 30 days -version = 0.10.0.dev0 -archive_dir = sample-config/archive/a.encryption-example.com -cert = sample-config/live/a.encryption-example.com/cert.pem -privkey = sample-config/live/a.encryption-example.com/privkey.pem -chain = sample-config/live/a.encryption-example.com/chain.pem -fullchain = sample-config/live/a.encryption-example.com/fullchain.pem - -# Options used in the renewal process -[renewalparams] -authenticator = apache -installer = apache -account = 48d6b9e8d767eccf7e4d877d6ffa81e3 -config_dir = sample-config -server = https://acme-staging.api.letsencrypt.org/directory diff --git a/tests/letstest/testdata/sample-config/renewal/b.encryption-example.com.conf b/tests/letstest/testdata/sample-config/renewal/b.encryption-example.com.conf deleted file mode 100644 index 58d8a13d9..000000000 --- a/tests/letstest/testdata/sample-config/renewal/b.encryption-example.com.conf +++ /dev/null @@ -1,15 +0,0 @@ -# renew_before_expiry = 30 days -version = 0.10.0.dev0 -archive_dir = sample-config/archive/b.encryption-example.com -cert = sample-config/live/b.encryption-example.com/cert.pem -privkey = sample-config/live/b.encryption-example.com/privkey.pem -chain = sample-config/live/b.encryption-example.com/chain.pem -fullchain = sample-config/live/b.encryption-example.com/fullchain.pem - -# Options used in the renewal process -[renewalparams] -authenticator = apache -installer = apache -account = 48d6b9e8d767eccf7e4d877d6ffa81e3 -config_dir = sample-config -server = https://acme-staging.api.letsencrypt.org/directory -- cgit v1.2.3 From a809c3697d433515517ba07b85c5ba42b34ff7c1 Mon Sep 17 00:00:00 2001 From: schoen Date: Wed, 27 Feb 2019 16:32:57 -0800 Subject: Warn sysadmins about privilege escalation risk (#6795) --- docs/install.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/install.rst b/docs/install.rst index 35b262482..eae40c1f0 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -11,6 +11,8 @@ About Certbot *Certbot is meant to be run directly on a web server*, normally by a system administrator. In most cases, running Certbot on your personal computer is not a useful option. The instructions below relate to installing and running Certbot on a server. +System administrators can use Certbot directly to request certificates; they should *not* allow unprivileged users to run arbitrary Certbot commands as ``root``, because Certbot allows its user to specify arbitrary file locations and run arbitrary scripts. + Certbot is packaged for many common operating systems and web servers. Check whether ``certbot`` (or ``letsencrypt``) is packaged for your web server's OS by visiting certbot.eff.org_, where you will also find the correct installation instructions for -- cgit v1.2.3 From 5e849e03f6e8dd2aca31888f9482da63359bb961 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 28 Feb 2019 20:35:58 +0100 Subject: [Windows] Fix pipstrap (#6775) * Fix pipstrap on windows * Pipstrap pin setuptools version, so explicit install it is not needed anymore. * Rebuild letsencrypt-auto source * Use sys.executable in pipstrap to allow straightforward execution in a venv by choosing the python interpreter from this venv. * Update letsencrypt-auto * Simulate test-everything * Revert "Simulate test-everything" This reverts commit b62c4d719a6e741cb11126c7490097a79c68cf4d. * Clean pipstrap code --- letsencrypt-auto-source/letsencrypt-auto | 22 +++++++++++----------- letsencrypt-auto-source/pieces/pipstrap.py | 22 +++++++++++----------- tools/_venv_common.py | 27 ++++++++++----------------- 3 files changed, 32 insertions(+), 39 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 19aaa4eeb..c390f28ec 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1221,7 +1221,6 @@ from distutils.version import StrictVersion from hashlib import sha256 from os import environ from os.path import join -from pipes import quote from shutil import rmtree try: from subprocess import check_output @@ -1241,7 +1240,7 @@ except ImportError: cmd = popenargs[0] raise CalledProcessError(retcode, cmd) return output -from sys import exit, version_info +import sys from tempfile import mkdtemp try: from urllib2 import build_opener, HTTPHandler, HTTPSHandler @@ -1263,7 +1262,7 @@ maybe_argparse = ( [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/' 'argparse-1.4.0.tar.gz', '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] - if version_info < (2, 7, 0) else []) + if sys.version_info < (2, 7, 0) else []) PACKAGES = maybe_argparse + [ @@ -1344,7 +1343,8 @@ def get_index_base(): def main(): - pip_version = StrictVersion(check_output(['pip', '--version']) + python = sys.executable or 'python' + pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version']) .decode('utf-8').split()[1]) has_pip_cache = pip_version >= StrictVersion('6.0') index_base = get_index_base() @@ -1354,12 +1354,12 @@ def main(): temp, digest) for path, digest in PACKAGES] - 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 '') + - ' '.join(quote(d) for d in downloads), - shell=True) + # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] + # Disable cache since it is not used and it otherwise sometimes throws permission warnings: + command.extend(['--no-cache-dir'] if has_pip_cache else []) + command.extend(downloads) + check_output(command) except HashError as exc: print(exc) except Exception: @@ -1372,7 +1372,7 @@ def main(): if __name__ == '__main__': - exit(main()) + sys.exit(main()) UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/pieces/pipstrap.py b/letsencrypt-auto-source/pieces/pipstrap.py index 6a00dd9cb..f04ebe6fe 100755 --- a/letsencrypt-auto-source/pieces/pipstrap.py +++ b/letsencrypt-auto-source/pieces/pipstrap.py @@ -23,7 +23,6 @@ from distutils.version import StrictVersion from hashlib import sha256 from os import environ from os.path import join -from pipes import quote from shutil import rmtree try: from subprocess import check_output @@ -43,7 +42,7 @@ except ImportError: cmd = popenargs[0] raise CalledProcessError(retcode, cmd) return output -from sys import exit, version_info +import sys from tempfile import mkdtemp try: from urllib2 import build_opener, HTTPHandler, HTTPSHandler @@ -65,7 +64,7 @@ maybe_argparse = ( [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/' 'argparse-1.4.0.tar.gz', '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] - if version_info < (2, 7, 0) else []) + if sys.version_info < (2, 7, 0) else []) PACKAGES = maybe_argparse + [ @@ -146,7 +145,8 @@ def get_index_base(): def main(): - pip_version = StrictVersion(check_output(['pip', '--version']) + python = sys.executable or 'python' + pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version']) .decode('utf-8').split()[1]) has_pip_cache = pip_version >= StrictVersion('6.0') index_base = get_index_base() @@ -156,12 +156,12 @@ def main(): temp, digest) for path, digest in PACKAGES] - 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 '') + - ' '.join(quote(d) for d in downloads), - shell=True) + # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] + # Disable cache since it is not used and it otherwise sometimes throws permission warnings: + command.extend(['--no-cache-dir'] if has_pip_cache else []) + command.extend(downloads) + check_output(command) except HashError as exc: print(exc) except Exception: @@ -174,4 +174,4 @@ def main(): if __name__ == '__main__': - exit(main()) + sys.exit(main()) diff --git a/tools/_venv_common.py b/tools/_venv_common.py index 540842773..09383b4c0 100755 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -107,13 +107,13 @@ def subprocess_with_print(cmd, env=os.environ, shell=False): subprocess.check_call(cmd, env=env, shell=shell) -def get_venv_bin_path(venv_path): +def get_venv_python_path(venv_path): python_linux = os.path.join(venv_path, 'bin/python') if os.path.isfile(python_linux): - return os.path.abspath(os.path.dirname(python_linux)) + return os.path.abspath(python_linux) python_windows = os.path.join(venv_path, 'Scripts\\python.exe') if os.path.isfile(python_windows): - return os.path.abspath(os.path.dirname(python_windows)) + return os.path.abspath(python_windows) raise ValueError(( 'Error, could not find python executable in venv path {0}: is it a valid venv ?' @@ -149,17 +149,12 @@ def main(venv_name, venv_args, args): command.extend(shlex.split(venv_args)) subprocess_with_print(command) - # We execute the following commands in the context of the virtual environment, to install - # the packages in it. To do so, we append the venv binary to the PATH that will be used for - # these commands. With this trick, correct python executable will be selected. - new_environ = os.environ.copy() - new_environ['PATH'] = os.pathsep.join([get_venv_bin_path(venv_name), new_environ['PATH']]) - subprocess_with_print('python {0}'.format('./letsencrypt-auto-source/pieces/pipstrap.py'), - env=new_environ, shell=True) - subprocess_with_print('python -m pip install --upgrade "setuptools>=30.3"', - env=new_environ, shell=True) - subprocess_with_print('python {0} {1}'.format('./tools/pip_install.py', ' '.join(args)), - env=new_environ, shell=True) + # Using the python executable from venv, we ensure to execute following commands in this venv. + py_venv = get_venv_python_path(venv_name) + subprocess_with_print([py_venv, os.path.abspath('letsencrypt-auto-source/pieces/pipstrap.py')]) + command = [py_venv, os.path.abspath('tools/pip_install.py')] + command.extend(args) + subprocess_with_print(command) if os.path.isdir(os.path.join(venv_name, 'bin')): # Linux/OSX specific @@ -179,6 +174,4 @@ def main(venv_name, venv_args, args): if __name__ == '__main__': - main('venv', - '', - sys.argv[1:]) + main('venv', '', sys.argv[1:]) -- cgit v1.2.3 From efc8d49806b14a31d88cfc0f1b6daca1dd373d8d Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 28 Feb 2019 21:49:36 +0100 Subject: Disable rerun feature of Travis (#6800) The rerun capability in a test campaign can be a nice feature. It can also a bad design. It is right that with high level tests, like performance or end-to-end tests, a given test runtime can depend on an external component, outside the scope of the developers, that would spuriously fail. In theses situations, having the capacity to rerun several time a test can be a great benefit. Indeed, as these tests are inherently flaky, the rerun greatly reduces their failure because of external reasons, reducing also the Pierre et le Loup effect (from a well-known french book for children): because tests constantly fail for no reason, you stop to listen to them and to not see when they fail for a real reason. However this not apply to unit tests, or operations about code quality. For theses executions, the flakiness should not exist: a unit test is supposed to have no external dependency. A rerun approach would just hide a situation that is not desirable for this kind of tests Also effectively I see that our tests are usually not flaky: so the only effect of the rerun is to give the failure state about a test three times slower. If a test becomes flaky, this should be fixed, and so be visible immediately in our CI. For these reasons, I remove the travis_retry in the script section of .travis-ci.yml, to call directly tox and let the pipeline fails on the first error. * Disable rerun feature of Travis * Update .travis.yml * Remove completely the retry logic --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d59ec9e1..4adab1e4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -197,10 +197,10 @@ addons: - nginx-light - openssl -install: "travis_retry $(command -v pip || command -v pip3) install codecov tox" +install: "$(command -v pip || command -v pip3) install codecov tox" script: - - travis_retry tox - - '[ -z "${BOULDER_INTEGRATION+x}" ] || (travis_retry tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)' + - tox + - '[ -z "${BOULDER_INTEGRATION+x}" ] || (tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)' after_success: '[ "$TOXENV" == "py27-cover" ] && codecov' -- cgit v1.2.3 From 841f8efd0aa7dc2ba249b303e6be664d0a6647e3 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 1 Mar 2019 22:18:06 +0100 Subject: [Unix] Create a framework for certbot integration tests: PART 1 (#6578) * First part * Several optimizations about the docker env setup * Documentation * Various corrections and documentation. Add acme and certbot explicitly as dependencies of certbot-ci. * Correct a variable misinterpreted as a pytest hook * Correct strict parsing option on pebble * Refactor acme setup to be executed from pytest hooks. * Pass TRAVIS env variable to trigger specific xdist logic * Retrigger build. * Work in progress * Config operational * Propagate to xdist * Corrections on acme and misc * Correct subnet for pebble * Remove gobetween, as tls-sni challenges are not tested anymore. * Improve pebble setup. Reduce LOC. * Update acme.py * Optimize acme ca setup, with less temporary assets * Silent setup * Clean code * Remove unused workspace * Use default network driver * Remove bridge * Update package documentation * Remove rerun capability for integration tests, not needed. * Add documentation * Variable for all ports and subnets used by the stack * Update certbot-ci/certbot_integration_tests/conftest.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/acme.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update tox.ini Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/acme.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/acme.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/conftest.py Co-Authored-By: adferrand * Rename to acme_server * Add comment * Refactor in a unique context fixture * Remove the need of CERTBOT_ACME_XDIST environment variable * Remove nonstrict/strict options in pebble * Clean dependencies * Clean tox * Change function name * Add comment about coveragerc specificities * Change a comment. * Update setup.py * Update conftest.py * Use the production-ready docker-compose.yml file for Pebble * New style class * Tune pebble to have a stable test environment * Pin a dependency --- certbot-ci/certbot_integration_tests/.coveragerc | 8 + certbot-ci/certbot_integration_tests/__init__.py | 1 + .../certbot_tests/__init__.py | 0 .../certbot_tests/context.py | 16 ++ .../certbot_tests/test_main.py | 40 +++++ certbot-ci/certbot_integration_tests/conftest.py | 92 ++++++++++ .../nginx_tests/__init__.py | 0 .../nginx_tests/context.py | 5 + .../nginx_tests/test_main.py | 17 ++ .../certbot_integration_tests/utils/__init__.py | 0 .../certbot_integration_tests/utils/acme_server.py | 194 +++++++++++++++++++++ certbot-ci/certbot_integration_tests/utils/misc.py | 45 +++++ certbot-ci/setup.py | 45 +++++ tools/dev_constraints.txt | 1 + tox.ini | 12 ++ 15 files changed, 476 insertions(+) create mode 100644 certbot-ci/certbot_integration_tests/.coveragerc create mode 100644 certbot-ci/certbot_integration_tests/__init__.py create mode 100644 certbot-ci/certbot_integration_tests/certbot_tests/__init__.py create mode 100644 certbot-ci/certbot_integration_tests/certbot_tests/context.py create mode 100644 certbot-ci/certbot_integration_tests/certbot_tests/test_main.py create mode 100644 certbot-ci/certbot_integration_tests/conftest.py create mode 100644 certbot-ci/certbot_integration_tests/nginx_tests/__init__.py create mode 100644 certbot-ci/certbot_integration_tests/nginx_tests/context.py create mode 100644 certbot-ci/certbot_integration_tests/nginx_tests/test_main.py create mode 100644 certbot-ci/certbot_integration_tests/utils/__init__.py create mode 100644 certbot-ci/certbot_integration_tests/utils/acme_server.py create mode 100644 certbot-ci/certbot_integration_tests/utils/misc.py create mode 100644 certbot-ci/setup.py diff --git a/certbot-ci/certbot_integration_tests/.coveragerc b/certbot-ci/certbot_integration_tests/.coveragerc new file mode 100644 index 000000000..de36d4e02 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/.coveragerc @@ -0,0 +1,8 @@ +[run] +# Avoid false warnings because certbot packages are not installed in the thread that executes +# the coverage: indeed, certbot is launched as a CLI from a subprocess. +disable_warnings = module-not-imported,no-data-collected + +[report] +# Exclude unit tests in coverage during integration tests. +omit = **/*_test.py,**/tests/*,**/certbot_nginx/parser_obj.py diff --git a/certbot-ci/certbot_integration_tests/__init__.py b/certbot-ci/certbot_integration_tests/__init__.py new file mode 100644 index 000000000..434a85a23 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/__init__.py @@ -0,0 +1 @@ +"""Package certbot_integration_test is for tests that require a live acme ca server instance""" diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py b/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py new file mode 100644 index 000000000..9045cd37d --- /dev/null +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -0,0 +1,16 @@ +class IntegrationTestsContext(object): + """General fixture describing a certbot integration tests context""" + def __init__(self, request): + self.request = request + if hasattr(request.config, 'slaveinput'): # Worker node + self.worker_id = request.config.slaveinput['slaveid'] + self.acme_xdist = request.config.slaveinput['acme_xdist'] + else: # Primary node + self.worker_id = 'primary' + self.acme_xdist = request.config.acme_xdist + self.directory_url = self.acme_xdist['directory_url'] + self.tls_alpn_01_port = self.acme_xdist['https_port'][self.worker_id] + self.http_01_port = self.acme_xdist['http_port'][self.worker_id] + + def cleanup(self): + pass diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py new file mode 100644 index 000000000..5b0981b36 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -0,0 +1,40 @@ +import requests +import urllib3 + +import pytest + +from certbot_integration_tests.certbot_tests import context as certbot_context + + +@pytest.fixture() +def context(request): + # Fixture request is a built-in pytest fixture describing current test request. + integration_test_context = certbot_context.IntegrationTestsContext(request) + try: + yield integration_test_context + finally: + integration_test_context.cleanup() + + +def test_hello_1(context): + assert context.http_01_port + assert context.tls_alpn_01_port + try: + response = requests.get(context.directory_url, verify=False) + response.raise_for_status() + assert response.json() + response.close() + except urllib3.exceptions.InsecureRequestWarning: + pass + + +def test_hello_2(context): + assert context.http_01_port + assert context.tls_alpn_01_port + try: + response = requests.get(context.directory_url, verify=False) + response.raise_for_status() + assert response.json() + response.close() + except urllib3.exceptions.InsecureRequestWarning: + pass diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py new file mode 100644 index 000000000..892c16266 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -0,0 +1,92 @@ +""" +General conftest for pytest execution of all integration tests lying +in the certbot_integration tests package. +As stated by pytest documentation, conftest module is used to set on +for a directory a specific configuration using built-in pytest hooks. + +See https://docs.pytest.org/en/latest/reference.html#hook-reference +""" +import contextlib +import sys +import subprocess + +from certbot_integration_tests.utils import acme_server as acme_lib + + +def pytest_addoption(parser): + """ + Standard pytest hook to add options to the pytest parser. + :param parser: current pytest parser that will be used on the CLI + """ + parser.addoption('--acme-server', default='pebble', + choices=['boulder-v1', 'boulder-v2', 'pebble'], + help='select the ACME server to use (boulder-v1, boulder-v2, ' + 'pebble), defaulting to pebble') + + +def pytest_configure(config): + """ + Standard pytest hook used to add a configuration logic for each node of a pytest run. + :param config: the current pytest configuration + """ + if not hasattr(config, 'slaveinput'): # If true, this is the primary node + with _print_on_err(): + config.acme_xdist = _setup_primary_node(config) + + +def pytest_configure_node(node): + """ + Standard pytest-xdist hook used to configure a worker node. + :param node: current worker node + """ + node.slaveinput['acme_xdist'] = node.config.acme_xdist + + +@contextlib.contextmanager +def _print_on_err(): + """ + During pytest-xdist setup, stdout is used for nodes communication, so print is useless. + However, stderr is still available. This context manager transfers stdout to stderr + for the duration of the context, allowing to display prints to the user. + """ + old_stdout = sys.stdout + sys.stdout = sys.stderr + try: + yield + finally: + sys.stdout = old_stdout + + +def _setup_primary_node(config): + """ + Setup the environment for integration tests. + Will: + - check runtime compatiblity (Docker, docker-compose, Nginx) + - create a temporary workspace and the persistent GIT repositories space + - configure and start paralleled ACME CA servers using Docker + - transfer ACME CA servers configurations to pytest nodes using env variables + :param config: Configuration of the pytest primary node + """ + # Check for runtime compatibility: some tools are required to be available in PATH + try: + subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker is required in PATH to launch the integration tests, ' + 'but is not installed or not available for current user.') + + try: + subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, ' + 'but is not installed or not available for current user.') + + # Parameter numprocesses is added to option by pytest-xdist + workers = ['primary'] if not config.option.numprocesses\ + else ['gw{0}'.format(i) for i in range(config.option.numprocesses)] + + # By calling setup_acme_server we ensure that all necessary acme server instances will be + # fully started. This runtime is reflected by the acme_xdist returned. + acme_xdist = acme_lib.setup_acme_server(config.option.acme_server, workers) + print('ACME xdist config:\n{0}'.format(acme_xdist)) + + return acme_xdist diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/__init__.py b/certbot-ci/certbot_integration_tests/nginx_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py new file mode 100644 index 000000000..6d7d6012b --- /dev/null +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -0,0 +1,5 @@ +from certbot_integration_tests.certbot_tests import context as certbot_context + + +class IntegrationTestsContext(certbot_context.IntegrationTestsContext): + """General fixture describing a certbot-nginx integration tests context""" diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py new file mode 100644 index 000000000..472e5e7b7 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py @@ -0,0 +1,17 @@ +import pytest + +from certbot_integration_tests.nginx_tests import context as nginx_context + + +@pytest.fixture() +def context(request): + # Fixture request is a built-in pytest fixture describing current test request. + integration_test_context = nginx_context.IntegrationTestsContext(request) + try: + yield integration_test_context + finally: + integration_test_context.cleanup() + + +def test_hello(context): + print(context.directory_url) diff --git a/certbot-ci/certbot_integration_tests/utils/__init__.py b/certbot-ci/certbot_integration_tests/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py new file mode 100644 index 000000000..33ef05194 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -0,0 +1,194 @@ +"""Module to setup an ACME CA server environment able to run multiple tests in parallel""" +from __future__ import print_function +import tempfile +import atexit +import os +import subprocess +import shutil +import sys +from os.path import join + +import requests +import json +import yaml + +from certbot_integration_tests.utils import misc + +# These ports are set implicitly in the docker-compose.yml files of Boulder/Pebble. +CHALLTESTSRV_PORT = 8055 +HTTP_01_PORT = 5002 + + +def setup_acme_server(acme_server, nodes): + """ + This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel + execution of integration tests against the unique http-01 port expected by the ACME CA server. + Instances are properly closed and cleaned when the Python process exits using atexit. + Typically all pytest integration tests will be executed in this context. + This method returns an object describing ports and directory url to use for each pytest node + with the relevant pytest xdist node. + :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) + :param str[] nodes: list of node names that will be setup by pytest xdist + :return: a dict describing the challenge ports that have been setup for the nodes + :rtype: dict + """ + acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' + acme_xdist = _construct_acme_xdist(acme_server, nodes) + workspace = _construct_workspace(acme_type) + + _prepare_traefik_proxy(workspace, acme_xdist) + _prepare_acme_server(workspace, acme_type, acme_xdist) + + return acme_xdist + + +def _construct_acme_xdist(acme_server, nodes): + """Generate and return the acme_xdist dict""" + acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT} + + # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. + if acme_server == 'pebble': + acme_xdist['directory_url'] = 'https://localhost:14000/dir' + else: # boulder + port = 4001 if acme_server == 'boulder-v2' else 4000 + acme_xdist['directory_url'] = 'http://localhost:{0}/directory'.format(port) + + acme_xdist['http_port'] = {node: port for (node, port) + in zip(nodes, range(5200, 5200 + len(nodes)))} + acme_xdist['https_port'] = {node: port for (node, port) + in zip(nodes, range(5100, 5100 + len(nodes)))} + + return acme_xdist + + +def _construct_workspace(acme_type): + """Create a temporary workspace for integration tests stack""" + workspace = tempfile.mkdtemp() + + def cleanup(): + """Cleanup function to call that will teardown relevant dockers and their configuration.""" + for instance in [acme_type, 'traefik']: + print('=> Tear down the {0} instance...'.format(instance)) + instance_path = join(workspace, instance) + try: + if os.path.isfile(join(instance_path, 'docker-compose.yml')): + _launch_command(['docker-compose', 'down'], cwd=instance_path) + except subprocess.CalledProcessError: + pass + print('=> Finished tear down of {0} instance.'.format(acme_type)) + + shutil.rmtree(workspace) + + # Here with atexit we ensure that clean function is called no matter what. + atexit.register(cleanup) + + return workspace + + +def _prepare_acme_server(workspace, acme_type, acme_xdist): + """Configure and launch the ACME server, Boulder or Pebble""" + print('=> Starting {0} instance deployment...'.format(acme_type)) + instance_path = join(workspace, acme_type) + try: + # Load Boulder/Pebble from git, that includes a docker-compose.yml ready for production. + _launch_command(['git', 'clone', 'https://github.com/letsencrypt/{0}'.format(acme_type), + '--single-branch', '--depth=1', instance_path]) + if acme_type == 'boulder': + # Allow Boulder to ignore usual limit rate policies, useful for tests. + os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), + join(instance_path, 'test/rate-limit-policies.yml')) + if acme_type == 'pebble': + # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid + # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. + with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler: + config = yaml.load(file_handler.read()) + + config['services']['pebble'].setdefault('environment', [])\ + .extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0']) + with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler: + file_handler.write(yaml.dump(config)) + + # Launch the ACME CA server. + _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) + + # Wait for the ACME CA server to be up. + print('=> Waiting for {0} instance to respond...'.format(acme_type)) + misc.check_until_timeout(acme_xdist['directory_url']) + + # Configure challtestsrv to answer any A record request with ip of the docker host. + acme_subnet = '10.77.77' if acme_type == 'boulder' else '10.30.50' + response = requests.post('http://localhost:{0}/set-default-ipv4' + .format(acme_xdist['challtestsrv_port']), + json={'ip': '{0}.1'.format(acme_subnet)}) + response.raise_for_status() + + print('=> Finished {0} instance deployment.'.format(acme_type)) + except BaseException: + print('Error while setting up {0} instance.'.format(acme_type)) + raise + + +def _prepare_traefik_proxy(workspace, acme_xdist): + """Configure and launch Traefik, the HTTP reverse proxy""" + print('=> Starting traefik instance deployment...') + instance_path = join(workspace, 'traefik') + traefik_subnet = '10.33.33' + traefik_api_port = 8056 + try: + os.mkdir(instance_path) + + with open(join(instance_path, 'docker-compose.yml'), 'w') as file_h: + file_h.write('''\ +version: '3' +services: + traefik: + image: traefik + command: --api --rest + ports: + - {http_01_port}:80 + - {traefik_api_port}:8080 + networks: + traefiknet: + ipv4_address: {traefik_subnet}.2 +networks: + traefiknet: + ipam: + config: + - subnet: {traefik_subnet}.0/24 +'''.format(traefik_subnet=traefik_subnet, + traefik_api_port=traefik_api_port, + http_01_port=HTTP_01_PORT)) + + _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) + + misc.check_until_timeout('http://localhost:{0}/api'.format(traefik_api_port)) + config = { + 'backends': { + node: { + 'servers': {node: {'url': 'http://{0}.1:{1}'.format(traefik_subnet, port)}} + } for node, port in acme_xdist['http_port'].items() + }, + 'frontends': { + node: { + 'backend': node, 'passHostHeader': True, + 'routes': {node: {'rule': 'HostRegexp: {{subdomain:.+}}.{0}.wtf'.format(node)}} + } for node in acme_xdist['http_port'].keys() + } + } + response = requests.put('http://localhost:{0}/api/providers/rest'.format(traefik_api_port), + data=json.dumps(config)) + response.raise_for_status() + + print('=> Finished traefik instance deployment.') + except BaseException: + print('Error while setting up traefik instance.') + raise + + +def _launch_command(command, cwd=os.getcwd()): + """Launch silently an OS command, output will be displayed in case of failure""" + try: + subprocess.check_output(command, stderr=subprocess.STDOUT, cwd=cwd, universal_newlines=True) + except subprocess.CalledProcessError as e: + sys.stderr.write(e.output) + raise diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py new file mode 100644 index 000000000..a3b134788 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -0,0 +1,45 @@ +""" +Misc module contains stateless functions that could be used during pytest execution, +or outside during setup/teardown of the integration tests environment. +""" +import os +import time +import contextlib + +import requests + + +def check_until_timeout(url): + """ + Wait and block until given url responds with status 200, or raise an exception + after 150 attempts. + :param str url: the URL to test + :raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL + """ + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + for _ in range(0, 150): + time.sleep(1) + try: + if requests.get(url, verify=False).status_code == 200: + return + except requests.exceptions.ConnectionError: + pass + + raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url)) + + +@contextlib.contextmanager +def execute_in_given_cwd(cwd): + """ + Context manager that will execute any command in the given cwd after entering context, + and restore current cwd when context is destroyed. + :param str cwd: the path to use as the temporary current workspace for python execution + """ + current_cwd = os.getcwd() + try: + os.chdir(cwd) + yield + finally: + os.chdir(current_cwd) diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py new file mode 100644 index 000000000..595bba69e --- /dev/null +++ b/certbot-ci/setup.py @@ -0,0 +1,45 @@ +from setuptools import setup +from setuptools import find_packages + + +version = '0.32.0.dev0' + +install_requires = [ + 'pytest', + 'pytest-cov', + 'pytest-xdist', + 'pytest-sugar', + 'coverage', + 'requests', + 'pyyaml', +] + +setup( + name='certbot-ci', + version=version, + description="Certbot continuous integration framework", + url='https://github.com/certbot/certbot', + author="Certbot Project", + author_email='client-dev@letsencrypt.org', + license='Apache License 2.0', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Security', + ], + + packages=find_packages(), + include_package_data=True, + install_requires=install_requires, +) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 88340cb00..f8c3f4461 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -51,6 +51,7 @@ pytest==3.2.5 pytest-cov==2.5.1 pytest-forked==0.2 pytest-xdist==1.22.5 +pytest-sugar==0.9.2 python-dateutil==2.6.1 python-digitalocean==1.11 PyYAML==3.13 diff --git a/tox.ini b/tox.ini index b386ebc86..2c5fe0644 100644 --- a/tox.ini +++ b/tox.ini @@ -250,3 +250,15 @@ commands = whitelist_externals = docker-compose passenv = DOCKER_* + +[testenv:integration] +commands = + {[base]pip_install} acme . certbot-nginx certbot-ci + pytest {toxinidir}/certbot-ci/certbot_integration_tests \ + --acme-server={env:ACME_SERVER:pebble} \ + --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ + --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ + -W 'ignore:Unverified HTTPS request' + coverage report --fail-under=65 --show-missing +passenv = + DOCKER_* -- cgit v1.2.3 From 7161e792e80d4b0ffa404199248334b2b34c0235 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 1 Mar 2019 22:54:09 +0100 Subject: Fix the Nginx configuration during integration tests (#6801) If you execute `tests/lock_test.py` or `tox -e integration` on a fairly recent machine, you will get the following error during tests executing against a live Nginx instance: ``` no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: x.x.x.x, server: y:y:y:y:z ``` Indeed, having no defined ssl certificate for a ssl port would inevitably lead to an error during the handshake SSL process between a client and this mis-configured nginx instance. However it was not a problem one year before, because the handshake was not occurring in practice: the test just need to have a nginx started, and then immediately proceed to modify the configuration with a correct SSL setup. And nginx was able to start with a mis-configuration on SSL. But then this fix has been done: https://trac.nginx.org/nginx/ticket/178 Basically with this, validation of the configuration is done during nginx startup, that will refuse to start with invalid configuration on SSL. Consequently, all related tests are failing with a sufficiently up-to-date nginx. For now, it is not seen on Travis because Ubuntu Trusty is used, with an old Nginx. The PR fixes that, by generating on the fly self-signed certificates in the two impacted tests, and pushing the right parameters in the Nginx configuration. * Fix nginx configuration with self-signed certificates generated on the fly * Fix lint/mypy * Fix old cryptography * Unattended openssl * Update lock_test.py --- certbot-nginx/tests/boulder-integration.conf.sh | 29 ++++++++---- certbot-nginx/tests/boulder-integration.sh | 6 ++- tests/lock_test.py | 62 +++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/certbot-nginx/tests/boulder-integration.conf.sh b/certbot-nginx/tests/boulder-integration.conf.sh index 470eab28e..80cca2682 100755 --- a/certbot-nginx/tests/boulder-integration.conf.sh +++ b/certbot-nginx/tests/boulder-integration.conf.sh @@ -3,16 +3,22 @@ # https://www.exratione.com/2014/03/running-nginx-as-a-non-root-user/ # https://github.com/exratione/non-root-nginx/blob/9a77f62e5d5cb9c9026fd62eece76b9514011019/nginx.conf +# USAGE: ./boulder-integration.conf.sh /path/to/root cert.key cert.pem >> nginx.conf + +ROOT=$1 +CERT_KEY_PATH=$2 +CERT_PATH=$3 + cat < $nginx_conf diff --git a/tests/lock_test.py b/tests/lock_test.py index 0266cf029..aaa8ce2d9 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -2,6 +2,7 @@ from __future__ import print_function import atexit +import datetime import functools import logging import os @@ -11,6 +12,13 @@ import subprocess import sys import tempfile +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +# TODO: once mypy has cryptography types bundled, type: ignore can be removed. +# See https://github.com/python/typeshed/tree/master/third_party/2/cryptography +from cryptography.hazmat.primitives import serialization, hashes # type: ignore +from cryptography.hazmat.primitives.asymmetric import rsa + from certbot import lock from certbot import util @@ -102,12 +110,11 @@ def set_up_nginx_dir(root_path): repo_root = check_call('git rev-parse --show-toplevel'.split()).strip() conf_script = os.path.join( repo_root, 'certbot-nginx', 'tests', 'boulder-integration.conf.sh') - # boulder-integration.conf.sh uses the root environment variable as - # the Nginx server root when writing paths - os.environ['root'] = root_path + # Prepare self-signed certificates for Nginx + key_path, cert_path = setup_certificate(root_path) + # Generate Nginx configuration with open(os.path.join(root_path, 'nginx.conf'), 'w') as f: - f.write(check_call(['/bin/sh', conf_script])) - del os.environ['root'] + f.write(check_call(['/bin/sh', conf_script, root_path, key_path, cert_path])) def set_up_command(config_dir, logs_dir, work_dir, nginx_dir): @@ -134,6 +141,51 @@ def set_up_command(config_dir, logs_dir, work_dir, nginx_dir): config_dir, logs_dir, work_dir, nginx_dir).split()) +def setup_certificate(workspace): + """Generate a self-signed certificate for nginx. + :param workspace: path of folder where to put the certificate + :return: tuple containing the key path and certificate path + :rtype: `tuple` + """ + # Generate key + # See comment on cryptography import about type: ignore + private_key = rsa.generate_private_key( # type: ignore + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + subject = issuer = x509.Name([ + x509.NameAttribute(x509.NameOID.COMMON_NAME, u'nginx.wtf') + ]) + certificate = x509.CertificateBuilder().subject_name( + subject + ).issuer_name( + issuer + ).public_key( + private_key.public_key() + ).serial_number( + 1 + ).not_valid_before( + datetime.datetime.utcnow() + ).not_valid_after( + datetime.datetime.utcnow() + datetime.timedelta(days=1) + ).sign(private_key, hashes.SHA256(), default_backend()) + + key_path = os.path.join(workspace, 'cert.key') + with open(key_path, 'wb') as file_handle: + file_handle.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + + cert_path = os.path.join(workspace, 'cert.pem') + with open(cert_path, 'wb') as file_handle: + file_handle.write(certificate.public_bytes(serialization.Encoding.PEM)) + + return key_path, cert_path + + def test_command(command, directories): """Assert Certbot acquires locks in a specific order. -- cgit v1.2.3 From a468a3b255e326dbfa2b7d629eb20fa275631254 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 1 Mar 2019 23:21:07 +0100 Subject: Disable build for commits pushed on master (#6804) Fixes #6746. Every commit on master is always the result of a merged PR, that has been tested by Travis. So retesting the merge commit on master is superfluous. This PR uses build conditions to avoid to launch a build for a commit push on master. I also added the equivalent logic for AppVeyor. Builds cannot received conditions, so it needs to be done on init using Exit-AppVeyorBuild. This command does not fail the build, it finishes it prematurely with success. * Disable build for commit pushed on master (PR are still tested of course) * Equivalent exclusion code for AppVeyor --- .travis.yml | 5 +++++ appveyor.yml | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4adab1e4e..108f853aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,11 @@ branches: - /^\d+\.\d+\.x$/ - /^test-.*$/ +# Since master can receive only commits from PR that have already been tested, we avoid with the +# the following condition to launch again a pipeline when the merge commit is pushed to master. +# However master still needs to be set in branches section to allow PR for master to be built. +if: NOT (type = push AND branch = master) + matrix: include: # These environments are always executed diff --git a/appveyor.yml b/appveyor.yml index 2b6b82747..5dcc80ba9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,14 @@ branches: - /^\d+\.\d+\.x$/ # Version branches like X.X.X - /^test-.*$/ +init: + # Since master can receive only commits from PR that have already been tested, we avoid with the + # the following condition to launch again a pipeline when the merge commit is pushed to master. + # However master still needs to be set in branches section to allow PR for master to be built. + - ps: | + if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master') + { $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild } + install: # Use Python 3.7 by default - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" -- cgit v1.2.3 From c7f8f15e9b6c51d67f28af96c84d35b814ba2f73 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Mar 2019 16:04:26 -0800 Subject: Fix spurious test_relevant_values* failures (#6799) * Mock set_by_cli in _test_relevant_values_common. * Empty commit * Revert "Mock set_by_cli in _test_relevant_values_common." This reverts commit 9dfec8dfa9124d573362f282b691b66af2860fe6. * mock less * Use plugin_common code instead of reimplementing. * Revert 2nd implementation. * Simplify certbot.storage.relevant_values() tests. In addition to cleaning up the code a bit, it also removes the problems we've seen in these tests with the global state used in cli.py. --- certbot/tests/cli_test.py | 2 + certbot/tests/storage_test.py | 109 ++++++++++++++++-------------------------- 2 files changed, 44 insertions(+), 67 deletions(-) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index e16a1bdcf..f6669e7be 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -340,6 +340,8 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods config_dir_option = 'config_dir' self.assertFalse(cli.option_was_set( config_dir_option, cli.flag_default(config_dir_option))) + self.assertFalse(cli.option_was_set( + 'authenticator', cli.flag_default('authenticator'))) def test_encode_revocation_reason(self): for reason, code in constants.REVOCATION_REASONS.items(): diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index e61ed2aca..872fb4302 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -12,7 +12,6 @@ import pytz import six import certbot -from certbot import cli from certbot import compat from certbot import errors from certbot.storage import ALL_FOUR @@ -37,6 +36,48 @@ def fill_with_sample_data(rc_object): f.write(kind) +class RelevantValuesTest(unittest.TestCase): + """Tests for certbot.storage.relevant_values.""" + + def setUp(self): + self.values = {"server": "example.org"} + + def _call(self, *args, **kwargs): + from certbot.storage import relevant_values + return relevant_values(*args, **kwargs) + + @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot.plugins.disco.PluginsRegistry.find_all") + def test_namespace(self, mock_find_all, mock_option_was_set): + mock_find_all.return_value = ["certbot-foo:bar"] + mock_option_was_set.return_value = True + + self.values["certbot_foo:bar_baz"] = 42 + self.assertEqual( + self._call(self.values.copy()), self.values) + + @mock.patch("certbot.cli.option_was_set") + def test_option_set(self, mock_option_was_set): + mock_option_was_set.return_value = True + + self.values["allow_subset_of_names"] = True + self.values["authenticator"] = "apache" + self.values["rsa_key_size"] = 1337 + expected_relevant_values = self.values.copy() + self.values["hello"] = "there" + + self.assertEqual(self._call(self.values), expected_relevant_values) + + @mock.patch("certbot.cli.option_was_set") + def test_option_unset(self, mock_option_was_set): + mock_option_was_set.return_value = False + + expected_relevant_values = self.values.copy() + self.values["rsa_key_size"] = 2048 + + self.assertEqual(self._call(self.values), expected_relevant_values) + + class BaseRenewableCertTest(test_util.ConfigTestCase): """Base class for setting up Renewable Cert tests. @@ -563,72 +604,6 @@ class RenewableCertTests(BaseRenewableCertTest): 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", - "rsa_key_size", "server",)) - mock_parser = mock.Mock(args=[], verb="plugins", - defaults=defaults) - - # make a copy to ensure values isn't modified - values = values.copy() - values.setdefault("server", defaults["server"]) - expected_server = values["server"] - - from certbot.storage import relevant_values - with mock.patch("certbot.cli.helpful_parser", mock_parser): - rv = relevant_values(values) - self.assertIn("server", rv) - self.assertEqual(rv.pop("server"), expected_server) - return rv - - def test_relevant_values(self): - """Test that relevant_values() can reject an irrelevant value.""" - self.assertEqual( - self._test_relevant_values_common({"hello": "there"}), {}) - - def test_relevant_values_default(self): - """Test that relevant_values() can reject a default value.""" - option = "rsa_key_size" - values = {option: cli.flag_default(option)} - self.assertEqual(self._test_relevant_values_common(values), {}) - - def test_relevant_values_nondefault(self): - """Test that relevant_values() can retain a non-default value.""" - values = {"rsa_key_size": 12} - self.assertEqual( - self._test_relevant_values_common(values), values) - - def test_relevant_values_bool(self): - values = {"allow_subset_of_names": True} - self.assertEqual( - self._test_relevant_values_common(values), values) - - def test_relevant_values_str(self): - values = {"authenticator": "apache"} - self.assertEqual( - self._test_relevant_values_common(values), values) - - def test_relevant_values_plugins_none(self): - self.assertEqual( - self._test_relevant_values_common( - {"authenticator": None, "installer": None}), {}) - - @mock.patch("certbot.cli.set_by_cli") - @mock.patch("certbot.plugins.disco.PluginsRegistry.find_all") - def test_relevant_values_namespace(self, mock_find_all, mock_set_by_cli): - mock_set_by_cli.return_value = True - mock_find_all.return_value = ["certbot-foo:bar"] - values = {"certbot_foo:bar_baz": 42} - self.assertEqual( - self._test_relevant_values_common(values), values) - - def test_relevant_values_server(self): - self.assertEqual( - # _test_relevant_values_common handles testing the server - # value and removes it - self._test_relevant_values_common({"server": "example.org"}), {}) - @mock.patch("certbot.storage.relevant_values") def test_new_lineage(self, mock_rv): """Test for new_lineage() class method.""" -- cgit v1.2.3 From 3ed3787bd8792d6ed5c58537a66425a503107721 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 2 Mar 2019 02:03:33 +0100 Subject: Implement Retry-After, and refactor authorization polling (#6766) Fixes #5789 This PR is about allowing Certbot to respect the Retry-After HTTP header that an ACME CA server can return to a client POSTing to a challenge, to instruct him and retry the request later. However, this feature was not easily implementable in the current code of certbot.auth_handler, because the code became really hard to read. In fact, @bmw was thinking that the code was really deceiving, and a lot of supposed functionalities declared in the comments were in fact not implemented or not functional. So I took the time to understand what was going on, and effectively, most of the code is in fact not usable or not used. Then I did a refactoring against the bare ACME spec about what to do to prepare challenges, instruct the ACME CA server to perform them, then polling regularly the authorization resources until they are decided (valid or invalid). And of course this implementation takes care of Retry-After ^^ I added a lot of comments in the new implementation, to explain what is going on for a future developer. The workflow I used is relying on the relationships between authorizations and challenges states as described in section 7.1.6 of the ACME spec draft: https://datatracker.ietf.org/doc/draft-ietf-acme-acme/ * Clean auth_handler a bit, and implement retry-after. * Remove a debug logger * Correct tests * Fix mypy and lint. Setup max retries and default retry after accordingly. * Ease a comparison in tests * Update documentation * Add tests * Adapt windows coverage threshold to the global LOC reduction * Update certbot/auth_handler.py Co-Authored-By: adferrand * Corrections under review * Correction under review * Update certbot/auth_handler.py Co-Authored-By: adferrand * Corrections under review * Update auth_handler_test.py * Reimplementing user readable report for failed authorizations * Fixes two tests * Fix another test + lint + mypy * Update auth_handler.py * Update auth_handler_test.py * Fix tests * Update certbot/auth_handler.py Co-Authored-By: adferrand * Raise directly the exception on polling timout * Improve interface documentation * Move the wait on top of the loop, to be used initially or after a new loop iteration. Do not wait for negative values. * Always display the report about failed authorizations. * Clarify an exception. * Return, instead of break * Use setdefault * Remove useless assertion * Adapt tests * Improve a test about retry after value. * Update certbot/auth_handler.py Co-Authored-By: adferrand * Add a complete test on best_effort * Add entry to the changelog * Gather all failed authzrs to be reported in one unique report in case of best_effort * Build complete warn/report/raise process about failed authzrs --- CHANGELOG.md | 3 + certbot/auth_handler.py | 398 +++++++++++++------------------------ certbot/interfaces.py | 13 +- certbot/plugins/common.py | 2 +- certbot/tests/auth_handler_test.py | 313 +++++++++++------------------ tox.cover.py | 2 +- 6 files changed, 255 insertions(+), 476 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eea1923d..764fc0946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * The running of manual plugin hooks is now always included in Certbot's log output. * Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. +* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as + specified in the ACME protocol, to indicate when the next polling should occur. Certbot now + reads this header if set and respect its value. * The `acme` module avoids sending the `keyAuthorization` field in the JWS payload when responding to a challenge as the field is not included in the current ACME protocol. To ease the migration path for ACME CA servers, diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py index 3dfaaf26f..517b56351 100644 --- a/certbot/auth_handler.py +++ b/certbot/auth_handler.py @@ -1,29 +1,23 @@ """ACME AuthHandler.""" -import collections import logging import time +import datetime -import six import zope.component from acme import challenges from acme import messages # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import DefaultDict, Dict, List, Set, Collection +from acme.magic_typing import Dict, List # pylint: enable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors from certbot import error_handler from certbot import interfaces - logger = logging.getLogger(__name__) -AnnotatedAuthzr = collections.namedtuple("AnnotatedAuthzr", ["authzr", "achalls"]) -"""Stores an authorization resource and its active annotated challenges.""" - - class AuthHandler(object): """ACME Authorization Handler for a client. @@ -47,242 +41,151 @@ class AuthHandler(object): self.account = account self.pref_challs = pref_challs - def handle_authorizations(self, orderr, best_effort=False): - """Retrieve all authorizations for challenges. - - :param acme.messages.OrderResource orderr: must have - authorizations filled in - :param bool best_effort: Whether or not all authorizations are - required (this is useful in renewal) - - :returns: List of authorization resources - :rtype: list - - :raises .AuthorizationError: If unable to retrieve all - authorizations - + def handle_authorizations(self, orderr, best_effort=False, max_retries=30): """ - aauthzrs = [AnnotatedAuthzr(authzr, []) - for authzr in orderr.authorizations] - - self._choose_challenges(aauthzrs) - config = zope.component.getUtility(interfaces.IConfig) - notify = zope.component.getUtility(interfaces.IDisplay).notification - - # While there are still challenges remaining... - while self._has_challenges(aauthzrs): - with error_handler.ExitHandler(self._cleanup_challenges, aauthzrs): - resp = self._solve_challenges(aauthzrs) - logger.info("Waiting for verification...") + Retrieve all authorizations, perform all challenges required to validate + these authorizations, then poll and wait for the authorization to be checked. + :param acme.messages.OrderResource orderr: must have authorizations filled in + :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) + :param int max_retries: maximum number of retries to poll authorizations + :returns: list of all validated authorizations + :rtype: List + + :raises .AuthorizationError: If unable to retrieve all authorizations + """ + authzrs = orderr.authorizations[:] + if not authzrs: + raise errors.AuthorizationError('No authorization to handle.') + + # Retrieve challenges that need to be performed to validate authorizations. + achalls = self._choose_challenges(authzrs) + if not achalls: + return authzrs + + # Starting now, challenges will be cleaned at the end no matter what. + with error_handler.ExitHandler(self._cleanup_challenges, achalls): + # To begin, let's ask the authenticator plugin to perform all challenges. + try: + resps = self.auth.perform(achalls) + + # If debug is on, wait for user input before starting the verification process. + logger.info('Waiting for verification...') + config = zope.component.getUtility(interfaces.IConfig) if config.debug_challenges: + notify = zope.component.getUtility(interfaces.IDisplay).notification notify('Challenges loaded. Press continue to submit to CA. ' 'Pass "-v" for more info about challenges.', pause=True) + except errors.AuthorizationError as error: + logger.critical('Failure in setting up challenges.') + logger.info('Attempting to clean up outstanding challenges...') + raise error + # All challenges should have been processed by the authenticator. + assert len(resps) == len(achalls), 'Some challenges have not been performed.' - # Send all Responses - this modifies achalls - self._respond(aauthzrs, resp, best_effort) - - # Just make sure all decisions are complete. - self.verify_authzr_complete(aauthzrs) + # Inform the ACME CA server that challenges are available for validation. + for achall, resp in zip(achalls, resps): + self.acme.answer_challenge(achall.challb, resp) - # Only return valid authorizations - ret_val = [aauthzr.authzr for aauthzr in aauthzrs - if aauthzr.authzr.body.status == messages.STATUS_VALID] + # Wait for authorizations to be checked. + self._poll_authorizations(authzrs, max_retries, best_effort) - if not ret_val: - raise errors.AuthorizationError( - "Challenges failed for all domains") + # Keep validated authorizations only. If there is none, no certificate can be issued. + authzrs_validated = [authzr for authzr in authzrs + if authzr.body.status == messages.STATUS_VALID] + if not authzrs_validated: + raise errors.AuthorizationError('All challenges have failed.') - return ret_val + return authzrs_validated - def _choose_challenges(self, aauthzrs): + def _poll_authorizations(self, authzrs, max_retries, best_effort): + """ + Poll the ACME CA server, to wait for confirmation that authorizations have their challenges + all verified. The poll may occur several times, until all authorizations are checked + (valid or invalid), or after a maximum of retries. + """ + authzrs_to_check = {index: (authzr, None) + for index, authzr in enumerate(authzrs)} + authzrs_failed_to_report = [] + # Give an initial second to the ACME CA server to check the authorizations + sleep_seconds = 1 + for _ in range(max_retries): + # Wait for appropriate time (from Retry-After, initial wait, or no wait) + if sleep_seconds > 0: + time.sleep(sleep_seconds) + # Poll all updated authorizations. + authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _) + in authzrs_to_check.items()} + # Update the original list of authzr with the updated authzrs from server. + for index, (authzr, _) in authzrs_to_check.items(): + authzrs[index] = authzr + + # Gather failed authorizations + authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values() + if authzr.body.status == messages.STATUS_INVALID] + for authzr_failed in authzrs_failed: + logger.warning('Challenge failed for domain %s', + authzr_failed.body.identifier.value) + # Accumulating all failed authzrs to build a consolidated report + # on them at the end of the polling. + authzrs_failed_to_report.extend(authzrs_failed) + + # Extract out the authorization already checked for next poll iteration. + # Poll may stop here because there is no pending authorizations anymore. + authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp) + in authzrs_to_check.items() + if authzr.body.status == messages.STATUS_PENDING} + if not authzrs_to_check: + # Polling process is finished, we can leave the loop + break + + # Be merciful with the ACME server CA, check the Retry-After header returned, + # and wait this time before polling again in next loop iteration. + # From all the pending authorizations, we take the greatest Retry-After value + # to avoid polling an authorization before its relevant Retry-After value. + retry_after = max(self.acme.retry_after(resp, 3) + for _, resp in authzrs_to_check.values()) + sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() + + # In case of failed authzrs, create a report to the user. + if authzrs_failed_to_report: + _report_failed_authzrs(authzrs_failed_to_report, self.account.key) + if not best_effort: + # Without best effort, having failed authzrs is critical and fail the process. + raise errors.AuthorizationError('Some challenges have failed.') + + if authzrs_to_check: + # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. + raise errors.AuthorizationError('All authorizations were not finalized by the CA.') + + def _choose_challenges(self, authzrs): """ Retrieve necessary and pending challenges to satisfy server. NB: Necessary and already validated challenges are not retrieved, as they can be reused for a certificate issuance. """ - pending_authzrs = [aauthzr for aauthzr in aauthzrs - if aauthzr.authzr.body.status != messages.STATUS_VALID] + pending_authzrs = [authzr for authzr in authzrs + if authzr.body.status != messages.STATUS_VALID] + achalls = [] # type: List[achallenges.AnnotatedChallenge] if pending_authzrs: logger.info("Performing the following challenges:") - for aauthzr in pending_authzrs: - aauthzr_challenges = aauthzr.authzr.body.challenges + for authzr in pending_authzrs: + authzr_challenges = authzr.body.challenges if self.acme.acme_version == 1: - combinations = aauthzr.authzr.body.combinations + combinations = authzr.body.combinations else: - combinations = tuple((i,) for i in range(len(aauthzr_challenges))) + combinations = tuple((i,) for i in range(len(authzr_challenges))) path = gen_challenge_path( - aauthzr_challenges, - self._get_chall_pref(aauthzr.authzr.body.identifier.value), + authzr_challenges, + self._get_chall_pref(authzr.body.identifier.value), combinations) - aauthzr_achalls = self._challenge_factory( - aauthzr.authzr, path) - aauthzr.achalls.extend(aauthzr_achalls) - - for aauthzr in aauthzrs: - for achall in aauthzr.achalls: - if isinstance(achall.chall, challenges.TLSSNI01): - logger.warning("TLS-SNI-01 is deprecated, and will stop working soon.") - return - - def _has_challenges(self, aauthzrs): - """Do we have any challenges to perform?""" - return any(aauthzr.achalls for aauthzr in aauthzrs) - - def _solve_challenges(self, aauthzrs): - """Get Responses for challenges from authenticators.""" - resp = [] # type: Collection[challenges.ChallengeResponse] - all_achalls = self._get_all_achalls(aauthzrs) - try: - if all_achalls: - resp = self.auth.perform(all_achalls) - except errors.AuthorizationError: - logger.critical("Failure in setting up challenges.") - logger.info("Attempting to clean up outstanding challenges...") - raise + achalls.extend(self._challenge_factory(authzr, path)) - assert len(resp) == len(all_achalls) + if any(isinstance(achall.chall, challenges.TLSSNI01) for achall in achalls): + logger.warning("TLS-SNI-01 is deprecated, and will stop working soon.") - return resp - - def _get_all_achalls(self, aauthzrs): - """Return all active challenges.""" - all_achalls = [] # type: Collection[challenges.ChallengeResponse] - for aauthzr in aauthzrs: - all_achalls.extend(aauthzr.achalls) - return all_achalls - - def _respond(self, aauthzrs, resp, best_effort): - """Send/Receive confirmation of all challenges. - - .. note:: This method also cleans up the auth_handler state. - - """ - # TODO: chall_update is a dirty hack to get around acme-spec #105 - chall_update = dict() \ - # type: Dict[int, List[achallenges.KeyAuthorizationAnnotatedChallenge]] - self._send_responses(aauthzrs, resp, chall_update) - - # Check for updated status... - self._poll_challenges(aauthzrs, chall_update, best_effort) - - def _send_responses(self, aauthzrs, resps, chall_update): - """Send responses and make sure errors are handled. - - :param aauthzrs: authorizations and the selected annotated challenges - to try and perform - :type aauthzrs: `list` of `AnnotatedAuthzr` - :param resps: challenge responses from the authenticator where - each response at index i corresponds to the annotated - challenge at index i in the list returned by - :func:`_get_all_achalls` - :type resps: `collections.abc.Iterable` of - :class:`~acme.challenges.ChallengeResponse` or `False` or - `None` - :param dict chall_update: parameter that is updated to hold - aauthzr index to list of outstanding solved annotated challenges - - """ - active_achalls = [] - resps_iter = iter(resps) - for i, aauthzr in enumerate(aauthzrs): - for achall in aauthzr.achalls: - # This line needs to be outside of the if block below to - # ensure failed challenges are cleaned up correctly - active_achalls.append(achall) - - resp = next(resps_iter) - # Don't send challenges for None and False authenticator responses - if resp: - self.acme.answer_challenge(achall.challb, resp) - # TODO: answer_challenge returns challr, with URI, - # that can be used in _find_updated_challr - # comparisons... - chall_update.setdefault(i, []).append(achall) - - return active_achalls - - def _poll_challenges(self, aauthzrs, chall_update, - best_effort, min_sleep=3, max_rounds=30): - """Wait for all challenge results to be determined.""" - indices_to_check = set(chall_update.keys()) - comp_indices = set() - rounds = 0 - - while indices_to_check and rounds < max_rounds: - # TODO: Use retry-after... - time.sleep(min_sleep) - all_failed_achalls = set() # type: Set[achallenges.KeyAuthorizationAnnotatedChallenge] - for index in indices_to_check: - comp_achalls, failed_achalls = self._handle_check( - aauthzrs, index, chall_update[index]) - - if len(comp_achalls) == len(chall_update[index]): - comp_indices.add(index) - elif not failed_achalls: - for achall, _ in comp_achalls: - chall_update[index].remove(achall) - # We failed some challenges... damage control - else: - if best_effort: - comp_indices.add(index) - logger.warning( - "Challenge failed for domain %s", - aauthzrs[index].authzr.body.identifier.value) - else: - all_failed_achalls.update( - updated for _, updated in failed_achalls) - - if all_failed_achalls: - _report_failed_challs(all_failed_achalls) - raise errors.FailedChallenges(all_failed_achalls) - - indices_to_check -= comp_indices - comp_indices.clear() - rounds += 1 - - def _handle_check(self, aauthzrs, index, achalls): - """Returns tuple of ('completed', 'failed').""" - completed = [] - failed = [] - - original_aauthzr = aauthzrs[index] - updated_authzr, _ = self.acme.poll(original_aauthzr.authzr) - aauthzrs[index] = AnnotatedAuthzr(updated_authzr, original_aauthzr.achalls) - if updated_authzr.body.status == messages.STATUS_VALID: - return achalls, [] - - # Note: if the whole authorization is invalid, the individual failed - # challenges will be determined here... - for achall in achalls: - updated_achall = achall.update(challb=self._find_updated_challb( - updated_authzr, achall)) - - # This does nothing for challenges that have yet to be decided yet. - if updated_achall.status == messages.STATUS_VALID: - completed.append((achall, updated_achall)) - elif updated_achall.status == messages.STATUS_INVALID: - failed.append((achall, updated_achall)) - - return completed, failed - - def _find_updated_challb(self, authzr, achall): # pylint: disable=no-self-use - """Find updated challenge body within Authorization Resource. - - .. warning:: This assumes only one instance of type of challenge in - each challenge resource. - - :param .AuthorizationResource authzr: Authorization Resource - :param .AnnotatedChallenge achall: Annotated challenge for which - to get status - - """ - for authzr_challb in authzr.body.challenges: - if type(authzr_challb.chall) is type(achall.challb.chall): # noqa - return authzr_challb - raise errors.AuthorizationError( - "Target challenge not found in authorization resource") + return achalls def _get_chall_pref(self, domain): """Return list of challenge preferences. @@ -306,43 +209,15 @@ class AuthHandler(object): chall_prefs.extend(plugin_pref) return chall_prefs - def _cleanup_challenges(self, aauthzrs, achalls=None): + def _cleanup_challenges(self, achalls): """Cleanup challenges. - :param aauthzrs: authorizations and their selected annotated - challenges - :type aauthzrs: `list` of `AnnotatedAuthzr` :param achalls: annotated challenges to cleanup :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge` """ logger.info("Cleaning up challenges") - if achalls is None: - achalls = self._get_all_achalls(aauthzrs) - if achalls: - self.auth.cleanup(achalls) - for achall in achalls: - for aauthzr in aauthzrs: - if achall in aauthzr.achalls: - aauthzr.achalls.remove(achall) - break - - def verify_authzr_complete(self, aauthzrs): - """Verifies that all authorizations have been decided. - - :param aauthzrs: authorizations and their selected annotated - challenges - :type aauthzrs: `list` of `AnnotatedAuthzr` - - :returns: Whether all authzr are complete - :rtype: bool - - """ - for aauthzr in aauthzrs: - authzr = aauthzr.authzr - if (authzr.body.status != messages.STATUS_VALID and - authzr.body.status != messages.STATUS_INVALID): - raise errors.AuthorizationError("Incomplete authorizations") + self.auth.cleanup(achalls) def _challenge_factory(self, authzr, path): """Construct Namedtuple Challenges @@ -530,22 +405,19 @@ _ERROR_HELP = { } -def _report_failed_challs(failed_achalls): - """Notifies the user about failed challenges. +def _report_failed_authzrs(failed_authzrs, account_key): + """Notifies the user about failed authorizations.""" + problems = {} # type: Dict[str, List[achallenges.AnnotatedChallenge]] + failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value) + for authzr in failed_authzrs for challb in authzr.body.challenges + if challb.error] - :param set failed_achalls: A set of failed - :class:`certbot.achallenges.AnnotatedChallenge`. - - """ - problems = collections.defaultdict(list)\ - # type: DefaultDict[str, List[achallenges.KeyAuthorizationAnnotatedChallenge]] for achall in failed_achalls: - if achall.error: - problems[achall.error.typ].append(achall) + problems.setdefault(achall.error.typ, []).append(achall) + reporter = zope.component.getUtility(interfaces.IReporter) - for achalls in six.itervalues(problems): - reporter.add_message( - _generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) + for achalls in problems.values(): + reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) def _generate_failed_chall_msg(failed_achalls): diff --git a/certbot/interfaces.py b/certbot/interfaces.py index bb9a91b0f..bd91d2272 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -159,21 +159,14 @@ class IAuthenticator(IPlugin): :func:`get_chall_pref` only. :returns: `collections.Iterable` of ACME - :class:`~acme.challenges.ChallengeResponse` instances - or if the :class:`~acme.challenges.Challenge` cannot - be fulfilled then: - - ``None`` - Authenticator can perform challenge, but not at this time. - ``False`` - Authenticator will never be able to perform (error). - + :class:`~acme.challenges.ChallengeResponse` instances corresponding to each provided + :class:`~acme.challenges.Challenge`. :rtype: :class:`collections.Iterable` of :class:`acme.challenges.ChallengeResponse`, where responses are required to be returned in the same order as corresponding input challenges - :raises .PluginError: If challenges cannot be performed + :raises .PluginError: If some or all challenges cannot be performed """ diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index ee1af4978..c14129d87 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -351,7 +351,7 @@ class ChallengePerformer(object): def perform(self): """Perform all added challenges. - :returns: challenge respones + :returns: challenge responses :rtype: `list` of `acme.challenges.KeyAuthorizationChallengeResponse` diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index fe0ece12e..353c34da2 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -4,13 +4,11 @@ import logging import unittest import mock -import six import zope.component from acme import challenges from acme import client as acme_client from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors @@ -82,6 +80,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.mock_account = mock.Mock(key=util.Key("file_path", "PEM")) self.mock_net = mock.MagicMock(spec=acme_client.Client) self.mock_net.acme_version = 1 + self.mock_net.retry_after.side_effect = acme_client.Client.retry_after self.handler = AuthHandler( self.mock_auth, self.mock_net, self.mock_account, []) @@ -95,23 +94,26 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos) mock_order = mock.MagicMock(authorizations=[authzr]) - with mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") as mock_poll: - mock_poll.side_effect = self._validate_all + self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=1, wait_value=30) + with mock.patch('certbot.auth_handler.time') as mock_time: authzr = self.handler.handle_authorizations(mock_order) - self.assertEqual(self.mock_net.answer_challenge.call_count, 1) + self.assertEqual(self.mock_net.answer_challenge.call_count, 1) - self.assertEqual(mock_poll.call_count, 1) - chall_update = mock_poll.call_args[0][1] - self.assertEqual(list(six.iterkeys(chall_update)), [0]) - self.assertEqual(len(chall_update.values()), 1) + self.assertEqual(self.mock_net.poll.call_count, 2) # Because there is one retry + self.assertEqual(mock_time.sleep.call_count, 2) + # Retry-After header is 30 seconds, but at the time sleep is invoked, several + # instructions are executed, and next pool is in less than 30 seconds. + self.assertTrue(mock_time.sleep.call_args_list[1][0][0] <= 30) + # However, assert that we did not took the default value of 3 seconds. + self.assertTrue(mock_time.sleep.call_args_list[1][0][0] > 3) - self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall - self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.assertEqual(self.mock_auth.cleanup.call_count, 1) + # Test if list first element is TLSSNI01, use typ because it is an achall + self.assertEqual( + self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") - self.assertEqual(len(authzr), 1) + self.assertEqual(len(authzr), 1) def test_name1_tls_sni_01_1_acme_1(self): self._test_name1_tls_sni_01_1_common(combos=True) @@ -120,9 +122,8 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.mock_net.acme_version = 2 self._test_name1_tls_sni_01_1_common(combos=False) - @mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_1(self, mock_poll): - mock_poll.side_effect = self._validate_all + def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_1(self): + self.mock_net.poll.side_effect = _gen_mock_on_poll() self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) @@ -132,10 +133,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_net.answer_challenge.call_count, 3) - self.assertEqual(mock_poll.call_count, 1) - chall_update = mock_poll.call_args[0][1] - self.assertEqual(list(six.iterkeys(chall_update)), [0]) - self.assertEqual(len(chall_update.values()), 1) + self.assertEqual(self.mock_net.poll.call_count, 1) self.assertEqual(self.mock_auth.cleanup.call_count, 1) # Test if list first element is TLSSNI01, use typ because it is an achall @@ -145,10 +143,9 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p # Length of authorizations list self.assertEqual(len(authzr), 1) - @mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_2(self, mock_poll): + def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_2(self): self.mock_net.acme_version = 2 - mock_poll.side_effect = self._validate_all + self.mock_net.poll.side_effect = _gen_mock_on_poll() self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) @@ -158,10 +155,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_net.answer_challenge.call_count, 1) - self.assertEqual(mock_poll.call_count, 1) - chall_update = mock_poll.call_args[0][1] - self.assertEqual(list(six.iterkeys(chall_update)), [0]) - self.assertEqual(len(chall_update.values()), 1) + self.assertEqual(self.mock_net.poll.call_count, 1) self.assertEqual(self.mock_auth.cleanup.call_count, 1) cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0] @@ -175,27 +169,18 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.CHALLENGES, combos=combos) - authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES), gen_dom_authzr(domain="1", challs=acme_util.CHALLENGES), gen_dom_authzr(domain="2", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) - with mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") as mock_poll: - mock_poll.side_effect = self._validate_all - authzr = self.handler.handle_authorizations(mock_order) + + self.mock_net.poll.side_effect = _gen_mock_on_poll() + authzr = self.handler.handle_authorizations(mock_order) self.assertEqual(self.mock_net.answer_challenge.call_count, 3) # Check poll call - self.assertEqual(mock_poll.call_count, 1) - chall_update = mock_poll.call_args[0][1] - self.assertEqual(len(list(six.iterkeys(chall_update))), 3) - self.assertTrue(0 in list(six.iterkeys(chall_update))) - self.assertEqual(len(chall_update[0]), 1) - self.assertTrue(1 in list(six.iterkeys(chall_update))) - self.assertEqual(len(chall_update[1]), 1) - self.assertTrue(2 in list(six.iterkeys(chall_update))) - self.assertEqual(len(chall_update[2]), 1) + self.assertEqual(self.mock_net.poll.call_count, 3) self.assertEqual(self.mock_auth.cleanup.call_count, 1) @@ -208,14 +193,13 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.mock_net.acme_version = 2 self._test_name3_tls_sni_01_3_common(combos=False) - @mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") - def test_debug_challenges(self, mock_poll): + def test_debug_challenges(self): zope.component.provideUtility( mock.Mock(debug_challenges=True), interfaces.IConfig) authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) - mock_poll.side_effect = self._validate_all + self.mock_net.poll.side_effect = _gen_mock_on_poll() self.handler.handle_authorizations(mock_order) @@ -231,6 +215,18 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) + def test_max_retries_exceeded(self): + authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] + mock_order = mock.MagicMock(authorizations=authzrs) + + # We will return STATUS_PENDING twice before returning STATUS_VALID. + self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=2) + + with self.assertRaises(errors.AuthorizationError) as error: + # We retry only once, so retries will be exhausted before STATUS_VALID is returned. + self.handler.handle_authorizations(mock_order, False, 1) + self.assertTrue('All authorizations were not finalized by the CA.' in str(error.exception)) + def test_no_domains(self): mock_order = mock.MagicMock(authorizations=[]) self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order) @@ -244,9 +240,8 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.handler.pref_challs.extend((challenges.HTTP01.typ, challenges.DNS01.typ,)) - with mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") as mock_poll: - mock_poll.side_effect = self._validate_all - self.handler.handle_authorizations(mock_order) + self.mock_net.poll.side_effect = _gen_mock_on_poll() + self.handler.handle_authorizations(mock_order) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( @@ -290,11 +285,11 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual( self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") - @mock.patch("certbot.auth_handler.AuthHandler._respond") - def test_respond_error(self, mock_respond): + def test_answer_error(self): + self.mock_net.answer_challenge.side_effect = errors.AuthorizationError + authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) - mock_respond.side_effect = errors.AuthorizationError self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) @@ -302,20 +297,52 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual( self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") - @mock.patch("certbot.auth_handler.AuthHandler._poll_challenges") - @mock.patch("certbot.auth_handler.AuthHandler.verify_authzr_complete") - def test_incomplete_authzr_error(self, mock_verify, mock_poll): + def test_incomplete_authzr_error(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] mock_order = mock.MagicMock(authorizations=authzrs) - mock_verify.side_effect = errors.AuthorizationError - mock_poll.side_effect = self._validate_all + self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID) - self.assertRaises( - errors.AuthorizationError, self.handler.handle_authorizations, mock_order) + with test_util.patch_get_utility(): + with self.assertRaises(errors.AuthorizationError) as error: + self.handler.handle_authorizations(mock_order, False) + self.assertTrue('Some challenges have failed.' in str(error.exception)) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + def test_best_effort(self): + def _conditional_mock_on_poll(authzr): + """This mock will invalidate one authzr, and invalidate the other one""" + valid_mock = _gen_mock_on_poll(messages.STATUS_VALID) + invalid_mock = _gen_mock_on_poll(messages.STATUS_INVALID) + + if authzr.body.identifier.value == 'will-be-invalid': + return invalid_mock(authzr) + return valid_mock(authzr) + + # Two authzrs. Only one will be valid. + authzrs = [gen_dom_authzr(domain="will-be-valid", challs=acme_util.CHALLENGES), + gen_dom_authzr(domain="will-be-invalid", challs=acme_util.CHALLENGES)] + self.mock_net.poll.side_effect = _conditional_mock_on_poll + + mock_order = mock.MagicMock(authorizations=authzrs) + + with mock.patch('certbot.auth_handler._report_failed_authzrs') as mock_report: + valid_authzr = self.handler.handle_authorizations(mock_order, True) + + # Because best_effort=True, we did not blow up. Instead ... + self.assertEqual(len(valid_authzr), 1) # ... the valid authzr has been processed + self.assertEqual(mock_report.call_count, 1) # ... the invalid authzr has been reported + + self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID) + + with test_util.patch_get_utility(): + with self.assertRaises(errors.AuthorizationError) as error: + self.handler.handle_authorizations(mock_order, True) + + # Despite best_effort=True, process will fail because no authzr is valid. + self.assertTrue('All challenges have failed.' in str(error.exception)) + def test_validated_challenge_not_rerun(self): # With pending challenge, we expect the challenge to be tried, and fail. authzr = acme_util.gen_authzr( @@ -334,138 +361,26 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p mock_order = mock.MagicMock(authorizations=[authzr]) self.handler.handle_authorizations(mock_order) - def _validate_all(self, aauthzrs, unused_1, unused_2): - for i, aauthzr in enumerate(aauthzrs): - azr = aauthzr.authzr - updated_azr = acme_util.gen_authzr( - messages.STATUS_VALID, - azr.body.identifier.value, - [challb.chall for challb in azr.body.challenges], - [messages.STATUS_VALID] * len(azr.body.challenges), - azr.body.combinations) - aauthzrs[i] = type(aauthzr)(updated_azr, aauthzr.achalls) - @mock.patch("certbot.auth_handler.logger") def test_tls_sni_logs(self, logger): self._test_name1_tls_sni_01_1_common(combos=True) self.assertTrue("deprecated" in logger.warning.call_args[0][0]) -class PollChallengesTest(unittest.TestCase): - # pylint: disable=protected-access - """Test poll challenges.""" +def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): + state = {'count': retry} - def setUp(self): - from certbot.auth_handler import challb_to_achall - from certbot.auth_handler import AuthHandler, AnnotatedAuthzr - - # Account and network are mocked... - self.mock_net = mock.MagicMock() - self.handler = AuthHandler( - None, self.mock_net, mock.Mock(key="mock_key"), []) - - self.doms = ["0", "1", "2"] - self.aauthzrs = [ - AnnotatedAuthzr(acme_util.gen_authzr( - messages.STATUS_PENDING, self.doms[0], - [acme_util.HTTP01, acme_util.TLSSNI01], - [messages.STATUS_PENDING] * 2, False), []), - AnnotatedAuthzr(acme_util.gen_authzr( - messages.STATUS_PENDING, self.doms[1], - acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False), []), - AnnotatedAuthzr(acme_util.gen_authzr( - messages.STATUS_PENDING, self.doms[2], - acme_util.CHALLENGES, [messages.STATUS_PENDING] * 3, False), []) - ] - - self.chall_update = {} # type: Dict[int, achallenges.KeyAuthorizationAnnotatedChallenge] - for i, aauthzr in enumerate(self.aauthzrs): - self.chall_update[i] = [ - challb_to_achall(challb, mock.Mock(key="dummy_key"), self.doms[i]) - for challb in aauthzr.authzr.body.challenges] - - - @mock.patch("certbot.auth_handler.time") - def test_poll_challenges(self, unused_mock_time): - self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid - self.handler._poll_challenges(self.aauthzrs, self.chall_update, False) - - for aauthzr in self.aauthzrs: - self.assertEqual(aauthzr.authzr.body.status, messages.STATUS_VALID) - - @mock.patch("certbot.auth_handler.time") - def test_poll_challenges_failure_best_effort(self, unused_mock_time): - self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid - self.handler._poll_challenges(self.aauthzrs, self.chall_update, True) - - for aauthzr in self.aauthzrs: - self.assertEqual(aauthzr.authzr.body.status, messages.STATUS_PENDING) - - @mock.patch("certbot.auth_handler.time") - @test_util.patch_get_utility() - def test_poll_challenges_failure(self, unused_mock_time, unused_mock_zope): - self.mock_net.poll.side_effect = self._mock_poll_solve_one_invalid - self.assertRaises( - errors.AuthorizationError, self.handler._poll_challenges, - self.aauthzrs, self.chall_update, False) - - @mock.patch("certbot.auth_handler.time") - def test_unable_to_find_challenge_status(self, unused_mock_time): - from certbot.auth_handler import challb_to_achall - self.mock_net.poll.side_effect = self._mock_poll_solve_one_valid - self.chall_update[0].append( - challb_to_achall(acme_util.DNS01_P, "key", self.doms[0])) - self.assertRaises( - errors.AuthorizationError, self.handler._poll_challenges, - self.aauthzrs, self.chall_update, False) - - def test_verify_authzr_failure(self): - self.assertRaises(errors.AuthorizationError, - self.handler.verify_authzr_complete, self.aauthzrs) - - def _mock_poll_solve_one_valid(self, authzr): - # Pending here because my dummy script won't change the full status. - # Basically it didn't raise an error and it stopped earlier than - # Making all challenges invalid which would make mock_poll_solve_one - # change authzr to invalid - return self._mock_poll_solve_one_chall(authzr, messages.STATUS_VALID) - - def _mock_poll_solve_one_invalid(self, authzr): - return self._mock_poll_solve_one_chall(authzr, messages.STATUS_INVALID) - - def _mock_poll_solve_one_chall(self, authzr, desired_status): - # pylint: disable=no-self-use - """Dummy method that solves one chall at a time to desired_status. - - When all are solved.. it changes authzr.status to desired_status - - """ - new_challbs = authzr.body.challenges - for challb in authzr.body.challenges: - if challb.status != desired_status: - new_challbs = tuple( - challb_temp if challb_temp != challb - else acme_util.chall_to_challb(challb.chall, desired_status) - for challb_temp in authzr.body.challenges - ) - break - - if all(test_challb.status == desired_status - for test_challb in new_challbs): - status_ = desired_status - else: - status_ = authzr.body.status - - new_authzr = messages.AuthorizationResource( - uri=authzr.uri, - body=messages.Authorization( - identifier=authzr.body.identifier, - challenges=new_challbs, - combinations=authzr.body.combinations, - status=status_, - ), - ) - return (new_authzr, "response") + def _mock(authzr): + state['count'] = state['count'] - 1 + effective_status = status if state['count'] < 0 else messages.STATUS_PENDING + updated_azr = acme_util.gen_authzr( + effective_status, + authzr.body.identifier.value, + [challb.chall for challb in authzr.body.challenges], + [effective_status] * len(authzr.body.challenges), + authzr.body.combinations) + return updated_azr, mock.MagicMock(headers={'Retry-After': str(wait_value)}) + return _mock class ChallbToAchallTest(unittest.TestCase): @@ -527,8 +442,8 @@ class GenChallengePathTest(unittest.TestCase): errors.AuthorizationError, self._call, challbs, prefs, None) -class ReportFailedChallsTest(unittest.TestCase): - """Tests for certbot.auth_handler._report_failed_challs.""" +class ReportFailedAuthzrsTest(unittest.TestCase): + """Tests for certbot.auth_handler._report_failed_authzrs.""" # pylint: disable=protected-access def setUp(self): @@ -542,31 +457,27 @@ class ReportFailedChallsTest(unittest.TestCase): # Prevent future regressions if the error type changes self.assertTrue(kwargs["error"].description is not None) - self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args - challb=messages.ChallengeBody(**kwargs), - domain="example.com", - account_key="key") + http_01 = messages.ChallengeBody(**kwargs) # pylint: disable=star-args kwargs["chall"] = acme_util.TLSSNI01 - self.tls_sni_same = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args - challb=messages.ChallengeBody(**kwargs), - domain="example.com", - account_key="key") + tls_sni_01 = messages.ChallengeBody(**kwargs) # pylint: disable=star-args + + self.authzr1 = mock.MagicMock() + self.authzr1.body.identifier.value = 'example.com' + self.authzr1.body.challenges = [http_01, tls_sni_01] kwargs["error"] = messages.Error(typ="dnssec", detail="detail") - self.tls_sni_diff = achallenges.KeyAuthorizationAnnotatedChallenge( - # pylint: disable=star-args - challb=messages.ChallengeBody(**kwargs), - domain="foo.bar", - account_key="key") + tls_sni_01_diff = messages.ChallengeBody(**kwargs) # pylint: disable=star-args + + self.authzr2 = mock.MagicMock() + self.authzr2.body.identifier.value = 'foo.bar' + self.authzr2.body.challenges = [tls_sni_01_diff] @test_util.patch_get_utility() def test_same_error_and_domain(self, mock_zope): from certbot import auth_handler - auth_handler._report_failed_challs([self.http01, self.tls_sni_same]) + auth_handler._report_failed_authzrs([self.authzr1], 'key') call_list = mock_zope().add_message.call_args_list self.assertTrue(len(call_list) == 1) self.assertTrue("Domain: example.com\nType: tls\nDetail: detail" in call_list[0][0][0]) @@ -575,7 +486,7 @@ class ReportFailedChallsTest(unittest.TestCase): def test_different_errors_and_domains(self, mock_zope): from certbot import auth_handler - auth_handler._report_failed_challs([self.http01, self.tls_sni_diff]) + auth_handler._report_failed_authzrs([self.authzr1, self.authzr2], 'key') self.assertTrue(mock_zope().add_message.call_count == 2) diff --git a/tox.cover.py b/tox.cover.py index e323ba255..d0f97626a 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -12,7 +12,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_sakuracloud', 'certbot_nginx', 'certbot_postfix', 'letshelp_certbot'] COVER_THRESHOLDS = { - 'certbot': {'linux': 98, 'windows': 94}, + 'certbot': {'linux': 98, 'windows': 93}, 'acme': {'linux': 100, 'windows': 99}, 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, -- cgit v1.2.3 From bf1f83f47b49f746f1cd54076749a08e7b04250d Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 2 Mar 2019 02:16:22 +0100 Subject: Revert "Disable build for commits pushed on master (#6804)" (#6807) By removing all the builds on push to master, done in #6804, we also removing the coveralls reports that are necessary to calculate the effect of a PR on code coverage, that is part of the quality gate process. This PR is reverting #6804, and another implementation preserving the coveralls reports will be done soon. This reverts commit a468a3b255e326dbfa2b7d629eb20fa275631254. --- .travis.yml | 5 ----- appveyor.yml | 8 -------- 2 files changed, 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 108f853aa..4adab1e4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,11 +18,6 @@ branches: - /^\d+\.\d+\.x$/ - /^test-.*$/ -# Since master can receive only commits from PR that have already been tested, we avoid with the -# the following condition to launch again a pipeline when the merge commit is pushed to master. -# However master still needs to be set in branches section to allow PR for master to be built. -if: NOT (type = push AND branch = master) - matrix: include: # These environments are always executed diff --git a/appveyor.yml b/appveyor.yml index 5dcc80ba9..2b6b82747 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,14 +11,6 @@ branches: - /^\d+\.\d+\.x$/ # Version branches like X.X.X - /^test-.*$/ -init: - # Since master can receive only commits from PR that have already been tested, we avoid with the - # the following condition to launch again a pipeline when the merge commit is pushed to master. - # However master still needs to be set in branches section to allow PR for master to be built. - - ps: | - if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master') - { $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild } - install: # Use Python 3.7 by default - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" -- cgit v1.2.3 From 6ff101dcbbe66943487633391a4f03f27bcc6aaa Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 2 Mar 2019 03:07:07 +0100 Subject: Cover case of OpenSUSE Leap 15+ in certbot-auto (#6794) Fixes #6228. Since OpenSUSE Leap 15, python-virtualenv became a source package, breaking certbot-auto bootstrap on this version. Then python2-virtualenv must be used to create Python 2.x virtual environments. This PR makes certbot-auto compatible to prior and after Leap 15, by testing the existence of python-virtualenv on current OpenSUSE system, and then use appropriate packages. * Cover case of OpenSUSE Leap 15+ in certbot-auto * Revert increment on bootstrap for OpenSUSE * Fix configuration for Leap15+ * Add comment about explicit installation of python2-setuptools * Update letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh Co-Authored-By: adferrand * Update letsencrypt-auto --- letsencrypt-auto-source/letsencrypt-auto | 12 +++++++++++- letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index c390f28ec..ce3b35e86 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -521,10 +521,20 @@ BootstrapSuseCommon() { QUIET_FLAG='-qq' fi + if zypper search -x python-virtualenv >/dev/null 2>&1; then + OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv" + else + # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv + # is a source package, and python2-virtualenv must be used instead. + # Also currently python2-setuptools is not a dependency of python2-virtualenv, + # while it should be. Installing it explicitly until upstreqm fix. + OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" + fi + zypper $QUIET_FLAG $zypper_flags in $install_flags \ python \ python-devel \ - python-virtualenv \ + $OPENSUSE_VIRTUALENV_PACKAGES \ gcc \ augeas-lenses \ libopenssl-devel \ diff --git a/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh b/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh index c531cbe99..ac66119c3 100755 --- a/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh @@ -14,10 +14,20 @@ BootstrapSuseCommon() { QUIET_FLAG='-qq' fi + if zypper search -x python-virtualenv >/dev/null 2>&1; then + OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv" + else + # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv + # is a source package, and python2-virtualenv must be used instead. + # Also currently python2-setuptools is not a dependency of python2-virtualenv, + # while it should be. Installing it explicitly until upstreqm fix. + OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" + fi + zypper $QUIET_FLAG $zypper_flags in $install_flags \ python \ python-devel \ - python-virtualenv \ + $OPENSUSE_VIRTUALENV_PACKAGES \ gcc \ augeas-lenses \ libopenssl-devel \ -- cgit v1.2.3 From d8a3fa3904ecddd00be871847c773c9370780449 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Mon, 4 Mar 2019 15:52:38 +0200 Subject: Address review comments --- tools/merge_requirements.py | 25 ++++++++++++++----------- tools/pip_install.py | 2 +- tools/strip_hashes.py | 35 ++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/tools/merge_requirements.py b/tools/merge_requirements.py index 521d28b8f..0d41d12c4 100755 --- a/tools/merge_requirements.py +++ b/tools/merge_requirements.py @@ -55,21 +55,24 @@ def main(*paths): """Merges multiple requirements files together and prints the result. Requirement files specified later in the list take precedence over earlier - files. + files. Files are read from file paths passed from the command line arguments. - :param tuple paths: paths to requirements files + If no command line arguments are defined, data is read from stdin instead. + + :param tuple paths: paths to requirements files provided on command line """ data = {} - for path in paths: - data.update(process_entries(read_file(path))) - - # Need to check if interactive to avoid blocking if nothing is piped - if not sys.stdin.isatty(): - stdin_data = [] - for line in sys.stdin: - stdin_data.append(line) - data.update(process_entries(stdin_data)) + if paths: + for path in paths: + data.update(process_entries(read_file(path))) + else: + # Need to check if interactive to avoid blocking if nothing is piped + if not sys.stdin.isatty(): + stdin_data = [] + for line in sys.stdin: + stdin_data.append(line) + data.update(process_entries(stdin_data)) return output_requirements(data) diff --git a/tools/pip_install.py b/tools/pip_install.py index 8f0437d9c..15dc2f0c0 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -48,7 +48,7 @@ def certbot_normal_processing(tools_path, test_constraints): with open(certbot_requirements, 'r') as fd: data = fd.readlines() with open(test_constraints, 'w') as fd: - data = os.linesep.join(strip_hashes.process_entries(data)) + data = "\n".join(strip_hashes.process_entries(data)) fd.write(data) diff --git a/tools/strip_hashes.py b/tools/strip_hashes.py index 7e7809458..988e72eb8 100755 --- a/tools/strip_hashes.py +++ b/tools/strip_hashes.py @@ -2,7 +2,6 @@ """Removes hash information from requirement files passed to it as file path arguments or simply piped to stdin.""" -import os import re import sys @@ -24,22 +23,28 @@ def process_entries(entries): return out_lines def main(*paths): - """Reads dependency definitions from a (list of) file(s) or stdin and - removes hashes from returned entries""" + """ + Reads dependency definitions from a (list of) file(s) provided on the + command line. If no command line arguments are present, data is read from + stdin instead. + + Hashes are removed from returned entries. + """ deps = [] - for path in paths: - with open(path) as file_h: - deps += process_entries(file_h.readlines()) - - # Need to check if interactive to avoid blocking if nothing is piped - if not sys.stdin.isatty(): - stdin_data = [] - for line in sys.stdin: - stdin_data.append(line) - deps += process_entries(stdin_data) - - return os.linesep.join(deps) + if paths: + for path in paths: + with open(path) as file_h: + deps += process_entries(file_h.readlines()) + else: + # Need to check if interactive to avoid blocking if nothing is piped + if not sys.stdin.isatty(): + stdin_data = [] + for line in sys.stdin: + stdin_data.append(line) + deps += process_entries(stdin_data) + + return "\n".join(deps) if __name__ == '__main__': print(main(*sys.argv[1:])) # pylint: disable=star-args -- cgit v1.2.3 From 6a0f3248a8f7a55b2de33283e498458e3a7b1a81 Mon Sep 17 00:00:00 2001 From: Yohann Leon Date: Tue, 5 Mar 2019 22:27:07 +0100 Subject: Replace deprecated Gandi plugin link --- docs/using.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index e17e56b64..de17742a1 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -277,7 +277,7 @@ Plugin Auth Inst Notes plesk_ Y Y Integration with the Plesk web hosting tool haproxy_ Y Y Integration with the HAProxy load balancer s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets -gandi_ Y Y Integration with Gandi's hosting products and API +gandi_ Y Y Integration with Gandi LiveDNS API varnish_ Y N Obtain certificates via a Varnish server external_ Y N A plugin for convenient scripting (See also ticket 2782_) icecast_ N Y Deploy certificates to Icecast 2 streaming media servers @@ -290,7 +290,7 @@ heroku_ Y Y Integration with Heroku SSL .. _plesk: https://github.com/plesk/letsencrypt-plesk .. _haproxy: https://github.com/greenhost/certbot-haproxy .. _s3front: https://github.com/dlapiduz/letsencrypt-s3front -.. _gandi: https://github.com/Gandi/letsencrypt-gandi +.. _gandi: https://github.com/obynio/certbot-plugin-gandi .. _icecast: https://github.com/e00E/lets-encrypt-icecast .. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin .. _2782: https://github.com/certbot/certbot/issues/2782 -- cgit v1.2.3 From 198c447e77c785842a68c2fdc9326f156f29d915 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 5 Mar 2019 14:01:08 -0800 Subject: Move the OCSP change to the right section. (#6818) Looks like this got added to our changelog for the released 0.31.0 instead of the upcoming release. We want this change for the release tomorrow. --- CHANGELOG.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 764fc0946..ce0dddd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* If possible, Certbot uses built-in support for OCSP from recent cryptography + versions instead of the OpenSSL binary: as a consequence Certbot does not need + the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. ### Changed @@ -49,9 +51,6 @@ More details about these changes can be found on our GitHub repo. * Avoid reprocessing challenges that are already validated when a certificate is issued. -* If possible, Certbot uses built-in support for OCSP from recent cryptography - versions instead of the OpenSSL binary: as a consequence Certbot does not need - the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. * Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges with the `acme` module. -- cgit v1.2.3 From 670e9d89b70258a1a8fb963f4e208de0e22aab6d Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 6 Mar 2019 03:30:33 +0100 Subject: Fix faulty test (#6816) --- .../certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf index 0918e5669..dbfae3765 100644 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf +++ b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf @@ -1,7 +1,7 @@ #LoadModule ssl_module modules/mod_ssl.so -Listen 443 - +Listen 4443 + # The ServerName directive sets the request scheme, hostname and port that # the server uses to identify itself. This is used when creating # redirection URLs. In the context of virtual hosts, the ServerName -- cgit v1.2.3 From be6df7de044988613ff14ac042cda3a7e863b076 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 6 Mar 2019 12:16:13 -0800 Subject: Remove Fixed section from changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0dddd16..cc007b46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,10 +30,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). field included if a `malformed` error is received. This fallback will be removed in version 0.34.0. -### 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: -- cgit v1.2.3 From a276523c096bfa46c5948b24e37cccb0d6a2bd25 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 6 Mar 2019 12:18:08 -0800 Subject: Update changelog for 0.32.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc007b46f..fdb3b5f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.32.0 - master +## 0.32.0 - 2019-03-06 ### Added -- cgit v1.2.3 From 0492855166f104bba8a7dd0395b925d82c8cc759 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 6 Mar 2019 12:47:27 -0800 Subject: Release 0.32.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 100 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 10 ++- letsencrypt-auto | 100 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 ++-- letsencrypt-auto-source/letsencrypt-auto | 26 +++--- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 ++--- 26 files changed, 168 insertions(+), 146 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6cb5c3f92..00e9a0971 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.32.0.dev0' +version = '0.32.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 5d15611dd..1b3173ada 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 03c2994e3..0c82a7437 100755 --- a/certbot-auto +++ b/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.31.0" +LE_AUTO_VERSION="0.32.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -521,10 +521,20 @@ BootstrapSuseCommon() { QUIET_FLAG='-qq' fi + if zypper search -x python-virtualenv >/dev/null 2>&1; then + OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv" + else + # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv + # is a source package, and python2-virtualenv must be used instead. + # Also currently python2-setuptools is not a dependency of python2-virtualenv, + # while it should be. Installing it explicitly until upstreqm fix. + OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" + fi + zypper $QUIET_FLAG $zypper_flags in $install_flags \ python \ python-devel \ - python-virtualenv \ + $OPENSUSE_VIRTUALENV_PACKAGES \ gcc \ augeas-lenses \ libopenssl-devel \ @@ -1034,26 +1044,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 @@ -1180,18 +1190,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.31.0 \ - --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ - --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 -acme==0.31.0 \ - --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ - --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf -certbot-apache==0.31.0 \ - --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ - --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd -certbot-nginx==0.31.0 \ - --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ - --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 +certbot==0.32.0 \ + --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ + --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 +acme==0.32.0 \ + --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ + --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec +certbot-apache==0.32.0 \ + --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ + --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 +certbot-nginx==0.32.0 \ + --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ + --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1221,7 +1231,6 @@ from distutils.version import StrictVersion from hashlib import sha256 from os import environ from os.path import join -from pipes import quote from shutil import rmtree try: from subprocess import check_output @@ -1241,7 +1250,7 @@ except ImportError: cmd = popenargs[0] raise CalledProcessError(retcode, cmd) return output -from sys import exit, version_info +import sys from tempfile import mkdtemp try: from urllib2 import build_opener, HTTPHandler, HTTPSHandler @@ -1263,7 +1272,7 @@ maybe_argparse = ( [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/' 'argparse-1.4.0.tar.gz', '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] - if version_info < (2, 7, 0) else []) + if sys.version_info < (2, 7, 0) else []) PACKAGES = maybe_argparse + [ @@ -1344,7 +1353,8 @@ def get_index_base(): def main(): - pip_version = StrictVersion(check_output(['pip', '--version']) + python = sys.executable or 'python' + pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version']) .decode('utf-8').split()[1]) has_pip_cache = pip_version >= StrictVersion('6.0') index_base = get_index_base() @@ -1354,12 +1364,12 @@ def main(): temp, digest) for path, digest in PACKAGES] - 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 '') + - ' '.join(quote(d) for d in downloads), - shell=True) + # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] + # Disable cache since it is not used and it otherwise sometimes throws permission warnings: + command.extend(['--no-cache-dir'] if has_pip_cache else []) + command.extend(downloads) + check_output(command) except HashError as exc: print(exc) except Exception: @@ -1372,7 +1382,7 @@ def main(): if __name__ == '__main__': - exit(main()) + sys.exit(main()) UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index e1c82b063..c11e32cd1 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index d6a16f468..8c697d0a5 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index f0f2446e1..ed3ac8c78 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index c61f05339..f9d2c430f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 7d39a10d0..2039d27c7 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 8a5f234b5..6df71672f 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 67ae1d4eb..3aee64ba4 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 429702d75..e7ff6ac78 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 6797501b9..60f6e0d36 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 07d9decdf..4406244fd 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 314b0a16c..bdd21f45b 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a878eac47..046c05382 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 39f35e953..580f4ac2a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index e915c6b86..24e097b9d 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 429f960f7..8c6e28c90 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0.dev0' +version = '0.32.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 8984704a6..b6cd0ea97 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.32.0.dev0' +version = '0.32.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 767448a12..a442d1e84 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.32.0.dev0' +__version__ = '0.32.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index e95b2fcd5..ed7794c3b 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.31.0 + "". (default: CertbotACMEClient/0.32.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -477,7 +477,9 @@ plugins: using Sakura Cloud for DNS). (default: False) apache: - Apache Web Server plugin + Apache Web Server plugin (Please note that the default values of the + Apache plugin options change depending on the operating system Certbot is + run on.) --apache-enmod APACHE_ENMOD Path to the Apache 'a2enmod' binary (default: None) @@ -496,7 +498,7 @@ 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) @@ -505,7 +507,7 @@ apache: Ubuntu/Debian currently) (default: False) --apache-ctl APACHE_CTL Full path to Apache control script (default: - apachectl) + apache2ctl) dns-cloudflare: Obtain certificates using a DNS TXT record (if you are using Cloudflare diff --git a/letsencrypt-auto b/letsencrypt-auto index 03c2994e3..0c82a7437 100755 --- a/letsencrypt-auto +++ b/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.31.0" +LE_AUTO_VERSION="0.32.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -521,10 +521,20 @@ BootstrapSuseCommon() { QUIET_FLAG='-qq' fi + if zypper search -x python-virtualenv >/dev/null 2>&1; then + OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv" + else + # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv + # is a source package, and python2-virtualenv must be used instead. + # Also currently python2-setuptools is not a dependency of python2-virtualenv, + # while it should be. Installing it explicitly until upstreqm fix. + OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" + fi + zypper $QUIET_FLAG $zypper_flags in $install_flags \ python \ python-devel \ - python-virtualenv \ + $OPENSUSE_VIRTUALENV_PACKAGES \ gcc \ augeas-lenses \ libopenssl-devel \ @@ -1034,26 +1044,26 @@ ConfigArgParse==0.12.0 \ configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ --no-binary configobj -cryptography==2.2.2 \ - --hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \ - --hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \ - --hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \ - --hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \ - --hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \ - --hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \ - --hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \ - --hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \ - --hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \ - --hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \ - --hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \ - --hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \ - --hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \ - --hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \ - --hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \ - --hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \ - --hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \ - --hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \ - --hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887 +cryptography==2.5 \ + --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ + --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ + --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ + --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ + --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ + --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ + --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ + --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ + --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ + --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ + --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ + --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ + --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ + --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ + --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ + --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ + --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ + --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ + --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 enum34==1.1.2 ; python_version < '3.4' \ --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 @@ -1180,18 +1190,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.31.0 \ - --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ - --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 -acme==0.31.0 \ - --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ - --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf -certbot-apache==0.31.0 \ - --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ - --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd -certbot-nginx==0.31.0 \ - --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ - --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 +certbot==0.32.0 \ + --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ + --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 +acme==0.32.0 \ + --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ + --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec +certbot-apache==0.32.0 \ + --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ + --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 +certbot-nginx==0.32.0 \ + --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ + --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1221,7 +1231,6 @@ from distutils.version import StrictVersion from hashlib import sha256 from os import environ from os.path import join -from pipes import quote from shutil import rmtree try: from subprocess import check_output @@ -1241,7 +1250,7 @@ except ImportError: cmd = popenargs[0] raise CalledProcessError(retcode, cmd) return output -from sys import exit, version_info +import sys from tempfile import mkdtemp try: from urllib2 import build_opener, HTTPHandler, HTTPSHandler @@ -1263,7 +1272,7 @@ maybe_argparse = ( [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/' 'argparse-1.4.0.tar.gz', '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] - if version_info < (2, 7, 0) else []) + if sys.version_info < (2, 7, 0) else []) PACKAGES = maybe_argparse + [ @@ -1344,7 +1353,8 @@ def get_index_base(): def main(): - pip_version = StrictVersion(check_output(['pip', '--version']) + python = sys.executable or 'python' + pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version']) .decode('utf-8').split()[1]) has_pip_cache = pip_version >= StrictVersion('6.0') index_base = get_index_base() @@ -1354,12 +1364,12 @@ def main(): temp, digest) for path, digest in PACKAGES] - 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 '') + - ' '.join(quote(d) for d in downloads), - shell=True) + # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] + # Disable cache since it is not used and it otherwise sometimes throws permission warnings: + command.extend(['--no-cache-dir'] if has_pip_cache else []) + command.extend(downloads) + check_output(command) except HashError as exc: print(exc) except Exception: @@ -1372,7 +1382,7 @@ def main(): if __name__ == '__main__': - exit(main()) + sys.exit(main()) UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index d95f9abcb..eeb8d7d12 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlxcop8ACgkQTRfJlc2X -dfIbZwf/faKu7IjLi0qFQ+kw8zaAnV47JDgfWqbR5GSdwWPqld+QyHlcRfPgwYma -fKj9+g/FvPNPSfjHRRCoFrYvpZ4lZ+f4HPN9+OjydfM77rdDhVDwzs8dbKIk02yU -0IEJhXj5Q9hF3TSDZcyXAJdBU1lz51ohtVIXelMBPmzhYPCZF47iE9/k9pApQi86 -RTji7hxPcF/n7mzXrbyTvk+kDxSdDlE0Eg9syK7XaFDBTa2lqgG8wTnMPVqhc/hm -WM/uwkzbYarjy05ffV1kM683nP0rECnHlYT38pYcT2puw2kn/QthwR5j/jB/DWSc -94Kw7BeMH651V8EaNwYIiouylnVH3A== -=U+Qh +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlyAMZ0ACgkQTRfJlc2X +dfLItQf/SNv+at1Pw5oiEbWleNPpmz9srlkf9AHU92Hh3p7+OljcaWQindtCYtlO +UvWV/CjGzObmJvO+Pgy2epNhD8cTjPamI46l5UG2nwvy8V+JemS937Ae6paivt8T +/RaFKfyNDfxBjQhHS1ypVuRrFgAQ5CG0iGuJSMgwLpcKCZyKAim3+Vb57+Esq+zG +Cp7GmJk9h1z5FbNukbaFHBlJQIefJoQclh1yUw11pLab0uxIOdc9WiEWLLAVE512 +SMQM2sNv49uh7mRnxW+6WU6dor6JI9Ff1L4D5hfglzBJRM4qLU/hGv54oVycWq4i +eFjtqDfo5XMwnbnUnVkEB73pY6lzDg== +=YaE3 -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ce3b35e86..0c82a7437 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.32.0.dev0" +LE_AUTO_VERSION="0.32.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1190,18 +1190,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.31.0 \ - --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ - --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 -acme==0.31.0 \ - --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ - --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf -certbot-apache==0.31.0 \ - --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ - --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd -certbot-nginx==0.31.0 \ - --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ - --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 +certbot==0.32.0 \ + --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ + --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 +acme==0.32.0 \ + --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ + --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec +certbot-apache==0.32.0 \ + --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ + --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 +certbot-nginx==0.32.0 \ + --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ + --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 3ecca1510..17eea3eba 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 514d3271b..3e0dbde7e 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.31.0 \ - --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ - --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 -acme==0.31.0 \ - --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ - --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf -certbot-apache==0.31.0 \ - --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ - --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd -certbot-nginx==0.31.0 \ - --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ - --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 +certbot==0.32.0 \ + --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ + --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 +acme==0.32.0 \ + --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ + --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec +certbot-apache==0.32.0 \ + --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ + --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 +certbot-nginx==0.32.0 \ + --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ + --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 -- cgit v1.2.3 From 0343f365abcecd2737083b7fddc3f8ec3063e68c Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 6 Mar 2019 12:47:28 -0800 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdb3b5f13..c854c74bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.33.0 - 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. + ## 0.32.0 - 2019-03-06 ### Added -- cgit v1.2.3 From 8dda6cc68f7f2a0b2b05d223e27df487fb5c9b3c Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 6 Mar 2019 12:47:29 -0800 Subject: Bump version to 0.33.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 00e9a0971..cd4ea3ef5 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.32.0' +version = '0.33.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 1b3173ada..dfedc3f0d 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index c11e32cd1..858cef831 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 8c697d0a5..84414e0c0 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index ed3ac8c78..4b2fe15be 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index f9d2c430f..1de51f7ca 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 2039d27c7..113e55e12 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 6df71672f..6e882a4f7 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 3aee64ba4..bdbd198d1 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index e7ff6ac78..c2556797d 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 60f6e0d36..c494d6d4e 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 4406244fd..4c57c5709 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index bdd21f45b..9c9f233cc 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 046c05382..cf6fee5d7 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 580f4ac2a..dacf41101 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 24e097b9d..321ad3b5d 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 8c6e28c90..46d92a6ff 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.32.0' +version = '0.33.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index db6b261f0..2108298ae 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,2 @@ acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.32.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index b6cd0ea97..7494bf6bf 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.32.0' +version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index a442d1e84..38e25f3a4 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.32.0' +__version__ = '0.33.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 0c82a7437..af4b181cd 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.32.0" +LE_AUTO_VERSION="0.33.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From f378536ffab6213d1e34a8811e9d8a0e6ab9674b Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 6 Mar 2019 23:49:43 +0100 Subject: Do not run the full CI pipeline on master (#6811) * Configure appveyor * Renaming in travis yml * Update .travis.yml Co-Authored-By: adferrand * Update .travis.yml Co-Authored-By: adferrand --- .travis.yml | 68 ++++++++++++++++++++++++++++++++++++++++-------------------- appveyor.yml | 8 +++++++ 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4adab1e4e..cabd3cf73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,141 +18,163 @@ branches: - /^\d+\.\d+\.x$/ - /^test-.*$/ +# Jobs for the main test suite are always executed (including on PRs) except for pushes on master. +not-on-master: ¬-on-master + if: NOT (type = push AND branch = master) + +# Jobs for the extended test suite are executed for cron jobs and pushes on non-master branches. +extended-test-suite: &extended-test-suite + if: type = cron OR (type = push AND branch != master) + matrix: include: - # These environments are always executed + # Main test suite - python: "2.7" env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=all TOXENV=py27_install sudo: required services: docker + <<: *not-on-master - python: "2.7" env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=all TOXENV=py27_install sudo: required services: docker + <<: *not-on-master + + # This job is always executed, including on master - python: "2.7" env: TOXENV=py27-cover FYI="py27 tests + code coverage" + - sudo: required env: TOXENV=nginx_compat services: docker before_install: addons: + <<: *not-on-master - python: "2.7" env: TOXENV=lint + <<: *not-on-master - python: "3.4" env: TOXENV=mypy + <<: *not-on-master - python: "3.5" env: TOXENV=mypy + <<: *not-on-master - python: "2.7" env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest' sudo: required services: docker + <<: *not-on-master - python: "3.4" env: TOXENV=py34 sudo: required services: docker + <<: *not-on-master - python: "3.7" dist: xenial env: TOXENV=py37 sudo: required services: docker + <<: *not-on-master - sudo: required env: TOXENV=apache_compat services: docker before_install: addons: + <<: *not-on-master - sudo: required env: TOXENV=le_auto_trusty services: docker before_install: addons: + <<: *not-on-master - python: "2.7" env: TOXENV=apacheconftest-with-pebble sudo: required services: docker + <<: *not-on-master - python: "2.7" env: TOXENV=nginxroundtrip + <<: *not-on-master - # These environments are executed on cron events and commits to tested - # branches other than master. Which branches are tested is controlled by - # the "branches" section earlier in this file. + # Extended test suite on cron jobs and pushes to tested branches other than master - python: "3.7" dist: xenial env: TOXENV=py37 CERTBOT_NO_PIN=1 - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "2.7" env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "2.7" env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "2.7" env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "2.7" env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.4" env: TOXENV=py34 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.4" env: TOXENV=py34 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.5" env: TOXENV=py35 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.5" env: TOXENV=py35 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.6" env: TOXENV=py36 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.6" env: TOXENV=py36 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.7" dist: xenial env: TOXENV=py37 BOULDER_INTEGRATION=v1 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - python: "3.7" dist: xenial env: TOXENV=py37 BOULDER_INTEGRATION=v2 sudo: required services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_xenial services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_jessie services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_centos6 services: docker - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev services: docker @@ -160,7 +182,7 @@ matrix: apt: packages: # don't install nginx and apache - libaugeas0 - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - language: generic env: TOXENV=py27 os: osx @@ -169,7 +191,7 @@ matrix: packages: - augeas - python2 - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite - language: generic env: TOXENV=py3 os: osx @@ -178,7 +200,7 @@ matrix: packages: - augeas - python3 - if: type = cron OR (type = push AND branch != master) + <<: *extended-test-suite # container-based infrastructure sudo: false diff --git a/appveyor.yml b/appveyor.yml index 2b6b82747..12d882973 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,14 @@ branches: - /^\d+\.\d+\.x$/ # Version branches like X.X.X - /^test-.*$/ +init: + # Since master can receive only commits from PR that have already been tested, following + # condition avoid to launch all jobs except the coverage one for commits pushed to master. + - ps: | + if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master' ` + -And -Not ($Env:TOXENV -Like '*-cover')) + { $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild } + install: # Use Python 3.7 by default - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" -- cgit v1.2.3 From 34393f9bf4a0ca7045a4cfd1e4eafa5842b3a832 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 7 Mar 2019 19:05:20 +0100 Subject: Correct certbot-auto for Fedora 29+ (#6812) Fixes #6698 Fedora maintainers engaged a deprecation path for Python 2.x with Fedora 29. As a first step, python2-virtualenv does not install the virtualenv binary anymore, in favor of python3-virtualenv, and so the installation of Python 3 virtual environments by default. However, certbot-auto installs python2-virtualenv for all recent RPM distributions, and relies of the execution of virtualenv, and this is failing the process. Since the plan in the future is to remove Python 2.x from Fedora, this PR follows this logic to fix certbot-auto: started to Fedora 29, certbot-auto will install and execute certbot on Python 3. This implies to detect that we are on Fedora 29+, install python3-virtualenv that will install also Python 3 dependencies and virtualenv binary, then instruct the process to use Python 3. This is in fact similar to EOL distributions shipping with Python 2.6, and for which Python 3.4 from EPEL is installed and used. Older versions of Fedora continue to use Python 2.x, and their process is untouched. Four scenarios are covered here: fresh Fedora 28: old process is used, nothing changes fresh Fedora 29: new process is used, Python 3 is installed, certbot runs on it update Fedora 29 from 28, already installed certbot-auto without rebootstrapping required: existing venv continue to be used, certbot runs on it update Fedora 29 from 28, already installed certbot-auto with rebootstrapping required: new process is used, installing python3-virtualenv, python3-devel and python3-rpm-macros, Python 3 is installed, certbot runs on it * Add a step to handle python3 on fedora29 * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: adferrand * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: adferrand * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: adferrand * Update rpm_python3.sh * Rebuild certbot-auto * Empty commit to relaunch CI pipeline * Add changelog * Update CHANGELOG.md Co-Authored-By: adferrand * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- letsencrypt-auto-source/letsencrypt-auto | 14 ++++++++++++-- letsencrypt-auto-source/letsencrypt-auto.template | 5 ++++- .../pieces/bootstrappers/rpm_python3.sh | 9 ++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c854c74bf..3b49d5275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation + path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. ### Changed diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index af4b181cd..df7fc2dc1 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -488,11 +488,18 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: # - CentOS 6 + # - Fedora 29 InitializeRPMCommonBase + # Fedora 29 must use python3-virtualenv + if $TOOL list python3-virtualenv >/dev/null 2>&1; then + python_pkgs="python3 + python3-virtualenv + python3-devel + " # EPEL uses python34 - if $TOOL list python34 >/dev/null 2>&1; then + elif $TOOL list python34 >/dev/null 2>&1; then python_pkgs="python34 python34-devel python34-tools @@ -741,7 +748,10 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - if [ "$PYVER" -eq 26 ]; then + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release && echo $VERSION_ID) || echo "0"` + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 2cd3f4336..215fca04e 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -323,7 +323,10 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - if [ "$PYVER" -eq 26 ]; then + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release && echo $VERSION_ID) || echo "0"` + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index b011a7235..f33b07ca9 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -5,11 +5,18 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: # - CentOS 6 + # - Fedora 29 InitializeRPMCommonBase + # Fedora 29 must use python3-virtualenv + if $TOOL list python3-virtualenv >/dev/null 2>&1; then + python_pkgs="python3 + python3-virtualenv + python3-devel + " # EPEL uses python34 - if $TOOL list python34 >/dev/null 2>&1; then + elif $TOOL list python34 >/dev/null 2>&1; then python_pkgs="python34 python34-devel python34-tools -- cgit v1.2.3 From 45229eebdfd08baac26c9e8b78d76490b6f7cbce Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Mar 2019 11:57:08 -0800 Subject: Drop expected Apache coverage to workaround #6813. (#6826) * Drop expected Apache coverage to workaround #6813. * add comment --- tox.cover.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tox.cover.py b/tox.cover.py index d0f97626a..6f5392b71 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -14,7 +14,10 @@ DEFAULT_PACKAGES = [ COVER_THRESHOLDS = { 'certbot': {'linux': 98, 'windows': 93}, 'acme': {'linux': 100, 'windows': 99}, - 'certbot_apache': {'linux': 100, 'windows': 100}, + # certbot_apache coverage not being at 100% is a workaround for + # https://github.com/certbot/certbot/issues/6813. We should increase + # the minimum coverage back to 100% when this issue is resolved. + 'certbot_apache': {'linux': 99, 'windows': 99}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, 'certbot_dns_cloudxns': {'linux': 99, 'windows': 99}, 'certbot_dns_digitalocean': {'linux': 98, 'windows': 98}, -- cgit v1.2.3 From cac4be7046e7c349fd4f03ae8af51b7586de4138 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 11 Mar 2019 23:27:33 +0100 Subject: Calculate timedelta with thisUpdate/nextUpdate in UTC (#6838) Fixes #6836. OCSP responses contains a thisUpdate and nextUpdate that allow to calculate its validity. Certbot currently uses datetime.now() to get the current time when OCSP check is done through cryptography. But datetime.now() expresses the date in the machine local time, and comparison operators on datetime do not take into account the offset between two datetime objects expressed in difference timezones. As a consequence, a given thisUpdate may be seen as a future date depending on the local time, failing the OCSP check process. The error is not critical for certbot, as it will just make some valid OCSP responses giving an EXPIRED status been ignored. This PR fixes this comparison by taking the current time in UTC using datetime.utctime(). --- certbot/ocsp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 6f4c0b0fb..bc63c40f9 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -200,7 +200,7 @@ def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): # for OpenSSL, so we do not do it here. # See OpenSSL implementation as a reference: # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 - now = datetime.now() + now = datetime.utcnow() # thisUpdate/nextUpdate are expressed in UTC/GMT time zone if not response_ocsp.this_update: raise AssertionError('param thisUpdate is not set.') if response_ocsp.this_update > now + timedelta(minutes=5): -- cgit v1.2.3 From 81d9b5250e1d50b376170186b661c6c4ca132956 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 11 Mar 2019 23:42:32 +0100 Subject: Clean stderr in case of /etc/os-release does not exist (#6835) --- letsencrypt-auto-source/letsencrypt-auto | 4 ++-- letsencrypt-auto-source/letsencrypt-auto.template | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index df7fc2dc1..2a44a58dc 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -749,8 +749,8 @@ elif [ -f /etc/redhat-release ]; then unset LE_PYTHON DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - RPM_DIST_NAME=`(. /etc/os-release && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release && echo $VERSION_ID) || echo "0"` + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 215fca04e..29c54bf2d 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -324,8 +324,8 @@ elif [ -f /etc/redhat-release ]; then unset LE_PYTHON DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - RPM_DIST_NAME=`(. /etc/os-release && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release && echo $VERSION_ID) || echo "0"` + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" -- cgit v1.2.3 From e20adedb94fdf6774ad3789e6f493905d56f750f Mon Sep 17 00:00:00 2001 From: Seth Schoen Date: Mon, 11 Mar 2019 15:50:27 -0700 Subject: This is now at the top level of their site --- docs/ciphers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ciphers.rst b/docs/ciphers.rst index 1b320cdf9..b748dd87a 100644 --- a/docs/ciphers.rst +++ b/docs/ciphers.rst @@ -227,7 +227,7 @@ BetterCrypto.org BetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, "Applied Crypto Hardening" -https://bettercrypto.org/static/applied-crypto-hardening.pdf +https://bettercrypto.org/ FF-DHE Internet-Draft ~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From a7f2f24426e7d4025e5c32b684324a3fb8a81f01 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 11 Mar 2019 16:14:20 -0700 Subject: Mention OCSP UTC fix in changelog. (#6845) --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b49d5275..cc34634ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 + is installed. We fixed a bug in Certbot causing it to interpret timestamps in + the OCSP response as being in the local timezone rather than UTC. 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. -- cgit v1.2.3 From cf29e89366c7eb57dd48a99a06fe05ceaa9057fa Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 12 Mar 2019 00:16:48 +0100 Subject: Move coverage computation during certbot integration tests at the end of the script (#6842) Currently coverage invocation during integration tests on certbot core is misplaced, just before the OCSP statuses tests. This PR move back the coverage invocation at the end of the script. --- tests/certbot-boulder-integration.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/certbot-boulder-integration.sh b/tests/certbot-boulder-integration.sh index 9d1ae9ec2..05b144df4 100755 --- a/tests/certbot-boulder-integration.sh +++ b/tests/certbot-boulder-integration.sh @@ -528,8 +528,6 @@ if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then --manual-cleanup-hook ./tests/manual-dns-cleanup.sh fi -coverage report --fail-under 64 --include 'certbot/*' --show-missing - # Test OCSP status ## OCSP 1: Check stale OCSP status @@ -582,3 +580,5 @@ if [ "$REVOKED" != 1 ] ; then echo "Expected le-ocsp-check.wtf to be REVOKED" exit 1 fi + +coverage report --fail-under 64 --include 'certbot/*' --show-missing -- cgit v1.2.3 From acc918eee7fd5c6101a88b7b034a8c05c46483c6 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 13 Mar 2019 23:42:07 +0100 Subject: Remove tls-sni integration tests (#6852) This PR is a part of the tls-sni-01 removal plan described in #6849. This PR removes the tls-sni-01 challenge tests during the integration tests. The approach I used here is not to remove completely the existing test code, but simply editing it to use a http-01 challenge. Indeed: * the current integration tests are strongly coupled, and would require more modifications that it is worth, because ... * the certbot-ci project, that has already no tls-sni tests, will soon replace completely the current integration tests code. --- certbot-nginx/tests/boulder-integration.sh | 4 +--- tests/boulder-fetch.sh | 6 ------ tests/certbot-boulder-integration.sh | 16 ++++++++-------- tests/integration/_common.sh | 6 +++--- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/certbot-nginx/tests/boulder-integration.sh b/certbot-nginx/tests/boulder-integration.sh index 1312cd0e6..fbb34a273 100755 --- a/certbot-nginx/tests/boulder-integration.sh +++ b/certbot-nginx/tests/boulder-integration.sh @@ -43,8 +43,6 @@ nginx -v reload_nginx certbot_test_nginx --domains nginx.wtf run test_deployment_and_rollback nginx.wtf -certbot_test_nginx --domains nginx-tls.wtf run --preferred-challenges tls-sni -test_deployment_and_rollback nginx-tls.wtf certbot_test_nginx --domains nginx2.wtf --preferred-challenges http test_deployment_and_rollback nginx2.wtf # Overlapping location block and server-block-level return 301 @@ -70,4 +68,4 @@ test_deployment_and_rollback nginx6.wtf # top nginx -c $nginx_root/nginx.conf -s stop -coverage report --fail-under 75 --include 'certbot-nginx/*' --show-missing +coverage report --fail-under 72 --include 'certbot-nginx/*' --show-missing diff --git a/tests/boulder-fetch.sh b/tests/boulder-fetch.sh index a06d37325..f34deb74e 100755 --- a/tests/boulder-fetch.sh +++ b/tests/boulder-fetch.sh @@ -12,12 +12,6 @@ fi cd ${BOULDERPATH} -# Since https://github.com/letsencrypt/boulder/commit/92e8e1708a725e9d08a5da2f4a7132320ed2158b, -# Boulder support for tls-sni-01 challenges is disabled. We still need to support it until this -# challenge is officially removed from ACME CA server on production, and also removed from Certbot. -# This sed command reactivate tls-sni-01 challenges inplace temporarily. -sed -i 's/tls-alpn-01/tls-sni-01/g' test/config/ra.json - docker-compose up -d boulder set +x # reduce verbosity while waiting for boulder diff --git a/tests/certbot-boulder-integration.sh b/tests/certbot-boulder-integration.sh index 05b144df4..004423a92 100755 --- a/tests/certbot-boulder-integration.sh +++ b/tests/certbot-boulder-integration.sh @@ -223,20 +223,20 @@ common plugins --init --prepare | grep webroot # We start a server listening on the port for the # unrequested challenge to prevent regressions in #3601. -python ./tests/run_http_server.py $http_01_port & +python ./tests/run_http_server.py $tls_alpn_01_port & python_server_pid=$! - certname="le1.wtf" -common --domains le1.wtf --preferred-challenges tls-sni-01 auth \ +common --domains le1.wtf --preferred-challenges http-01 auth \ --cert-name $certname \ --pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \ --post-hook 'echo wtf.post >> "$HOOK_TEST"'\ --deploy-hook 'echo deploy >> "$HOOK_TEST"' -kill $python_server_pid CheckDeployHook $certname -python ./tests/run_http_server.py $tls_sni_01_port & -python_server_pid=$! +# Previous test used to be a tls-sni-01 challenge that is not supported anymore. +# Now it is a http-01 challenge and this makes it a duplicate of the following test. +# But removing it would break many tests here, as they are strongly coupled. +# See https://github.com/certbot/certbot/pull/6852 certname="le2.wtf" common --domains le2.wtf --preferred-challenges http-01 run \ --cert-name $certname \ @@ -256,7 +256,7 @@ common certonly -a manual -d le.wtf --rsa-key-size 4096 --cert-name $certname \ CheckRenewHook $certname certname="dns.le.wtf" -common -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni run \ +common -a manual -d dns.le.wtf --preferred-challenges dns run \ --cert-name $certname \ --manual-auth-hook ./tests/manual-dns-auth.sh \ --manual-cleanup-hook ./tests/manual-dns-cleanup.sh \ @@ -398,7 +398,7 @@ CheckDirHooks 1 # with fail. common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \ --allow-subset-of-names \ - --preferred-challenges dns,tls-sni \ + --preferred-challenges dns \ --manual-auth-hook ./tests/manual-dns-auth.sh \ --manual-cleanup-hook ./tests/manual-dns-cleanup.sh diff --git a/tests/integration/_common.sh b/tests/integration/_common.sh index 83aa91a9e..76b990ac4 100755 --- a/tests/integration/_common.sh +++ b/tests/integration/_common.sh @@ -3,10 +3,10 @@ root=${root:-$(mktemp -d -t leitXXXX)} echo "Root integration tests directory: $root" config_dir="$root/conf" -tls_sni_01_port=5001 +tls_alpn_01_port=5001 http_01_port=5002 sources="acme/,$(ls -dm certbot*/ | tr -d ' \n')" -export root config_dir tls_sni_01_port http_01_port sources +export root config_dir tls_alpn_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. @@ -60,7 +60,7 @@ certbot_test_no_force_renew () { "$certbot_path" \ --server "${SERVER:-http://localhost:4000/directory}" \ --no-verify-ssl \ - --tls-sni-01-port $tls_sni_01_port \ + --tls-sni-01-port $tls_alpn_01_port \ --http-01-port $http_01_port \ --manual-public-ip-logging-ok \ $other_flags \ -- cgit v1.2.3 From 8386d08de23c2bd214da0296365c381945af98d8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 14 Mar 2019 11:07:19 -0700 Subject: Use Python3 to run tools/venv3.py. (#6860) --- tools/venv3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/venv3.py b/tools/venv3.py index c2374ba5a..b837baf70 100755 --- a/tools/venv3.py +++ b/tools/venv3.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Developer virtualenv setup for Certbot client import _venv_common -- cgit v1.2.3 From 5e64349a4a76f591ec317d11dff87bc4d005dc2f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 15 Mar 2019 00:30:17 +0100 Subject: Remove tls-sni challenge in standalone plugin (#6856) --- certbot/plugins/standalone.py | 53 +++++++------------------------------- certbot/plugins/standalone_test.py | 32 +++++++---------------- 2 files changed, 20 insertions(+), 65 deletions(-) diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py index 16f872a3f..207eff0b6 100644 --- a/certbot/plugins/standalone.py +++ b/certbot/plugins/standalone.py @@ -6,7 +6,7 @@ import socket # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi from socket import errno as socket_errors # type: ignore -import OpenSSL +import OpenSSL # pylint: disable=unused-import import six import zope.interface @@ -55,25 +55,21 @@ class ServerManager(object): :param int port: Port to run the server on. :param challenge_type: Subclass of `acme.challenges.Challenge`, - either `acme.challenge.HTTP01` or `acme.challenges.TLSSNI01`. + currently only `acme.challenge.HTTP01`. :param str listenaddr: (optional) The address to listen on. Defaults to all addrs. :returns: DualNetworkedServers instance. :rtype: ACMEServerMixin """ - assert challenge_type in (challenges.TLSSNI01, challenges.HTTP01) + assert challenge_type == challenges.HTTP01 if port in self._instances: return self._instances[port] address = (listenaddr, port) try: - if challenge_type is challenges.TLSSNI01: - servers = acme_standalone.TLSSNI01DualNetworkedServers( - address, self.certs) # type: acme_standalone.BaseDualNetworkedServers - else: # challenges.HTTP01 - servers = acme_standalone.HTTP01DualNetworkedServers( - address, self.http_01_resources) + servers = acme_standalone.HTTP01DualNetworkedServers( + address, self.http_01_resources) except socket.error as error: raise errors.StandaloneBindError(error, port) @@ -96,8 +92,6 @@ class ServerManager(object): for sockname in instance.getsocknames(): logger.debug("Stopping server at %s:%d...", *sockname[:2]) - # Not calling server_close causes problems when renewing multiple - # certs with `certbot renew` using TLSSNI01 and PyOpenSSL 0.13 instance.shutdown_and_server_close() del self._instances[port] @@ -114,7 +108,7 @@ class ServerManager(object): return self._instances.copy() -SUPPORTED_CHALLENGES = [challenges.HTTP01, challenges.TLSSNI01] \ +SUPPORTED_CHALLENGES = [challenges.HTTP01] \ # type: List[Type[challenges.KeyAuthorizationChallenge]] @@ -132,8 +126,6 @@ class SupportedChallengesAction(argparse.Action): def _convert_and_validate(self, data): """Validate the value of supported challenges provided by the user. - References to "dvsni" are automatically converted to "tls-sni-01". - :param str data: comma delimited list of challenge types :returns: validated and converted list of challenge types @@ -141,15 +133,6 @@ class SupportedChallengesAction(argparse.Action): """ challs = data.split(",") - - # tls-sni-01 was dvsni during private beta - if "dvsni" in challs: - logger.info( - "Updating legacy standalone_supported_challenges value") - challs = [challenges.TLSSNI01.typ if chall == "dvsni" else chall - for chall in challs] - data = ",".join(challs) - unrecognized = [name for name in challs if name not in challenges.Challenge.TYPES] @@ -177,7 +160,7 @@ class Authenticator(common.Plugin): """Standalone Authenticator. This authenticator creates its own ephemeral TCP listener on the - necessary port in order to respond to incoming tls-sni-01 and http-01 + necessary port in order to respond to incoming http-01 challenges from the certificate authority. Therefore, it does not rely on any existing server program. """ @@ -187,10 +170,6 @@ class Authenticator(common.Plugin): def __init__(self, *args, **kwargs): super(Authenticator, self).__init__(*args, **kwargs) - # one self-signed key for all tls-sni-01 certificates - self.key = OpenSSL.crypto.PKey() - self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - self.served = collections.defaultdict(set) # type: ServedType # Stuff below is shared across threads (i.e. servers read @@ -219,9 +198,8 @@ class Authenticator(common.Plugin): def more_info(self): # pylint: disable=missing-docstring return("This authenticator creates its own ephemeral TCP listener " "on the necessary port in order to respond to incoming " - "tls-sni-01 and http-01 challenges from the certificate " - "authority. Therefore, it does not rely on any existing " - "server program.") + "http-01 challenges from the certificate authority. Therefore, " + "it does not rely on any existing server program.") def prepare(self): # pylint: disable=missing-docstring pass @@ -241,10 +219,7 @@ class Authenticator(common.Plugin): _handle_perform_error(error) def _perform_single(self, achall): - if isinstance(achall.chall, challenges.HTTP01): - servers, response = self._perform_http_01(achall) - else: # tls-sni-01 - servers, response = self._perform_tls_sni_01(achall) + servers, response = self._perform_http_01(achall) self.served[servers].add(achall) return response @@ -258,14 +233,6 @@ class Authenticator(common.Plugin): self.http_01_resources.add(resource) return servers, response - def _perform_tls_sni_01(self, achall): - port = self.config.tls_sni_01_port - addr = self.config.tls_sni_01_address - servers = self.servers.run(port, challenges.TLSSNI01, listenaddr=addr) - response, (cert, _) = achall.response_and_validation(cert_key=self.key) - self.certs[response.z_domain] = (self.key, cert) - return servers, response - def cleanup(self, achalls): # pylint: disable=missing-docstring # reduce self.served and close servers if no challenges are served for unused_servers, server_achalls in self.served.items(): diff --git a/certbot/plugins/standalone_test.py b/certbot/plugins/standalone_test.py index 9b741fc6f..db4dbeb3b 100644 --- a/certbot/plugins/standalone_test.py +++ b/certbot/plugins/standalone_test.py @@ -44,9 +44,6 @@ class ServerManagerTest(unittest.TestCase): self.mgr.stop(port=port) self.assertEqual(self.mgr.running(), {}) - def test_run_stop_tls_sni_01(self): - self._test_run_stop(challenges.TLSSNI01) - def test_run_stop_http_01(self): self._test_run_stop(challenges.HTTP01) @@ -98,10 +95,7 @@ class SupportedChallengesActionTest(unittest.TestCase): self.parser.add_argument(self.flag, action=SupportedChallengesAction) def test_correct(self): - self.assertEqual("tls-sni-01", self._call("tls-sni-01")) self.assertEqual("http-01", self._call("http-01")) - self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01")) - self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01")) def test_unrecognized(self): assert "foo" not in challenges.Challenge.TYPES @@ -110,11 +104,6 @@ class SupportedChallengesActionTest(unittest.TestCase): def test_not_subset(self): self.assertRaises(SystemExit, self._call, "dns") - def test_dvsni(self): - self.assertEqual("tls-sni-01", self._call("dvsni")) - self.assertEqual("http-01,tls-sni-01", self._call("http-01,dvsni")) - self.assertEqual("tls-sni-01,http-01", self._call("dvsni,http-01")) - def get_open_port(): """Gets an open port number from the OS.""" @@ -132,31 +121,31 @@ class AuthenticatorTest(unittest.TestCase): from certbot.plugins.standalone import Authenticator self.config = mock.MagicMock( - tls_sni_01_port=get_open_port(), http01_port=get_open_port(), - standalone_supported_challenges="tls-sni-01,http-01") + http01_port=get_open_port(), + standalone_supported_challenges="http-01") self.auth = Authenticator(self.config, name="standalone") self.auth.servers = mock.MagicMock() def test_supported_challenges(self): self.assertEqual(self.auth.supported_challenges, - [challenges.TLSSNI01, challenges.HTTP01]) + [challenges.HTTP01]) def test_supported_challenges_configured(self): - self.config.standalone_supported_challenges = "tls-sni-01" + self.config.standalone_supported_challenges = "http-01" self.assertEqual(self.auth.supported_challenges, - [challenges.TLSSNI01]) + [challenges.HTTP01]) def test_more_info(self): self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) def test_get_chall_pref(self): self.assertEqual(self.auth.get_chall_pref(domain=None), - [challenges.TLSSNI01, challenges.HTTP01]) + [challenges.HTTP01]) def test_get_chall_pref_configured(self): - self.config.standalone_supported_challenges = "tls-sni-01" + self.config.standalone_supported_challenges = "http-01" self.assertEqual(self.auth.get_chall_pref(domain=None), - [challenges.TLSSNI01]) + [challenges.HTTP01]) def test_perform(self): achalls = self._get_achalls() @@ -212,10 +201,8 @@ class AuthenticatorTest(unittest.TestCase): key = jose.JWK.load(test_util.load_vector('rsa512_key.pem')) http_01 = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.HTTP01_P, domain=domain, account_key=key) - tls_sni_01 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.TLSSNI01_P, domain=domain, account_key=key) - return [http_01, tls_sni_01] + return [http_01] def test_cleanup(self): self.auth.servers.running.return_value = { @@ -243,5 +230,6 @@ class AuthenticatorTest(unittest.TestCase): "server1": set(), "server2": set([])}) self.auth.servers.stop.assert_called_with(2) + if __name__ == "__main__": unittest.main() # pragma: no cover -- cgit v1.2.3 From c2f2aa5ee076594ad8abc4570df4cbacf7f948b6 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 15 Mar 2019 01:07:49 +0100 Subject: Remove tls-sni in compatibility tests (#6854) * Reconfigure compatibility tests to use http challenge * Correct simple test * Add a fake DNS resolution for HTTP simple_verify * Debug * More subtle approach: we monkey patch urllib3 to fake a dns resolution to the target IP, allowing every host header to be preserved. * Private package * Relaxed permissions on certbot temp working dir * Move the fake DNS logic in compatibility test, to avoid degrading the acme coverage * Fix lint * Update certbot-compatibility-test/certbot_compatibility_test/configurators/common.py Co-Authored-By: adferrand --- .../configurators/common.py | 3 ++ .../certbot_compatibility_test/test_driver.py | 41 ++++++++++++++++------ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index 2a800c1c2..58ac58a15 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -23,6 +23,9 @@ class Proxy(object): def __init__(self, args): """Initializes the plugin with the given command line args""" self._temp_dir = tempfile.mkdtemp() + # tempfile.mkdtemp() creates folders with too restrictive permissions to be accessible + # to an Apache worker, leading to HTTP challenge failures. Let's fix that. + os.chmod(self._temp_dir, 0o755) self.le_config = util.create_le_config(self._temp_dir) config_dir = util.extract_configs(args.configs, self._temp_dir) self._configs = [ diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 9eea95e67..f94aee3c1 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -1,5 +1,6 @@ """Tests Certbot plugins against different server configurations.""" import argparse +import contextlib import filecmp import logging import os @@ -7,6 +8,7 @@ import shutil import tempfile import time import sys +from urllib3.util import connection import OpenSSL @@ -64,18 +66,19 @@ def test_authenticator(plugin, config, temp_dir): "Plugin failed to complete %s for %s in %s", type(achalls[i]), achalls[i].domain, config) success = False - elif isinstance(responses[i], challenges.TLSSNI01Response): - verified = responses[i].simple_verify(achalls[i].chall, - achalls[i].domain, - util.JWK.public_key(), - host="127.0.0.1", - port=plugin.https_port) + elif isinstance(responses[i], challenges.HTTP01Response): + # We fake the DNS resolution to ensure that any domain is resolved + # to the local HTTP server setup for the compatibility tests + with _fake_dns_resolution("127.0.0.1"): + verified = responses[i].simple_verify( + achalls[i].chall, achalls[i].domain, + util.JWK.public_key(), port=plugin.http_port) if verified: logger.info( - "tls-sni-01 verification for %s succeeded", achalls[i].domain) + "http-01 verification for %s succeeded", achalls[i].domain) else: logger.error( - "**** tls-sni-01 verification for %s in %s failed", + "**** http-01 verification for %s in %s failed", achalls[i].domain, config) success = False @@ -102,9 +105,9 @@ def _create_achalls(plugin): for domain in names: prefs = plugin.get_chall_pref(domain) for chall_type in prefs: - if chall_type == challenges.TLSSNI01: - chall = challenges.TLSSNI01( - token=os.urandom(challenges.TLSSNI01.TOKEN_SIZE)) + if chall_type == challenges.HTTP01: + chall = challenges.HTTP01( + token=os.urandom(challenges.HTTP01.TOKEN_SIZE)) challb = acme_util.chall_to_challb( chall, messages.STATUS_PENDING) achall = achallenges.KeyAuthorizationAnnotatedChallenge( @@ -369,5 +372,21 @@ def main(): sys.exit(1) +@contextlib.contextmanager +def _fake_dns_resolution(resolved_ip): + """Monkey patch urllib3 to make any hostname be resolved to the provided IP""" + _original_create_connection = connection.create_connection + + def _patched_create_connection(address, *args, **kwargs): + _, port = address + return _original_create_connection((resolved_ip, port), *args, **kwargs) + + try: + connection.create_connection = _patched_create_connection + yield + finally: + connection.create_connection = _original_create_connection + + if __name__ == "__main__": main() -- cgit v1.2.3 From e909b0852c124a3121b6c5d67fa604b1a4b998cf Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 15 Mar 2019 01:56:56 +0100 Subject: Remove tls-sni challenge in manual plugin (#6855) * Remove tls-sni challenge in manual * Remove unused logic --- certbot/plugins/manual.py | 76 ++++-------------------------------------- certbot/plugins/manual_test.py | 51 ++++------------------------ 2 files changed, 13 insertions(+), 114 deletions(-) diff --git a/certbot/plugins/manual.py b/certbot/plugins/manual.py index 123a5bfea..b4d20478a 100644 --- a/certbot/plugins/manual.py +++ b/certbot/plugins/manual.py @@ -15,34 +15,6 @@ from certbot import reverter from certbot.plugins import common -class ManualTlsSni01(common.TLSSNI01): - """TLS-SNI-01 authenticator for the Manual plugin - - :ivar configurator: Authenticator object - :type configurator: :class:`~certbot.plugins.manual.Authenticator` - - :ivar list achalls: Annotated - class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge` - challenges - - :param list indices: Meant to hold indices of challenges in a - larger array. NginxTlsSni01 is capable of solving many challenges - at once which causes an indexing issue within NginxConfigurator - who must return all responses in order. Imagine NginxConfigurator - maintaining state about where all of the http-01 Challenges, - TLS-SNI-01 Challenges belong in the response array. This is an - optional utility. - - :param str challenge_conf: location of the challenge config file - """ - - def perform(self): - """Create the SSL certificates and private keys""" - - for achall in self.achalls: - self._setup_challenge_cert(achall) - - @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(common.Plugin): @@ -63,14 +35,9 @@ class Authenticator(common.Plugin): 'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' - 'resource requested when performing an HTTP-01 challenge. When ' - 'performing a TLS-SNI-01 challenge, $CERTBOT_SNI_DOMAIN will contain ' - 'the SNI name for which the ACME server expects to be presented with ' - 'the self-signed certificate located at $CERTBOT_CERT_PATH. The ' - 'secret key needed to complete the TLS handshake is located at ' - '$CERTBOT_KEY_PATH. An additional cleanup script can also be ' - 'provided and can use the additional variable $CERTBOT_AUTH_OUTPUT ' - 'which contains the stdout output from the auth script.') + 'resource requested when performing an HTTP-01 challenge. An additional ' + 'cleanup script can also be provided and can use the additional variable ' + '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') _DNS_INSTRUCTIONS = """\ Please deploy a DNS TXT record under the name {domain} with the following value: @@ -86,14 +53,6 @@ Create a file containing just this data: And make it available on your web server at this URL: {uri} -""" - _TLSSNI_INSTRUCTIONS = """\ -Configure the service listening on port {port} to present the certificate -{cert} -using the secret key -{key} -when it receives a TLS ClientHello with the SNI extension set to -{sni_domain} """ _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ (This must be set up in addition to the previous challenges; do not remove, @@ -112,7 +71,6 @@ permitted by DNS standards.) self.reverter.recovery_routine() self.env = dict() \ # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] - self.tls_sni_01 = None self.subsequent_dns_challenge = False self.subsequent_any_challenge = False @@ -149,7 +107,7 @@ permitted by DNS standards.) def get_chall_pref(self, domain): # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.HTTP01, challenges.DNS01, challenges.TLSSNI01] + return [challenges.HTTP01, challenges.DNS01] def perform(self, achalls): # pylint: disable=missing-docstring self._verify_ip_logging_ok() @@ -160,12 +118,6 @@ permitted by DNS standards.) responses = [] for achall in achalls: - if isinstance(achall.chall, challenges.TLSSNI01): - # Make a new ManualTlsSni01 instance for each challenge - # because the manual plugin deals with one challenge at a time. - self.tls_sni_01 = ManualTlsSni01(self) - self.tls_sni_01.add_chall(achall) - self.tls_sni_01.perform() perform_achall(achall) responses.append(achall.response(achall.account_key)) return responses @@ -191,16 +143,6 @@ permitted by DNS standards.) env['CERTBOT_TOKEN'] = achall.chall.encode('token') else: os.environ.pop('CERTBOT_TOKEN', None) - if isinstance(achall.chall, challenges.TLSSNI01): - env['CERTBOT_CERT_PATH'] = self.tls_sni_01.get_cert_path(achall) - env['CERTBOT_KEY_PATH'] = self.tls_sni_01.get_key_path(achall) - env['CERTBOT_SNI_DOMAIN'] = self.tls_sni_01.get_z_domain(achall) - os.environ.pop('CERTBOT_VALIDATION', None) - env.pop('CERTBOT_VALIDATION') - else: - os.environ.pop('CERTBOT_CERT_PATH', None) - os.environ.pop('CERTBOT_KEY_PATH', None) - os.environ.pop('CERTBOT_SNI_DOMAIN', None) os.environ.update(env) _, out = self._execute_hook('auth-hook') env['CERTBOT_AUTH_OUTPUT'] = out.strip() @@ -213,17 +155,11 @@ permitted by DNS standards.) achall=achall, encoded_token=achall.chall.encode('token'), port=self.config.http01_port, uri=achall.chall.uri(achall.domain), validation=validation) - elif isinstance(achall.chall, challenges.DNS01): + else: + assert isinstance(achall.chall, challenges.DNS01) msg = self._DNS_INSTRUCTIONS.format( domain=achall.validation_domain_name(achall.domain), validation=validation) - else: - assert isinstance(achall.chall, challenges.TLSSNI01) - msg = self._TLSSNI_INSTRUCTIONS.format( - cert=self.tls_sni_01.get_cert_path(achall), - key=self.tls_sni_01.get_key_path(achall), - port=self.config.tls_sni_01_port, - sni_domain=self.tls_sni_01.get_z_domain(achall)) if isinstance(achall.chall, challenges.DNS01): if self.subsequent_dns_challenge: # 2nd or later dns-01 challenge diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index 0938e8a7d..b566f6340 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -22,8 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.http_achall = acme_util.HTTP01_A self.dns_achall = acme_util.DNS01_A self.dns_achall_2 = acme_util.DNS01_A_2 - self.tls_sni_achall = acme_util.TLSSNI01_A - self.achalls = [self.http_achall, self.dns_achall, self.tls_sni_achall, self.dns_achall_2] + self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2] for d in ["config_dir", "work_dir", "in_progress"]: os.mkdir(os.path.join(self.tempdir, d)) # "backup_dir" and "temp_checkpoint_dir" get created in @@ -38,8 +37,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): backup_dir=os.path.join(self.tempdir, "backup_dir"), temp_checkpoint_dir=os.path.join( self.tempdir, "temp_checkpoint_dir"), - in_progress_dir=os.path.join(self.tempdir, "in_progess"), - tls_sni_01_port=5001) + in_progress_dir=os.path.join(self.tempdir, "in_progess")) from certbot.plugins.manual import Authenticator self.auth = Authenticator(self.config, name='manual') @@ -58,9 +56,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): def test_get_chall_pref(self): self.assertEqual(self.auth.get_chall_pref('example.org'), - [challenges.HTTP01, - challenges.DNS01, - challenges.TLSSNI01]) + [challenges.HTTP01, challenges.DNS01]) @test_util.patch_get_utility() def test_ip_logging_not_ok(self, mock_get_utility): @@ -79,18 +75,13 @@ class AuthenticatorTest(test_util.TempDirTestCase): '{0} -c "from __future__ import print_function;' 'import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' - 'print(os.environ.get(\'CERTBOT_CERT_PATH\', \'nocert\'));' - 'print(os.environ.get(\'CERTBOT_KEY_PATH\', \'nokey\'));' - 'print(os.environ.get(\'CERTBOT_SNI_DOMAIN\', \'nosnidomain\'));' 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' .format(sys.executable)) - dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( + dns_expected = '{0}\n{1}\n{2}'.format( self.dns_achall.domain, 'notoken', - 'nocert', 'nokey', 'nosnidomain', self.dns_achall.validation(self.dns_achall.account_key)) - http_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( + http_expected = '{0}\n{1}\n{2}'.format( self.http_achall.domain, self.http_achall.chall.encode('token'), - 'nocert', 'nokey', 'nosnidomain', self.http_achall.validation(self.http_achall.account_key)) self.assertEqual( @@ -102,17 +93,6 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.assertEqual( self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'], http_expected) - # tls_sni_01 challenge must be perform()ed above before we can - # get the cert_path and key_path. - tls_sni_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}'.format( - self.tls_sni_achall.domain, 'notoken', - self.auth.tls_sni_01.get_cert_path(self.tls_sni_achall), - self.auth.tls_sni_01.get_key_path(self.tls_sni_achall), - self.auth.tls_sni_01.get_z_domain(self.tls_sni_achall), - 'novalidation') - self.assertEqual( - self.auth.env[self.tls_sni_achall]['CERTBOT_AUTH_OUTPUT'], - tls_sni_expected) @test_util.patch_get_utility() def test_manual_perform(self, mock_get_utility): @@ -122,13 +102,8 @@ class AuthenticatorTest(test_util.TempDirTestCase): [achall.response(achall.account_key) for achall in self.achalls]) for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list): achall = self.achalls[i] - if isinstance(achall.chall, challenges.TLSSNI01): - self.assertTrue( - self.auth.tls_sni_01.get_cert_path( - self.tls_sni_achall) in args[0]) - else: - self.assertTrue( - achall.validation(achall.account_key) in args[0]) + self.assertTrue( + achall.validation(achall.account_key) in args[0]) self.assertFalse(kwargs['wrap']) @test_util.broken_on_windows @@ -153,18 +128,6 @@ class AuthenticatorTest(test_util.TempDirTestCase): achall.chall.encode('token')) else: self.assertFalse('CERTBOT_TOKEN' in os.environ) - if isinstance(achall.chall, challenges.TLSSNI01): - self.assertEqual( - os.environ['CERTBOT_CERT_PATH'], - self.auth.tls_sni_01.get_cert_path(achall)) - self.assertEqual( - os.environ['CERTBOT_KEY_PATH'], - self.auth.tls_sni_01.get_key_path(achall)) - self.assertFalse( - os.path.exists(os.environ['CERTBOT_CERT_PATH'])) - self.assertFalse( - os.path.exists(os.environ['CERTBOT_KEY_PATH'])) - if __name__ == '__main__': -- cgit v1.2.3 From b447b0a8e9d593e085187f30de927a201bac1006 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 16 Mar 2019 00:39:43 +0100 Subject: Remove tls sni in apache plugin (#6858) * Add a dedicated configuration to define what is the HTTPS port for this certbot instance. * Remove tls-sni in apache plugin * Update constants.py * Update interfaces.py * Remove option * Simplify a test --- certbot-apache/certbot_apache/configurator.py | 23 +-- .../certbot_apache/tests/configurator_test.py | 21 +-- .../certbot_apache/tests/tls_sni_01_test.py | 151 ------------------ certbot-apache/certbot_apache/tls_sni_01.py | 174 --------------------- 4 files changed, 8 insertions(+), 361 deletions(-) delete mode 100644 certbot-apache/certbot_apache/tests/tls_sni_01_test.py delete mode 100644 certbot-apache/certbot_apache/tls_sni_01.py diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index f7f4ff925..a3061119b 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -32,7 +32,6 @@ from certbot_apache import display_ops from certbot_apache import http_01 from certbot_apache import obj from certbot_apache import parser -from certbot_apache import tls_sni_01 from collections import defaultdict @@ -215,15 +214,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): @property def mod_ssl_conf(self): """Full absolute path to SSL configuration file.""" - return os.path.join(self.config.config_dir, - constants.MOD_SSL_CONF_DEST) + return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) @property def updated_mod_ssl_conf_digest(self): """Full absolute path to digest of updated SSL configuration file.""" return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) - def prepare(self): """Prepare the authenticator/installer. @@ -396,7 +393,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if len(name.split(".")) == len(domain.split(".")): return fnmatch.fnmatch(name, domain) - def _choose_vhosts_wildcard(self, domain, create_ssl=True): """Prompts user to choose vhosts to install a wildcard certificate for""" @@ -441,7 +437,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self._wildcard_vhosts[domain] = return_vhosts return return_vhosts - def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): """ Helper function for deploy_cert() that handles the actual deployment @@ -449,8 +444,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): domain originally passed for deploy_cert(). This is especially true with wildcard certificates """ - - # This is done first so that ssl module is enabled and cert_path, # cert_key... can all be parsed appropriately self.prepare_server_https("443") @@ -1925,7 +1918,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.parser.add_dir(vhost.path, "RewriteRule", constants.REWRITE_HTTPS_ARGS) - def _verify_no_certbot_redirect(self, vhost): """Checks to see if a redirect was already installed by certbot. @@ -2193,7 +2185,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :raises .errors.MisconfigurationError: If reload fails """ - error = "" try: util.run_script(self.option("restart_cmd")) except errors.SubprocessError as err: @@ -2267,7 +2258,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): ########################################################################### def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" - return [challenges.HTTP01, challenges.TLSSNI01] + return [challenges.HTTP01] def perform(self, achalls): """Perform the configuration related challenge. @@ -2280,20 +2271,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self._chall_out.update(achalls) responses = [None] * len(achalls) http_doer = http_01.ApacheHttp01(self) - sni_doer = tls_sni_01.ApacheTlsSni01(self) for i, achall in enumerate(achalls): # Currently also have chall_doer hold associated index of the # challenge. This helps to put all of the responses back together # when they are all complete. - if isinstance(achall.chall, challenges.HTTP01): - http_doer.add_chall(achall, i) - else: # tls-sni-01 - sni_doer.add_chall(achall, i) + http_doer.add_chall(achall, i) http_response = http_doer.perform() - sni_response = sni_doer.perform() - if http_response or sni_response: + if http_response: # Must reload in order to activate the challenges. # Handled here because we may be able to load up other challenge # types @@ -2304,7 +2290,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): time.sleep(3) self._update_responses(responses, http_response, http_doer) - self._update_responses(responses, sni_response, sni_doer) return responses diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 7c281d707..ee4833ebb 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -812,32 +812,19 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.config.add_name_vhost.call_count, 2) @mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform") - @mock.patch("certbot_apache.configurator.tls_sni_01.ApacheTlsSni01.perform") @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - def test_perform(self, mock_restart, mock_tls_perform, mock_http_perform): + def test_perform(self, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded account_key, achalls = self.get_key_and_achalls() - all_expected = [] - http_expected = [] - tls_expected = [] - for achall in achalls: - response = achall.response(account_key) - if isinstance(achall.chall, challenges.HTTP01): - http_expected.append(response) - else: - tls_expected.append(response) - all_expected.append(response) - - mock_http_perform.return_value = http_expected - mock_tls_perform.return_value = tls_expected + expected = [achall.response(account_key) for achall in achalls] + mock_http_perform.return_value = expected responses = self.config.perform(achalls) self.assertEqual(mock_http_perform.call_count, 1) - self.assertEqual(mock_tls_perform.call_count, 1) - self.assertEqual(responses, all_expected) + self.assertEqual(responses, expected) self.assertEqual(mock_restart.call_count, 1) diff --git a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py b/certbot-apache/certbot_apache/tests/tls_sni_01_test.py deleted file mode 100644 index 8cea97f04..000000000 --- a/certbot-apache/certbot_apache/tests/tls_sni_01_test.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Test for certbot_apache.tls_sni_01.""" -import shutil -import unittest - -import mock - -from certbot import errors -from certbot.plugins import common_test - -from certbot_apache import obj -from certbot_apache.tests import util - -from six.moves import xrange # pylint: disable=redefined-builtin, import-error - - -class TlsSniPerformTest(util.ApacheTest): - """Test the ApacheTlsSni01 challenge.""" - - auth_key = common_test.AUTH_KEY - achalls = common_test.ACHALLS - - def setUp(self): # pylint: disable=arguments-differ - super(TlsSniPerformTest, self).setUp() - - config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, - self.work_dir) - config.config.tls_sni_01_port = 443 - - from certbot_apache import tls_sni_01 - self.sni = tls_sni_01.ApacheTlsSni01(config) - - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - - def test_perform0(self): - resp = self.sni.perform() - self.assertEqual(len(resp), 0) - - @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot.util.run_script") - def test_perform1(self, _, mock_exists): - self.sni.configurator.parser.modules.add("socache_shmcb_module") - self.sni.configurator.parser.modules.add("ssl_module") - - mock_exists.return_value = True - self.sni.configurator.parser.update_runtime_variables = mock.Mock() - - achall = self.achalls[0] - self.sni.add_chall(achall) - response = self.achalls[0].response(self.auth_key) - mock_setup_cert = mock.MagicMock(return_value=response) - # pylint: disable=protected-access - self.sni._setup_challenge_cert = mock_setup_cert - - responses = self.sni.perform() - mock_setup_cert.assert_called_once_with(achall) - - # Check to make sure challenge config path is included in apache config - self.assertEqual( - len(self.sni.configurator.parser.find_dir( - "Include", self.sni.challenge_conf)), 1) - self.assertEqual(len(responses), 1) - self.assertEqual(responses[0], response) - - def test_perform2(self): - # Avoid load module - self.sni.configurator.parser.modules.add("ssl_module") - self.sni.configurator.parser.modules.add("socache_shmcb_module") - acme_responses = [] - for achall in self.achalls: - self.sni.add_chall(achall) - acme_responses.append(achall.response(self.auth_key)) - - mock_setup_cert = mock.MagicMock(side_effect=acme_responses) - # pylint: disable=protected-access - self.sni._setup_challenge_cert = mock_setup_cert - - with mock.patch( - "certbot_apache.override_debian.DebianConfigurator.enable_mod"): - sni_responses = self.sni.perform() - - self.assertEqual(mock_setup_cert.call_count, 2) - - # Make sure calls made to mocked function were correct - self.assertEqual( - mock_setup_cert.call_args_list[0], mock.call(self.achalls[0])) - self.assertEqual( - mock_setup_cert.call_args_list[1], mock.call(self.achalls[1])) - - self.assertEqual( - len(self.sni.configurator.parser.find_dir( - "Include", self.sni.challenge_conf)), - 1) - self.assertEqual(len(sni_responses), 2) - for i in xrange(2): - self.assertEqual(sni_responses[i], acme_responses[i]) - - def test_mod_config(self): - z_domains = [] - for achall in self.achalls: - self.sni.add_chall(achall) - z_domain = achall.response(self.auth_key).z_domain - z_domains.append(set([z_domain.decode('ascii')])) - - self.sni._mod_config() # pylint: disable=protected-access - self.sni.configurator.save() - - self.sni.configurator.parser.find_dir( - "Include", self.sni.challenge_conf) - vh_match = self.sni.configurator.aug.match( - "/files" + self.sni.challenge_conf + "//VirtualHost") - - vhs = [] - for match in vh_match: - # pylint: disable=protected-access - vhs.append(self.sni.configurator._create_vhost(match)) - self.assertEqual(len(vhs), 2) - for vhost in vhs: - self.assertEqual(vhost.addrs, set([obj.Addr.fromstring("*:443")])) - names = vhost.get_names() - self.assertTrue(names in z_domains) - - def test_get_addrs_default(self): - self.sni.configurator.choose_vhost = mock.Mock( - return_value=obj.VirtualHost( - "path", "aug_path", - set([obj.Addr.fromstring("_default_:443")]), - False, False) - ) - - # pylint: disable=protected-access - self.assertEqual( - set([obj.Addr.fromstring("*:443")]), - self.sni._get_addrs(self.achalls[0])) - - def test_get_addrs_no_vhost_found(self): - self.sni.configurator.choose_vhost = mock.Mock( - side_effect=errors.MissingCommandlineFlag( - "Failed to run Apache plugin non-interactively")) - - # pylint: disable=protected-access - self.assertEqual( - set([obj.Addr.fromstring("*:443")]), - self.sni._get_addrs(self.achalls[0])) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tls_sni_01.py b/certbot-apache/certbot_apache/tls_sni_01.py deleted file mode 100644 index 65230cdcb..000000000 --- a/certbot-apache/certbot_apache/tls_sni_01.py +++ /dev/null @@ -1,174 +0,0 @@ -"""A class that performs TLS-SNI-01 challenges for Apache""" - -import os -import logging - -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from certbot.plugins import common -from certbot.errors import PluginError, MissingCommandlineFlag - -from certbot_apache import obj - -logger = logging.getLogger(__name__) - - -class ApacheTlsSni01(common.TLSSNI01): - """Class that performs TLS-SNI-01 challenges within the Apache configurator - - :ivar configurator: ApacheConfigurator object - :type configurator: :class:`~apache.configurator.ApacheConfigurator` - - :ivar list achalls: Annotated TLS-SNI-01 - (`.KeyAuthorizationAnnotatedChallenge`) challenges. - - :param list indices: Meant to hold indices of challenges in a - larger array. ApacheTlsSni01 is capable of solving many challenges - at once which causes an indexing issue within ApacheConfigurator - who must return all responses in order. Imagine ApacheConfigurator - maintaining state about where all of the http-01 Challenges, - TLS-SNI-01 Challenges belong in the response array. This is an - optional utility. - - :param str challenge_conf: location of the challenge config file - - """ - - VHOST_TEMPLATE = """\ - - ServerName {server_name} - UseCanonicalName on - SSLStrictSNIVHostCheck on - - LimitRequestBody 1048576 - - Include {ssl_options_conf_path} - SSLCertificateFile {cert_path} - SSLCertificateKeyFile {key_path} - - DocumentRoot {document_root} - - -""" - - def __init__(self, *args, **kwargs): - super(ApacheTlsSni01, self).__init__(*args, **kwargs) - - self.challenge_conf = os.path.join( - self.configurator.conf("challenge-location"), - "le_tls_sni_01_cert_challenge.conf") - - def perform(self): - """Perform a TLS-SNI-01 challenge.""" - if not self.achalls: - return [] - # Save any changes to the configuration as a precaution - # About to make temporary changes to the config - self.configurator.save("Changes before challenge setup", True) - - # Prepare the server for HTTPS - self.configurator.prepare_server_https( - str(self.configurator.config.tls_sni_01_port), True) - - responses = [] - - # Create all of the challenge certs - for achall in self.achalls: - responses.append(self._setup_challenge_cert(achall)) - - # Setup the configuration - addrs = self._mod_config() - self.configurator.save("Don't lose mod_config changes", True) - self.configurator.make_addrs_sni_ready(addrs) - - # Save reversible changes - self.configurator.save("SNI Challenge", True) - - return responses - - def _mod_config(self): - """Modifies Apache config files to include challenge vhosts. - - Result: Apache config includes virtual servers for issued challs - - :returns: All TLS-SNI-01 addresses used - :rtype: set - - """ - addrs = set() # type: Set[obj.Addr] - config_text = "\n" - - for achall in self.achalls: - achall_addrs = self._get_addrs(achall) - addrs.update(achall_addrs) - - config_text += self._get_config_text(achall, achall_addrs) - - config_text += "\n" - - self.configurator.parser.add_include( - self.configurator.parser.loc["default"], self.challenge_conf) - self.configurator.reverter.register_file_creation( - True, self.challenge_conf) - - logger.debug("writing a config file with text:\n %s", config_text) - with open(self.challenge_conf, "w") as new_conf: - new_conf.write(config_text) - - return addrs - - def _get_addrs(self, achall): - """Return the Apache addresses needed for TLS-SNI-01.""" - # TODO: Checkout _default_ rules. - addrs = set() - default_addr = obj.Addr(("*", str( - self.configurator.config.tls_sni_01_port))) - - try: - vhost = self.configurator.choose_vhost(achall.domain, - create_if_no_ssl=False) - except (PluginError, MissingCommandlineFlag): - # We couldn't find the virtualhost for this domain, possibly - # because it's a new vhost that's not configured yet - # (GH #677). See also GH #2600. - logger.warning("Falling back to default vhost %s...", default_addr) - addrs.add(default_addr) - return addrs - - for addr in vhost.addrs: - if "_default_" == addr.get_addr(): - addrs.add(default_addr) - else: - addrs.add( - addr.get_sni_addr( - self.configurator.config.tls_sni_01_port)) - - return addrs - - def _get_config_text(self, achall, ip_addrs): - """Chocolate virtual server configuration text - - :param .KeyAuthorizationAnnotatedChallenge achall: Annotated - TLS-SNI-01 challenge. - - :param list ip_addrs: addresses of challenged domain - :class:`list` of type `~.obj.Addr` - - :returns: virtual host configuration text - :rtype: str - - """ - ips = " ".join(str(i) for i in ip_addrs) - document_root = os.path.join( - self.configurator.config.work_dir, "tls_sni_01_page/") - # TODO: Python docs is not clear how multiline string literal - # newlines are parsed on different platforms. At least on - # Linux (Debian sid), when source file uses CRLF, Python still - # parses it as "\n"... c.f.: - # https://docs.python.org/2.7/reference/lexical_analysis.html - return self.VHOST_TEMPLATE.format( - vhost=ips, - server_name=achall.response(achall.account_key).z_domain.decode('ascii'), - ssl_options_conf_path=self.configurator.mod_ssl_conf, - cert_path=self.get_cert_path(achall), - key_path=self.get_key_path(achall), - document_root=document_root).replace("\n", os.linesep) -- cgit v1.2.3 From d9880721b31ab4e9e9d2fdc38d83ff2bd9078378 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 18 Mar 2019 18:22:19 +0100 Subject: Remove tls sni in nginx plugin (#6857) * Remove tls-sni from nginx config * Add a dedicated configuration to define what is the HTTPS port for this certbot instance. * Correct some tests * Reestablish default vhost creation * Clean tls references for nginx integration tests * Associate https_port only to tests and nginx --- certbot-nginx/certbot_nginx/configurator.py | 26 ++- .../certbot_nginx/tests/configurator_test.py | 23 +-- .../certbot_nginx/tests/tls_sni_01_test.py | 158 ------------------ certbot-nginx/certbot_nginx/tests/util.py | 2 +- certbot-nginx/certbot_nginx/tls_sni_01.py | 177 --------------------- certbot-nginx/tests/boulder-integration.conf.sh | 2 +- tests/certbot-boulder-integration.sh | 2 +- tests/integration/_common.sh | 6 +- 8 files changed, 21 insertions(+), 375 deletions(-) delete mode 100644 certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py delete mode 100644 certbot-nginx/certbot_nginx/tls_sni_01.py diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index ffe1ddac7..2357735f9 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -26,7 +26,6 @@ from certbot_nginx import constants from certbot_nginx import display_ops from certbot_nginx import nginxparser from certbot_nginx import parser -from certbot_nginx import tls_sni_01 from certbot_nginx import http_01 from certbot_nginx import obj # pylint: disable=unused-import from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module @@ -149,7 +148,6 @@ class NginxConfigurator(common.Installer): # Make sure configuration is valid self.config_test() - self.parser = parser.NginxParser(self.conf('server-root')) install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) @@ -561,7 +559,7 @@ class NginxConfigurator(common.Installer): :rtype: set """ - all_names = set() # type: Set[str] + all_names = set() # type: Set[str] for vhost in self.parser.get_vhosts(): all_names.update(vhost.names) @@ -611,7 +609,8 @@ class NginxConfigurator(common.Installer): :type vhost: :class:`~certbot_nginx.obj.VirtualHost` """ - ipv6info = self.ipv6_info(self.config.tls_sni_01_port) + https_port = self.config.tls_sni_01_port + ipv6info = self.ipv6_info(https_port) ipv6_block = [''] ipv4_block = [''] @@ -625,7 +624,7 @@ class NginxConfigurator(common.Installer): ipv6_block = ['\n ', 'listen', ' ', - '[::]:{0}'.format(self.config.tls_sni_01_port), + '[::]:{0}'.format(https_port), ' ', 'ssl'] if not ipv6info[1]: @@ -637,7 +636,7 @@ class NginxConfigurator(common.Installer): ipv4_block = ['\n ', 'listen', ' ', - '{0}'.format(self.config.tls_sni_01_port), + '{0}'.format(https_port), ' ', 'ssl'] @@ -799,8 +798,6 @@ class NginxConfigurator(common.Installer): :param str domain: domain to enable redirect for :param `~obj.Vhost` vhost: vhost to enable redirect for """ - - http_vhost = None if vhost.ssl: http_vhost, _ = self._split_block(vhost, ['listen', 'server_name']) @@ -1051,19 +1048,14 @@ class NginxConfigurator(common.Installer): """ self._chall_out += len(achalls) responses = [None] * len(achalls) - sni_doer = tls_sni_01.NginxTlsSni01(self) http_doer = http_01.NginxHttp01(self) for i, achall in enumerate(achalls): # Currently also have chall_doer hold associated index of the # challenge. This helps to put all of the responses back together # when they are all complete. - if isinstance(achall.chall, challenges.HTTP01): - http_doer.add_chall(achall, i) - else: # tls-sni-01 - sni_doer.add_chall(achall, i) + http_doer.add_chall(achall, i) - sni_response = sni_doer.perform() http_response = http_doer.perform() # Must restart in order to activate the challenges. # Handled here because we may be able to load up other challenge types @@ -1072,9 +1064,8 @@ class NginxConfigurator(common.Installer): # Go through all of the challenges and assign them to the proper place # in the responses return value. All responses must be in the same order # as the original challenges. - for chall_response, chall_doer in ((sni_response, sni_doer), (http_response, http_doer)): - for i, resp in enumerate(chall_response): - responses[chall_doer.indices[i]] = resp + for i, resp in enumerate(http_response): + responses[http_doer.indices[i]] = resp return responses @@ -1152,6 +1143,7 @@ def install_ssl_options_conf(options_ssl, options_ssl_digest): return common.install_version_controlled_file(options_ssl, options_ssl_digest, constants.MOD_SSL_CONF_SRC, constants.ALL_SSL_OPTIONS_HASHES) + def _determine_default_server_root(): if os.environ.get("CERTBOT_DOCS") == "1": default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT, diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 706e2637a..08e4a56ae 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -318,21 +318,13 @@ class NginxConfiguratorTest(util.NginxTest): ]], parsed_migration_conf[0]) - @mock.patch("certbot_nginx.configurator.tls_sni_01.NginxTlsSni01.perform") @mock.patch("certbot_nginx.configurator.http_01.NginxHttp01.perform") @mock.patch("certbot_nginx.configurator.NginxConfigurator.restart") @mock.patch("certbot_nginx.configurator.NginxConfigurator.revert_challenge_config") - def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform, - mock_tls_perform): + def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded - achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=messages.ChallengeBody( - chall=challenges.TLSSNI01(token=b"kNdwjwOeX0I_A8DXt9Msmg"), - uri="https://ca.org/chall0_uri", - status=messages.Status("pending"), - ), domain="localhost", account_key=self.rsa512jwk) - achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( + achall = achallenges.KeyAuthorizationAnnotatedChallenge( challb=messages.ChallengeBody( chall=challenges.HTTP01(token=b"m8TdO1qik4JVFtgPPurJmg"), uri="https://ca.org/chall1_uri", @@ -340,19 +332,16 @@ class NginxConfiguratorTest(util.NginxTest): ), domain="example.com", account_key=self.rsa512jwk) expected = [ - achall1.response(self.rsa512jwk), - achall2.response(self.rsa512jwk), + achall.response(self.rsa512jwk), ] - mock_tls_perform.return_value = expected[:1] - mock_http_perform.return_value = expected[1:] - responses = self.config.perform([achall1, achall2]) + mock_http_perform.return_value = expected[:] + responses = self.config.perform([achall]) - self.assertEqual(mock_tls_perform.call_count, 1) self.assertEqual(mock_http_perform.call_count, 1) self.assertEqual(responses, expected) - self.config.cleanup([achall1, achall2]) + self.config.cleanup([achall]) self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access self.assertEqual(mock_revert.call_count, 1) self.assertEqual(mock_restart.call_count, 2) diff --git a/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py b/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py deleted file mode 100644 index 62ca085ef..000000000 --- a/certbot-nginx/certbot_nginx/tests/tls_sni_01_test.py +++ /dev/null @@ -1,158 +0,0 @@ -"""Tests for certbot_nginx.tls_sni_01""" -import unittest - -import mock -import six - -from acme import challenges - -from certbot import achallenges -from certbot import errors - -from certbot.plugins import common_test -from certbot.tests import acme_util - -from certbot_nginx import obj -from certbot_nginx.tests import util - - -class TlsSniPerformTest(util.NginxTest): - """Test the NginxTlsSni01 challenge.""" - - account_key = common_test.AUTH_KEY - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01(token=b"kNdwjwOeX0I_A8DXt9Msmg"), "pending"), - domain="www.example.com", account_key=account_key), - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01( - token=b"\xba\xa9\xda? Date: Mon, 25 Mar 2019 18:52:59 +0100 Subject: Construct the sanitized, pinned and hashed requirements file for certbot-auto (#6839) * Setup an independant create_venv piece for certbot-auto * Debug * First implementation * Some corrections, disable python 3 * Continue work * Add hashin * Polish CLI * Fix logic * Add executable permissions * Assynchronous process * Correction * Add comments * More controls * Correct image name * Fix image * Test with 2 * Test timeout * Remove parallelization for now. To much bugs. * Add comments * Correct installation * Correct keys map view usage * Improve filtering * Correction * Improve filtering, again * Remove dependency on python 3 * Remove necessity to run from certbot root * Add constraints. Clean code. * Pure constraints * More involved base test * Update certbot-auto with calculated dependencies * Update header * Rebuild UI * Correction * Remove debug info * Ensure docker exit when process finish * Another try to stop docker * Clean stdout/stderr * Fix python-augeas * Catch stderr * Update dependencies with new constraints * Update certbot-auto * Corrections after review. * Clean endline * Silent execution * Filter editable installation of local certbot packages, strict check on package names --- letsencrypt-auto-source/letsencrypt-auto | 415 +++++++++++---------- letsencrypt-auto-source/letsencrypt-auto.template | 25 +- letsencrypt-auto-source/pieces/create_venv.py | 27 ++ .../pieces/dependency-requirements.txt | 361 +++++++++--------- letsencrypt-auto-source/pieces/pipstrap.py | 2 +- letsencrypt-auto-source/rebuild_dependencies.py | 275 ++++++++++++++ 6 files changed, 703 insertions(+), 402 deletions(-) create mode 100755 letsencrypt-auto-source/pieces/create_venv.py create mode 100755 letsencrypt-auto-source/rebuild_dependencies.py diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2a44a58dc..822266785 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -908,6 +908,41 @@ else: UNLIKELY_EOF } +# Create a new virtual environment for Certbot. It will overwrite any existing one. +# Parameters: LE_PYTHON, VENV_PATH, PYVER, VERBOSE +CreateVenv() { + "$1" - "$2" "$3" "$4" << "UNLIKELY_EOF" +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys + + +def create_venv(venv_path, pyver, verbose): + if os.path.exists(venv_path): + shutil.rmtree(venv_path) + + stdout = sys.stdout if verbose == '1' else open(os.devnull, 'w') + + if int(pyver) <= 27: + # Use virtualenv binary + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + command = ['virtualenv', '--no-site-packages', '--python', sys.executable, venv_path] + subprocess.check_call(command, stdout=stdout, env=environ) + else: + # Use embedded venv module in Python 3 + command = [sys.executable, '-m', 'venv', venv_path] + subprocess.check_call(command, stdout=stdout) + + +if __name__ == '__main__': + create_venv(*sys.argv[1:]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -963,22 +998,7 @@ if [ "$1" = "--le-auto-phase2" ]; then if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then say "Creating virtual environment..." DeterminePythonVersion - rm -rf "$VENV_PATH" - if [ "$PYVER" -le 27 ]; then - # Use an environment variable instead of a flag for compatibility with old versions - if [ "$VERBOSE" = 1 ]; then - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" - else - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" \ - > /dev/null - fi - else - if [ "$VERBOSE" = 1 ]; then - "$LE_PYTHON" -m venv "$VENV_PATH" - else - "$LE_PYTHON" -m venv "$VENV_PATH" > /dev/null - fi - fi + CreateVenv "$LE_PYTHON" "$VENV_PATH" "$PYVER" "$VERBOSE" if [ -n "$BOOTSTRAP_VERSION" ]; then echo "$BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" @@ -992,202 +1012,195 @@ if [ "$1" = "--le-auto-phase2" ]; then # There is no $ interpolation due to quotes on starting heredoc delimiter. # ------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt" -# This is the flattened list of packages certbot-auto installs. To generate -# this, do -# `pip install --no-cache-dir -e acme -e . -e certbot-apache -e certbot-nginx`, -# and then use `hashin` or a more secure method to gather the hashes. - -# Hashin example: -# pip install hashin -# hashin -r dependency-requirements.txt cryptography==1.5.2 -# sets the new certbot-auto pinned version of cryptography to 1.5.2 - -argparse==1.4.0 \ - --hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314 \ - --hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 - -# This comes before cffi because cffi will otherwise install an unchecked -# version via setup_requires. -pycparser==2.14 \ - --hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \ - --no-binary pycparser - -asn1crypto==0.22.0 \ - --hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \ - --hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a -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 +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \ +# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# ``` +ConfigArgParse==0.14.0 \ + --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 +asn1crypto==0.24.0 \ + --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ + --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 +certifi==2019.3.9 \ + --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ + --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae +cffi==1.12.2 \ + --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ + --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ + --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ + --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ + --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ + --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ + --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ + --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ + --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ + --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ + --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ + --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ + --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ + --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ + --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ + --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ + --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ + --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ + --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ + --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ + --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ + --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ + --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ + --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ + --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ + --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ + --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ + --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ - --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ - --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 -enum34==1.1.2 ; python_version < '3.4' \ - --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ - --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 + --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 +cryptography==2.6.1 \ + --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ + --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ + --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ + --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ + --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ + --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ + --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ + --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ + --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ + --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ + --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ + --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ + --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ + --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ + --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ + --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ + --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ + --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ + --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 +enum34==1.1.6 \ + --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ + --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ + --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ + --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -idna==2.5 \ - --hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70 \ - --hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab -ipaddress==1.0.16 \ - --hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \ - --hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0 +future==0.17.1 \ + --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +idna==2.8 \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c +ipaddress==1.0.22 \ + --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ + --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c josepy==1.1.0 \ --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 -linecache2==1.0.0 \ - --hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \ - --hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c -# Using an older version of mock here prevents regressions of #5276. mock==1.3.0 \ - --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \ - --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 -ordereddict==1.1 \ - --hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \ - --no-binary ordereddict -packaging==16.8 \ - --hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \ - --hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e -parsedatetime==2.1 \ - --hash=sha256:ce9d422165cf6e963905cd5f74f274ebf7cc98c941916169178ef93f0e557838 \ - --hash=sha256:17c578775520c99131634e09cfca5a05ea9e1bd2a05cd06967ebece10df7af2d -pbr==1.8.1 \ - --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ - --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==18.0.0 \ - --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ - --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 -pyparsing==2.1.8 \ - --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ - --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ - --hash=sha256:ab09aee814c0241ff0c503cff30018219fe1fc14501d89f406f4664a0ec9fbcd \ - --hash=sha256:6e9a7f052f8e26bcf749e4033e3115b6dc7e3c85aafcb794b9a88c9d9ef13c97 \ - --hash=sha256:9f463a6bcc4eeb6c08f1ed84439b17818e2085937c0dee0d7674ac127c67c12b \ - --hash=sha256:3626b4d81cfb300dad57f52f2f791caaf7b06c09b368c0aa7b868e53a5775424 \ - --hash=sha256:367b90cc877b46af56d4580cd0ae278062903f02b8204ab631f5a2c0f50adfd0 \ - --hash=sha256:9f1ea360086cd68681e7f4ca8f1f38df47bf81942a0d76a9673c2d23eff35b13 -pyRFC3339==1.0 \ - --hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \ - --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 + --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ + --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb +parsedatetime==2.4 \ + --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ + --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 +pbr==5.1.3 \ + --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ + --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pyOpenSSL==19.0.0 \ + --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ + --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +pyRFC3339==1.1 \ + --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ + --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +pyparsing==2.3.1 \ + --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ + --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 python-augeas==0.5.0 \ - --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \ - --no-binary python-augeas -pytz==2015.7 \ - --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ - --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ - --hash=sha256:ead4aefa7007249e05e51b01095719d5a8dd95760089f5730aac5698b1932918 \ - --hash=sha256:3cca0df08bd0ed98432390494ce3ded003f5e661aa460be7a734bffe35983605 \ - --hash=sha256:3ede470d3d17ba3c07638dfa0d10452bc1b6e5ad326127a65ba77e6aaeb11bec \ - --hash=sha256:68c47964f7186eec306b13629627722b9079cd4447ed9e5ecaecd4eac84ca734 \ - --hash=sha256:dd5d3991950aae40a6c81de1578942e73d629808cefc51d12cd157980e6cfc18 \ - --hash=sha256:a77c52062c07eb7c7b30545dbc73e32995b7e117eea750317b5cb5c7a4618f14 \ - --hash=sha256:81af9aec4bc960a9a0127c488f18772dae4634689233f06f65443e7b11ebeb51 \ - --hash=sha256:e079b1dadc5c06246cc1bb6fe1b23a50b1d1173f2edd5104efd40bb73a28f406 \ - --hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \ - --hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \ - --hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3 -requests==2.20.0 \ - --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \ - --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 -six==1.10.0 \ - --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ - --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a -traceback2==1.4.0 \ - --hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23 \ - --hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 -unittest2==1.1.0 \ - --hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \ - --hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579 -zope.component==4.2.2 \ - --hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \ - --no-binary zope.component -zope.event==4.1.0 \ - --hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \ - --no-binary zope.event -zope.interface==4.1.3 \ - --hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \ - --hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \ - --hash=sha256:6788416f7ea7f5b8a97be94825377aa25e8bdc73463e07baaf9858b29e737077 \ - --hash=sha256:6f3230f7254518201e5a3708cbb2de98c848304f06e3ded8bfb39e5825cba2e1 \ - --hash=sha256:5fa575a5240f04200c3088427d0d4b7b737f6e9018818a51d8d0f927a6a2517a \ - --hash=sha256:522194ad6a545735edd75c8a83f48d65d1af064e432a7d320d64f56bafc12e99 \ - --hash=sha256:e8c7b2d40943f71c99148c97f66caa7f5134147f57423f8db5b4825099ce9a09 \ - --hash=sha256:279024f0208601c3caa907c53876e37ad88625f7eaf1cb3842dbe360b2287017 \ - --hash=sha256:2e221a9eec7ccc58889a278ea13dcfed5ef939d80b07819a9a8b3cb1c681484f \ - --hash=sha256:69118965410ec86d44dc6b9017ee3ddbd582e0c0abeef62b3a19dbf6c8ad132b \ - --hash=sha256:d04df8686ec864d0cade8cf199f7f83aecd416109a20834d568f8310ded12dea \ - --hash=sha256:e75a947e15ee97e7e71e02ea302feb2fc62d3a2bb4668bf9dfbed43a506ac7e7 \ - --hash=sha256:4e45d22fb883222a5ab9f282a116fec5ee2e8d1a568ccff6a2d75bbd0eb6bcfc \ - --hash=sha256:bce9339bb3c7a55e0803b63d21c5839e8e479bc85c4adf42ae415b72f94facb2 \ - --hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \ - --hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \ - --hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392 -requests-toolbelt==0.8.0 \ - --hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \ - --hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5 -chardet==3.0.2 \ - --hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \ - --hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0 + --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 +pytz==2018.9 \ + --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ + --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +requests==2.21.0 \ + --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ + --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 urllib3==1.24.1 \ --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 -certifi==2017.4.17 \ - --hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \ - --hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a +zope.component==4.5 \ + --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ + --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.deferredimport==4.3 \ + --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ + --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deprecation==4.4.0 \ + --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ + --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 +zope.event==4.4 \ + --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ + --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 +zope.hookable==4.2.0 \ + --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ + --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ + --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ + --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ + --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ + --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ + --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ + --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ + --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ + --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 +zope.interface==4.6.0 \ + --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ + --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ + --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ + --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ + --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ + --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ + --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ + --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ + --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ + --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ + --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ + --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ + --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ + --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ + --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ + --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ + --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ + --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ + --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ + --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ + --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ + --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ + --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ + --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ + --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ + --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ + --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ + --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ + --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.proxy==4.3.1 \ + --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ + --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ + --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ + --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ + --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ + --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ + --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ + --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ + --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ + --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ + --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed # Contains the requirements for the letsencrypt package. # @@ -1374,7 +1387,7 @@ def main(): temp, digest) for path, digest in PACKAGES] - # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + # Calling pip as a module is the preferred way to avoid problems about pip self-upgrade. command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] # Disable cache since it is not used and it otherwise sometimes throws permission warnings: command.extend(['--no-cache-dir'] if has_pip_cache else []) diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 29c54bf2d..0d3312968 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -483,6 +483,14 @@ else: UNLIKELY_EOF } +# Create a new virtual environment for Certbot. It will overwrite any existing one. +# Parameters: LE_PYTHON, VENV_PATH, PYVER, VERBOSE +CreateVenv() { + "$1" - "$2" "$3" "$4" << "UNLIKELY_EOF" +{{ create_venv.py }} +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -538,22 +546,7 @@ if [ "$1" = "--le-auto-phase2" ]; then if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then say "Creating virtual environment..." DeterminePythonVersion - rm -rf "$VENV_PATH" - if [ "$PYVER" -le 27 ]; then - # Use an environment variable instead of a flag for compatibility with old versions - if [ "$VERBOSE" = 1 ]; then - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" - else - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" \ - > /dev/null - fi - else - if [ "$VERBOSE" = 1 ]; then - "$LE_PYTHON" -m venv "$VENV_PATH" - else - "$LE_PYTHON" -m venv "$VENV_PATH" > /dev/null - fi - fi + CreateVenv "$LE_PYTHON" "$VENV_PATH" "$PYVER" "$VERBOSE" if [ -n "$BOOTSTRAP_VERSION" ]; then echo "$BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" diff --git a/letsencrypt-auto-source/pieces/create_venv.py b/letsencrypt-auto-source/pieces/create_venv.py new file mode 100755 index 000000000..a618e228a --- /dev/null +++ b/letsencrypt-auto-source/pieces/create_venv.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys + + +def create_venv(venv_path, pyver, verbose): + if os.path.exists(venv_path): + shutil.rmtree(venv_path) + + stdout = sys.stdout if verbose == '1' else open(os.devnull, 'w') + + if int(pyver) <= 27: + # Use virtualenv binary + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + command = ['virtualenv', '--no-site-packages', '--python', sys.executable, venv_path] + subprocess.check_call(command, stdout=stdout, env=environ) + else: + # Use embedded venv module in Python 3 + command = [sys.executable, '-m', 'venv', venv_path] + subprocess.check_call(command, stdout=stdout) + + +if __name__ == '__main__': + create_venv(*sys.argv[1:]) diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index dff57dfd5..625ae45f1 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -1,196 +1,189 @@ -# This is the flattened list of packages certbot-auto installs. To generate -# this, do -# `pip install --no-cache-dir -e acme -e . -e certbot-apache -e certbot-nginx`, -# and then use `hashin` or a more secure method to gather the hashes. - -# Hashin example: -# pip install hashin -# hashin -r dependency-requirements.txt cryptography==1.5.2 -# sets the new certbot-auto pinned version of cryptography to 1.5.2 - -argparse==1.4.0 \ - --hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314 \ - --hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 - -# This comes before cffi because cffi will otherwise install an unchecked -# version via setup_requires. -pycparser==2.14 \ - --hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \ - --no-binary pycparser - -asn1crypto==0.22.0 \ - --hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \ - --hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a -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 +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \ +# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# ``` +ConfigArgParse==0.14.0 \ + --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 +asn1crypto==0.24.0 \ + --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ + --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 +certifi==2019.3.9 \ + --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ + --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae +cffi==1.12.2 \ + --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ + --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ + --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ + --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ + --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ + --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ + --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ + --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ + --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ + --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ + --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ + --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ + --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ + --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ + --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ + --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ + --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ + --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ + --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ + --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ + --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ + --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ + --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ + --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ + --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ + --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ + --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ + --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ - --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ - --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 -enum34==1.1.2 ; python_version < '3.4' \ - --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ - --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 + --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 +cryptography==2.6.1 \ + --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ + --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ + --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ + --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ + --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ + --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ + --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ + --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ + --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ + --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ + --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ + --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ + --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ + --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ + --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ + --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ + --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ + --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ + --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 +enum34==1.1.6 \ + --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ + --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ + --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ + --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -idna==2.5 \ - --hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70 \ - --hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab -ipaddress==1.0.16 \ - --hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \ - --hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0 +future==0.17.1 \ + --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +idna==2.8 \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c +ipaddress==1.0.22 \ + --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ + --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c josepy==1.1.0 \ --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 -linecache2==1.0.0 \ - --hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \ - --hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c -# Using an older version of mock here prevents regressions of #5276. mock==1.3.0 \ - --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \ - --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 -ordereddict==1.1 \ - --hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \ - --no-binary ordereddict -packaging==16.8 \ - --hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \ - --hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e -parsedatetime==2.1 \ - --hash=sha256:ce9d422165cf6e963905cd5f74f274ebf7cc98c941916169178ef93f0e557838 \ - --hash=sha256:17c578775520c99131634e09cfca5a05ea9e1bd2a05cd06967ebece10df7af2d -pbr==1.8.1 \ - --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ - --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==18.0.0 \ - --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ - --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 -pyparsing==2.1.8 \ - --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ - --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ - --hash=sha256:ab09aee814c0241ff0c503cff30018219fe1fc14501d89f406f4664a0ec9fbcd \ - --hash=sha256:6e9a7f052f8e26bcf749e4033e3115b6dc7e3c85aafcb794b9a88c9d9ef13c97 \ - --hash=sha256:9f463a6bcc4eeb6c08f1ed84439b17818e2085937c0dee0d7674ac127c67c12b \ - --hash=sha256:3626b4d81cfb300dad57f52f2f791caaf7b06c09b368c0aa7b868e53a5775424 \ - --hash=sha256:367b90cc877b46af56d4580cd0ae278062903f02b8204ab631f5a2c0f50adfd0 \ - --hash=sha256:9f1ea360086cd68681e7f4ca8f1f38df47bf81942a0d76a9673c2d23eff35b13 -pyRFC3339==1.0 \ - --hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \ - --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 + --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ + --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb +parsedatetime==2.4 \ + --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ + --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 +pbr==5.1.3 \ + --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ + --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pyOpenSSL==19.0.0 \ + --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ + --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +pyRFC3339==1.1 \ + --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ + --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +pyparsing==2.3.1 \ + --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ + --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 python-augeas==0.5.0 \ - --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \ - --no-binary python-augeas -pytz==2015.7 \ - --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ - --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ - --hash=sha256:ead4aefa7007249e05e51b01095719d5a8dd95760089f5730aac5698b1932918 \ - --hash=sha256:3cca0df08bd0ed98432390494ce3ded003f5e661aa460be7a734bffe35983605 \ - --hash=sha256:3ede470d3d17ba3c07638dfa0d10452bc1b6e5ad326127a65ba77e6aaeb11bec \ - --hash=sha256:68c47964f7186eec306b13629627722b9079cd4447ed9e5ecaecd4eac84ca734 \ - --hash=sha256:dd5d3991950aae40a6c81de1578942e73d629808cefc51d12cd157980e6cfc18 \ - --hash=sha256:a77c52062c07eb7c7b30545dbc73e32995b7e117eea750317b5cb5c7a4618f14 \ - --hash=sha256:81af9aec4bc960a9a0127c488f18772dae4634689233f06f65443e7b11ebeb51 \ - --hash=sha256:e079b1dadc5c06246cc1bb6fe1b23a50b1d1173f2edd5104efd40bb73a28f406 \ - --hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \ - --hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \ - --hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3 -requests==2.20.0 \ - --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \ - --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 -six==1.10.0 \ - --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ - --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a -traceback2==1.4.0 \ - --hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23 \ - --hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 -unittest2==1.1.0 \ - --hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \ - --hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579 -zope.component==4.2.2 \ - --hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \ - --no-binary zope.component -zope.event==4.1.0 \ - --hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \ - --no-binary zope.event -zope.interface==4.1.3 \ - --hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \ - --hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \ - --hash=sha256:6788416f7ea7f5b8a97be94825377aa25e8bdc73463e07baaf9858b29e737077 \ - --hash=sha256:6f3230f7254518201e5a3708cbb2de98c848304f06e3ded8bfb39e5825cba2e1 \ - --hash=sha256:5fa575a5240f04200c3088427d0d4b7b737f6e9018818a51d8d0f927a6a2517a \ - --hash=sha256:522194ad6a545735edd75c8a83f48d65d1af064e432a7d320d64f56bafc12e99 \ - --hash=sha256:e8c7b2d40943f71c99148c97f66caa7f5134147f57423f8db5b4825099ce9a09 \ - --hash=sha256:279024f0208601c3caa907c53876e37ad88625f7eaf1cb3842dbe360b2287017 \ - --hash=sha256:2e221a9eec7ccc58889a278ea13dcfed5ef939d80b07819a9a8b3cb1c681484f \ - --hash=sha256:69118965410ec86d44dc6b9017ee3ddbd582e0c0abeef62b3a19dbf6c8ad132b \ - --hash=sha256:d04df8686ec864d0cade8cf199f7f83aecd416109a20834d568f8310ded12dea \ - --hash=sha256:e75a947e15ee97e7e71e02ea302feb2fc62d3a2bb4668bf9dfbed43a506ac7e7 \ - --hash=sha256:4e45d22fb883222a5ab9f282a116fec5ee2e8d1a568ccff6a2d75bbd0eb6bcfc \ - --hash=sha256:bce9339bb3c7a55e0803b63d21c5839e8e479bc85c4adf42ae415b72f94facb2 \ - --hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \ - --hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \ - --hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392 -requests-toolbelt==0.8.0 \ - --hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \ - --hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5 -chardet==3.0.2 \ - --hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \ - --hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0 + --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 +pytz==2018.9 \ + --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ + --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +requests==2.21.0 \ + --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ + --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 urllib3==1.24.1 \ --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 -certifi==2017.4.17 \ - --hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \ - --hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a +zope.component==4.5 \ + --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ + --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.deferredimport==4.3 \ + --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ + --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deprecation==4.4.0 \ + --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ + --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 +zope.event==4.4 \ + --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ + --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 +zope.hookable==4.2.0 \ + --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ + --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ + --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ + --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ + --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ + --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ + --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ + --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ + --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ + --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 +zope.interface==4.6.0 \ + --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ + --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ + --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ + --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ + --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ + --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ + --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ + --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ + --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ + --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ + --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ + --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ + --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ + --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ + --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ + --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ + --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ + --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ + --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ + --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ + --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ + --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ + --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ + --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ + --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ + --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ + --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ + --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ + --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.proxy==4.3.1 \ + --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ + --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ + --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ + --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ + --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ + --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ + --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ + --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ + --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ + --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ + --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed diff --git a/letsencrypt-auto-source/pieces/pipstrap.py b/letsencrypt-auto-source/pieces/pipstrap.py index f04ebe6fe..346e23938 100755 --- a/letsencrypt-auto-source/pieces/pipstrap.py +++ b/letsencrypt-auto-source/pieces/pipstrap.py @@ -156,7 +156,7 @@ def main(): temp, digest) for path, digest in PACKAGES] - # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + # Calling pip as a module is the preferred way to avoid problems about pip self-upgrade. command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] # Disable cache since it is not used and it otherwise sometimes throws permission warnings: command.extend(['--no-cache-dir'] if has_pip_cache else []) diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py new file mode 100755 index 000000000..aab7d546b --- /dev/null +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +""" +Gather and consolidate the up-to-date dependencies available and required to install certbot +on various Linux distributions. It generates a requirements file contained the pinned and hashed +versions, ready to be used by pip to install the certbot dependencies. + +This script is typically used to update the certbot-requirements.txt file of certbot-auto. + +To achieve its purpose, this script will start a certbot installation with unpinned dependencies, +then gather them, on various distributions started as Docker containers. + +Usage: letsencrypt-auto-source/rebuild_dependencies new_requirements.txt + +NB1: Docker must be installed on the machine running this script. +NB2: Python library 'hashin' must be installed on the machine running this script. +""" +from __future__ import print_function +import re +import shutil +import subprocess +import tempfile +import os +from os.path import dirname, abspath, join +import sys +import argparse + +# The list of docker distributions to test dependencies against with. +DISTRIBUTION_LIST = [ + 'ubuntu:18.04', 'ubuntu:14.04', + 'debian:stretch', 'debian:jessie', + 'centos:7', 'centos:6', + 'opensuse/leap:15', + 'fedora:29', +] + +# Theses constraints will be added while gathering dependencies on each distribution. +# It can be used because a particular version for a package is required for any reason, +# or to solve a version conflict between two distributions requirements. +AUTHORITATIVE_CONSTRAINTS = { + # Using an older version of mock here prevents regressions of #5276. + 'mock': '1.3.0', + # Too touchy to move to a new version. And will be removed soon + # in favor of pure python parser for Apache. + 'python-augeas': '0.5.0', +} + + +# ./certbot/letsencrypt-auto-source/rebuild_dependencies.py (2 levels from certbot root path) +CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) + +# The script will be used to gather dependencies for a given distribution. +# - certbot-auto is used to install relevant OS packages, and set up an initial venv +# - then this venv is used to consistently construct an empty new venv +# - once pipstraped, this new venv pip-installs certbot runtime (including apache/nginx), +# without pinned dependencies, and respecting input authoritative requirements +# - `certbot plugins` is called to check we have an healthy environment +# - finally current set of dependencies is extracted out of the docker using pip freeze +SCRIPT = """\ +#!/bin/sh +set -e + +cd /tmp/certbot +letsencrypt-auto-source/letsencrypt-auto --install-only -n +PYVER=`/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + +/opt/eff.org/certbot/venv/bin/python letsencrypt-auto-source/pieces/create_venv.py /tmp/venv "$PYVER" 1 + +/tmp/venv/bin/python letsencrypt-auto-source/pieces/pipstrap.py +/tmp/venv/bin/pip install -e acme -e . -e certbot-apache -e certbot-nginx -c /tmp/constraints.txt +/tmp/venv/bin/certbot plugins +/tmp/venv/bin/pip freeze >> /tmp/workspace/requirements.txt +""" + + +def _read_from(file): + """Read all content of the file, and return it as a string.""" + with open(file, 'r') as file_h: + return file_h.read() + + +def _write_to(file, content): + """Write given string content to the file, overwriting its initial content.""" + with open(file, 'w') as file_h: + file_h.write(content) + + +def _requirements_from_one_distribution(distribution, verbose): + """ + Calculate the Certbot dependencies expressed for the given distribution, using the official + Docker for this distribution, and return the lines of the generated requirements file. + """ + print('===> Gathering dependencies for {0}.'.format(distribution)) + workspace = tempfile.mkdtemp() + script = join(workspace, 'script.sh') + authoritative_constraints = join(workspace, 'constraints.txt') + cid_file = join(workspace, 'cid') + + try: + _write_to(script, SCRIPT) + os.chmod(script, 0o755) + + _write_to(authoritative_constraints, '\n'.join( + ['{0}=={1}'.format(package, version) for package, version in AUTHORITATIVE_CONSTRAINTS.items()])) + + command = ['docker', 'run', '--rm', '--cidfile', cid_file, + '-v', '{0}:/tmp/certbot'.format(CERTBOT_REPO_PATH), + '-v', '{0}:/tmp/workspace'.format(workspace), + '-v', '{0}:/tmp/constraints.txt'.format(authoritative_constraints), + distribution, '/tmp/workspace/script.sh'] + sub_stdout = sys.stdout if verbose else subprocess.PIPE + sub_stderr = sys.stderr if verbose else subprocess.STDOUT + process = subprocess.Popen(command, stdout=sub_stdout, stderr=sub_stderr, universal_newlines=True) + stdoutdata, _ = process.communicate() + + if process.returncode: + if stdoutdata: + sys.stderr.write('Output was:\n{0}'.format(stdoutdata)) + raise RuntimeError('Error while gathering dependencies for {0}.'.format(distribution)) + + with open(join(workspace, 'requirements.txt'), 'r') as file_h: + return file_h.readlines() + finally: + if os.path.isfile(cid_file): + cid = _read_from(cid_file) + try: + subprocess.check_output(['docker', 'kill', cid], stderr=subprocess.PIPE) + except subprocess.CalledProcessError: + pass + shutil.rmtree(workspace) + + +def _parse_and_merge_requirements(dependencies_map, requirements_file_lines, distribution): + """ + Extract every requirement from the given requirements file, and merge it in the dependency map. + Merging here means that the map contain every encountered dependency, and the version used in + each distribution. + + Example: + # dependencies_map = { + # } + _parse_and_merge_requirements(['cryptography=='1.2','requests=='2.1.0'], dependencies_map, 'debian:stretch') + # dependencies_map = { + # 'cryptography': [('1.2', 'debian:stretch)], + # 'requests': [('2.1.0', 'debian:stretch')] + # } + _parse_and_merge_requirements(['requests=='2.4.0', 'mock==1.3'], dependencies_map, 'centos:7') + # dependencies_map = { + # 'cryptography': [('1.2', 'debian:stretch)], + # 'requests': [('2.1.0', 'debian:stretch'), ('2.4.0', 'centos:7')], + # 'mock': [('2.4.0', 'centos:7')] + # } + """ + for line in requirements_file_lines: + match = re.match(r'([^=]+)==([^=]+)', line.strip()) + if not line.startswith('-e') and match: + package, version = match.groups() + if package not in ['acme', 'certbot', 'certbot-apache', 'certbot-nginx', 'pkg-resources']: + dependencies_map.setdefault(package, []).append((version, distribution)) + + +def _consolidate_and_validate_dependencies(dependency_map): + """ + Given the dependency map of all requirements found in all distributions for Certbot, + construct an array containing the unit requirements for Certbot to be used by pip, + and the version conflicts, if any, between several distributions for a package. + Return requirements and conflicts as a tuple. + """ + print('===> Consolidate and validate the dependency map.') + requirements = [] + conflicts = [] + for package, versions in dependency_map.items(): + reduced_versions = _reduce_versions(versions) + + if len(reduced_versions) > 1: + version_list = ['{0} ({1})'.format(version, ','.join(distributions)) + for version, distributions in reduced_versions.items()] + conflict = ('package {0} is declared with several versions: {1}' + .format(package, ', '.join(version_list))) + conflicts.append(conflict) + sys.stderr.write('ERROR: {0}\n'.format(conflict)) + else: + requirements.append((package, list(reduced_versions)[0])) + + requirements.sort(key=lambda x: x[0]) + return requirements, conflicts + + +def _reduce_versions(version_dist_tuples): + """ + Get an array of version/distribution tuples, + and reduce it to a map based on the version values. + + Example: [('1.2.0', 'debian:stretch'), ('1.4.0', 'ubuntu:18.04'), ('1.2.0', 'centos:6')] + => {'1.2.0': ['debiqn:stretch', 'centos:6'], '1.4.0': ['ubuntu:18.04']} + """ + version_dist_map = {} + for version, distribution in version_dist_tuples: + version_dist_map.setdefault(version, []).append(distribution) + + return version_dist_map + + +def _write_requirements(dest_file, requirements, conflicts): + """ + Given the list of requirements and conflicts, write a well-formatted requirements file, + whose requirements are hashed signed using hashin library. Conflicts are written at the end + of the generated file. + """ + print('===> Calculating hashes for the requirement file.') + + _write_to(dest_file, '''\ +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \\ +# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# ``` +''') + + for req in requirements: + subprocess.check_call(['hashin', '{0}=={1}'.format(req[0], req[1]), + '--requirements-file', dest_file]) + + if conflicts: + with open(dest_file, 'a') as file_h: + file_h.write('\n## ! SOME ERRORS OCCURRED ! ##\n') + file_h.write('\n'.join('# {0}'.format(conflict) for conflict in conflicts)) + file_h.write('\n') + + return _read_from(dest_file) + + +def _gather_dependencies(dest_file, verbose): + """ + Main method of this script. Given a destination file path, will write the file + containing the consolidated and hashed requirements for Certbot, validated + against several Linux distributions. + """ + dependencies_map = {} + + for distribution in DISTRIBUTION_LIST: + requirements_file_lines = _requirements_from_one_distribution(distribution, verbose) + _parse_and_merge_requirements(dependencies_map, requirements_file_lines, distribution) + + requirements, conflicts = _consolidate_and_validate_dependencies(dependencies_map) + + return _write_requirements(dest_file, requirements, conflicts) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=('Build a sanitized, pinned and hashed requirements file for certbot-auto, ' + 'validated against several OS distributions using Docker.')) + parser.add_argument('requirements_path', + help='path for the generated requirements file') + parser.add_argument('--verbose', '-v', action='store_true', + help='verbose will display all output during docker execution') + + namespace = parser.parse_args() + + try: + subprocess.check_output(['hashin', '--version']) + except subprocess.CalledProcessError: + raise RuntimeError('Python library hashin is not installed in the current environment.') + + try: + subprocess.check_output(['docker', '--version'], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + raise RuntimeError('Docker is not installed or accessible to current user.') + + file_content = _gather_dependencies(namespace.requirements_path, namespace.verbose) + + print(file_content) + print('===> Rebuilt requirement file is available on path {0}' + .format(abspath(namespace.requirements_path))) -- cgit v1.2.3 From 537bffbc23341e6e7973429ff26cc3cfa72c2ec4 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 25 Mar 2019 20:56:28 +0100 Subject: [Windows] Fix some unit tests (#6865) This PR is a part of the effort to remove the last broken unit tests in certbot codebase for Windows, as described in #6850. This PR fixes various unit tests on Windows, whose resolution was only to modify some logic in the tests, or minor changes in certbot codecase impacting Windows only (like handling correctly paths with DOS-style). * Correct several tests * Skip test definitively * Test to be reactivated with #6497 * Mock log system to avoid errors due to multiple calls to main in main_test * Simplify mock * Update cli_test.py * One test to be repaired when windows file permissions PR is merged --- certbot/plugins/manual_test.py | 4 ++-- certbot/tests/cli_test.py | 36 ++++++++++++++++++------------------ certbot/tests/lock_test.py | 12 ++++++------ certbot/tests/main_test.py | 38 +++++++++++++++++++------------------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index b566f6340..13e79ce6e 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -106,10 +106,10 @@ class AuthenticatorTest(test_util.TempDirTestCase): achall.validation(achall.account_key) in args[0]) self.assertFalse(kwargs['wrap']) - @test_util.broken_on_windows def test_cleanup(self): self.config.manual_public_ip_logging_ok = True - self.config.manual_auth_hook = 'echo foo;' + self.config.manual_auth_hook = ('{0} -c "import sys; sys.stdout.write(\'foo\')"' + .format(sys.executable)) self.config.manual_cleanup_hook = '# cleanup' self.auth.perform(self.achalls) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index f6669e7be..60191730f 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -86,26 +86,26 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods return output.getvalue() - @test_util.broken_on_windows @mock.patch("certbot.cli.flag_default") def test_cli_ini_domains(self, mock_flag_default): - tmp_config = tempfile.NamedTemporaryFile() - # use a shim to get ConfigArgParse to pick up tmp_config - shim = ( - lambda v: copy.deepcopy(constants.CLI_DEFAULTS[v]) - if v != "config_files" - else [tmp_config.name] - ) - mock_flag_default.side_effect = shim - - namespace = self.parse(["certonly"]) - self.assertEqual(namespace.domains, []) - tmp_config.write(b"domains = example.com") - tmp_config.flush() - namespace = self.parse(["certonly"]) - self.assertEqual(namespace.domains, ["example.com"]) - namespace = self.parse(["renew"]) - self.assertEqual(namespace.domains, []) + with tempfile.NamedTemporaryFile() as tmp_config: + tmp_config.close() # close now because of compatibility issues on Windows + # use a shim to get ConfigArgParse to pick up tmp_config + shim = ( + lambda v: copy.deepcopy(constants.CLI_DEFAULTS[v]) + if v != "config_files" + else [tmp_config.name] + ) + mock_flag_default.side_effect = shim + + namespace = self.parse(["certonly"]) + self.assertEqual(namespace.domains, []) + with open(tmp_config.name, 'w') as file_h: + file_h.write("domains = example.com") + namespace = self.parse(["certonly"]) + self.assertEqual(namespace.domains, ["example.com"]) + namespace = self.parse(["renew"]) + self.assertEqual(namespace.domains, []) def test_no_args(self): namespace = self.parse([]) diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index 6379693ae..d2e61e386 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -41,7 +41,6 @@ class LockFileTest(test_util.TempDirTestCase): super(LockFileTest, self).setUp() self.lock_path = os.path.join(self.tempdir, 'test.lock') - @test_util.broken_on_windows def test_acquire_without_deletion(self): # acquire the lock in another process but don't delete the file child = multiprocessing.Process(target=self._call, @@ -59,12 +58,14 @@ class LockFileTest(test_util.TempDirTestCase): self.assertRaises, errors.LockError, self._call, self.lock_path) test_util.lock_and_call(assert_raises, self.lock_path) - @test_util.broken_on_windows def test_locked_repr(self): lock_file = self._call(self.lock_path) - locked_repr = repr(lock_file) - self._test_repr_common(lock_file, locked_repr) - self.assertTrue('acquired' in locked_repr) + try: + locked_repr = repr(lock_file) + self._test_repr_common(lock_file, locked_repr) + self.assertTrue('acquired' in locked_repr) + finally: + lock_file.release() def test_released_repr(self): lock_file = self._call(self.lock_path) @@ -94,7 +95,6 @@ class LockFileTest(test_util.TempDirTestCase): self._call(self.lock_path) self.assertFalse(should_delete) - @test_util.broken_on_windows def test_removed(self): lock_file = self._call(self.lock_path) lock_file.release() diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 786b91a94..e1be6e023 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -593,20 +593,20 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue(message in str(exc)) self.assertTrue(exc is not None) - @test_util.broken_on_windows - def test_noninteractive(self): + @mock.patch('certbot.log.post_arg_parse_setup') + def test_noninteractive(self, _): args = ['-n', 'certonly'] self._cli_missing_flag(args, "specify a plugin") args.extend(['--standalone', '-d', 'eg.is']) self._cli_missing_flag(args, "register before running") - @test_util.broken_on_windows + @mock.patch('certbot.log.post_arg_parse_setup') @mock.patch('certbot.main._report_new_cert') @mock.patch('certbot.main.client.acme_client.Client') @mock.patch('certbot.main._determine_account') @mock.patch('certbot.main.client.Client.obtain_and_enroll_certificate') @mock.patch('certbot.main._get_and_save_cert') - def test_user_agent(self, gsc, _obt, det, _client, unused_report): + def test_user_agent(self, gsc, _obt, det, _client, _, __): # Normally the client is totally mocked out, but here we need more # arguments to automate it... args = ["--standalone", "certonly", "-m", "none@none.com", @@ -655,11 +655,11 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) - @test_util.broken_on_windows + @mock.patch('certbot.log.post_arg_parse_setup') @mock.patch('certbot.main._install_cert') @mock.patch('certbot.main.plug_sel.record_chosen_plugins') @mock.patch('certbot.main.plug_sel.pick_installer') - def test_installer_param_override(self, _inst, _rec, mock_install): + def test_installer_param_override(self, _inst, _rec, mock_install, _): mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'), chain_path=test_util.temp_join('chain'), fullchain_path=test_util.temp_join('chain'), @@ -706,10 +706,10 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue(mock_getcert.called) self.assertTrue(mock_inst.called) - @test_util.broken_on_windows + @mock.patch('certbot.log.post_arg_parse_setup') @mock.patch('certbot.main._report_new_cert') @mock.patch('certbot.util.exe_exists') - def test_configurator_selection(self, mock_exe_exists, unused_report): + def test_configurator_selection(self, mock_exe_exists, _, __): mock_exe_exists.return_value = True real_plugins = disco.PluginsRegistry.find_all() args = ['--apache', '--authenticator', 'standalone'] @@ -746,8 +746,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._call(["auth", "--standalone"]) self.assertEqual(1, mock_certonly.call_count) - @test_util.broken_on_windows - def test_rollback(self): + @mock.patch('certbot.log.post_arg_parse_setup') + def test_rollback(self, _): _, _, _, client = self._call(['rollback']) self.assertEqual(1, client.rollback.call_count) @@ -774,8 +774,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._call_no_clientmock(['delete']) self.assertEqual(1, mock_cert_manager.call_count) - @test_util.broken_on_windows - def test_plugins(self): + @mock.patch('certbot.log.post_arg_parse_setup') + def test_plugins(self, _): flags = ['--init', '--prepare', '--authenticators', '--installers'] for args in itertools.chain( *(itertools.combinations(flags, r) @@ -1047,7 +1047,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met return mock_lineage, mock_get_utility, stdout @mock.patch('certbot.crypto_util.notAfter') - def test_certonly_renewal(self, unused_notafter): + def test_certonly_renewal(self, _): lineage, get_utility, _ = self._test_renewal_common(True, []) self.assertEqual(lineage.save_successor.call_count, 1) lineage.update_all_links_to.assert_called_once_with( @@ -1056,9 +1056,9 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('fullchain.pem' in cert_msg) self.assertTrue('donate' in get_utility().add_message.call_args[0][0]) - @test_util.broken_on_windows + @mock.patch('certbot.log.logging.handlers.RotatingFileHandler.doRollover') @mock.patch('certbot.crypto_util.notAfter') - def test_certonly_renewal_triggers(self, unused_notafter): + def test_certonly_renewal_triggers(self, _, __): # --dry-run should force renewal _, get_utility, _ = self._test_renewal_common(False, ['--dry-run', '--keep'], log_out="simulating renewal") @@ -1125,8 +1125,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('No renewals were attempted.' in stdout.getvalue()) self.assertTrue('The following certs are not due for renewal yet:' in stdout.getvalue()) - @test_util.broken_on_windows - def test_quiet_renew(self): + @mock.patch('certbot.log.post_arg_parse_setup') + def test_quiet_renew(self, _): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run"] _, _, stdout = self._test_renewal_common(True, [], args=args, should_renew=True) @@ -1381,8 +1381,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._call(['-c', test_util.vector_path('cli.ini')]) self.assertTrue(mocked_run.called) - @test_util.broken_on_windows - def test_register(self): + @mock.patch('certbot.log.post_arg_parse_setup') + def test_register(self, _): with mock.patch('certbot.main.client') as mocked_client: acc = mock.MagicMock() acc.id = "imaginary_account" -- cgit v1.2.3 From 4d2dfab4dda7e81e7a09246fd10d34b518001adf Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 25 Mar 2019 21:22:56 +0100 Subject: Simplify a branching that is not totally covered. (#6881) --- certbot-apache/certbot_apache/parser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 148f052d0..b9f23f6cf 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -93,12 +93,7 @@ class ApacheParser(object): # Add new path to parser paths new_dir = os.path.dirname(inc_path) new_file = os.path.basename(inc_path) - if new_dir in self.existing_paths.keys(): - # Add to existing path - self.existing_paths[new_dir].append(new_file) - else: - # Create a new path - self.existing_paths[new_dir] = [new_file] + self.existing_paths.setdefault(new_dir, []).append(new_file) def add_mod(self, mod_name): """Shortcut for updating parser modules.""" -- cgit v1.2.3 From 20ed165699da7e4c082abb844cca662d0e045d85 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 25 Mar 2019 21:48:36 +0100 Subject: Remove unused code in apache (#6882) To fix one of the two uncovered lines in certbot-apache, given in #6880. Instead of adding a test to just increase the coverage, this fixes the uncovered line by removing the unused code. --- certbot-apache/certbot_apache/configurator.py | 17 ----------------- .../certbot_apache/tests/configurator_test.py | 9 --------- 2 files changed, 26 deletions(-) diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index a3061119b..49591a141 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -1076,23 +1076,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if "ssl_module" not in self.parser.modules: self.enable_mod("ssl", temp=temp) - def make_addrs_sni_ready(self, addrs): - """Checks to see if the server is ready for SNI challenges. - - :param addrs: Addresses to check SNI compatibility - :type addrs: :class:`~certbot_apache.obj.Addr` - - """ - # Version 2.4 and later are automatically SNI ready. - if self.version >= (2, 4): - return - - for addr in addrs: - if not self.is_name_vhost(addr): - logger.debug("Setting VirtualHost at %s to be a name " - "based virtual host", addr) - self.add_name_vhost(addr) - def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals """Makes an ssl_vhost version of a nonssl_vhost. diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index ee4833ebb..a8d5dd96e 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1336,15 +1336,6 @@ class MultipleVhostsTest(util.ApacheTest): return account_key, (achall1, achall2, achall3) - def test_make_addrs_sni_ready(self): - self.config.version = (2, 2) - self.config.make_addrs_sni_ready( - set([obj.Addr.fromstring("*:443"), obj.Addr.fromstring("*:80")])) - self.assertTrue(self.config.parser.find_dir( - "NameVirtualHost", "*:80", exclude=False)) - self.assertTrue(self.config.parser.find_dir( - "NameVirtualHost", "*:443", exclude=False)) - def test_aug_version(self): mock_match = mock.Mock(return_value=["something"]) self.config.aug.match = mock_match -- cgit v1.2.3 From 97d269ceb542168816fb6381927766dae05c7d43 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 25 Mar 2019 23:06:13 +0100 Subject: Raise explicitly an error (#6883) Explicit is better than implicit When calling raise without an argument, Python will raise the last error occured from the caller except block. This makes my PyCharm very sad however. So this PR makes the function handling the error raising explicitly the error received as an argument. --- certbot/plugins/standalone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py index 207eff0b6..804d5f8dc 100644 --- a/certbot/plugins/standalone.py +++ b/certbot/plugins/standalone.py @@ -263,4 +263,4 @@ def _handle_perform_error(error): if not should_retry: raise errors.PluginError(msg) else: - raise + raise error -- cgit v1.2.3 From 50607eb0ff4279f3395231f3c6f35586d21557f3 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 25 Mar 2019 15:57:53 -0700 Subject: Document dropped tls-sni-01 support in plugins. (#6884) --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc34634ac..3617c0e70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Support for TLS-SNI-01 has been removed from all official Certbot plugins. ### Fixed @@ -24,6 +24,8 @@ all Certbot components during releases for the time being, however, the only package with changes other than its version number was: * certbot +* certbot-apache +* certbot-nginx More details about these changes can be found on our GitHub repo. -- cgit v1.2.3 From a27bd28b39e5312ef77d5767a989fc4661e179fb Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 26 Mar 2019 19:35:43 +0100 Subject: Configure jessie repos in LTS mode during Docker build (#6887) Currently, `tox -e le_auto_jessie` job fails. It breaks in particular the cron pipeline that test everything each night. The failure occurs while setting up the Jessie Docker container to run the tests for certbot-auto, when `apt-get update` is invoked, with this error: ``` W: Failed to fetch http://deb.debian.org/debian/dists/jessie-updates/main/binary-amd64/Packages 404 Not Found ``` Indeed, if there are `stretch-updates`, `buster-updates` and so on in the repository, there is no `jessie-updates`. I do not know exactly the logic of Debian here, but as `*-updates` folders store stable updates, a distribution moving to LTS support like Jessie has no stable updates anymore. I suppose `jessie-updates have been decommissioned recently, and the official Docker has not been updated yet to use the LTS configuration for repositories. This PR does that live in the Dockerfile, using official instructions from https://wiki.debian.org/LTS/Using, and fixes this specific job. An example of a successful job with this modification can be found here: https://travis-ci.com/certbot/certbot/jobs/187864341 --- letsencrypt-auto-source/Dockerfile.jessie | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/letsencrypt-auto-source/Dockerfile.jessie b/letsencrypt-auto-source/Dockerfile.jessie index 9ee37b763..528ab5c4a 100644 --- a/letsencrypt-auto-source/Dockerfile.jessie +++ b/letsencrypt-auto-source/Dockerfile.jessie @@ -7,7 +7,9 @@ FROM debian:jessie RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups sudo --uid 1000 lea # Install pip, sudo, and openssl: -RUN apt-get update && \ +RUN echo "deb http://deb.debian.org/debian/ jessie main" > /etc/apt/sources.list && \ + echo "deb http://security.debian.org/ jessie/updates main" >> /etc/apt/sources.list && \ + apt-get update && \ apt-get -q -y install python-pip sudo openssl && \ apt-get clean # Use pipstrap to update to a stable and tested version of pip -- cgit v1.2.3 From 821bec69979ecd699c92206316b9e3514b6efd62 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 27 Mar 2019 01:46:32 +0100 Subject: Remove tls-sni related flags in cli. Add a deprecation warning instead. (#6853) This PR is a part of the tls-sni-01 removal plan described in #6849. This PR removes --tls-sni-01-port, --tls-sni-01-address and tls-sni-01/tls-sni options from --preferred-challenges. They are replace by deprecation warning, indicating that these options will be removed soon. This deprecation, instead of complete removal, is done to avoid certbot instances to hard fail if some automated scripts still use these flags for some users. Once this PR lands, we can remove completely theses flags in one or two release. * Remove tls-sni related flags in cli. Add a deprecation warning instead. * Adapt tests to cli and renewal towards tls-sni flags deprecation * Add https_port option. Make tls_sni_01_port show a deprecation warning, but silently modify https_port if set * Migrate last items * Fix lint * Update certbot/cli.py Co-Authored-By: adferrand * Ensure to remove all occurences of tls-sni-01 * Remove unused parameter * Revert modifications on cli-help.txt * Use logger.warning instead of sys.stderr * Update the logger warning message * Remove standalone_supported_challenges option. * Fix order of preferred-challenges * Remove supported_challenges property * Fix some tests * Fix lint * Fix tests * Add a changelog * Clean code, fix test * Update CI * Reload * No hard date for tls-sni removal * Remove useless cast to list * Update certbot/tests/renewal_test.py Co-Authored-By: adferrand * Add entry to the changelog * Add entry to the changelog --- CHANGELOG.md | 8 +++ certbot-nginx/certbot_nginx/configurator.py | 4 +- certbot-nginx/certbot_nginx/tests/util.py | 2 +- certbot/cli.py | 36 +++++++++---- certbot/configuration.py | 6 +-- certbot/constants.py | 3 +- certbot/interfaces.py | 11 ++-- certbot/plugins/standalone.py | 60 +--------------------- certbot/plugins/standalone_test.py | 51 +----------------- certbot/renewal.py | 6 +-- certbot/tests/cli_test.py | 19 ++++--- certbot/tests/configuration_test.py | 8 +-- certbot/tests/renewal_test.py | 10 ++-- certbot/tests/testdata/sample-renewal-ancient.conf | 2 - certbot/tests/testdata/sample-renewal.conf | 2 - certbot/tests/util_test.py | 23 ++++----- certbot/util.py | 4 +- docs/cli-help.txt | 2 +- docs/using.rst | 3 -- examples/cli.ini | 1 - tests/integration/_common.sh | 2 +- 21 files changed, 90 insertions(+), 173 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3617c0e70..9761ad3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,18 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. +* CLI flag `--http-port` has been added for Nginx plugin exclusively, and replaces + `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while + setting up a new SSL vhost. By default the HTTPS port is 443. ### Changed * Support for TLS-SNI-01 has been removed from all official Certbot plugins. +* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will + generate a deprecation warning if used, and will be removed soon. +* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, + will generate a deprecation warning if used, and will be removed soon. +* CLI flag `--standalone-supported-challenges` has been removed. ### Fixed diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 2357735f9..8b39ee664 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -295,7 +295,7 @@ class NginxConfigurator(common.Installer): if create_if_no_match: # result will not be [None] because it errors on failure vhosts = [self._vhost_from_duplicated_default(target_name, True, - str(self.config.tls_sni_01_port))] + str(self.config.https_port))] else: # No matches. Raise a misconfiguration error. raise errors.MisconfigurationError( @@ -609,7 +609,7 @@ class NginxConfigurator(common.Installer): :type vhost: :class:`~certbot_nginx.obj.VirtualHost` """ - https_port = self.config.tls_sni_01_port + https_port = self.config.https_port ipv6info = self.ipv6_info(https_port) ipv6_block = [''] ipv4_block = [''] diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index 93b0b4c7f..e33c2c16c 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -82,7 +82,7 @@ def get_nginx_configurator( in_progress_dir=os.path.join(backups, "IN_PROGRESS"), server="https://acme-server.org:443/new", http01_port=80, - tls_sni_01_port=5001, + https_port=5001, ), name="nginx", version=version) diff --git a/certbot/cli.py b/certbot/cli.py index 398124510..1472b0139 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1117,14 +1117,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis "testing", "--no-verify-ssl", action="store_true", help=config_help("no_verify_ssl"), default=flag_default("no_verify_ssl")) - helpful.add( - ["testing", "standalone", "apache", "nginx"], "--tls-sni-01-port", type=int, - default=flag_default("tls_sni_01_port"), - help=config_help("tls_sni_01_port")) - helpful.add( - ["testing", "standalone"], "--tls-sni-01-address", - default=flag_default("tls_sni_01_address"), - help=config_help("tls_sni_01_address")) helpful.add( ["testing", "standalone", "manual"], "--http-01-port", type=int, dest="http01_port", @@ -1133,6 +1125,10 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis ["testing", "standalone"], "--http-01-address", dest="http01_address", default=flag_default("http01_address"), help=config_help("http01_address")) + helpful.add( + ["testing", "nginx"], "--https-port", type=int, + default=flag_default("https_port"), + help=config_help("https_port")) helpful.add( "testing", "--break-my-certs", action="store_true", default=flag_default("break_my_certs"), @@ -1193,7 +1189,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis action=_PrefChallAction, default=flag_default("pref_challs"), help='A sorted, comma delimited list of the preferred challenge to ' 'use during authorization with the most preferred challenge ' - 'listed first (Eg, "dns" or "tls-sni-01,http,dns"). ' + 'listed first (Eg, "dns" or "http,dns"). ' 'Not all plugins support all challenges. See ' 'https://certbot.eff.org/docs/using.html#plugins for details. ' 'ACME Challenges are versioned, but if you pick "http" rather ' @@ -1264,6 +1260,17 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis helpful.add_deprecated_argument("--agree-dev-preview", 0) helpful.add_deprecated_argument("--dialog", 0) + # Deprecation of tls-sni-01 related cli flags + # TODO: remove theses flags completely in few releases + class _DeprecatedTLSSNIAction(util._ShowWarning): # pylint: disable=protected-access + def __call__(self, parser, namespace, values, option_string=None): + super(_DeprecatedTLSSNIAction, self).__call__(parser, namespace, values, option_string) + namespace.https_port = values + helpful.add( + ["testing", "standalone", "apache", "nginx"], "--tls-sni-01-port", + type=int, action=_DeprecatedTLSSNIAction, help=argparse.SUPPRESS) + helpful.add_deprecated_argument("--tls-sni-01-address", 1) + # Populate the command line parameters for new style enhancements enhancements.populate_cli(helpful.add) @@ -1556,6 +1563,15 @@ def parse_preferred_challenges(pref_challs): aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"} challs = [c.strip() for c in pref_challs] challs = [aliases.get(c, c) for c in challs] + + # Ignore tls-sni-01 from the list, and generates a deprecation warning + # TODO: remove this option completely in few releases + if "tls-sni-01" in challs: + logger.warning('TLS-SNI-01 support is deprecated. This value is being dropped from the ' + 'setting of --preferred-challenges and future versions of Certbot will ' + 'error if it is included.') + challs = [chall for chall in challs if chall != "tls-sni-01"] + unrecognized = ", ".join(name for name in challs if name not in challenges.Challenge.TYPES) if unrecognized: @@ -1563,11 +1579,13 @@ def parse_preferred_challenges(pref_challs): "Unrecognized challenges: {0}".format(unrecognized)) return challs + def _user_agent_comment_type(value): if "(" in value or ")" in value: raise argparse.ArgumentTypeError("may not contain parentheses") return value + class _DeployHookAction(argparse.Action): """Action class for parsing deploy hooks.""" diff --git a/certbot/configuration.py b/certbot/configuration.py index 15f9fa3e0..5d248f39e 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -148,10 +148,10 @@ def check_config_sanity(config): """ # Port check - if config.http01_port == config.tls_sni_01_port: + if config.http01_port == config.https_port: raise errors.ConfigurationError( - "Trying to run http-01 and tls-sni-01 " - "on the same port ({0})".format(config.tls_sni_01_port)) + "Trying to run http-01 and https-port " + "on the same port ({0})".format(config.https_port)) # Domain checks if config.namespace.domains is not None: diff --git a/certbot/constants.py b/certbot/constants.py index c6a80747e..31b243518 100644 --- a/certbot/constants.py +++ b/certbot/constants.py @@ -50,10 +50,9 @@ CLI_DEFAULTS = dict( debug=False, debug_challenges=False, no_verify_ssl=False, - tls_sni_01_port=challenges.TLSSNI01Response.PORT, - tls_sni_01_address="", http01_port=challenges.HTTP01Response.PORT, http01_address="", + https_port=443, break_my_certs=False, rsa_key_size=2048, must_staple=False, diff --git a/certbot/interfaces.py b/certbot/interfaces.py index bd91d2272..96155fa88 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -220,12 +220,6 @@ class IConfig(zope.interface.Interface): no_verify_ssl = zope.interface.Attribute( "Disable verification of the ACME server's certificate.") - tls_sni_01_port = zope.interface.Attribute( - "Port used during tls-sni-01 challenge. " - "This only affects the port Certbot listens on. " - "A conforming ACME server will still attempt to connect on port 443.") - tls_sni_01_address = zope.interface.Attribute( - "The address the server listens to during tls-sni-01 challenge.") http01_port = zope.interface.Attribute( "Port used in the http-01 challenge. " @@ -235,6 +229,11 @@ class IConfig(zope.interface.Interface): http01_address = zope.interface.Attribute( "The address the server listens to during http-01 challenge.") + https_port = zope.interface.Attribute( + "Port used to serve HTTPS. " + "This affects which port Nginx will listen on after a LE certificate " + "is installed.") + pref_challs = zope.interface.Attribute( "Sorted user specified preferred challenges" "type strings with the most preferred challenge listed first") diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py index 804d5f8dc..9723116c1 100644 --- a/certbot/plugins/standalone.py +++ b/certbot/plugins/standalone.py @@ -1,5 +1,4 @@ """Standalone Authenticator.""" -import argparse import collections import logging import socket @@ -108,52 +107,6 @@ class ServerManager(object): return self._instances.copy() -SUPPORTED_CHALLENGES = [challenges.HTTP01] \ -# type: List[Type[challenges.KeyAuthorizationChallenge]] - - -class SupportedChallengesAction(argparse.Action): - """Action class for parsing standalone_supported_challenges.""" - - def __call__(self, parser, namespace, values, option_string=None): - logger.warning( - "The standalone specific supported challenges flag is " - "deprecated. Please use the --preferred-challenges flag " - "instead.") - converted_values = self._convert_and_validate(values) - namespace.standalone_supported_challenges = converted_values - - def _convert_and_validate(self, data): - """Validate the value of supported challenges provided by the user. - - :param str data: comma delimited list of challenge types - - :returns: validated and converted list of challenge types - :rtype: str - - """ - challs = data.split(",") - unrecognized = [name for name in challs - if name not in challenges.Challenge.TYPES] - - # argparse.ArgumentErrors raised out of argparse.Action objects - # are caught by argparse which prints usage information and the - # error that occurred before calling sys.exit. - if unrecognized: - raise argparse.ArgumentError( - self, - "Unrecognized challenges: {0}".format(", ".join(unrecognized))) - - choices = set(chall.typ for chall in SUPPORTED_CHALLENGES) - if not set(challs).issubset(choices): - raise argparse.ArgumentError( - self, - "Plugin does not support the following (valid) " - "challenges: {0}".format(", ".join(set(challs) - choices))) - - return data - - @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(common.Plugin): @@ -184,16 +137,7 @@ class Authenticator(common.Plugin): @classmethod def add_parser_arguments(cls, add): - add("supported-challenges", - help=argparse.SUPPRESS, - action=SupportedChallengesAction, - default=",".join(chall.typ for chall in SUPPORTED_CHALLENGES)) - - @property - def supported_challenges(self): - """Challenges supported by this plugin.""" - return [challenges.Challenge.TYPES[name] for name in - self.conf("supported-challenges").split(",")] + pass # No additional argument for the standalone plugin parser def more_info(self): # pylint: disable=missing-docstring return("This authenticator creates its own ephemeral TCP listener " @@ -206,7 +150,7 @@ class Authenticator(common.Plugin): def get_chall_pref(self, domain): # pylint: disable=unused-argument,missing-docstring - return self.supported_challenges + return [challenges.HTTP01] def perform(self, achalls): # pylint: disable=missing-docstring return [self._try_perform_single(achall) for achall in achalls] diff --git a/certbot/plugins/standalone_test.py b/certbot/plugins/standalone_test.py index db4dbeb3b..b2a7c32d4 100644 --- a/certbot/plugins/standalone_test.py +++ b/certbot/plugins/standalone_test.py @@ -1,5 +1,4 @@ """Tests for certbot.plugins.standalone.""" -import argparse import socket import unittest # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi @@ -73,38 +72,6 @@ class ServerManagerTest(unittest.TestCase): maybe_another_server.close() -class SupportedChallengesActionTest(unittest.TestCase): - """Tests for plugins.standalone.SupportedChallengesAction.""" - - def _call(self, value): - with mock.patch("certbot.plugins.standalone.logger") as mock_logger: - # stderr is mocked to prevent potential argparse error - # output from cluttering test output - with mock.patch("sys.stderr"): - config = self.parser.parse_args([self.flag, value]) - - self.assertTrue(mock_logger.warning.called) - return getattr(config, self.dest) - - def setUp(self): - self.flag = "--standalone-supported-challenges" - self.dest = self.flag[2:].replace("-", "_") - self.parser = argparse.ArgumentParser() - - from certbot.plugins.standalone import SupportedChallengesAction - self.parser.add_argument(self.flag, action=SupportedChallengesAction) - - def test_correct(self): - self.assertEqual("http-01", self._call("http-01")) - - def test_unrecognized(self): - assert "foo" not in challenges.Challenge.TYPES - self.assertRaises(SystemExit, self._call, "foo") - - def test_not_subset(self): - self.assertRaises(SystemExit, self._call, "dns") - - def get_open_port(): """Gets an open port number from the OS.""" open_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) @@ -120,21 +87,10 @@ class AuthenticatorTest(unittest.TestCase): def setUp(self): from certbot.plugins.standalone import Authenticator - self.config = mock.MagicMock( - http01_port=get_open_port(), - standalone_supported_challenges="http-01") + self.config = mock.MagicMock(http01_port=get_open_port()) self.auth = Authenticator(self.config, name="standalone") self.auth.servers = mock.MagicMock() - def test_supported_challenges(self): - self.assertEqual(self.auth.supported_challenges, - [challenges.HTTP01]) - - def test_supported_challenges_configured(self): - self.config.standalone_supported_challenges = "http-01" - self.assertEqual(self.auth.supported_challenges, - [challenges.HTTP01]) - def test_more_info(self): self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) @@ -142,11 +98,6 @@ class AuthenticatorTest(unittest.TestCase): self.assertEqual(self.auth.get_chall_pref(domain=None), [challenges.HTTP01]) - def test_get_chall_pref_configured(self): - self.config.standalone_supported_challenges = "http-01" - self.assertEqual(self.auth.get_chall_pref(domain=None), - [challenges.HTTP01]) - def test_perform(self): achalls = self._get_achalls() response = self.auth.perform(achalls) diff --git a/certbot/renewal.py b/certbot/renewal.py index 4c592b27f..9da0ec596 100644 --- a/certbot/renewal.py +++ b/certbot/renewal.py @@ -35,10 +35,8 @@ logger = logging.getLogger(__name__) # the renewal configuration process loses this information. STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent", "server", "account", "authenticator", "installer", - "standalone_supported_challenges", "renew_hook", - "pre_hook", "post_hook", "tls_sni_01_address", - "http01_address"] -INT_CONFIG_ITEMS = ["rsa_key_size", "tls_sni_01_port", "http01_port"] + "renew_hook", "pre_hook", "post_hook", "http01_address"] +INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"] BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key", "autorenew"] diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 60191730f..ebba0b0c0 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -235,13 +235,19 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertEqual(namespace.domains, ['example.com', 'another.net']) def test_preferred_challenges(self): - short_args = ['--preferred-challenges', 'http, tls-sni-01, dns'] + short_args = ['--preferred-challenges', 'http, dns'] namespace = self.parse(short_args) - expected = [challenges.HTTP01.typ, - challenges.TLSSNI01.typ, challenges.DNS01.typ] + expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(namespace.pref_challs, expected) + # TODO: to be removed once tls-sni deprecation logic is removed + with mock.patch('certbot.cli.logger.warning') as mock_warn: + self.assertEqual(self.parse(['--preferred-challenges', 'http, tls-sni']).pref_challs, + [challenges.HTTP01.typ]) + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue('deprecated' in mock_warn.call_args[0][0]) + short_args = ['--preferred-challenges', 'jumping-over-the-moon'] # argparse.ArgumentError makes argparse print more information # to stderr and call sys.exit() @@ -260,12 +266,13 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods def test_no_gui(self): args = ['renew', '--dialog'] - stderr = six.StringIO() - with mock.patch('certbot.main.sys.stderr', new=stderr): + with mock.patch("certbot.util.logger.warning") as mock_warn: namespace = self.parse(args) self.assertTrue(namespace.noninteractive_mode) - self.assertTrue("--dialog is deprecated" in stderr.getvalue()) + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) + self.assertEqual("--dialog", mock_warn.call_args[0][1]) def _check_server_conflict_message(self, parser_args, conflicting_args): try: diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index 10d9059b3..ce3cded20 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -17,11 +17,11 @@ class NamespaceConfigTest(test_util.ConfigTestCase): super(NamespaceConfigTest, self).setUp() self.config.foo = 'bar' self.config.server = 'https://acme-server.org:443/new' - self.config.tls_sni_01_port = 1234 + self.config.https_port = 1234 self.config.http01_port = 4321 def test_init_same_ports(self): - self.config.namespace.tls_sni_01_port = 4321 + self.config.namespace.https_port = 4321 from certbot.configuration import NamespaceConfig self.assertRaises(errors.Error, NamespaceConfig, self.config.namespace) @@ -79,7 +79,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir', 'logs_dir', 'http01_port', - 'tls_sni_01_port', + 'https_port', 'domains', 'server']) mock_namespace.config_dir = config_base mock_namespace.work_dir = work_base @@ -126,7 +126,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir', 'logs_dir', 'http01_port', - 'tls_sni_01_port', + 'https_port', 'domains', 'server']) mock_namespace.config_dir = config_base mock_namespace.work_dir = work_base diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index d292d909a..94e321f0d 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -58,11 +58,15 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_list(self, mock_set_by_cli): mock_set_by_cli.return_value = False + # TODO: remove tls-sni and related assertions to logger.warning call once + # the deprecation logic has been removed renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')} - self._call(self.config, renewalparams) - expected = [challenges.TLSSNI01.typ, - challenges.HTTP01.typ, challenges.DNS01.typ] + with mock.patch('certbot.renewal.cli.logger.warning') as mock_warn: + self._call(self.config, renewalparams) + expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(self.config.pref_challs, expected) + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue('deprecated' in mock_warn.call_args[0][0]) @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_str(self, mock_set_by_cli): diff --git a/certbot/tests/testdata/sample-renewal-ancient.conf b/certbot/tests/testdata/sample-renewal-ancient.conf index 333bcaa18..9586d5492 100644 --- a/certbot/tests/testdata/sample-renewal-ancient.conf +++ b/certbot/tests/testdata/sample-renewal-ancient.conf @@ -62,14 +62,12 @@ break_my_certs = False standalone = True manual = False server = https://acme-staging.api.letsencrypt.org/directory -standalone_supported_challenges = "tls-sni-01,http-01" webroot = True os_packages_only = False apache_init_script = None user_agent = None apache_le_vhost_ext = -le-ssl.conf debug = False -tls_sni_01_port = 443 logs_dir = /var/log/letsencrypt apache_vhost_root = /etc/apache2/sites-available configurator = None diff --git a/certbot/tests/testdata/sample-renewal.conf b/certbot/tests/testdata/sample-renewal.conf index 04f9ae8ca..936c5c0e0 100644 --- a/certbot/tests/testdata/sample-renewal.conf +++ b/certbot/tests/testdata/sample-renewal.conf @@ -62,14 +62,12 @@ break_my_certs = False standalone = True manual = False server = https://acme-staging-v02.api.letsencrypt.org/directory -standalone_supported_challenges = "tls-sni-01,http-01" webroot = False os_packages_only = False apache_init_script = None user_agent = None apache_le_vhost_ext = -le-ssl.conf debug = False -tls_sni_01_port = 443 logs_dir = /var/log/letsencrypt apache_vhost_root = /etc/apache2/sites-available configurator = None diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index cac587995..b848dbb9f 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -351,29 +351,28 @@ class AddDeprecatedArgumentTest(unittest.TestCase): def _call(self, argument_name, nargs): from certbot.util import add_deprecated_argument - add_deprecated_argument(self.parser.add_argument, argument_name, nargs) def test_warning_no_arg(self): self._call("--old-option", 0) - stderr = self._get_argparse_warnings(["--old-option"]) - self.assertTrue("--old-option is deprecated" in stderr) + with mock.patch("certbot.util.logger.warning") as mock_warn: + self.parser.parse_args(["--old-option"]) + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) + self.assertEqual("--old-option", mock_warn.call_args[0][1]) def test_warning_with_arg(self): self._call("--old-option", 1) - stderr = self._get_argparse_warnings(["--old-option", "42"]) - self.assertTrue("--old-option is deprecated" in stderr) - - def _get_argparse_warnings(self, args): - stderr = six.StringIO() - with mock.patch("certbot.util.sys.stderr", new=stderr): - self.parser.parse_args(args) - return stderr.getvalue() + with mock.patch("certbot.util.logger.warning") as mock_warn: + self.parser.parse_args(["--old-option", "42"]) + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) + self.assertEqual("--old-option", mock_warn.call_args[0][1]) def test_help(self): self._call("--old-option", 2) stdout = six.StringIO() - with mock.patch("certbot.util.sys.stdout", new=stdout): + with mock.patch("sys.stdout", new=stdout): try: self.parser.parse_args(["-h"]) except SystemExit: diff --git a/certbot/util.py b/certbot/util.py index 097593e9d..6481e30d2 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -13,7 +13,6 @@ import re import six import socket import subprocess -import sys from collections import OrderedDict @@ -477,8 +476,7 @@ def safe_email(email): class _ShowWarning(argparse.Action): """Action to log a warning when an argument is used.""" def __call__(self, unused1, unused2, unused3, option_string=None): - sys.stderr.write( - "Use of {0} is deprecated.\n".format(option_string)) + logger.warning("Use of %s is deprecated.", option_string) def add_deprecated_argument(add_argument, argument_name, nargs): diff --git a/docs/cli-help.txt b/docs/cli-help.txt index ed7794c3b..d883319f4 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -1,4 +1,4 @@ -usage: +usage: certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, diff --git a/docs/using.rst b/docs/using.rst index de17742a1..d2799f7dc 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -169,9 +169,6 @@ the bound IPv6 port and the failure during the second bind is expected. Use ``---address`` to explicitly tell Certbot which interface (and protocol) to bind. -.. note:: The ``--standalone-supported-challenges`` option has been - deprecated since ``certbot`` version 0.9.0. - .. _dns_plugins: DNS Plugins diff --git a/examples/cli.ini b/examples/cli.ini index dbaa9c599..4215fda5b 100644 --- a/examples/cli.ini +++ b/examples/cli.ini @@ -15,7 +15,6 @@ rsa-key-size = 4096 # Uncomment to use the standalone authenticator on port 443 # authenticator = standalone -# standalone-supported-challenges = tls-sni-01 # Uncomment to use the webroot authenticator. Replace webroot-path with the # path to the public_html / webroot folder being served by your web server. diff --git a/tests/integration/_common.sh b/tests/integration/_common.sh index bccba0891..a0cf3d1b4 100755 --- a/tests/integration/_common.sh +++ b/tests/integration/_common.sh @@ -61,7 +61,7 @@ certbot_test_no_force_renew () { --server "${SERVER:-http://localhost:4000/directory}" \ --no-verify-ssl \ --http-01-port $http_01_port \ - --tls-sni-01-port $https_port \ + --https-port $https_port \ --manual-public-ip-logging-ok \ $other_flags \ --non-interactive \ -- cgit v1.2.3 From a03e7b95d3adf029f91aa48b7903f765b6cb936f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 27 Mar 2019 02:26:38 +0100 Subject: Deprecate all tls-sni related objects in acme module (#6859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is a part of the tls-sni-01 removal plan described in #6849. As `acme` is a library, we need to put some efforts to make a decent deprecation path before totally removing tls-sni in it. While initialization of `acme.challenges.TLSSNI01` was already creating deprecation warning, not all cases were covered. For instance, and innocent call like this ... ```python if not isinstance(challenge, acme.challenges.TLSSNI01): print('I am not using this TLS-SNI deprecated stuff, what could possibly go wrong?') ``` ... would break if we suddenly remove all objects related to this challenge. So, I use the _Deprecator Warning Machine, Let's Pacify this Technical Debt_ (Guido ®), to make `acme.challenges` and `acme.standalone` patch themselves, and display a deprecation warning on stderr for any access to the tls-sni challenge objects. No dev should be able to avoid the deprecation warning. I set the deprecation warning in the idea to remove the code on `0.34.0`, but the exact deprecation window is open to discussion of course. * Modules challenges and standalone patch themselves to generated deprecation warning when tls-sni related objects are accessed. * Correct unit tests * Correct lint * Update challenges_test.py * Correct lint * Fix an error during tests * Update coverage * Use multiprocessing for coverage * Add coverage * Update test_util.py * Factor the logic about global deprecation warning when accessing TLS-SNI-01 attributes * Fix coverage * Add comment for cryptography example. * Use warnings. * Add a changelog * Fix deprecation during tests * Reload * Update acme/acme/__init__.py Co-Authored-By: adferrand * Update CHANGELOG.md * Pick a random free port. --- CHANGELOG.md | 2 ++ acme/acme/__init__.py | 28 ++++++++++++++++++++++++ acme/acme/challenges.py | 9 +++++--- acme/acme/challenges_test.py | 28 +++++++++++------------- acme/acme/crypto_util.py | 13 +++++------ acme/acme/messages.py | 2 +- acme/acme/standalone.py | 7 +++++- acme/acme/standalone_test.py | 52 +++++++++++++++++++++++++------------------- acme/acme/test_util.py | 9 -------- pytest.ini | 2 +- 10 files changed, 92 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9761ad3e9..2cc823d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Support for TLS-SNI-01 has been removed from all official Certbot plugins. +* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone` + modules are deprecated and will be removed soon. * CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will generate a deprecation warning if used, and will be removed soon. * Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index d91072a3b..20c008d64 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -6,6 +6,7 @@ This module is an implementation of the `ACME protocol`_. """ import sys +import warnings # This code exists to keep backwards compatibility with people using acme.jose # before it became the standalone josepy package. @@ -20,3 +21,30 @@ for mod in list(sys.modules): # preserved (acme.jose.* is josepy.*) if mod == 'josepy' or mod.startswith('josepy.'): sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod] + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if 'TLSSNI01' in attr: + warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr), + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 6f2b3757b..36e7ab41c 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -4,7 +4,7 @@ import functools import hashlib import logging import socket -import warnings +import sys from cryptography.hazmat.primitives import hashes # type: ignore import josepy as jose @@ -15,6 +15,7 @@ import six from acme import errors from acme import crypto_util from acme import fields +from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -515,8 +516,6 @@ class TLSSNI01(KeyAuthorizationChallenge): #n = jose.Field("n", encoder=int, decoder=int) def __init__(self, *args, **kwargs): - warnings.warn("TLS-SNI-01 is deprecated, and will stop working soon.", - DeprecationWarning, stacklevel=2) super(TLSSNI01, self).__init__(*args, **kwargs) def validation(self, account_key, **kwargs): @@ -641,3 +640,7 @@ class DNSResponse(ChallengeResponse): """ return chall.check_validation(self.validation, account_public_key) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 4b905c1e5..edfaa3423 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -1,6 +1,5 @@ """Tests for acme.challenges.""" import unittest -import warnings import josepy as jose import mock @@ -374,25 +373,16 @@ class TLSSNI01Test(unittest.TestCase): 'type': 'tls-sni-01', 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', } - - def _msg(self): from acme.challenges import TLSSNI01 - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") - msg = TLSSNI01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - assert warn is not None # using a raw assert for mypy - self.assertTrue(len(warn) == 1) - self.assertTrue(issubclass(warn[-1].category, DeprecationWarning)) - self.assertTrue('deprecated' in str(warn[-1].message)) - return msg + self.msg = TLSSNI01( + token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) def test_to_partial_json(self): - self.assertEqual(self.jmsg, self._msg().to_partial_json()) + self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import TLSSNI01 - self.assertEqual(self._msg(), TLSSNI01.from_json(self.jmsg)) + self.assertEqual(self.msg, TLSSNI01.from_json(self.jmsg)) def test_from_json_hashable(self): from acme.challenges import TLSSNI01 @@ -407,10 +397,18 @@ class TLSSNI01Test(unittest.TestCase): @mock.patch('acme.challenges.TLSSNI01Response.gen_cert') def test_validation(self, mock_gen_cert): mock_gen_cert.return_value = ('cert', 'key') - self.assertEqual(('cert', 'key'), self._msg().validation( + self.assertEqual(('cert', 'key'), self.msg.validation( KEY, cert_key=mock.sentinel.cert_key)) mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key) + def test_deprecation_message(self): + with mock.patch('acme.warnings.warn') as mock_warn: + from acme.challenges import TLSSNI01 + assert TLSSNI01 + self.assertEqual(mock_warn.call_count, 1) + self.assertTrue('deprecated' in mock_warn.call_args[0][0]) + + class TLSALPN01ResponseTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index c88cab943..c47e88e73 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -18,17 +18,14 @@ from acme.magic_typing import Callable, Union, Tuple, Optional logger = logging.getLogger(__name__) -# TLSSNI01 certificate serving and probing is not affected by SSL -# vulnerabilities: prober needs to check certificate for expected -# contents anyway. Working SNI is the only thing that's necessary for -# the challenge and thus scoping down SSL/TLS method (version) would -# cause interoperability issues: TLSv1_METHOD is only compatible with +# Default SSL method selected here is the most compatible, while secure +# SSL method: TLSv1_METHOD is only compatible with # TLSv1_METHOD, while SSLv23_METHOD is compatible with all other # methods, including TLSv2_METHOD (read more at # https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni # should be changed to use "set_options" to disable SSLv2 and SSLv3, # in case it's used for things other than probing/serving! -_DEFAULT_TLSSNI01_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore +_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore class SSLSocket(object): # pylint: disable=too-few-public-methods @@ -40,7 +37,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods :ivar method: See `OpenSSL.SSL.Context` for allowed values. """ - def __init__(self, sock, certs, method=_DEFAULT_TLSSNI01_SSL_METHOD): + def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD): self.sock = sock self.certs = certs self.method = method @@ -112,7 +109,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods def probe_sni(name, host, port=443, timeout=300, - method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('', 0)): + method=_DEFAULT_SSL_METHOD, source_address=('', 0)): """Probe SNI server for SSL certificate. :param bytes name: Byte string to send as the server name in the diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 7c82c8507..d8684603c 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -3,7 +3,7 @@ import six import json try: from collections.abc import Hashable # pylint: disable=no-name-in-module -except ImportError: +except ImportError: # pragma: no cover from collections import Hashable import josepy as jose diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index ff9159933..c82967897 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -17,6 +17,7 @@ import OpenSSL from acme import challenges from acme import crypto_util from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -37,7 +38,7 @@ class TLSServer(socketserver.TCPServer): self.certs = kwargs.pop("certs", {}) self.method = kwargs.pop( # pylint: disable=protected-access - "method", crypto_util._DEFAULT_TLSSNI01_SSL_METHOD) + "method", crypto_util._DEFAULT_SSL_METHOD) self.allow_reuse_address = kwargs.pop("allow_reuse_address", True) socketserver.TCPServer.__init__(self, *args, **kwargs) @@ -296,5 +297,9 @@ def simple_tls_sni_01_server(cli_args, forever=True): server.handle_request() +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) + + if __name__ == "__main__": sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index ee527782a..953df40d4 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -1,13 +1,15 @@ """Tests for acme.standalone.""" +import multiprocessing import os import shutil import socket import threading import tempfile import unittest +import time +from contextlib import closing from six.moves import http_client # pylint: disable=import-error -from six.moves import queue # pylint: disable=import-error from six.moves import socketserver # type: ignore # pylint: disable=import-error import josepy as jose @@ -16,6 +18,7 @@ import requests from acme import challenges from acme import crypto_util +from acme import errors from acme import test_util from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module @@ -248,7 +251,6 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): self.assertFalse(self._test_http01(add=False)) -@test_util.broken_on_windows class TestSimpleTLSSNI01Server(unittest.TestCase): """Tests for acme.standalone.simple_tls_sni_01_server.""" @@ -263,35 +265,41 @@ class TestSimpleTLSSNI01Server(unittest.TestCase): shutil.copy(test_util.vector_path('rsa2048_key.pem'), os.path.join(localhost_dir, 'key.pem')) + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.bind(('', 0)) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.port = sock.getsockname()[1] + from acme.standalone import simple_tls_sni_01_server - self.thread = threading.Thread( - target=simple_tls_sni_01_server, kwargs={ - 'cli_args': ('filename',), - 'forever': False, - }, - ) + self.process = multiprocessing.Process(target=simple_tls_sni_01_server, + args=(['path', '-p', str(self.port)],)) self.old_cwd = os.getcwd() os.chdir(self.test_cwd) def tearDown(self): os.chdir(self.old_cwd) - self.thread.join() + if self.process.is_alive(): + self.process.terminate() shutil.rmtree(self.test_cwd) - @mock.patch('acme.standalone.logger') - def test_it(self, mock_logger): - # Use a Queue because mock objects aren't thread safe. - q = queue.Queue() # type: queue.Queue[int] - # Add port number to the queue. - mock_logger.info.side_effect = lambda *args: q.put(args[-1]) - self.thread.start() - - # After the timeout, an exception is raised if the queue is empty. - port = q.get(timeout=5) - cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', port) + @mock.patch('acme.standalone.TLSSNI01Server.handle_request') + def test_mock(self, handle): + from acme.standalone import simple_tls_sni_01_server + simple_tls_sni_01_server(cli_args=['path', '-p', str(self.port)], forever=False) + self.assertEqual(handle.call_count, 1) + + def test_live(self): + self.process.start() + cert = None + for _ in range(50): + time.sleep(0.1) + try: + cert = crypto_util.probe_sni(b'localhost', b'127.0.0.1', self.port) + break + except errors.Error: # pragma: no cover + pass self.assertEqual(jose.ComparableX509(cert), - test_util.load_comparable_cert( - 'rsa2048_cert.pem')) + test_util.load_comparable_cert('rsa2048_cert.pem')) if __name__ == "__main__": diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py index f97614700..1a0b67056 100644 --- a/acme/acme/test_util.py +++ b/acme/acme/test_util.py @@ -4,7 +4,6 @@ """ import os -import sys import pkg_resources import unittest @@ -95,11 +94,3 @@ def skip_unless(condition, reason): # pragma: no cover return lambda cls: cls else: return lambda cls: None - -def broken_on_windows(function): - """Decorator to skip temporarily a broken test on Windows.""" - reason = 'Test is broken and ignored on windows but should be fixed.' - return unittest.skipIf( - sys.platform == 'win32' - and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', - reason)(function) diff --git a/pytest.ini b/pytest.ini index 49db7da09..2531e50d2 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,6 +13,6 @@ filterwarnings = error ignore:decodestring:DeprecationWarning - ignore:TLS-SNI-01:DeprecationWarning + ignore:(TLSSNI01|TLS-SNI-01):DeprecationWarning ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* -- cgit v1.2.3 From 491d6c8f4538c28f7c05d780db9a700982460242 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 26 Mar 2019 23:27:06 -0700 Subject: Revert "Configure jessie repos in LTS mode during Docker build (#6887)" (#6889) This reverts commit a27bd28b39e5312ef77d5767a989fc4661e179fb. --- letsencrypt-auto-source/Dockerfile.jessie | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/letsencrypt-auto-source/Dockerfile.jessie b/letsencrypt-auto-source/Dockerfile.jessie index 528ab5c4a..9ee37b763 100644 --- a/letsencrypt-auto-source/Dockerfile.jessie +++ b/letsencrypt-auto-source/Dockerfile.jessie @@ -7,9 +7,7 @@ FROM debian:jessie RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups sudo --uid 1000 lea # Install pip, sudo, and openssl: -RUN echo "deb http://deb.debian.org/debian/ jessie main" > /etc/apt/sources.list && \ - echo "deb http://security.debian.org/ jessie/updates main" >> /etc/apt/sources.list && \ - apt-get update && \ +RUN apt-get update && \ apt-get -q -y install python-pip sudo openssl && \ apt-get clean # Use pipstrap to update to a stable and tested version of pip -- cgit v1.2.3 From b30a5e5b738f812c23937ea63eedb6443f7b98fd Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 27 Mar 2019 19:10:52 +0200 Subject: Add a test to ensure test coverage regardless of the vhost order (#6873) Add a new test to make sure that we are covering all the branches of get_virtual_hosts() regardless of the order that Augeas returns the found VirtualHost paths. Fixes: #6813 * Add a test to ensure test coverage regardless of the order of returned vhosts * Use deepcopy instead, and increase coverage requirement back to 100% --- .../certbot_apache/tests/configurator_test.py | 24 ++++++++++++++++++++++ tox.cover.py | 5 +---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index a8d5dd96e..884e82cb3 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1,5 +1,6 @@ # pylint: disable=too-many-public-methods,too-many-lines """Test for certbot_apache.configurator.""" +import copy import os import shutil import socket @@ -1512,6 +1513,29 @@ class MultipleVhostsTest(util.ApacheTest): second_id = self.config.add_vhost_id(self.vh_truth[0]) self.assertEqual(first_id, second_id) + def test_realpath_replaces_symlink(self): + orig_match = self.config.aug.match + mock_vhost = copy.deepcopy(self.vh_truth[0]) + mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') + mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') + mock_vhost.enabled = False + self.config.parser.parse_file(mock_vhost.filep) + + def mock_match(aug_expr): + """Return a mocked match list of VirtualHosts""" + if "/mocked/path" in aug_expr: + return [self.vh_truth[1].path, self.vh_truth[0].path, mock_vhost.path] + return orig_match(aug_expr) + + self.config.parser.parser_paths = ["/mocked/path"] + self.config.aug.match = mock_match + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + self.assertTrue(vhs[0] == self.vh_truth[1]) + # mock_vhost should have replaced the vh_truth[0], because its filepath + # isn't a symlink + self.assertTrue(vhs[1] == mock_vhost) + class AugeasVhostsTest(util.ApacheTest): """Test vhosts with illegal names dependent on augeas version.""" diff --git a/tox.cover.py b/tox.cover.py index 6f5392b71..d0f97626a 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -14,10 +14,7 @@ DEFAULT_PACKAGES = [ COVER_THRESHOLDS = { 'certbot': {'linux': 98, 'windows': 93}, 'acme': {'linux': 100, 'windows': 99}, - # certbot_apache coverage not being at 100% is a workaround for - # https://github.com/certbot/certbot/issues/6813. We should increase - # the minimum coverage back to 100% when this issue is resolved. - 'certbot_apache': {'linux': 99, 'windows': 99}, + 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, 'certbot_dns_cloudxns': {'linux': 99, 'windows': 99}, 'certbot_dns_digitalocean': {'linux': 98, 'windows': 98}, -- cgit v1.2.3 From 414c70aa6ce3d4773259f5d4c077252c8d01ef79 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 27 Mar 2019 13:07:42 -0700 Subject: Bump the min Certbot version for nginx plugin. (#6890) * Bump the min Certbot version for nginx plugin. * s/certbot/./g --- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 2108298ae..db6b261f0 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,2 @@ acme[dev]==0.29.0 -certbot[dev]==0.32.0 +-e .[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 7494bf6bf..49432a00d 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '0.33.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.26.0', - 'certbot>=0.22.0', + 'certbot>=0.33.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? -- cgit v1.2.3 From 8b8fc5ae543e3ce81db2f65e445ec2b67f223c50 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 27 Mar 2019 13:27:38 -0700 Subject: Fix acme race condition (#6892) * Fix acme race condition. * Assert process has executed. --- acme/acme/standalone_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 953df40d4..90e1af37f 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -280,6 +280,10 @@ class TestSimpleTLSSNI01Server(unittest.TestCase): os.chdir(self.old_cwd) if self.process.is_alive(): self.process.terminate() + self.process.join(timeout=5) + # Check that we didn't timeout waiting for the process to + # terminate. + self.assertNotEqual(self.process.exitcode, None) shutil.rmtree(self.test_cwd) @mock.patch('acme.standalone.TLSSNI01Server.handle_request') -- cgit v1.2.3 From b0fb570c1c9b5391babd35806cfa0caf8f8b3bf3 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 27 Mar 2019 14:38:28 -0700 Subject: Bump min nginx requirements to tested versions. (#6891) --- certbot-nginx/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 49432a00d..f768ed5d9 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -9,7 +9,7 @@ version = '0.33.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.26.0', + 'acme>=0.29.0', 'certbot>=0.33.0.dev0', 'mock', 'PyOpenSSL', -- cgit v1.2.3 From 6ce6c679320c53373b1da3180d664a34b943d883 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 28 Mar 2019 23:51:48 +0100 Subject: [Windows] Security model for files permissions - STEP 1 (#6893) This PR is the first part of #6497 to ease the integration, following the new plan propose by @bmw here: #6497 (comment) This step 1 refactor existing certbot.compat module into certbot.compat.misc, without any logic changed. Package certbot.compat will host the new modules that constitute the security model for Windows. * Create the certbot.compat package. Move logic in certbot.compat.misc * Add doc * Fix lint * Correct mypy * Update client.py --- certbot-nginx/certbot_nginx/configurator.py | 12 +- certbot/account.py | 9 +- certbot/cert_manager.py | 11 +- certbot/client.py | 16 +-- certbot/compat.py | 167 ---------------------------- certbot/compat/__init__.py | 6 + certbot/compat/misc.py | 165 +++++++++++++++++++++++++++ certbot/configuration.py | 6 +- certbot/constants.py | 12 +- certbot/crypto_util.py | 18 +-- certbot/display/ops.py | 5 +- certbot/display/util.py | 6 +- certbot/log.py | 5 +- certbot/main.py | 15 ++- certbot/plugins/webroot_test.py | 8 +- certbot/reverter.py | 11 +- certbot/storage.py | 9 +- certbot/tests/account_test.py | 8 +- certbot/tests/compat_test.py | 5 +- certbot/tests/configuration_test.py | 6 +- certbot/tests/display/util_test.py | 5 +- certbot/tests/log_test.py | 5 +- certbot/tests/main_test.py | 23 ++-- certbot/tests/reverter_test.py | 3 +- certbot/tests/storage_test.py | 16 ++- certbot/tests/util_test.py | 20 ++-- certbot/util.py | 9 +- 27 files changed, 284 insertions(+), 297 deletions(-) delete mode 100644 certbot/compat.py create mode 100644 certbot/compat/__init__.py create mode 100644 certbot/compat/misc.py diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 8b39ee664..dbcd07a03 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -12,24 +12,22 @@ import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util +from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module -from certbot import compat from certbot import constants as core_constants from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import util - +from certbot.compat import misc from certbot.plugins import common from certbot_nginx import constants from certbot_nginx import display_ops +from certbot_nginx import http_01 from certbot_nginx import nginxparser +from certbot_nginx import obj # pylint: disable=unused-import from certbot_nginx import parser -from certbot_nginx import http_01 -from certbot_nginx import obj # pylint: disable=unused-import -from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module - NAME_RANK = 0 START_WILDCARD_RANK = 1 @@ -895,7 +893,7 @@ class NginxConfigurator(common.Installer): have permissions of root. """ - uid = compat.os_geteuid() + uid = misc.os_geteuid() util.make_or_verify_dir( self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) util.make_or_verify_dir( diff --git a/certbot/account.py b/certbot/account.py index 0c653f6dd..a37b49eb4 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -7,22 +7,21 @@ import os import shutil import socket -from cryptography.hazmat.primitives import serialization import josepy as jose import pyrfc3339 import pytz import six import zope.component +from cryptography.hazmat.primitives import serialization from acme import fields as acme_fields from acme import messages -from certbot import compat from certbot import constants from certbot import errors from certbot import interfaces from certbot import util - +from certbot.compat import misc logger = logging.getLogger(__name__) @@ -141,7 +140,7 @@ class AccountFileStorage(interfaces.AccountStorage): """ def __init__(self, config): self.config = config - util.make_or_verify_dir(config.accounts_dir, 0o700, compat.os_geteuid(), + util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(), self.config.strict_permissions) def _account_dir_path(self, account_id): @@ -324,7 +323,7 @@ class AccountFileStorage(interfaces.AccountStorage): def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) - util.make_or_verify_dir(account_dir_path, 0o700, compat.os_geteuid(), + util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index 2a67f8765..8d6f04b71 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -2,20 +2,21 @@ import datetime import logging import os -import pytz import re import traceback + +import pytz import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from certbot import compat + from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import ocsp from certbot import storage from certbot import util - +from certbot.compat import misc from certbot.display import util as display_util logger = logging.getLogger(__name__) @@ -105,7 +106,7 @@ def lineage_for_certname(cli_config, certname): """Find a lineage object with name certname.""" configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=compat.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) try: renewal_file = storage.renewal_file_for_certname(cli_config, certname) except errors.CertStorageError: @@ -375,7 +376,7 @@ def _search_lineages(cli_config, func, initial_rv, *args): """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=compat.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): diff --git a/certbot/client.py b/certbot/client.py index 38b77a772..a6ca17945 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -4,14 +4,13 @@ import logging import os import platform - +import OpenSSL +import josepy as jose +import zope.component from cryptography.hazmat.backends import default_backend # https://github.com/python/typeshed/blob/master/third_party/ # 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore -import josepy as jose -import OpenSSL -import zope.component from acme import client as acme_client from acme import crypto_util as acme_crypto_util @@ -20,11 +19,9 @@ from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import,no-name-in-module import certbot - from certbot import account from certbot import auth_handler from certbot import cli -from certbot import compat from certbot import constants from certbot import crypto_util from certbot import eff @@ -34,12 +31,11 @@ from certbot import interfaces from certbot import reverter from certbot import storage from certbot import util - -from certbot.display import ops as display_ops +from certbot.compat import misc from certbot.display import enhancements +from certbot.display import ops as display_ops from certbot.plugins import selection as plugin_selection - logger = logging.getLogger(__name__) @@ -466,7 +462,7 @@ class Client(object): """ for path in cert_path, chain_path, fullchain_path: util.make_or_verify_dir( - os.path.dirname(path), 0o755, compat.os_geteuid(), + os.path.dirname(path), 0o755, misc.os_geteuid(), self.config.strict_permissions) diff --git a/certbot/compat.py b/certbot/compat.py deleted file mode 100644 index f533f5954..000000000 --- a/certbot/compat.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Compatibility layer to run certbot both on Linux and Windows. - -This module contains all required platform specific code, -allowing the rest of Certbot codebase to be platform agnostic. -""" -import os -import select -import sys -import errno -import ctypes -import stat - -from certbot import errors - -UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ - 'certificates', 'enhance', 'revoke', 'delete', - 'register', 'unregister', 'config_changes', 'plugins'] - - -def raise_for_non_administrative_windows_rights(subcommand): - """ - On Windows, raise if current shell does not have the administrative rights. - Do nothing on Linux. - - :param str subcommand: The subcommand (like 'certonly') passed to the certbot client. - - :raises .errors.Error: If the provided subcommand must be run on a shell with - administrative rights, and current shell does not have these rights. - - """ - # Why not simply try ctypes.windll.shell32.IsUserAnAdmin() and catch AttributeError ? - # Because windll exists only on a Windows runtime, and static code analysis engines - # do not like at all non existent objects when run from Linux (even if we handle properly - # all the cases in the code). - # So we access windll only by reflection to trick theses engines. - if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED: - windll = getattr(ctypes, 'windll') - if windll.shell32.IsUserAnAdmin() == 0: - raise errors.Error( - 'Error, "{0}" subcommand must be run on a shell with administrative rights.' - .format(subcommand)) - - -def os_geteuid(): - """ - Get current user uid - - :returns: The current user uid. - :rtype: int - - """ - try: - # Linux specific - return os.geteuid() - except AttributeError: - # Windows specific - return 0 - - -def os_rename(src, dst): - """ - Rename a file to a destination path and handles situations where the destination exists. - - :param str src: The current file path. - :param str dst: The new file path. - """ - try: - os.rename(src, dst) - except OSError as err: - # Windows specific, renaming a file on an existing path is not possible. - # On Python 3, the best fallback with atomic capabilities we have is os.replace. - if err.errno != errno.EEXIST: - # Every other error is a legitimate exception. - raise - if not hasattr(os, 'replace'): # pragma: no cover - # We should never go on this line. Either we are on Linux and os.rename has succeeded, - # either we are on Windows, and only Python >= 3.4 is supported where os.replace is - # available. - raise RuntimeError('Error: tried to run os_rename on Python < 3.3. ' - 'Certbot supports only Python 3.4 >= on Windows.') - getattr(os, 'replace')(src, dst) - - -def readline_with_timeout(timeout, prompt): - """ - Read user input to return the first line entered, or raise after specified timeout. - - :param float timeout: The timeout in seconds given to the user. - :param str prompt: The prompt message to display to the user. - - :returns: The first line entered by the user. - :rtype: str - - """ - try: - # Linux specific - # - # Call to select can only be done like this on UNIX - rlist, _, _ = select.select([sys.stdin], [], [], timeout) - if not rlist: - raise errors.Error( - "Timed out waiting for answer to prompt '{0}'".format(prompt)) - return rlist[0].readline() - except OSError: - # Windows specific - # - # No way with select to make a timeout to the user input on Windows, - # as select only supports socket in this case. - # So no timeout on Windows for now. - return sys.stdin.readline() - - -def compare_file_modes(mode1, mode2): - """Return true if the two modes can be considered as equals for this platform""" - if os.name != 'nt': - # Linux specific: standard compare - return oct(stat.S_IMODE(mode1)) == oct(stat.S_IMODE(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 os.name != 'nt': - # Linux specific - return LINUX_DEFAULT_FOLDERS[folder_type] - # Windows specific - return WINDOWS_DEFAULT_FOLDERS[folder_type] - - -def underscores_for_unsupported_characters_in_path(path): - # type: (str) -> str - """ - Replace unsupported characters in path for current OS by underscores. - :param str path: the path to normalize - :return: the normalized path - :rtype: str - """ - if os.name != 'nt': - # Linux specific - return path - - # Windows specific - drive, tail = os.path.splitdrive(path) - return drive + tail.replace(':', '_') diff --git a/certbot/compat/__init__.py b/certbot/compat/__init__.py new file mode 100644 index 000000000..74451131a --- /dev/null +++ b/certbot/compat/__init__.py @@ -0,0 +1,6 @@ +""" +Compatibility layer to run certbot both on Linux and Windows. + +This package contains all logic that needs to be implemented specifically for Linux and for Windows. +Then the rest of certbot code relies on this module to be platform agnostic. +""" diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py new file mode 100644 index 000000000..3ea4a7908 --- /dev/null +++ b/certbot/compat/misc.py @@ -0,0 +1,165 @@ +""" +This compat module handles various platform specific calls that do not fall into one +particular category. +""" +import os +import select +import sys +import errno +import ctypes +import stat + +from certbot import errors + +UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ + 'certificates', 'enhance', 'revoke', 'delete', + 'register', 'unregister', 'config_changes', 'plugins'] + + +def raise_for_non_administrative_windows_rights(subcommand): + """ + On Windows, raise if current shell does not have the administrative rights. + Do nothing on Linux. + + :param str subcommand: The subcommand (like 'certonly') passed to the certbot client. + + :raises .errors.Error: If the provided subcommand must be run on a shell with + administrative rights, and current shell does not have these rights. + + """ + # Why not simply try ctypes.windll.shell32.IsUserAnAdmin() and catch AttributeError ? + # Because windll exists only on a Windows runtime, and static code analysis engines + # do not like at all non existent objects when run from Linux (even if we handle properly + # all the cases in the code). + # So we access windll only by reflection to trick theses engines. + if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED: + windll = getattr(ctypes, 'windll') + if windll.shell32.IsUserAnAdmin() == 0: + raise errors.Error( + 'Error, "{0}" subcommand must be run on a shell with administrative rights.' + .format(subcommand)) + + +def os_geteuid(): + """ + Get current user uid + + :returns: The current user uid. + :rtype: int + + """ + try: + # Linux specific + return os.geteuid() + except AttributeError: + # Windows specific + return 0 + + +def os_rename(src, dst): + """ + Rename a file to a destination path and handles situations where the destination exists. + + :param str src: The current file path. + :param str dst: The new file path. + """ + try: + os.rename(src, dst) + except OSError as err: + # Windows specific, renaming a file on an existing path is not possible. + # On Python 3, the best fallback with atomic capabilities we have is os.replace. + if err.errno != errno.EEXIST: + # Every other error is a legitimate exception. + raise + if not hasattr(os, 'replace'): # pragma: no cover + # We should never go on this line. Either we are on Linux and os.rename has succeeded, + # either we are on Windows, and only Python >= 3.4 is supported where os.replace is + # available. + raise RuntimeError('Error: tried to run os_rename on Python < 3.3. ' + 'Certbot supports only Python 3.4 >= on Windows.') + getattr(os, 'replace')(src, dst) + + +def readline_with_timeout(timeout, prompt): + """ + Read user input to return the first line entered, or raise after specified timeout. + + :param float timeout: The timeout in seconds given to the user. + :param str prompt: The prompt message to display to the user. + + :returns: The first line entered by the user. + :rtype: str + + """ + try: + # Linux specific + # + # Call to select can only be done like this on UNIX + rlist, _, _ = select.select([sys.stdin], [], [], timeout) + if not rlist: + raise errors.Error( + "Timed out waiting for answer to prompt '{0}'".format(prompt)) + return rlist[0].readline() + except OSError: + # Windows specific + # + # No way with select to make a timeout to the user input on Windows, + # as select only supports socket in this case. + # So no timeout on Windows for now. + return sys.stdin.readline() + + +def compare_file_modes(mode1, mode2): + """Return true if the two modes can be considered as equals for this platform""" + if os.name != 'nt': + # Linux specific: standard compare + return oct(stat.S_IMODE(mode1)) == oct(stat.S_IMODE(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 os.name != 'nt': + # Linux specific + return LINUX_DEFAULT_FOLDERS[folder_type] + # Windows specific + return WINDOWS_DEFAULT_FOLDERS[folder_type] + + +def underscores_for_unsupported_characters_in_path(path): + # type: (str) -> str + """ + Replace unsupported characters in path for current OS by underscores. + :param str path: the path to normalize + :return: the normalized path + :rtype: str + """ + if os.name != 'nt': + # Linux specific + return path + + # Windows specific + drive, tail = os.path.splitdrive(path) + return drive + tail.replace(':', '_') diff --git a/certbot/configuration.py b/certbot/configuration.py index 5d248f39e..2e7e39e28 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -2,14 +2,14 @@ import copy import os -from six.moves.urllib import parse # pylint: disable=import-error import zope.interface +from six.moves.urllib import parse # pylint: disable=import-error -from certbot import compat from certbot import constants from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import misc @zope.interface.implementer(interfaces.IConfig) @@ -70,7 +70,7 @@ class NamespaceConfig(object): def accounts_dir_for_server_path(self, server_path): """Path to accounts directory based on server_path""" - server_path = compat.underscores_for_unsupported_characters_in_path(server_path) + server_path = misc.underscores_for_unsupported_characters_in_path(server_path) return os.path.join( self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) diff --git a/certbot/constants.py b/certbot/constants.py index 31b243518..3b2f7a2d9 100644 --- a/certbot/constants.py +++ b/certbot/constants.py @@ -1,10 +1,12 @@ """Certbot constants.""" import logging import os + import pkg_resources from acme import challenges -from certbot import compat + +from certbot.compat import misc SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" """Setuptools entry point group name for plugins.""" @@ -14,7 +16,7 @@ OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" CLI_DEFAULTS = dict( config_files=[ - os.path.join(compat.get_default_folder('config'), 'cli.ini'), + os.path.join(misc.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"), @@ -87,9 +89,9 @@ CLI_DEFAULTS = dict( auth_cert_path="./cert.pem", auth_chain_path="./chain.pem", key_path=None, - config_dir=compat.get_default_folder('config'), - work_dir=compat.get_default_folder('work'), - logs_dir=compat.get_default_folder('logs'), + config_dir=misc.get_default_folder('config'), + work_dir=misc.get_default_folder('work'), + logs_dir=misc.get_default_folder('logs'), server="https://acme-v02.api.letsencrypt.org/directory", # Plugins parsers diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 66c68eb38..f976372c5 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -12,24 +12,24 @@ import warnings import pyrfc3339 import six import zope.component +from OpenSSL import SSL # type: ignore +from OpenSSL import crypto +# https://github.com/python/typeshed/tree/master/third_party/2/cryptography +from cryptography import x509 # type: ignore from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey -# https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography import x509 # type: ignore -from OpenSSL import crypto -from OpenSSL import SSL # type: ignore from acme import crypto_util as acme_crypto_util from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module -from certbot import compat + from certbot import errors from certbot import interfaces from certbot import util - +from certbot.compat import misc logger = logging.getLogger(__name__) @@ -61,7 +61,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): config = zope.component.getUtility(interfaces.IConfig) # Save file - util.make_or_verify_dir(key_dir, 0o700, compat.os_geteuid(), + util.make_or_verify_dir(key_dir, 0o700, misc.os_geteuid(), config.strict_permissions) key_f, key_path = util.unique_file( os.path.join(key_dir, keyname), 0o600, "wb") @@ -92,8 +92,8 @@ def init_save_csr(privkey, names, path): privkey.pem, names, must_staple=config.must_staple) # Save CSR - util.make_or_verify_dir(path, 0o755, compat.os_geteuid(), - config.strict_permissions) + util.make_or_verify_dir(path, 0o755, misc.os_geteuid(), + config.strict_permissions) csr_f, csr_filename = util.unique_file( os.path.join(path, "csr-certbot.pem"), 0o644, "wb") with csr_f: diff --git a/certbot/display/ops.py b/certbot/display/ops.py index 3dae1070b..db69f6c9f 100644 --- a/certbot/display/ops.py +++ b/certbot/display/ops.py @@ -4,11 +4,10 @@ import os import zope.component -from certbot import compat from certbot import errors from certbot import interfaces from certbot import util - +from certbot.compat import misc from certbot.display import util as display_util logger = logging.getLogger(__name__) @@ -36,7 +35,7 @@ def get_email(invalid=False, optional=True): "the client with --register-unsafely-without-email " "but make sure you then backup your account key from " "{0}\n\n".format(os.path.join( - compat.get_default_folder('config'), 'accounts'))) + misc.get_default_folder('config'), 'accounts'))) if optional: if invalid: msg += unsafe_suggestion diff --git a/certbot/display/util.py b/certbot/display/util.py index 772b67d74..72ee892a7 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -6,10 +6,10 @@ import textwrap import zope.interface -from certbot import compat from certbot import constants -from certbot import interfaces from certbot import errors +from certbot import interfaces +from certbot.compat import misc from certbot.display import completer logger = logging.getLogger(__name__) @@ -79,7 +79,7 @@ def input_with_timeout(prompt=None, timeout=36000.0): sys.stdout.write(prompt) sys.stdout.flush() - line = compat.readline_with_timeout(timeout, prompt) + line = misc.readline_with_timeout(timeout, prompt) if not line: raise EOFError diff --git a/certbot/log.py b/certbot/log.py index b883936f3..7c86dbdf0 100644 --- a/certbot/log.py +++ b/certbot/log.py @@ -13,6 +13,7 @@ and properly flushed before program exit. """ from __future__ import print_function + import functools import logging import logging.handlers @@ -23,10 +24,10 @@ import traceback from acme import messages -from certbot import compat from certbot import constants from certbot import errors from certbot import util +from certbot.compat import misc # Logging format CLI_FMT = "%(message)s" @@ -134,7 +135,7 @@ def setup_log_file_handler(config, logfile, fmt): # TODO: logs might contain sensitive data such as contents of the # private key! #525 util.set_up_core_dir( - config.logs_dir, 0o700, compat.os_geteuid(), config.strict_permissions) + config.logs_dir, 0o700, misc.os_geteuid(), config.strict_permissions) log_file_path = os.path.join(config.logs_dir, logfile) try: handler = logging.handlers.RotatingFileHandler( diff --git a/certbot/main.py b/certbot/main.py index a0c0ab64d..484ac52ea 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1,6 +1,7 @@ """Certbot main entry point.""" # pylint: disable=too-many-lines from __future__ import print_function + import functools import logging.handlers import os @@ -14,12 +15,10 @@ from acme import errors as acme_errors from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module import certbot - from certbot import account from certbot import cert_manager from certbot import cli from certbot import client -from certbot import compat from certbot import configuration from certbot import constants from certbot import crypto_util @@ -33,11 +32,11 @@ from certbot import reporter from certbot import storage from certbot import updater from certbot import util - +from certbot.compat import misc from certbot.display import util as display_util, ops as display_ops from certbot.plugins import disco as plugins_disco -from certbot.plugins import selection as plug_sel from certbot.plugins import enhancements +from certbot.plugins import selection as plug_sel USER_CANCELLED = ("User chose to cancel the operation and may " "reinvoke the client.") @@ -1285,16 +1284,16 @@ def make_or_verify_needed_dirs(config): """ util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, - compat.os_geteuid(), config.strict_permissions) + misc.os_geteuid(), config.strict_permissions) util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, - compat.os_geteuid(), config.strict_permissions) + misc.os_geteuid(), config.strict_permissions) hook_dirs = (config.renewal_pre_hooks_dir, config.renewal_deploy_hooks_dir, config.renewal_post_hooks_dir,) for hook_dir in hook_dirs: util.make_or_verify_dir(hook_dir, - uid=compat.os_geteuid(), + uid=misc.os_geteuid(), strict=config.strict_permissions) @@ -1345,7 +1344,7 @@ def main(cli_args=sys.argv[1:]): # On windows, shell without administrative right cannot create symlinks required by certbot. # So we check the rights before continuing. - compat.raise_for_non_administrative_windows_rights(config.verb) + misc.raise_for_non_administrative_windows_rights(config.verb) try: log.post_arg_parse_setup(config) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index 5303fe4da..a67ddbb83 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -17,14 +17,12 @@ import six from acme import challenges from certbot import achallenges -from certbot import compat from certbot import errors +from certbot.compat import misc from certbot.display import util as display_util - from certbot.tests import acme_util from certbot.tests import util as test_util - KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -171,14 +169,14 @@ class AuthenticatorTest(unittest.TestCase): # Remove exec bit from permission check, so that it # matches the file self.auth.perform([self.achall]) - self.assertTrue(compat.compare_file_modes(os.stat(self.validation_path).st_mode, 0o644)) + self.assertTrue(misc.compare_file_modes(os.stat(self.validation_path).st_mode, 0o644)) # Check permissions of the directories for dirpath, dirnames, _ in os.walk(self.path): for directory in dirnames: full_path = os.path.join(dirpath, directory) - self.assertTrue(compat.compare_file_modes(os.stat(full_path).st_mode, 0o755)) + self.assertTrue(misc.compare_file_modes(os.stat(full_path).st_mode, 0o755)) parent_gid = os.stat(self.path).st_gid parent_uid = os.stat(self.path).st_uid diff --git a/certbot/reverter.py b/certbot/reverter.py index 919037358..e08b468ac 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -10,12 +10,11 @@ import traceback import six import zope.component -from certbot import compat from certbot import constants from certbot import errors from certbot import interfaces from certbot import util - +from certbot.compat import misc logger = logging.getLogger(__name__) @@ -66,7 +65,7 @@ class Reverter(object): self.config = config util.make_or_verify_dir( - config.backup_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(), + config.backup_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), self.config.strict_permissions) def revert_temporary_config(self): @@ -220,7 +219,7 @@ class Reverter(object): """ util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(), + cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), self.config.strict_permissions) op_fd, existing_filepaths = self._read_and_append( @@ -434,7 +433,7 @@ class Reverter(object): cp_dir = self.config.in_progress_dir util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(), + cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), self.config.strict_permissions) return cp_dir @@ -576,7 +575,7 @@ class Reverter(object): timestamp = self._checkpoint_timestamp() final_dir = os.path.join(self.config.backup_dir, timestamp) try: - compat.os_rename(self.config.in_progress_dir, final_dir) + misc.os_rename(self.config.in_progress_dir, final_dir) return except OSError: logger.warning("Extreme, unexpected race condition, retrying (%s)", timestamp) diff --git a/certbot/storage.py b/certbot/storage.py index d17a0f29d..d0bc36c08 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -4,23 +4,22 @@ import glob import logging import os import re +import shutil import stat import configobj import parsedatetime import pytz -import shutil import six import certbot from certbot import cli -from certbot import compat from certbot import constants from certbot import crypto_util -from certbot import errors from certbot import error_handler +from certbot import errors from certbot import util - +from certbot.compat import misc from certbot.plugins import common as plugins_common from certbot.plugins import disco as plugins_disco @@ -192,7 +191,7 @@ def update_configuration(lineagename, archive_dir, target, cli_config): # Save only the config items that are relevant to renewal values = relevant_values(vars(cli_config.namespace)) write_renewal_config(config_filename, temp_filename, archive_dir, target, values) - compat.os_rename(temp_filename, config_filename) + misc.os_rename(temp_filename, config_filename) return configobj.ConfigObj(config_filename) diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index b062f437b..fb0205f9c 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -12,11 +12,9 @@ import pytz from acme import messages -from certbot import compat -from certbot import errors - import certbot.tests.util as test_util - +from certbot import errors +from certbot.compat import misc KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -116,7 +114,7 @@ class AccountFileStorageTest(test_util.ConfigTestCase): def test_init_creates_dir(self): self.assertTrue(os.path.isdir( - compat.underscores_for_unsupported_characters_in_path(self.config.accounts_dir))) + misc.underscores_for_unsupported_characters_in_path(self.config.accounts_dir))) @test_util.broken_on_windows def test_save_and_restore(self): diff --git a/certbot/tests/compat_test.py b/certbot/tests/compat_test.py index 552aa5645..ffbba24fd 100644 --- a/certbot/tests/compat_test.py +++ b/certbot/tests/compat_test.py @@ -1,8 +1,9 @@ """Tests for certbot.compat.""" import os -from certbot import compat import certbot.tests.util as test_util +from certbot.compat import misc + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of os_rename method""" @@ -15,7 +16,7 @@ class OsReplaceTest(test_util.TempDirTestCase): open(dst, 'w').close() # On Windows, a direct call to os.rename will fail because dst already exists. - compat.os_rename(src, dst) + misc.os_rename(src, dst) self.assertFalse(os.path.exists(src)) self.assertTrue(os.path.exists(dst)) diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index ce3cded20..a4d32a57f 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -4,12 +4,12 @@ import unittest import mock -from certbot import compat from certbot import constants from certbot import errors - +from certbot.compat import misc from certbot.tests import util as test_util + class NamespaceConfigTest(test_util.ConfigTestCase): """Tests for certbot.configuration.NamespaceConfig.""" @@ -48,7 +48,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): mock_constants.KEY_DIR = 'keys' mock_constants.TEMP_CHECKPOINT_DIR = 't' - ref_path = compat.underscores_for_unsupported_characters_in_path( + ref_path = misc.underscores_for_unsupported_characters_in_path( 'acc/acme-server.org:443/new') self.assertEqual( os.path.normpath(self.config.accounts_dir), diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 726eb0b0f..8ee0501ef 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -4,14 +4,13 @@ import socket import tempfile import unittest -import six import mock +import six from certbot import errors from certbot import interfaces from certbot.display import util as display_util - CHOICES = [("First", "Description1"), ("Second", "Description2")] TAGS = ["tag1", "tag2", "tag3"] TAGS_CHOICES = [("1", "tag1"), ("2", "tag2"), ("3", "tag3")] @@ -32,7 +31,7 @@ class InputWithTimeoutTest(unittest.TestCase): def test_input(self, prompt=None): expected = "foo bar" stdin = six.StringIO(expected + "\n") - with mock.patch("certbot.compat.select.select") as mock_select: + with mock.patch("certbot.compat.misc.select.select") as mock_select: mock_select.return_value = ([stdin], [], [],) self.assertEqual(self._call(prompt), expected) diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index b82cc6ca1..c929853dd 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -8,14 +8,13 @@ import unittest import mock import six - from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from certbot import compat from certbot import constants from certbot import errors from certbot import util +from certbot.compat import misc from certbot.tests import util as test_util @@ -261,7 +260,7 @@ class TempHandlerTest(unittest.TestCase): def test_permissions(self): self.assertTrue( - util.check_permissions(self.handler.path, 0o600, compat.os_geteuid())) + util.check_permissions(self.handler.path, 0o600, misc.os_geteuid())) def test_delete(self): self.handler.close() diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index e1be6e023..ba1799f32 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -3,42 +3,41 @@ # pylint: disable=too-many-lines from __future__ import print_function +import datetime import itertools import json -import mock import os import shutil +import sys +import tempfile import traceback import unittest -import datetime -import pytz -import tempfile -import sys import josepy as jose +import mock +import pytz import six from six.moves import reload_module # pylint: disable=import-error from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +import certbot.tests.util as test_util from certbot import account from certbot import cli -from certbot import compat -from certbot import constants from certbot import configuration +from certbot import constants from certbot import crypto_util from certbot import errors from certbot import interfaces # pylint: disable=unused-import from certbot import main from certbot import updater from certbot import util - +from certbot.compat import misc from certbot.plugins import disco from certbot.plugins import enhancements from certbot.plugins import manual from certbot.plugins import null -import certbot.tests.util as test_util - CERT_PATH = test_util.vector_path('cert_512.pem') CERT = test_util.vector_path('cert_512.pem') CSR = test_util.vector_path('csr_512.der') @@ -1587,7 +1586,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): for core_dir in (self.config.config_dir, self.config.work_dir,): mock_util.set_up_core_dir.assert_any_call( core_dir, constants.CONFIG_DIRS_MODE, - compat.os_geteuid(), self.config.strict_permissions + misc.os_geteuid(), self.config.strict_permissions ) hook_dirs = (self.config.renewal_pre_hooks_dir, @@ -1596,7 +1595,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): for hook_dir in hook_dirs: # default mode of 755 is used mock_util.make_or_verify_dir.assert_any_call( - hook_dir, uid=compat.os_geteuid(), + hook_dir, uid=misc.os_geteuid(), strict=self.config.strict_permissions) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index d04e3c641..8e05d4f1c 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -10,7 +10,6 @@ import mock import six from certbot import errors - from certbot.tests import util as test_util @@ -356,7 +355,7 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.finalize_checkpoint, "Title") - @mock.patch("certbot.reverter.compat.os_rename") + @mock.patch("certbot.reverter.misc.os_rename") def test_finalize_checkpoint_no_rename_directory(self, mock_rename): self.reverter.add_to_checkpoint(self.sets[0], "perm save") diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 872fb4302..a6577deb3 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -12,13 +12,11 @@ import pytz import six import certbot -from certbot import compat +import certbot.tests.util as test_util from certbot import errors +from certbot.compat import misc from certbot.storage import ALL_FOUR -import certbot.tests.util as test_util - - CERT = test_util.load_cert('cert_512.pem') @@ -572,21 +570,21 @@ class RenewableCertTests(BaseRenewableCertTest): 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( + self.assertTrue(misc.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( + self.assertTrue(misc.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( + self.assertTrue(misc.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( + self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600)) @test_util.broken_on_windows @@ -624,7 +622,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)) + self.assertTrue(misc.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 diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index b848dbb9f..2974f8f77 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -8,9 +8,9 @@ import mock import six from six.moves import reload_module # pylint: disable=import-error -from certbot import compat -from certbot import errors import certbot.tests.util as test_util +from certbot import errors +from certbot.compat import misc class RunScriptTest(unittest.TestCase): @@ -119,7 +119,7 @@ class SetUpCoreDirTest(test_util.TempDirTestCase): @mock.patch('certbot.util.lock_dir_until_exit') def test_success(self, mock_lock): new_dir = os.path.join(self.tempdir, 'new') - self._call(new_dir, 0o700, compat.os_geteuid(), False) + self._call(new_dir, 0o700, misc.os_geteuid(), False) self.assertTrue(os.path.exists(new_dir)) self.assertEqual(mock_lock.call_count, 1) @@ -127,7 +127,7 @@ class SetUpCoreDirTest(test_util.TempDirTestCase): def test_failure(self, mock_make_or_verify): mock_make_or_verify.side_effect = OSError self.assertRaises(errors.Error, self._call, - self.tempdir, 0o700, compat.os_geteuid(), False) + self.tempdir, 0o700, misc.os_geteuid(), False) class MakeOrVerifyDirTest(test_util.TempDirTestCase): @@ -144,7 +144,7 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): self.path = os.path.join(self.tempdir, "foo") os.mkdir(self.path, 0o600) - self.uid = compat.os_geteuid() + self.uid = misc.os_geteuid() def _call(self, directory, mode): from certbot.util import make_or_verify_dir @@ -154,11 +154,11 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): path = os.path.join(self.tempdir, "bar") self._call(path, 0o650) self.assertTrue(os.path.isdir(path)) - self.assertTrue(compat.compare_file_modes(os.stat(path).st_mode, 0o650)) + self.assertTrue(misc.compare_file_modes(os.stat(path).st_mode, 0o650)) def test_existing_correct_mode_does_not_fail(self): self._call(self.path, 0o600) - self.assertTrue(compat.compare_file_modes(os.stat(self.path).st_mode, 0o600)) + self.assertTrue(misc.compare_file_modes(os.stat(self.path).st_mode, 0o600)) @test_util.skip_on_windows('Umask modes are mostly ignored on Windows.') def test_existing_wrong_mode_fails(self): @@ -181,7 +181,7 @@ class CheckPermissionsTest(test_util.TempDirTestCase): def setUp(self): super(CheckPermissionsTest, self).setUp() - self.uid = compat.os_geteuid() + self.uid = misc.os_geteuid() def _call(self, mode): from certbot.util import check_permissions @@ -223,8 +223,8 @@ class UniqueFileTest(test_util.TempDirTestCase): def test_right_mode(self): 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)) + self.assertTrue(misc.compare_file_modes(0o700, os.stat(name1).st_mode)) + self.assertTrue(misc.compare_file_modes(0o600, os.stat(name2).st_mode)) fd1.close() fd2.close() diff --git a/certbot/util.py b/certbot/util.py index 6481e30d2..fbdfd685a 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -10,20 +10,19 @@ import logging import os import platform import re -import six import socket import subprocess - from collections import OrderedDict import configargparse +import six from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module -from certbot import compat + from certbot import constants from certbot import errors from certbot import lock - +from certbot.compat import misc logger = logging.getLogger(__name__) @@ -204,7 +203,7 @@ def check_permissions(filepath, mode, uid=0): """ file_stat = os.stat(filepath) - return compat.compare_file_modes(file_stat.st_mode, mode) and file_stat.st_uid == uid + return misc.compare_file_modes(file_stat.st_mode, mode) and file_stat.st_uid == uid def safe_open(path, mode="w", chmod=None, buffering=None): -- cgit v1.2.3 From ea568d4dc2411214850f04943ffba4e799197f64 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 29 Mar 2019 00:50:42 +0100 Subject: [Windows] Fix ErrorHandler tests, by disabling signal error handling (#6868) This PR is a part of the effort to remove the last broken unit tests in certbot codebase for Windows, as described in #6850. It solves the problems associated to ErrorHandler in Windows (enlighted by tests errors) by ... wipping out the problem: no signal is handled by ErrorHandler on Windows. See the relevant inline comment in certbot.error_handler for explanation and sources. --- certbot/error_handler.py | 18 +++++++++++++++++- certbot/tests/error_handler_test.py | 12 +++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/certbot/error_handler.py b/certbot/error_handler.py index 5e72f8153..64e9a1488 100644 --- a/certbot/error_handler.py +++ b/certbot/error_handler.py @@ -19,14 +19,30 @@ logger = logging.getLogger(__name__) # potentially occur from inside Python. Signals such as SIGILL were not # included as they could be a sign of something devious and we should terminate # immediately. -_SIGNALS = [signal.SIGTERM] if os.name != "nt": + _SIGNALS = [signal.SIGTERM] for signal_code in [signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, signal.SIGXFSZ]: # Adding only those signals that their default action is not Ignore. # This is platform-dependent, so we check it dynamically. if signal.getsignal(signal_code) != signal.SIG_IGN: _SIGNALS.append(signal_code) +else: + # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. + # As consumed by CPython, most of handlers on theses signals are useless, in particular + # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops + # immediately the process without calling the attached handler. Besides, non-POSIX signals + # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the + # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable + # behavior in fact, and maps to the handler to SIGINT. However in this case, a + # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager + # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. + # + # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, + # https://docs.python.org/2/library/os.html#os.kill, + # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question + _SIGNALS = [] + class ErrorHandler(object): """Context manager for running code that must be cleaned up on failure. diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index 8508a3df5..bc6d4fe3f 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -10,7 +10,6 @@ import mock from acme.magic_typing import Callable, Dict, Union # pylint: enable=unused-import, no-name-in-module -import certbot.tests.util as test_util def get_signals(signums): """Get the handlers for an iterable of signums.""" @@ -66,9 +65,9 @@ class ErrorHandlerTest(unittest.TestCase): self.init_func.assert_called_once_with(*self.init_args, **self.init_kwargs) - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_context_manager_with_signal(self): + if not self.signals: + self.skipTest(reason='Signals cannot be handled on Windows.') init_signals = get_signals(self.signals) with signal_receiver(self.signals) as signals_received: with self.handler: @@ -98,9 +97,9 @@ class ErrorHandlerTest(unittest.TestCase): **self.init_kwargs) bad_func.assert_called_once_with() - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_bad_recovery_with_signal(self): + if not self.signals: + self.skipTest(reason='Signals cannot be handled on Windows.') sig1 = self.signals[0] sig2 = self.signals[-1] bad_func = mock.MagicMock(side_effect=lambda: send_signal(sig1)) @@ -149,10 +148,9 @@ class ExitHandlerTest(ErrorHandlerTest): **self.init_kwargs) func.assert_called_once_with() - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_bad_recovery_with_signal(self): super(ExitHandlerTest, self).test_bad_recovery_with_signal() + if __name__ == "__main__": unittest.main() # pragma: no cover -- cgit v1.2.3 From 63c8f2e34d622ba71f7d15349ea09384bdd65a27 Mon Sep 17 00:00:00 2001 From: aditj Date: Sun, 31 Mar 2019 00:01:11 +0530 Subject: Changed the text of -h to add details regarding unregister and all --- certbot/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/certbot/cli.py b/certbot/cli.py index 1472b0139..320e5e96a 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -101,6 +101,7 @@ manage certificates: manage your account with Let's Encrypt: register Create a Let's Encrypt ACME account + unregister Deactivate 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 @@ -117,7 +118,7 @@ More detailed help: all, automation, commands, paths, security, testing, or any of the subcommands or plugins (certonly, renew, install, register, nginx, apache, standalone, webroot, etc.) - + -h all print a detailed help page including all topics --version print the version number """ -- cgit v1.2.3 From 232e0ea50fa7472803510260b8368aac33478ff8 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 1 Apr 2019 18:50:08 +0200 Subject: Rely on universal newline mode on python 3 for windows (#6866) --- certbot/reverter.py | 19 +++++++++++++------ certbot/tests/reverter_test.py | 10 ---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/certbot/reverter.py b/certbot/reverter.py index e08b468ac..f05e24b01 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -4,17 +4,18 @@ import glob import logging import os import shutil +import sys import time import traceback import six import zope.component +from certbot.compat import misc from certbot import constants from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc logger = logging.getLogger(__name__) @@ -237,7 +238,7 @@ class Reverter(object): try: shutil.copy2(filename, os.path.join( cp_dir, os.path.basename(filename) + "_" + str(idx))) - op_fd.write(filename + os.linesep) + op_fd.write('{0}\n'.format(filename)) # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 except IOError: op_fd.close() @@ -312,7 +313,10 @@ class Reverter(object): """Run all commands in a file.""" # NOTE: csv module uses native strings. That is, bytes on Python 2 and # unicode on Python 3 - with open(filepath, 'r') as csvfile: + # It is strongly advised to set newline = '' on Python 3 with CSV, + # and it fixes problems on Windows. + kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} + with open(filepath, 'r', **kwargs) as csvfile: # type: ignore # pylint: disable=star-args csvreader = csv.reader(csvfile) for command in reversed(list(csvreader)): try: @@ -381,7 +385,7 @@ class Reverter(object): for path in files: if path not in ex_files: - new_fd.write("{0}{1}".format(path, os.linesep)) + new_fd.write("{0}\n".format(path)) except (IOError, OSError): logger.error("Unable to register file creation(s) - %s", files) raise errors.ReverterError( @@ -408,11 +412,14 @@ class Reverter(object): """ commands_fp = os.path.join(self._get_cp_dir(temporary), "COMMANDS") command_file = None + # It is strongly advised to set newline = '' on Python 3 with CSV, + # and it fixes problems on Windows. + kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} try: if os.path.isfile(commands_fp): - command_file = open(commands_fp, "a") + command_file = open(commands_fp, "a", **kwargs) # type: ignore # pylint: disable=star-args else: - command_file = open(commands_fp, "w") + command_file = open(commands_fp, "w", **kwargs) # type: ignore # pylint: disable=star-args csvwriter = csv.writer(command_file) csvwriter.writerow(command) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 8e05d4f1c..18e698444 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -49,7 +49,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): x = f.read() self.assertTrue("No changes" in x) - @test_util.broken_on_windows def test_basic_add_to_temp_checkpoint(self): # These shouldn't conflict even though they are both named config.txt self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") @@ -91,7 +90,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.assertRaises(errors.ReverterError, self.reverter.add_to_checkpoint, set([config3]), "invalid save") - @test_util.broken_on_windows def test_multiple_saves_and_temp_revert(self): self.reverter.add_to_temp_checkpoint(self.sets[0], "save1") update_file(self.config1, "updated-directive") @@ -121,7 +119,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.assertFalse(os.path.isfile(config3)) self.assertFalse(os.path.isfile(config4)) - @test_util.broken_on_windows def test_multiple_registration_same_file(self): self.reverter.register_file_creation(True, self.config1) self.reverter.register_file_creation(True, self.config1) @@ -146,7 +143,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): errors.ReverterError, self.reverter.register_file_creation, "filepath") - @test_util.broken_on_windows def test_register_undo_command(self): coms = [ ["a2dismod", "ssl"], @@ -169,7 +165,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): errors.ReverterError, self.reverter.register_undo_command, True, ["command"]) - @test_util.broken_on_windows @mock.patch("certbot.util.run_script") def test_run_undo_commands(self, mock_run): mock_run.side_effect = ["", errors.SubprocessError] @@ -233,7 +228,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.revert_temporary_config) - @test_util.broken_on_windows @mock.patch("certbot.reverter.logger.warning") def test_recover_checkpoint_missing_new_files(self, mock_warn): self.reverter.register_file_creation( @@ -248,7 +242,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.revert_temporary_config) - @test_util.broken_on_windows def test_recovery_routine_temp_and_perm(self): # Register a new perm checkpoint file config3 = os.path.join(self.dir1, "config3.txt") @@ -312,7 +305,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.rollback_checkpoints, "one") - @test_util.broken_on_windows def test_rollback_finalize_checkpoint_valid_inputs(self): config3 = self._setup_three_checkpoints() @@ -364,7 +356,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.finalize_checkpoint, "Title") - @test_util.broken_on_windows @mock.patch("certbot.reverter.logger") def test_rollback_too_many(self, mock_logger): # Test no exist warning... @@ -377,7 +368,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.reverter.rollback_checkpoints(4) self.assertEqual(mock_logger.warning.call_count, 1) - @test_util.broken_on_windows def test_multi_rollback(self): config3 = self._setup_three_checkpoints() self.reverter.rollback_checkpoints(3) -- cgit v1.2.3 From fd6702b86951c96192336e4e77fca7d98e1425f3 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 2 Apr 2019 19:26:58 +0300 Subject: Fix CentOS 6 installer issue (#6784) In CentOS 6 default httpd configuration, the `LoadModule ssl_module ...` is handled in `conf.d/ssl.conf`. As the `VirtualHost` configuration files in `conf.d/` are loaded in alphabetical order, this means that all files that have `` and are loaded before `ssl.conf` are effectively ignored. This PR moves the `LoadModule ssl_module` to the main `httpd.conf` while leaving a conditional `LoadModule` directive in `ssl.conf`. Features - Reads the module configuration from `ssl.conf` in case some modifications to paths have been made by the user. - Falls back to default paths if the directive doesn't exist. - Moves the `LoadModule` directive in `ssl.conf` inside `` to avoid printing warning messages of duplicate module loads. - Adds `LoadModule ssl_module` inside of `` to the top of the main `httpd.conf`. - Ensures that these modifications are not made multiple times. Fixes: #6606 * Fix CentOS6 installer issue * Changelog entry * Address review comments * Do not enable mod_ssl if multiple different values were found * Add test comment * Address rest of the review comments * Address review comments * Better ifmodule argument checking * Test fixes * Make linter happy * Raise an exception when differing LoadModule ssl_module statements are found * If IfModule !mod_ssl.c with LoadModule ssl_module already exists in Augeas path, do not create new LoadModule directive * Do not use deprecated assertion functions * Address review comments * Kick tests * Revert "Kick tests" This reverts commit 967bb574c2d7d6175133931826cc2cdb4b997dda. * Address review comments * Add pydoc return value to create_ifmod --- CHANGELOG.md | 3 + certbot-apache/certbot_apache/override_centos.py | 116 +++ certbot-apache/certbot_apache/parser.py | 62 +- .../certbot_apache/tests/centos6_test.py | 224 +++++ .../centos6_apache/apache/httpd/conf.d/README | 9 + .../centos6_apache/apache/httpd/conf.d/ssl.conf | 222 +++++ .../apache/httpd/conf.d/test.example.com.conf | 7 + .../apache/httpd/conf.d/welcome.conf | 11 + .../centos6_apache/apache/httpd/conf/httpd.conf | 1009 ++++++++++++++++++++ 9 files changed, 1655 insertions(+), 8 deletions(-) create mode 100644 certbot-apache/certbot_apache/tests/centos6_test.py create mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README create mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf create mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf create mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf create mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc823d1c..e74779ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 is installed. We fixed a bug in Certbot causing it to interpret timestamps in the OCSP response as being in the local timezone rather than UTC. +* Issue causing the default CentOS 6 TLS configuration to ignore some of the + HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main + http.conf for this environment where possible. 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 diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index a4f1b84ec..1995fd2a2 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -1,13 +1,21 @@ """ Distribution specific override class for CentOS family (RHEL, Fedora) """ +import logging import pkg_resources +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + import zope.interface from certbot import interfaces + from certbot_apache import apache_util from certbot_apache import configurator from certbot_apache import parser +from certbot.errors import MisconfigurationError + +logger = logging.getLogger(__name__) + @zope.interface.provider(interfaces.IPluginFactory) class CentOSConfigurator(configurator.ApacheConfigurator): @@ -47,6 +55,84 @@ class CentOSConfigurator(configurator.ApacheConfigurator): self.aug, self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) + def _deploy_cert(self, *args, **kwargs): + """ + Override _deploy_cert in order to ensure that the Apache configuration + has "LoadModule ssl_module..." before parsing the VirtualHost configuration + that was created by Certbot + """ + super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs) + if self.version < (2, 4, 0): + self._deploy_loadmodule_ssl_if_needed() + + + def _deploy_loadmodule_ssl_if_needed(self): + """ + Add "LoadModule ssl_module " to main httpd.conf if + it doesn't exist there already. + """ + + loadmods = self.parser.find_dir("LoadModule", "ssl_module", exclude=False) + + correct_ifmods = [] # type: List[str] + loadmod_args = [] # type: List[str] + loadmod_paths = [] # type: List[str] + for m in loadmods: + noarg_path = m.rpartition("/")[0] + path_args = self.parser.get_all_args(noarg_path) + if loadmod_args: + if loadmod_args != path_args: + msg = ("Certbot encountered multiple LoadModule directives " + "for LoadModule ssl_module with differing library paths. " + "Please remove or comment out the one(s) that are not in " + "use, and run Certbot again.") + raise MisconfigurationError(msg) + else: + loadmod_args = path_args + + if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member + if self.parser.loc["default"] in noarg_path: + # LoadModule already in the main configuration file + if ("ifmodule/" in noarg_path.lower() or + "ifmodule[1]" in noarg_path.lower()): + # It's the first or only IfModule in the file + return + # Populate the list of known !mod_ssl.c IfModules + nodir_path = noarg_path.rpartition("/directive")[0] + correct_ifmods.append(nodir_path) + else: + loadmod_paths.append(noarg_path) + + if not loadmod_args: + # Do not try to enable mod_ssl + return + + # Force creation as the directive wasn't found from the beginning of + # httpd.conf + rootconf_ifmod = self.parser.create_ifmod( + parser.get_aug_path(self.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + # parser.get_ifmod returns a path postfixed with "/", remove that + self.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", loadmod_args) + correct_ifmods.append(rootconf_ifmod[:-1]) + self.save_notes += "Added LoadModule ssl_module to main configuration.\n" + + # Wrap LoadModule mod_ssl inside of if it's not + # configured like this already. + for loadmod_path in loadmod_paths: + nodir_path = loadmod_path.split("/directive")[0] + # Remove the old LoadModule directive + self.aug.remove(loadmod_path) + + # Create a new IfModule !mod_ssl.c if not already found on path + ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", + beginning=True)[:-1] + if ssl_ifmod not in correct_ifmods: + self.parser.add_dir(ssl_ifmod, "LoadModule", loadmod_args) + correct_ifmods.append(ssl_ifmod) + self.save_notes += ("Wrapped pre-existing LoadModule ssl_module " + "inside of block.\n") + class CentOSParser(parser.ApacheParser): """CentOS specific ApacheParser override class""" @@ -66,3 +152,33 @@ class CentOSParser(parser.ApacheParser): defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") for k in defines.keys(): self.variables[k] = defines[k] + + def not_modssl_ifmodule(self, path): + """Checks if the provided Augeas path has argument !mod_ssl""" + + if "ifmodule" not in path.lower(): + return False + + # Trim the path to the last ifmodule + workpath = path.lower() + while workpath: + # Get path to the last IfModule (ignore the tail) + parts = workpath.rpartition("ifmodule") + + if not parts[0]: + # IfModule not found + break + ifmod_path = parts[0] + parts[1] + # Check if ifmodule had an index + if parts[2].startswith("["): + # Append the index from tail + ifmod_path += parts[2].partition("/")[0] + # Get the original path trimmed to correct length + # This is required to preserve cases + ifmod_real_path = path[0:len(ifmod_path)] + if "!mod_ssl.c" in self.get_all_args(ifmod_real_path): + return True + # Set the workpath to the heading part + workpath = parts[0] + + return False diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index b9f23f6cf..b025396ad 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -281,7 +281,7 @@ class ApacheParser(object): """ # TODO: Add error checking code... does the path given even exist? # Does it throw exceptions? - if_mod_path = self._get_ifmod(aug_conf_path, "mod_ssl.c") + if_mod_path = self.get_ifmod(aug_conf_path, "mod_ssl.c") # IfModule can have only one valid argument, so append after self.aug.insert(if_mod_path + "arg", "directive", False) nvh_path = if_mod_path + "directive[1]" @@ -292,22 +292,54 @@ class ApacheParser(object): for i, arg in enumerate(args): self.aug.set("%s/arg[%d]" % (nvh_path, i + 1), arg) - def _get_ifmod(self, aug_conf_path, mod): + def get_ifmod(self, aug_conf_path, mod, beginning=False): """Returns the path to and creates one if it doesn't exist. :param str aug_conf_path: Augeas configuration path :param str mod: module ie. mod_ssl.c + :param bool beginning: If the IfModule should be created to the beginning + of augeas path DOM tree. + + :returns: Augeas path of the requested IfModule directive that pre-existed + or was created during the process. The path may be dynamic, + i.e. .../IfModule[last()] + :rtype: str """ if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % (aug_conf_path, mod))) - if len(if_mods) == 0: - self.aug.set("%s/IfModule[last() + 1]" % aug_conf_path, "") - self.aug.set("%s/IfModule[last()]/arg" % aug_conf_path, mod) - if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % - (aug_conf_path, mod))) + if not if_mods: + return self.create_ifmod(aug_conf_path, mod, beginning) + # Strip off "arg" at end of first ifmod path - return if_mods[0][:len(if_mods[0]) - 3] + return if_mods[0].rpartition("arg")[0] + + def create_ifmod(self, aug_conf_path, mod, beginning=False): + """Creates a new and returns its path. + + :param str aug_conf_path: Augeas configuration path + :param str mod: module ie. mod_ssl.c + :param bool beginning: If the IfModule should be created to the beginning + of augeas path DOM tree. + + :returns: Augeas path of the newly created IfModule directive. + The path may be dynamic, i.e. .../IfModule[last()] + :rtype: str + + """ + if beginning: + c_path_arg = "{}/IfModule[1]/arg".format(aug_conf_path) + # Insert IfModule before the first directive + self.aug.insert("{}/directive[1]".format(aug_conf_path), + "IfModule", True) + retpath = "{}/IfModule[1]/".format(aug_conf_path) + else: + c_path = "{}/IfModule[last() + 1]".format(aug_conf_path) + c_path_arg = "{}/IfModule[last()]/arg".format(aug_conf_path) + self.aug.set(c_path, "") + retpath = "{}/IfModule[last()]/".format(aug_conf_path) + self.aug.set(c_path_arg, mod) + return retpath def add_dir(self, aug_conf_path, directive, args): """Appends directive to the end fo the file given by aug_conf_path. @@ -453,6 +485,20 @@ class ApacheParser(object): return ordered_matches + def get_all_args(self, match): + """ + Tries to fetch all arguments for a directive. See get_arg. + + Note that if match is an ancestor node, it returns all names of + child directives as well as the list of arguments. + + """ + + if match[-1] != "/": + match = match+"/" + allargs = self.aug.match(match + '*') + return [self.get_arg(arg) for arg in allargs] + def get_arg(self, match): """Uses augeas.get to get argument value and interprets result. diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/certbot_apache/tests/centos6_test.py new file mode 100644 index 000000000..ea8a85ed7 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/centos6_test.py @@ -0,0 +1,224 @@ +"""Test for certbot_apache.configurator for CentOS 6 overrides""" +import os +import unittest + +from certbot_apache import obj +from certbot_apache import override_centos +from certbot_apache import parser +from certbot_apache.tests import util +from certbot.errors import MisconfigurationError + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "httpd/conf.d") + + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "test.example.com.conf"), + os.path.join(aug_pre, "test.example.com.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "test.example.com"), + obj.VirtualHost( + os.path.join(prefix, "ssl.conf"), + os.path.join(aug_pre, "ssl.conf/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), + True, True, None) + ] + return vh_truth + +class CentOS6Tests(util.ApacheTest): + """Tests for CentOS 6""" + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos6_apache/apache" + config_root = "centos6_apache/apache/httpd" + vhost_root = "centos6_apache/apache/httpd/conf.d" + super(CentOS6Tests, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + version=(2, 2, 15), os_info="centos") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos6_apache/apache") + + def test_get_parser(self): + self.assertTrue(isinstance(self.config.parser, + override_centos.CentOSParser)) + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + found = 0 + + for vhost in vhs: + for centos_truth in self.vh_truth: + if vhost == centos_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 2) + + def test_loadmod_default(self): + ssl_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + self.assertEqual(len(ssl_loadmods), 1) + # Make sure the LoadModule ssl_module is in ssl.conf (default) + self.assertTrue("ssl.conf" in ssl_loadmods[0]) + # ...and that it's not inside of + self.assertFalse("IfModule" in ssl_loadmods[0]) + + # Get the example vhost + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + + # We should now have LoadModule ssl_module in root conf and ssl.conf + self.assertEqual(len(post_loadmods), 2) + for lm in post_loadmods: + # lm[:-7] removes "/arg[#]" from the path + arguments = self.config.parser.get_all_args(lm[:-7]) + self.assertEqual(arguments, ["ssl_module", "modules/mod_ssl.so"]) + # ...and both of them should be wrapped in + # lm[:-17] strips off /directive/arg[1] from the path. + ifmod_args = self.config.parser.get_all_args(lm[:-17]) + self.assertTrue("!mod_ssl.c" in ifmod_args) + + def test_loadmod_multiple(self): + sslmod_args = ["ssl_module", "modules/mod_ssl.so"] + # Adds another LoadModule to main httpd.conf in addtition to ssl.conf + self.config.parser.add_dir(self.config.parser.loc["default"], "LoadModule", + sslmod_args) + self.config.save() + pre_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + # LoadModules are not within IfModule blocks + self.assertFalse(any(["ifmodule" in m.lower() for m in pre_loadmods])) + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + + for mod in post_loadmods: + self.assertTrue(self.config.parser.not_modssl_ifmodule(mod)) #pylint: disable=no-member + + def test_loadmod_rootconf_exists(self): + sslmod_args = ["ssl_module", "modules/mod_ssl.so"] + rootconf_ifmod = self.config.parser.get_ifmod( + parser.get_aug_path(self.config.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) + self.config.save() + # Get the example vhost + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + + root_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", + start=parser.get_aug_path(self.config.parser.loc["default"]), + exclude=False) + + mods = [lm for lm in root_loadmods if self.config.parser.loc["default"] in lm] + + self.assertEqual(len(mods), 1) + # [:-7] removes "/arg[#]" from the path + self.assertEqual( + self.config.parser.get_all_args(mods[0][:-7]), + sslmod_args) + + def test_neg_loadmod_already_on_path(self): + loadmod_args = ["ssl_module", "modules/mod_ssl.so"] + ifmod = self.config.parser.get_ifmod( + self.vh_truth[1].path, "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(ifmod[:-1], "LoadModule", loadmod_args) + self.config.parser.add_dir(self.vh_truth[1].path, "LoadModule", loadmod_args) + self.config.save() + pre_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) + self.assertEqual(len(pre_loadmods), 2) + # The ssl.conf now has two LoadModule directives, one inside of + # !mod_ssl.c IfModule + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + # Ensure that the additional LoadModule wasn't written into the IfModule + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) + self.assertEqual(len(post_loadmods), 1) + + + + + + def test_loadmod_non_duplicate(self): + # the modules/mod_ssl.so exists in ssl.conf + sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"] + rootconf_ifmod = self.config.parser.get_ifmod( + parser.get_aug_path(self.config.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) + self.config.save() + self.config.assoc["test.example.com"] = self.vh_truth[0] + pre_matches = self.config.parser.find_dir("LoadModule", + "ssl_module", exclude=False) + + self.assertRaises(MisconfigurationError, self.config.deploy_cert, + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + + post_matches = self.config.parser.find_dir("LoadModule", + "ssl_module", exclude=False) + # Make sure that none was changed + self.assertEqual(pre_matches, post_matches) + + def test_loadmod_not_found(self): + # Remove all existing LoadModule ssl_module... directives + orig_loadmods = self.config.parser.find_dir("LoadModule", + "ssl_module", + exclude=False) + for mod in orig_loadmods: + noarg_path = mod.rpartition("/")[0] + self.config.aug.remove(noarg_path) + self.config.save() + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + + post_loadmods = self.config.parser.find_dir("LoadModule", + "ssl_module", + exclude=False) + self.assertFalse(post_loadmods) + + def test_no_ifmod_search_false(self): + #pylint: disable=no-member + + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "/path/does/not/include/ifmod" + )) + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "" + )) + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "/path/includes/IfModule/but/no/arguments" + )) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README new file mode 100644 index 000000000..c12e149f2 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README @@ -0,0 +1,9 @@ + +This directory holds Apache 2.0 module-specific configuration files; +any files in this directory which have the ".conf" extension will be +processed as Apache configuration files. + +Files are processed in alphabetical order, so if using configuration +directives which depend on, say, mod_perl being loaded, ensure that +these are placed in a filename later in the sort order than "perl.conf". + diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf new file mode 100644 index 000000000..fb2174af1 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf @@ -0,0 +1,222 @@ +# +# This is the Apache server configuration file providing SSL support. +# It contains the configuration directives to instruct the server how to +# serve pages over an https connection. For detailing information about these +# directives see +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# + +LoadModule ssl_module modules/mod_ssl.so + +# +# When we also provide SSL we have to listen to the +# the HTTPS port in addition. +# +Listen 443 + +## +## SSL Global Context +## +## All SSL configuration in this context applies both to +## the main server and all SSL-enabled virtual hosts. +## + +# Pass Phrase Dialog: +# Configure the pass phrase gathering process. +# The filtering dialog program (`builtin' is a internal +# terminal dialog) has to provide the pass phrase on stdout. +SSLPassPhraseDialog builtin + +# Inter-Process Session Cache: +# Configure the SSL Session Cache: First the mechanism +# to use and second the expiring timeout (in seconds). +SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) +SSLSessionCacheTimeout 300 + +# Semaphore: +# Configure the path to the mutual exclusion semaphore the +# SSL engine uses internally for inter-process synchronization. +SSLMutex default + +# Pseudo Random Number Generator (PRNG): +# Configure one or more sources to seed the PRNG of the +# SSL library. The seed data should be of good random quality. +# WARNING! On some platforms /dev/random blocks if not enough entropy +# is available. This means you then cannot use the /dev/random device +# because it would lead to very long connection times (as long as +# it requires to make more entropy available). But usually those +# platforms additionally provide a /dev/urandom device which doesn't +# block. So, if available, use this one instead. Read the mod_ssl User +# Manual for more details. +SSLRandomSeed startup file:/dev/urandom 256 +SSLRandomSeed connect builtin +#SSLRandomSeed startup file:/dev/random 512 +#SSLRandomSeed connect file:/dev/random 512 +#SSLRandomSeed connect file:/dev/urandom 512 + +# +# Use "SSLCryptoDevice" to enable any supported hardware +# accelerators. Use "openssl engine -v" to list supported +# engine names. NOTE: If you enable an accelerator and the +# server does not start, consult the error logs and ensure +# your accelerator is functioning properly. +# +SSLCryptoDevice builtin +#SSLCryptoDevice ubsec + +## +## SSL Virtual Host Context +## + + + +# General setup for the virtual host, inherited from global configuration +#DocumentRoot "/var/www/html" +#ServerName www.example.com:443 + +# Use separate log files for the SSL virtual host; note that LogLevel +# is not inherited from httpd.conf. +ErrorLog logs/ssl_error_log +TransferLog logs/ssl_access_log +LogLevel warn + +# SSL Engine Switch: +# Enable/Disable SSL for this virtual host. +SSLEngine on + +# SSL Protocol support: +# List the enable protocol levels with which clients will be able to +# connect. Disable SSLv2 access by default: +SSLProtocol all -SSLv2 + +# SSL Cipher Suite: +# List the ciphers that the client is permitted to negotiate. +# See the mod_ssl documentation for a complete list. +SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:+3DES + +# Server Certificate: +# Point SSLCertificateFile at a PEM encoded certificate. If +# the certificate is encrypted, then you will be prompted for a +# pass phrase. Note that a kill -HUP will prompt again. A new +# certificate can be generated using the genkey(1) command. +SSLCertificateFile /etc/pki/tls/certs/localhost.crt + +# Server Private Key: +# If the key is not combined with the certificate, use this +# directive to point at the key file. Keep in mind that if +# you've both a RSA and a DSA private key you can configure +# both in parallel (to also allow the use of DSA ciphers, etc.) +SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + +# Server Certificate Chain: +# Point SSLCertificateChainFile at a file containing the +# concatenation of PEM encoded CA certificates which form the +# certificate chain for the server certificate. Alternatively +# the referenced file can be the same as SSLCertificateFile +# when the CA certificates are directly appended to the server +# certificate for convinience. +#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt + +# Certificate Authority (CA): +# Set the CA certificate verification path where to find CA +# certificates for client authentication or alternatively one +# huge file containing all of them (file must be PEM encoded) +#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt + +# Client Authentication (Type): +# Client certificate verification type and depth. Types are +# none, optional, require and optional_no_ca. Depth is a +# number which specifies how deeply to verify the certificate +# issuer chain before deciding the certificate is not valid. +#SSLVerifyClient require +#SSLVerifyDepth 10 + +# Access Control: +# With SSLRequire you can do per-directory access control based +# on arbitrary complex boolean expressions containing server +# variable checks and other lookup directives. The syntax is a +# mixture between C and Perl. See the mod_ssl documentation +# for more details. +# +#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ +# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ +# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ +# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ +# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ +# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ +# + +# SSL Engine Options: +# Set various options for the SSL engine. +# o FakeBasicAuth: +# Translate the client X.509 into a Basic Authorisation. This means that +# the standard Auth/DBMAuth methods can be used for access control. The +# user name is the `one line' version of the client's X.509 certificate. +# Note that no password is obtained from the user. Every entry in the user +# file needs this password: `xxj31ZMTZzkVA'. +# o ExportCertData: +# This exports two additional environment variables: SSL_CLIENT_CERT and +# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the +# server (always existing) and the client (only existing when client +# authentication is used). This can be used to import the certificates +# into CGI scripts. +# o StdEnvVars: +# This exports the standard SSL/TLS related `SSL_*' environment variables. +# Per default this exportation is switched off for performance reasons, +# because the extraction step is an expensive operation and is usually +# useless for serving static content. So one usually enables the +# exportation for CGI and SSI requests only. +# o StrictRequire: +# This denies access when "SSLRequireSSL" or "SSLRequire" applied even +# under a "Satisfy any" situation, i.e. when it applies access is denied +# and no other module can change it. +# o OptRenegotiate: +# This enables optimized SSL connection renegotiation handling when SSL +# directives are used in per-directory context. +#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + +# SSL Protocol Adjustments: +# The safe and default but still SSL/TLS standard compliant shutdown +# approach is that mod_ssl sends the close notify alert but doesn't wait for +# the close notify alert from client. When you need a different shutdown +# approach you can use one of the following variables: +# o ssl-unclean-shutdown: +# This forces an unclean shutdown when the connection is closed, i.e. no +# SSL close notify alert is send or allowed to received. This violates +# the SSL/TLS standard but is needed for some brain-dead browsers. Use +# this when you receive I/O errors because of the standard approach where +# mod_ssl sends the close notify alert. +# o ssl-accurate-shutdown: +# This forces an accurate shutdown when the connection is closed, i.e. a +# SSL close notify alert is send and mod_ssl waits for the close notify +# alert of the client. This is 100% SSL/TLS standard compliant, but in +# practice often causes hanging connections with brain-dead browsers. Use +# this only for browsers where you know that their SSL implementation +# works correctly. +# Notice: Most problems of broken clients are also related to the HTTP +# keep-alive facility, so you usually additionally want to disable +# keep-alive for those clients, too. Use variable "nokeepalive" for this. +# Similarly, one has to force some clients to use HTTP/1.0 to workaround +# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and +# "force-response-1.0" for this. +SetEnvIf User-Agent ".*MSIE.*" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + +# Per-Server Logging: +# The home of a custom SSL log file. Use this when you want a +# compact non-error SSL logfile on a virtual host basis. +CustomLog logs/ssl_request_log \ + "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" + + + diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf new file mode 100644 index 000000000..3dd7b18f1 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf @@ -0,0 +1,7 @@ + + ServerName test.example.com + ServerAdmin webmaster@dummy-host.example.com + DocumentRoot /var/www/htdocs + ErrorLog logs/dummy-host.example.com-error_log + CustomLog logs/dummy-host.example.com-access_log common + diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf new file mode 100644 index 000000000..c1d23c512 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf @@ -0,0 +1,11 @@ +# +# This configuration file enables the default "Welcome" +# page if there is no default index page present for +# the root URL. To disable the Welcome page, comment +# out all the lines below. +# + + Options -Indexes + ErrorDocument 403 /error/noindex.html + + diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf new file mode 100644 index 000000000..579d194ce --- /dev/null +++ b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf @@ -0,0 +1,1009 @@ +# +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# The configuration directives are grouped into three basic sections: +# 1. Directives that control the operation of the Apache server process as a +# whole (the 'global environment'). +# 2. Directives that define the parameters of the 'main' or 'default' server, +# which responds to requests that aren't handled by a virtual host. +# These directives also provide default values for the settings +# of all virtual hosts. +# 3. Settings for virtual hosts, which allow Web requests to be sent to +# different IP addresses or hostnames and have them handled by the +# same Apache server process. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/foo.log" +# with ServerRoot set to "/etc/httpd" will be interpreted by the +# server as "/etc/httpd/logs/foo.log". +# + +### Section 1: Global Environment +# +# The directives in this section affect the overall operation of Apache, +# such as the number of concurrent requests it can handle or where it +# can find its configuration files. +# + +# +# Don't give away too much information about all the subcomponents +# we are running. Comment out this line if you don't mind remote sites +# finding out what major optional modules you are running +ServerTokens OS + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the LockFile documentation +# (available at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +ServerRoot "/etc/httpd" + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. Note the PIDFILE variable in +# /etc/sysconfig/httpd must be set appropriately if this location is +# changed. +# +PidFile run/httpd.pid + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 60 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive Off + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 15 + +## +## Server-Pool Size Regulation (MPM specific) +## + +# prefork MPM +# StartServers: number of server processes to start +# MinSpareServers: minimum number of server processes which are kept spare +# MaxSpareServers: maximum number of server processes which are kept spare +# ServerLimit: maximum value for MaxClients for the lifetime of the server +# MaxClients: maximum number of server processes allowed to start +# MaxRequestsPerChild: maximum number of requests a server process serves + +StartServers 8 +MinSpareServers 5 +MaxSpareServers 20 +ServerLimit 256 +MaxClients 256 +MaxRequestsPerChild 4000 + + +# worker MPM +# StartServers: initial number of server processes to start +# MaxClients: maximum number of simultaneous client connections +# MinSpareThreads: minimum number of worker threads which are kept spare +# MaxSpareThreads: maximum number of worker threads which are kept spare +# ThreadsPerChild: constant number of worker threads in each server process +# MaxRequestsPerChild: maximum number of requests a server process serves + +StartServers 4 +MaxClients 300 +MinSpareThreads 25 +MaxSpareThreads 75 +ThreadsPerChild 25 +MaxRequestsPerChild 0 + + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, in addition to the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses (0.0.0.0) +# +#Listen 12.34.56.78:80 +Listen 80 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_alias_module modules/mod_authn_alias.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_default_module modules/mod_authn_default.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_default_module modules/mod_authz_default.so +LoadModule ldap_module modules/mod_ldap.so +LoadModule authnz_ldap_module modules/mod_authnz_ldap.so +LoadModule include_module modules/mod_include.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule env_module modules/mod_env.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule expires_module modules/mod_expires.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule headers_module modules/mod_headers.so +LoadModule usertrack_module modules/mod_usertrack.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule mime_module modules/mod_mime.so +LoadModule dav_module modules/mod_dav.so +LoadModule status_module modules/mod_status.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule info_module modules/mod_info.so +LoadModule dav_fs_module modules/mod_dav_fs.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule dir_module modules/mod_dir.so +LoadModule actions_module modules/mod_actions.so +LoadModule speling_module modules/mod_speling.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule alias_module modules/mod_alias.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +LoadModule proxy_connect_module modules/mod_proxy_connect.so +LoadModule cache_module modules/mod_cache.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule disk_cache_module modules/mod_disk_cache.so +LoadModule cgi_module modules/mod_cgi.so +LoadModule version_module modules/mod_version.so + +# +# The following modules are not loaded by default: +# +#LoadModule asis_module modules/mod_asis.so +#LoadModule authn_dbd_module modules/mod_authn_dbd.so +#LoadModule cern_meta_module modules/mod_cern_meta.so +#LoadModule cgid_module modules/mod_cgid.so +#LoadModule dbd_module modules/mod_dbd.so +#LoadModule dumpio_module modules/mod_dumpio.so +#LoadModule filter_module modules/mod_filter.so +#LoadModule ident_module modules/mod_ident.so +#LoadModule log_forensic_module modules/mod_log_forensic.so +#LoadModule unique_id_module modules/mod_unique_id.so +# + +# +# Load config files from the config directory "/etc/httpd/conf.d". +# +Include conf.d/*.conf + +# +# ExtendedStatus controls whether Apache will generate "full" status +# information (ExtendedStatus On) or just basic information (ExtendedStatus +# Off) when the "server-status" handler is called. The default is Off. +# +#ExtendedStatus On + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# . On SCO (ODT 3) use "User nouser" and "Group nogroup". +# . On HPUX you may not be able to use shared memory as nobody, and the +# suggested workaround is to create a user www and use that user. +# NOTE that some kernels refuse to setgid(Group) or semctl(IPC_SET) +# when the value of (unsigned)Group is above 60000; +# don't use Group #-1 on these systems! +# +User apache +Group apache + +### Section 2: 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin root@localhost + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If this is not set to valid DNS name for your host, server-generated +# redirections will not work. See also the UseCanonicalName directive. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# You will have to access it by its address anyway, and this will make +# redirections work in a sensible way. +# +#ServerName www.example.com:80 + +# +# UseCanonicalName: Determines how Apache constructs self-referencing +# URLs and the SERVER_NAME and SERVER_PORT variables. +# When set "Off", Apache will use the Hostname and Port supplied +# by the client. When set "On", Apache will use the value of the +# ServerName directive. +# +UseCanonicalName Off + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "/var/www/html" + +# +# Each directory to which Apache has access can be configured with respect +# to which services and features are allowed and/or disabled in that +# directory (and its subdirectories). +# +# First, we configure the "default" to be a very restrictive set of +# features. +# + + Options FollowSymLinks + AllowOverride None + + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# This should be changed to whatever you set DocumentRoot to. +# + + +# +# Possible values for the Options directive are "None", "All", +# or any combination of: +# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews +# +# Note that "MultiViews" must be named *explicitly* --- "Options All" +# doesn't give it to you. +# +# The Options directive is both complicated and important. Please see +# http://httpd.apache.org/docs/2.2/mod/core.html#options +# for more information. +# + Options Indexes FollowSymLinks + +# +# AllowOverride controls what directives may be placed in .htaccess files. +# It can be "All", "None", or any combination of the keywords: +# Options FileInfo AuthConfig Limit +# + AllowOverride None + +# +# Controls who can get stuff from this server. +# + Order allow,deny + Allow from all + + + +# +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. +# +# The path to the end user account 'public_html' directory must be +# accessible to the webserver userid. This usually means that ~userid +# must have permissions of 711, ~userid/public_html must have permissions +# of 755, and documents contained therein must be world-readable. +# Otherwise, the client will only receive a "403 Forbidden" message. +# +# See also: http://httpd.apache.org/docs/misc/FAQ.html#forbidden +# + + # + # UserDir is disabled by default since it can confirm the presence + # of a username on the system (depending on home directory + # permissions). + # + UserDir disabled + + # + # To enable requests to /~user/ to serve the user's public_html + # directory, remove the "UserDir disabled" line above, and uncomment + # the following line instead: + # + #UserDir public_html + + + +# +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. +# +# +# AllowOverride FileInfo AuthConfig Limit +# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec +# +# Order allow,deny +# Allow from all +# +# +# Order deny,allow +# Deny from all +# +# + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# +# The index.html.var file (a type-map) is used to deliver content- +# negotiated documents. The MultiViews Option can be used for the +# same purpose, but it is much slower. +# +DirectoryIndex index.html index.html.var + +# +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Order allow,deny + Deny from all + Satisfy All + + +# +# TypesConfig describes where the mime.types file (or equivalent) is +# to be found. +# +TypesConfig /etc/mime.types + +# +# DefaultType is the default MIME type the server will use for a document +# if it cannot otherwise determine one, such as from filename extensions. +# If your server contains mostly text or HTML documents, "text/plain" is +# a good value. If most of your content is binary, such as applications +# or images, you may want to use "application/octet-stream" instead to +# keep browsers from trying to display binary files as though they are +# text. +# +DefaultType text/plain + +# +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +# + +# MIMEMagicFile /usr/share/magic.mime + MIMEMagicFile conf/magic + + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# +# EnableMMAP: Control whether memory-mapping is used to deliver +# files (assuming that the underlying OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. On some systems, turning it off (regardless of +# filesystem) can improve performance; for details, please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablemmap +# +#EnableMMAP off + +# +# EnableSendfile: Control whether the sendfile kernel support is +# used to deliver files (assuming that the OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. Please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablesendfile +# +#EnableSendfile off + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog logs/error_log + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + +# +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). +# +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# "combinedio" includes actual counts of actual bytes received (%I) and sent (%O); this +# requires the mod_logio module to be loaded. +#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + +# +# The location and format of the access logfile (Common Logfile Format). +# If you do not define any access logfiles within a +# container, they will be logged here. Contrariwise, if you *do* +# define per- access logfiles, transactions will be +# logged therein and *not* in this file. +# +#CustomLog logs/access_log common + +# +# If you would like to have separate agent and referer logfiles, uncomment +# the following directives. +# +#CustomLog logs/referer_log referer +#CustomLog logs/agent_log agent + +# +# For a single logfile with access, agent, and referer information +# (Combined Logfile Format), use the following directive: +# +CustomLog logs/access_log combined + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +# +ServerSignature On + +# +# Aliases: Add here as many aliases as you need (with no limit). The format is +# Alias fakename realname +# +# Note that if you include a trailing / on fakename then the server will +# require it to be present in the URL. So "/icons" isn't aliased in this +# example, only "/icons/". If the fakename is slash-terminated, then the +# realname must also be slash terminated, and if the fakename omits the +# trailing slash, the realname must also omit it. +# +# We include the /icons/ alias for FancyIndexed directory listings. If you +# do not use FancyIndexing, you may comment this out. +# +Alias /icons/ "/var/www/icons/" + + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + +# +# WebDAV module configuration section. +# + + # Location of the WebDAV lock database. + DAVLockDB /var/lib/dav/lockdb + + +# +# ScriptAlias: This controls which directories contain server scripts. +# ScriptAliases are essentially the same as Aliases, except that +# documents in the realname directory are treated as applications and +# run by the server when requested rather than as documents sent to the client. +# The same rules about trailing "/" apply to ScriptAlias directives as to +# Alias. +# +ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" + +# +# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# + + AllowOverride None + Options None + Order allow,deny + Allow from all + + +# +# Redirect allows you to tell clients about documents which used to exist in +# your server's namespace, but do not anymore. This allows you to tell the +# clients where to look for the relocated document. +# Example: +# Redirect permanent /foo http://www.example.com/bar + +# +# Directives controlling the display of server-generated directory listings. +# + +# +# IndexOptions: Controls the appearance of server-generated directory +# listings. +# +IndexOptions FancyIndexing VersionSort NameWidth=* HTMLTable Charset=UTF-8 + +# +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. +# +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif /core + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. +# +DefaultIcon /icons/unknown.gif + +# +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename +# +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. +# +# HeaderName is the name of a file which should be prepended to +# directory indexes. +ReadmeName README.html +HeaderName HEADER.html + +# +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. +# +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + +# +# DefaultLanguage and AddLanguage allows you to specify the language of +# a document. You can then use content negotiation to give a browser a +# file in a language the user can understand. +# +# Specify a default language. This means that all data +# going out without a specific language tag (see below) will +# be marked with this one. You probably do NOT want to set +# this unless you are sure it is correct for all cases. +# +# * It is generally better to not mark a page as +# * being a certain language than marking it with the wrong +# * language! +# +# DefaultLanguage nl +# +# Note 1: The suffix does not have to be the same as the language +# keyword --- those with documents in Polish (whose net-standard +# language code is pl) may wish to use "AddLanguage pl .po" to +# avoid the ambiguity with the common suffix for perl scripts. +# +# Note 2: The example entries below illustrate that in some cases +# the two character 'Language' abbreviation is not identical to +# the two character 'Country' code for its country, +# E.g. 'Danmark/dk' versus 'Danish/da'. +# +# Note 3: In the case of 'ltz' we violate the RFC by using a three char +# specifier. There is 'work in progress' to fix this and get +# the reference data for rfc1766 cleaned up. +# +# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) +# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) +# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) +# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) +# Norwegian (no) - Polish (pl) - Portugese (pt) +# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) +# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) +# +AddLanguage ca .ca +AddLanguage cs .cz .cs +AddLanguage da .dk +AddLanguage de .de +AddLanguage el .el +AddLanguage en .en +AddLanguage eo .eo +AddLanguage es .es +AddLanguage et .et +AddLanguage fr .fr +AddLanguage he .he +AddLanguage hr .hr +AddLanguage it .it +AddLanguage ja .ja +AddLanguage ko .ko +AddLanguage ltz .ltz +AddLanguage nl .nl +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pl .po +AddLanguage pt .pt +AddLanguage pt-BR .pt-br +AddLanguage ru .ru +AddLanguage sv .sv +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw + +# +# LanguagePriority allows you to give precedence to some languages +# in case of a tie during content negotiation. +# +# Just list the languages in decreasing order of preference. We have +# more or less alphabetized them here. You probably want to change this. +# +LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW + +# +# ForceLanguagePriority allows you to serve a result page rather than +# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) +# [in case no accepted languages matched the available variants] +# +ForceLanguagePriority Prefer Fallback + +# +# Specify a default charset for all content served; this enables +# interpretation of all content as UTF-8 by default. To use the +# default browser choice (ISO-8859-1), or to allow the META tags +# in HTML content to override this choice, comment out this +# directive: +# +AddDefaultCharset UTF-8 + +# +# AddType allows you to add to or override the MIME configuration +# file mime.types for specific file types. +# +#AddType application/x-tar .tgz + +# +# AddEncoding allows you to have certain browsers uncompress +# information on the fly. Note: Not all browsers support this. +# Despite the name similarity, the following Add* directives have nothing +# to do with the FancyIndexing customization directives above. +# +#AddEncoding x-compress .Z +#AddEncoding x-gzip .gz .tgz + +# If the AddEncoding directives above are commented-out, then you +# probably should define those extensions to indicate media types: +# +AddType application/x-compress .Z +AddType application/x-gzip .gz .tgz + +# +# MIME-types for downloading Certificates and CRLs +# +AddType application/x-x509-ca-cert .crt +AddType application/x-pkcs7-crl .crl + +# +# AddHandler allows you to map certain file extensions to "handlers": +# actions unrelated to filetype. These can be either built into the server +# or added with the Action directive (see below) +# +# To use CGI scripts outside of ScriptAliased directories: +# (You will also need to add "ExecCGI" to the "Options" directive.) +# +#AddHandler cgi-script .cgi + +# +# For files that include their own HTTP headers: +# +#AddHandler send-as-is asis + +# +# For type maps (negotiated resources): +# (This is enabled by default to allow the Apache "It Worked" page +# to be distributed in multiple languages.) +# +AddHandler type-map var + +# +# Filters allow you to process content before it is sent to the client. +# +# To parse .shtml files for server-side includes (SSI): +# (You will also need to add "Includes" to the "Options" directive.) +# +AddType text/html .shtml +AddOutputFilter INCLUDES .shtml + +# +# Action lets you define media types that will execute a script whenever +# a matching file is called. This eliminates the need for repeated URL +# pathnames for oft-used CGI file processors. +# Format: Action media/type /cgi-script/location +# Format: Action handler-name /cgi-script/location +# + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# Putting this all together, we can internationalize error responses. +# +# We use Alias to redirect any /error/HTTP_.html.var response to +# our collection of by-error message multi-language collections. We use +# includes to substitute the appropriate text. +# +# You can modify the messages' appearance without changing any of the +# default HTTP_.html.var files by adding the line: +# +# Alias /error/include/ "/your/include/path/" +# +# which allows you to create your own set of files by starting with the +# /var/www/error/include/ files and +# copying them to /your/include/path/, even on a per-VirtualHost basis. +# + +Alias /error/ "/var/www/error/" + + + + + AllowOverride None + Options IncludesNoExec + AddOutputFilter Includes html + AddHandler type-map var + Order allow,deny + Allow from all + LanguagePriority en es de fr + ForceLanguagePriority Prefer Fallback + + +# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var +# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var +# ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var +# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var +# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var +# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var +# ErrorDocument 410 /error/HTTP_GONE.html.var +# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var +# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var +# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var +# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var +# ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var +# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var +# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var +# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var +# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var +# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var + + + + +# +# The following directives modify normal HTTP response behavior to +# handle known problems with browser implementations. +# +BrowserMatch "Mozilla/2" nokeepalive +BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 +BrowserMatch "RealPlayer 4\.0" force-response-1.0 +BrowserMatch "Java/1\.0" force-response-1.0 +BrowserMatch "JDK/1\.0" force-response-1.0 + +# +# The following directive disables redirects on non-GET requests for +# a directory that does not include the trailing slash. This fixes a +# problem with Microsoft WebFolders which does not appropriately handle +# redirects for folders with DAV methods. +# Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. +# +BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully +BrowserMatch "MS FrontPage" redirect-carefully +BrowserMatch "^WebDrive" redirect-carefully +BrowserMatch "^WebDAVFS/1.[0123]" redirect-carefully +BrowserMatch "^gnome-vfs/1.0" redirect-carefully +BrowserMatch "^XML Spy" redirect-carefully +BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully + +# +# Allow server status reports generated by mod_status, +# with the URL of http://servername/server-status +# Change the ".example.com" to match your domain to enable. +# +# +# SetHandler server-status +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Allow remote server configuration reports, with the URL of +# http://servername/server-info (requires that mod_info.c be loaded). +# Change the ".example.com" to match your domain to enable. +# +# +# SetHandler server-info +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Proxy Server directives. Uncomment the following lines to +# enable the proxy server: +# +# +#ProxyRequests On +# +# +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Enable/disable the handling of HTTP/1.1 "Via:" headers. +# ("Full" adds the server version; "Block" removes all outgoing Via: headers) +# Set to one of: Off | On | Full | Block +# +#ProxyVia On + +# +# To enable a cache of proxied content, uncomment the following lines. +# See http://httpd.apache.org/docs/2.2/mod/mod_cache.html for more details. +# +# +# CacheEnable disk / +# CacheRoot "/var/cache/mod_proxy" +# +# + +# +# End of proxy directives. + +### Section 3: Virtual Hosts +# +# VirtualHost: If you want to maintain multiple domains/hostnames on your +# machine you can setup VirtualHost containers for them. Most configurations +# use only name-based virtual hosts so the server doesn't need to worry about +# IP addresses. This is indicated by the asterisks in the directives below. +# +# Please see the documentation at +# +# for further details before you try to setup virtual hosts. +# +# You may use the command line option '-S' to verify your virtual host +# configuration. + +# +# Use name-based virtual hosting. +# +#NameVirtualHost *:80 +# +# NOTE: NameVirtualHost cannot be used without a port specifier +# (e.g. :80) if mod_ssl is being used, due to the nature of the +# SSL protocol. +# + +# +# VirtualHost example: +# Almost any Apache directive may go into a VirtualHost container. +# The first VirtualHost section is used for requests without a known +# server name. +# +# +# ServerAdmin webmaster@dummy-host.example.com +# DocumentRoot /www/docs/dummy-host.example.com +# ServerName dummy-host.example.com +# ErrorLog logs/dummy-host.example.com-error_log +# CustomLog logs/dummy-host.example.com-access_log common +# -- cgit v1.2.3 From 3830c0f9005ba8c301a6e0bedc129a3a4be723a6 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 3 Apr 2019 01:49:38 +0200 Subject: Reinsert fix for #5456 (#6904) Dependencies generated by the script introduced with #6839 were not including anymore the fix about enum34 for CentOS 6. This PR reinserts this fix, and updates the script overrides to ensure that this fix will stay in next dependencies generation. * Add the environment marker back. Ensure that it will stay by adding an override to dependencies generator. * Add comments, for future fix * Update letsencrypt-auto-source/rebuild_dependencies.py Co-Authored-By: adferrand * Update comment --- letsencrypt-auto-source/letsencrypt-auto | 4 +++- letsencrypt-auto-source/pieces/dependency-requirements.txt | 4 +++- letsencrypt-auto-source/rebuild_dependencies.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 822266785..f822b34f1 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1080,7 +1080,9 @@ cryptography==2.6.1 \ --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -enum34==1.1.6 \ +# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid +# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. +enum34==1.1.6 ; python_version < '3.4' \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 625ae45f1..8ee8fa5ea 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -66,7 +66,9 @@ cryptography==2.6.1 \ --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -enum34==1.1.6 \ +# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid +# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. +enum34==1.1.6 ; python_version < '3.4' \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index aab7d546b..22c89fae6 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -42,6 +42,10 @@ AUTHORITATIVE_CONSTRAINTS = { # Too touchy to move to a new version. And will be removed soon # in favor of pure python parser for Apache. 'python-augeas': '0.5.0', + # Package enum34 needs to be explicitly limited to Python2.x, in order to avoid + # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. + # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. + 'enum34': '1.1.6 ; python_version < \'3.4\'' } -- cgit v1.2.3 From 1bbfc669abb59838746521c38d71c42b2d0c8fb6 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 3 Apr 2019 11:53:40 -0700 Subject: Update changelog for 0.33.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74779ac0..d720861fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.33.0 - master +## 0.33.0 - 2019-04-03 ### Added -- cgit v1.2.3 From 58c21aa4847b6ee0a5da4bad65b4824da931d935 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 3 Apr 2019 13:08:02 -0700 Subject: Release 0.33.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 457 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 4 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 34 +- letsencrypt-auto | 457 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +- 26 files changed, 548 insertions(+), 506 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index cd4ea3ef5..1b9189e67 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.33.0.dev0' +version = '0.33.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index dfedc3f0d..faa750039 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 0c82a7437..ad7abfd08 100755 --- a/certbot-auto +++ b/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.32.0" +LE_AUTO_VERSION="0.33.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -488,11 +488,18 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: # - CentOS 6 + # - Fedora 29 InitializeRPMCommonBase + # Fedora 29 must use python3-virtualenv + if $TOOL list python3-virtualenv >/dev/null 2>&1; then + python_pkgs="python3 + python3-virtualenv + python3-devel + " # EPEL uses python34 - if $TOOL list python34 >/dev/null 2>&1; then + elif $TOOL list python34 >/dev/null 2>&1; then python_pkgs="python34 python34-devel python34-tools @@ -741,7 +748,10 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - if [ "$PYVER" -eq 26 ]; then + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -898,6 +908,41 @@ else: UNLIKELY_EOF } +# Create a new virtual environment for Certbot. It will overwrite any existing one. +# Parameters: LE_PYTHON, VENV_PATH, PYVER, VERBOSE +CreateVenv() { + "$1" - "$2" "$3" "$4" << "UNLIKELY_EOF" +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys + + +def create_venv(venv_path, pyver, verbose): + if os.path.exists(venv_path): + shutil.rmtree(venv_path) + + stdout = sys.stdout if verbose == '1' else open(os.devnull, 'w') + + if int(pyver) <= 27: + # Use virtualenv binary + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + command = ['virtualenv', '--no-site-packages', '--python', sys.executable, venv_path] + subprocess.check_call(command, stdout=stdout, env=environ) + else: + # Use embedded venv module in Python 3 + command = [sys.executable, '-m', 'venv', venv_path] + subprocess.check_call(command, stdout=stdout) + + +if __name__ == '__main__': + create_venv(*sys.argv[1:]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -953,22 +998,7 @@ if [ "$1" = "--le-auto-phase2" ]; then if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then say "Creating virtual environment..." DeterminePythonVersion - rm -rf "$VENV_PATH" - if [ "$PYVER" -le 27 ]; then - # Use an environment variable instead of a flag for compatibility with old versions - if [ "$VERBOSE" = 1 ]; then - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" - else - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" \ - > /dev/null - fi - else - if [ "$VERBOSE" = 1 ]; then - "$LE_PYTHON" -m venv "$VENV_PATH" - else - "$LE_PYTHON" -m venv "$VENV_PATH" > /dev/null - fi - fi + CreateVenv "$LE_PYTHON" "$VENV_PATH" "$PYVER" "$VERBOSE" if [ -n "$BOOTSTRAP_VERSION" ]; then echo "$BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" @@ -982,202 +1012,197 @@ if [ "$1" = "--le-auto-phase2" ]; then # There is no $ interpolation due to quotes on starting heredoc delimiter. # ------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt" -# This is the flattened list of packages certbot-auto installs. To generate -# this, do -# `pip install --no-cache-dir -e acme -e . -e certbot-apache -e certbot-nginx`, -# and then use `hashin` or a more secure method to gather the hashes. - -# Hashin example: -# pip install hashin -# hashin -r dependency-requirements.txt cryptography==1.5.2 -# sets the new certbot-auto pinned version of cryptography to 1.5.2 - -argparse==1.4.0 \ - --hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314 \ - --hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 - -# This comes before cffi because cffi will otherwise install an unchecked -# version via setup_requires. -pycparser==2.14 \ - --hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \ - --no-binary pycparser - -asn1crypto==0.22.0 \ - --hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \ - --hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a -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 +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \ +# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# ``` +ConfigArgParse==0.14.0 \ + --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 +asn1crypto==0.24.0 \ + --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ + --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 +certifi==2019.3.9 \ + --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ + --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae +cffi==1.12.2 \ + --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ + --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ + --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ + --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ + --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ + --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ + --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ + --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ + --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ + --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ + --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ + --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ + --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ + --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ + --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ + --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ + --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ + --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ + --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ + --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ + --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ + --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ + --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ + --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ + --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ + --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ + --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ + --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ - --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ - --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 -enum34==1.1.2 ; python_version < '3.4' \ - --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ - --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 + --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 +cryptography==2.6.1 \ + --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ + --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ + --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ + --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ + --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ + --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ + --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ + --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ + --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ + --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ + --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ + --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ + --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ + --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ + --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ + --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ + --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ + --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ + --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 +# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid +# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. +enum34==1.1.6 ; python_version < '3.4' \ + --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ + --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ + --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ + --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -idna==2.5 \ - --hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70 \ - --hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab -ipaddress==1.0.16 \ - --hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \ - --hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0 +future==0.17.1 \ + --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +idna==2.8 \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c +ipaddress==1.0.22 \ + --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ + --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c josepy==1.1.0 \ --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 -linecache2==1.0.0 \ - --hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \ - --hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c -# Using an older version of mock here prevents regressions of #5276. mock==1.3.0 \ - --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \ - --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 -ordereddict==1.1 \ - --hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \ - --no-binary ordereddict -packaging==16.8 \ - --hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \ - --hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e -parsedatetime==2.1 \ - --hash=sha256:ce9d422165cf6e963905cd5f74f274ebf7cc98c941916169178ef93f0e557838 \ - --hash=sha256:17c578775520c99131634e09cfca5a05ea9e1bd2a05cd06967ebece10df7af2d -pbr==1.8.1 \ - --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ - --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==18.0.0 \ - --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ - --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 -pyparsing==2.1.8 \ - --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ - --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ - --hash=sha256:ab09aee814c0241ff0c503cff30018219fe1fc14501d89f406f4664a0ec9fbcd \ - --hash=sha256:6e9a7f052f8e26bcf749e4033e3115b6dc7e3c85aafcb794b9a88c9d9ef13c97 \ - --hash=sha256:9f463a6bcc4eeb6c08f1ed84439b17818e2085937c0dee0d7674ac127c67c12b \ - --hash=sha256:3626b4d81cfb300dad57f52f2f791caaf7b06c09b368c0aa7b868e53a5775424 \ - --hash=sha256:367b90cc877b46af56d4580cd0ae278062903f02b8204ab631f5a2c0f50adfd0 \ - --hash=sha256:9f1ea360086cd68681e7f4ca8f1f38df47bf81942a0d76a9673c2d23eff35b13 -pyRFC3339==1.0 \ - --hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \ - --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 + --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ + --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb +parsedatetime==2.4 \ + --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ + --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 +pbr==5.1.3 \ + --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ + --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pyOpenSSL==19.0.0 \ + --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ + --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +pyRFC3339==1.1 \ + --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ + --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +pyparsing==2.3.1 \ + --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ + --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 python-augeas==0.5.0 \ - --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \ - --no-binary python-augeas -pytz==2015.7 \ - --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ - --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ - --hash=sha256:ead4aefa7007249e05e51b01095719d5a8dd95760089f5730aac5698b1932918 \ - --hash=sha256:3cca0df08bd0ed98432390494ce3ded003f5e661aa460be7a734bffe35983605 \ - --hash=sha256:3ede470d3d17ba3c07638dfa0d10452bc1b6e5ad326127a65ba77e6aaeb11bec \ - --hash=sha256:68c47964f7186eec306b13629627722b9079cd4447ed9e5ecaecd4eac84ca734 \ - --hash=sha256:dd5d3991950aae40a6c81de1578942e73d629808cefc51d12cd157980e6cfc18 \ - --hash=sha256:a77c52062c07eb7c7b30545dbc73e32995b7e117eea750317b5cb5c7a4618f14 \ - --hash=sha256:81af9aec4bc960a9a0127c488f18772dae4634689233f06f65443e7b11ebeb51 \ - --hash=sha256:e079b1dadc5c06246cc1bb6fe1b23a50b1d1173f2edd5104efd40bb73a28f406 \ - --hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \ - --hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \ - --hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3 -requests==2.20.0 \ - --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \ - --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 -six==1.10.0 \ - --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ - --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a -traceback2==1.4.0 \ - --hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23 \ - --hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 -unittest2==1.1.0 \ - --hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \ - --hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579 -zope.component==4.2.2 \ - --hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \ - --no-binary zope.component -zope.event==4.1.0 \ - --hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \ - --no-binary zope.event -zope.interface==4.1.3 \ - --hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \ - --hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \ - --hash=sha256:6788416f7ea7f5b8a97be94825377aa25e8bdc73463e07baaf9858b29e737077 \ - --hash=sha256:6f3230f7254518201e5a3708cbb2de98c848304f06e3ded8bfb39e5825cba2e1 \ - --hash=sha256:5fa575a5240f04200c3088427d0d4b7b737f6e9018818a51d8d0f927a6a2517a \ - --hash=sha256:522194ad6a545735edd75c8a83f48d65d1af064e432a7d320d64f56bafc12e99 \ - --hash=sha256:e8c7b2d40943f71c99148c97f66caa7f5134147f57423f8db5b4825099ce9a09 \ - --hash=sha256:279024f0208601c3caa907c53876e37ad88625f7eaf1cb3842dbe360b2287017 \ - --hash=sha256:2e221a9eec7ccc58889a278ea13dcfed5ef939d80b07819a9a8b3cb1c681484f \ - --hash=sha256:69118965410ec86d44dc6b9017ee3ddbd582e0c0abeef62b3a19dbf6c8ad132b \ - --hash=sha256:d04df8686ec864d0cade8cf199f7f83aecd416109a20834d568f8310ded12dea \ - --hash=sha256:e75a947e15ee97e7e71e02ea302feb2fc62d3a2bb4668bf9dfbed43a506ac7e7 \ - --hash=sha256:4e45d22fb883222a5ab9f282a116fec5ee2e8d1a568ccff6a2d75bbd0eb6bcfc \ - --hash=sha256:bce9339bb3c7a55e0803b63d21c5839e8e479bc85c4adf42ae415b72f94facb2 \ - --hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \ - --hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \ - --hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392 -requests-toolbelt==0.8.0 \ - --hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \ - --hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5 -chardet==3.0.2 \ - --hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \ - --hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0 + --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 +pytz==2018.9 \ + --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ + --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +requests==2.21.0 \ + --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ + --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 urllib3==1.24.1 \ --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 -certifi==2017.4.17 \ - --hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \ - --hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a +zope.component==4.5 \ + --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ + --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.deferredimport==4.3 \ + --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ + --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deprecation==4.4.0 \ + --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ + --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 +zope.event==4.4 \ + --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ + --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 +zope.hookable==4.2.0 \ + --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ + --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ + --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ + --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ + --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ + --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ + --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ + --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ + --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ + --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 +zope.interface==4.6.0 \ + --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ + --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ + --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ + --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ + --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ + --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ + --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ + --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ + --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ + --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ + --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ + --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ + --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ + --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ + --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ + --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ + --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ + --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ + --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ + --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ + --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ + --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ + --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ + --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ + --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ + --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ + --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ + --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ + --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.proxy==4.3.1 \ + --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ + --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ + --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ + --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ + --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ + --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ + --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ + --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ + --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ + --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ + --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed # Contains the requirements for the letsencrypt package. # @@ -1190,18 +1215,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.32.0 \ - --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ - --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 -acme==0.32.0 \ - --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ - --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec -certbot-apache==0.32.0 \ - --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ - --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 -certbot-nginx==0.32.0 \ - --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ - --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 +certbot==0.33.0 \ + --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ + --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 +acme==0.33.0 \ + --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ + --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e +certbot-apache==0.33.0 \ + --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ + --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d +certbot-nginx==0.33.0 \ + --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ + --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1364,7 +1389,7 @@ def main(): temp, digest) for path, digest in PACKAGES] - # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + # Calling pip as a module is the preferred way to avoid problems about pip self-upgrade. command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] # Disable cache since it is not used and it otherwise sometimes throws permission warnings: command.extend(['--no-cache-dir'] if has_pip_cache else []) diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 858cef831..d7cd17953 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 84414e0c0..6cfb24fa3 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4b2fe15be..dff377593 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 1de51f7ca..a5b805a4f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 113e55e12..9a9a0fee0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 6e882a4f7..4da007dc9 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index bdbd198d1..ad0b9fda2 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index c2556797d..ab186cf1d 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index c494d6d4e..c0ecb22e1 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 4c57c5709..5110aeb7b 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 9c9f233cc..71315e2d9 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index cf6fee5d7..5f3dae87d 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index dacf41101..29f699321 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 321ad3b5d..11abb3ae6 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 46d92a6ff..5428f42df 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0.dev0' +version = '0.33.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index f768ed5d9..fc36aeca6 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0.dev0' +version = '0.33.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.33.0.dev0', + 'certbot>=0.33.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/__init__.py b/certbot/__init__.py index 38e25f3a4..345acfc06 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.33.0.dev0' +__version__ = '0.33.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index d883319f4..89354625b 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -1,4 +1,4 @@ -usage: +usage: certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, @@ -29,6 +29,7 @@ manage certificates: manage your account with Let's Encrypt: register Create a Let's Encrypt ACME account + unregister Deactivate 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 @@ -99,11 +100,10 @@ optional arguments: --preferred-challenges PREF_CHALLS A sorted, comma delimited list of the preferred challenge to use during authorization with the most - preferred challenge listed first (Eg, "dns" or "tls- - sni-01,http,dns"). Not all plugins support all - challenges. See - https://certbot.eff.org/docs/using.html#plugins for - details. ACME Challenges are versioned, but if you + preferred challenge listed first (Eg, "dns" or + "http,dns"). Not all plugins support all challenges. + See https://certbot.eff.org/docs/using.html#plugins + for details. ACME Challenges are versioned, but if you pick "http" rather than "http-01", Certbot will select the latest version automatically. (default: []) --user-agent USER_AGENT @@ -113,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.32.0 + "". (default: CertbotACMEClient/0.33.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -217,14 +217,6 @@ testing: False) --no-verify-ssl Disable verification of the ACME server's certificate. (default: False) - --tls-sni-01-port TLS_SNI_01_PORT - Port used during tls-sni-01 challenge. This only - affects the port Certbot listens on. A conforming ACME - server will still attempt to connect on port 443. - (default: 443) - --tls-sni-01-address TLS_SNI_01_ADDRESS - The address the server listens to during tls-sni-01 - challenge. (default: ) --http-01-port HTTP01_PORT Port used in the http-01 challenge. This only affects the port Certbot listens on. A conforming ACME server @@ -233,6 +225,10 @@ testing: --http-01-address HTTP01_ADDRESS The address the server listens to during http-01 challenge. (default: ) + --https-port HTTPS_PORT + Port used to serve HTTPS. This affects which port + Nginx will listen on after a LE certificate is + installed. (default: 443) --break-my-certs Be willing to replace or renew valid certificates with invalid (testing/staging) certificates (default: False) @@ -672,12 +668,8 @@ manual: challenge. $CERTBOT_DOMAIN will always contain the domain being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION is the validation string, and $CERTBOT_TOKEN is the filename of the resource - requested when performing an HTTP-01 challenge. When performing a TLS- - SNI-01 challenge, $CERTBOT_SNI_DOMAIN will contain the SNI name for which - the ACME server expects to be presented with the self-signed certificate - located at $CERTBOT_CERT_PATH. The secret key needed to complete the TLS - handshake is located at $CERTBOT_KEY_PATH. An additional cleanup script - can also be provided and can use the additional variable + requested when performing an HTTP-01 challenge. An additional cleanup + script can also be provided and can use the additional variable $CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script. diff --git a/letsencrypt-auto b/letsencrypt-auto index 0c82a7437..ad7abfd08 100755 --- a/letsencrypt-auto +++ b/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.32.0" +LE_AUTO_VERSION="0.33.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -488,11 +488,18 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: # - CentOS 6 + # - Fedora 29 InitializeRPMCommonBase + # Fedora 29 must use python3-virtualenv + if $TOOL list python3-virtualenv >/dev/null 2>&1; then + python_pkgs="python3 + python3-virtualenv + python3-devel + " # EPEL uses python34 - if $TOOL list python34 >/dev/null 2>&1; then + elif $TOOL list python34 >/dev/null 2>&1; then python_pkgs="python34 python34-devel python34-tools @@ -741,7 +748,10 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - if [ "$PYVER" -eq 26 ]; then + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -898,6 +908,41 @@ else: UNLIKELY_EOF } +# Create a new virtual environment for Certbot. It will overwrite any existing one. +# Parameters: LE_PYTHON, VENV_PATH, PYVER, VERBOSE +CreateVenv() { + "$1" - "$2" "$3" "$4" << "UNLIKELY_EOF" +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys + + +def create_venv(venv_path, pyver, verbose): + if os.path.exists(venv_path): + shutil.rmtree(venv_path) + + stdout = sys.stdout if verbose == '1' else open(os.devnull, 'w') + + if int(pyver) <= 27: + # Use virtualenv binary + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + command = ['virtualenv', '--no-site-packages', '--python', sys.executable, venv_path] + subprocess.check_call(command, stdout=stdout, env=environ) + else: + # Use embedded venv module in Python 3 + command = [sys.executable, '-m', 'venv', venv_path] + subprocess.check_call(command, stdout=stdout) + + +if __name__ == '__main__': + create_venv(*sys.argv[1:]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -953,22 +998,7 @@ if [ "$1" = "--le-auto-phase2" ]; then if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then say "Creating virtual environment..." DeterminePythonVersion - rm -rf "$VENV_PATH" - if [ "$PYVER" -le 27 ]; then - # Use an environment variable instead of a flag for compatibility with old versions - if [ "$VERBOSE" = 1 ]; then - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" - else - VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" \ - > /dev/null - fi - else - if [ "$VERBOSE" = 1 ]; then - "$LE_PYTHON" -m venv "$VENV_PATH" - else - "$LE_PYTHON" -m venv "$VENV_PATH" > /dev/null - fi - fi + CreateVenv "$LE_PYTHON" "$VENV_PATH" "$PYVER" "$VERBOSE" if [ -n "$BOOTSTRAP_VERSION" ]; then echo "$BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" @@ -982,202 +1012,197 @@ if [ "$1" = "--le-auto-phase2" ]; then # There is no $ interpolation due to quotes on starting heredoc delimiter. # ------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt" -# This is the flattened list of packages certbot-auto installs. To generate -# this, do -# `pip install --no-cache-dir -e acme -e . -e certbot-apache -e certbot-nginx`, -# and then use `hashin` or a more secure method to gather the hashes. - -# Hashin example: -# pip install hashin -# hashin -r dependency-requirements.txt cryptography==1.5.2 -# sets the new certbot-auto pinned version of cryptography to 1.5.2 - -argparse==1.4.0 \ - --hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314 \ - --hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 - -# This comes before cffi because cffi will otherwise install an unchecked -# version via setup_requires. -pycparser==2.14 \ - --hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \ - --no-binary pycparser - -asn1crypto==0.22.0 \ - --hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \ - --hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a -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 +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \ +# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# ``` +ConfigArgParse==0.14.0 \ + --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 +asn1crypto==0.24.0 \ + --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ + --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 +certifi==2019.3.9 \ + --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ + --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae +cffi==1.12.2 \ + --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ + --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ + --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ + --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ + --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ + --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ + --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ + --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ + --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ + --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ + --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ + --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ + --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ + --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ + --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ + --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ + --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ + --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ + --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ + --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ + --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ + --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ + --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ + --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ + --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ + --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ + --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ + --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ - --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \ - --no-binary configobj -cryptography==2.5 \ - --hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \ - --hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \ - --hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \ - --hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \ - --hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \ - --hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \ - --hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \ - --hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \ - --hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \ - --hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \ - --hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \ - --hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \ - --hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \ - --hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \ - --hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \ - --hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \ - --hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \ - --hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \ - --hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401 -enum34==1.1.2 ; python_version < '3.4' \ - --hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \ - --hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501 + --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 +cryptography==2.6.1 \ + --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ + --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ + --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ + --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ + --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ + --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ + --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ + --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ + --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ + --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ + --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ + --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ + --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ + --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ + --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ + --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ + --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ + --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ + --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 +# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid +# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. +enum34==1.1.6 ; python_version < '3.4' \ + --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ + --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ + --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ + --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -idna==2.5 \ - --hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70 \ - --hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab -ipaddress==1.0.16 \ - --hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \ - --hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0 +future==0.17.1 \ + --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +idna==2.8 \ + --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ + --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c +ipaddress==1.0.22 \ + --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ + --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c josepy==1.1.0 \ --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 -linecache2==1.0.0 \ - --hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \ - --hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c -# Using an older version of mock here prevents regressions of #5276. mock==1.3.0 \ - --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \ - --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 -ordereddict==1.1 \ - --hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \ - --no-binary ordereddict -packaging==16.8 \ - --hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \ - --hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e -parsedatetime==2.1 \ - --hash=sha256:ce9d422165cf6e963905cd5f74f274ebf7cc98c941916169178ef93f0e557838 \ - --hash=sha256:17c578775520c99131634e09cfca5a05ea9e1bd2a05cd06967ebece10df7af2d -pbr==1.8.1 \ - --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ - --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==18.0.0 \ - --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ - --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 -pyparsing==2.1.8 \ - --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ - --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ - --hash=sha256:ab09aee814c0241ff0c503cff30018219fe1fc14501d89f406f4664a0ec9fbcd \ - --hash=sha256:6e9a7f052f8e26bcf749e4033e3115b6dc7e3c85aafcb794b9a88c9d9ef13c97 \ - --hash=sha256:9f463a6bcc4eeb6c08f1ed84439b17818e2085937c0dee0d7674ac127c67c12b \ - --hash=sha256:3626b4d81cfb300dad57f52f2f791caaf7b06c09b368c0aa7b868e53a5775424 \ - --hash=sha256:367b90cc877b46af56d4580cd0ae278062903f02b8204ab631f5a2c0f50adfd0 \ - --hash=sha256:9f1ea360086cd68681e7f4ca8f1f38df47bf81942a0d76a9673c2d23eff35b13 -pyRFC3339==1.0 \ - --hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \ - --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 + --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ + --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb +parsedatetime==2.4 \ + --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ + --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 +pbr==5.1.3 \ + --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ + --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pyOpenSSL==19.0.0 \ + --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ + --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +pyRFC3339==1.1 \ + --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ + --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +pyparsing==2.3.1 \ + --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ + --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 python-augeas==0.5.0 \ - --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \ - --no-binary python-augeas -pytz==2015.7 \ - --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ - --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ - --hash=sha256:ead4aefa7007249e05e51b01095719d5a8dd95760089f5730aac5698b1932918 \ - --hash=sha256:3cca0df08bd0ed98432390494ce3ded003f5e661aa460be7a734bffe35983605 \ - --hash=sha256:3ede470d3d17ba3c07638dfa0d10452bc1b6e5ad326127a65ba77e6aaeb11bec \ - --hash=sha256:68c47964f7186eec306b13629627722b9079cd4447ed9e5ecaecd4eac84ca734 \ - --hash=sha256:dd5d3991950aae40a6c81de1578942e73d629808cefc51d12cd157980e6cfc18 \ - --hash=sha256:a77c52062c07eb7c7b30545dbc73e32995b7e117eea750317b5cb5c7a4618f14 \ - --hash=sha256:81af9aec4bc960a9a0127c488f18772dae4634689233f06f65443e7b11ebeb51 \ - --hash=sha256:e079b1dadc5c06246cc1bb6fe1b23a50b1d1173f2edd5104efd40bb73a28f406 \ - --hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \ - --hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \ - --hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3 -requests==2.20.0 \ - --hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \ - --hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279 -six==1.10.0 \ - --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ - --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a -traceback2==1.4.0 \ - --hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23 \ - --hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 -unittest2==1.1.0 \ - --hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \ - --hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579 -zope.component==4.2.2 \ - --hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \ - --no-binary zope.component -zope.event==4.1.0 \ - --hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \ - --no-binary zope.event -zope.interface==4.1.3 \ - --hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \ - --hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \ - --hash=sha256:6788416f7ea7f5b8a97be94825377aa25e8bdc73463e07baaf9858b29e737077 \ - --hash=sha256:6f3230f7254518201e5a3708cbb2de98c848304f06e3ded8bfb39e5825cba2e1 \ - --hash=sha256:5fa575a5240f04200c3088427d0d4b7b737f6e9018818a51d8d0f927a6a2517a \ - --hash=sha256:522194ad6a545735edd75c8a83f48d65d1af064e432a7d320d64f56bafc12e99 \ - --hash=sha256:e8c7b2d40943f71c99148c97f66caa7f5134147f57423f8db5b4825099ce9a09 \ - --hash=sha256:279024f0208601c3caa907c53876e37ad88625f7eaf1cb3842dbe360b2287017 \ - --hash=sha256:2e221a9eec7ccc58889a278ea13dcfed5ef939d80b07819a9a8b3cb1c681484f \ - --hash=sha256:69118965410ec86d44dc6b9017ee3ddbd582e0c0abeef62b3a19dbf6c8ad132b \ - --hash=sha256:d04df8686ec864d0cade8cf199f7f83aecd416109a20834d568f8310ded12dea \ - --hash=sha256:e75a947e15ee97e7e71e02ea302feb2fc62d3a2bb4668bf9dfbed43a506ac7e7 \ - --hash=sha256:4e45d22fb883222a5ab9f282a116fec5ee2e8d1a568ccff6a2d75bbd0eb6bcfc \ - --hash=sha256:bce9339bb3c7a55e0803b63d21c5839e8e479bc85c4adf42ae415b72f94facb2 \ - --hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \ - --hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \ - --hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392 -requests-toolbelt==0.8.0 \ - --hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \ - --hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5 -chardet==3.0.2 \ - --hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \ - --hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0 + --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 +pytz==2018.9 \ + --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ + --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +requests==2.21.0 \ + --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ + --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 urllib3==1.24.1 \ --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 -certifi==2017.4.17 \ - --hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \ - --hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a +zope.component==4.5 \ + --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ + --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.deferredimport==4.3 \ + --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ + --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deprecation==4.4.0 \ + --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ + --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 +zope.event==4.4 \ + --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ + --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 +zope.hookable==4.2.0 \ + --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ + --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ + --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ + --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ + --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ + --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ + --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ + --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ + --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ + --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 +zope.interface==4.6.0 \ + --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ + --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ + --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ + --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ + --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ + --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ + --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ + --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ + --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ + --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ + --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ + --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ + --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ + --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ + --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ + --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ + --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ + --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ + --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ + --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ + --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ + --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ + --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ + --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ + --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ + --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ + --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ + --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ + --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.proxy==4.3.1 \ + --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ + --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ + --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ + --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ + --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ + --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ + --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ + --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ + --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ + --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ + --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed # Contains the requirements for the letsencrypt package. # @@ -1190,18 +1215,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.32.0 \ - --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ - --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 -acme==0.32.0 \ - --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ - --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec -certbot-apache==0.32.0 \ - --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ - --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 -certbot-nginx==0.32.0 \ - --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ - --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 +certbot==0.33.0 \ + --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ + --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 +acme==0.33.0 \ + --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ + --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e +certbot-apache==0.33.0 \ + --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ + --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d +certbot-nginx==0.33.0 \ + --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ + --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1364,7 +1389,7 @@ def main(): temp, digest) for path, digest in PACKAGES] - # On Windows, pip self-upgrade is not possible, it must be done through python interpreter. + # Calling pip as a module is the preferred way to avoid problems about pip self-upgrade. command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] # Disable cache since it is not used and it otherwise sometimes throws permission warnings: command.extend(['--no-cache-dir'] if has_pip_cache else []) diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index eeb8d7d12..f9e2b891d 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlyAMZ0ACgkQTRfJlc2X -dfLItQf/SNv+at1Pw5oiEbWleNPpmz9srlkf9AHU92Hh3p7+OljcaWQindtCYtlO -UvWV/CjGzObmJvO+Pgy2epNhD8cTjPamI46l5UG2nwvy8V+JemS937Ae6paivt8T -/RaFKfyNDfxBjQhHS1ypVuRrFgAQ5CG0iGuJSMgwLpcKCZyKAim3+Vb57+Esq+zG -Cp7GmJk9h1z5FbNukbaFHBlJQIefJoQclh1yUw11pLab0uxIOdc9WiEWLLAVE512 -SMQM2sNv49uh7mRnxW+6WU6dor6JI9Ff1L4D5hfglzBJRM4qLU/hGv54oVycWq4i -eFjtqDfo5XMwnbnUnVkEB73pY6lzDg== -=YaE3 +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlylEcIACgkQTRfJlc2X +dfJB6Qf/WNi6T6T0SL6dpdh4wyO7R70PJMA9BwOoqjUKM27/gF/j7MHFcURXK9Xj +Iva/RM6Nv7IhbCvtAkUfMue0pOFQf9aoSdpVTsFEZZJ/JpRqZBQcXWODu00hq6+G +13js56P2i2BTgjnwN8O2Yd6PXXxjvWuOsCfBJOS5gS8aepHkMnrZ+y/8rbNseGb/ +7w/5n5EcVNm5ahTJv7VpFI1Yz4K9rICn+NLe7ULeSm3bhw8Hxg6pfKsHBqlCMzU/ +eHI4/SQaTx75zZiyT8aa5EIECSfomHOs4JBJ3kpgpXPeSJ1s0wKpFLQK5pDbaPBR +oZaDeWNXXnBPHcjmlIpptK3lOpshQg== +=ddnT -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index f822b34f1..ad7abfd08 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.33.0.dev0" +LE_AUTO_VERSION="0.33.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1215,18 +1215,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.32.0 \ - --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ - --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 -acme==0.32.0 \ - --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ - --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec -certbot-apache==0.32.0 \ - --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ - --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 -certbot-nginx==0.32.0 \ - --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ - --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 +certbot==0.33.0 \ + --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ + --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 +acme==0.33.0 \ + --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ + --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e +certbot-apache==0.33.0 \ + --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ + --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d +certbot-nginx==0.33.0 \ + --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ + --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 17eea3eba..f8343c1e5 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 3e0dbde7e..3d35291f0 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.32.0 \ - --hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \ - --hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8 -acme==0.32.0 \ - --hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \ - --hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec -certbot-apache==0.32.0 \ - --hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \ - --hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97 -certbot-nginx==0.32.0 \ - --hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \ - --hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84 +certbot==0.33.0 \ + --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ + --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 +acme==0.33.0 \ + --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ + --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e +certbot-apache==0.33.0 \ + --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ + --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d +certbot-nginx==0.33.0 \ + --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ + --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 -- cgit v1.2.3 From 69bb3eac2cd6ef2c849ac205c7085ca99582caa9 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 3 Apr 2019 13:08:10 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d720861fa..30cb4d4de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.34.0 - 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. + ## 0.33.0 - 2019-04-03 ### Added -- cgit v1.2.3 From 7b7f7b25fb25414156c9e80d4829531faa78a05e Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 3 Apr 2019 13:08:11 -0700 Subject: Bump version to 0.34.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 1b9189e67..11e4f3372 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index faa750039..978f7752e 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index d7cd17953..926a5e7b4 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 6cfb24fa3..878ecd5f9 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index dff377593..e2462ac64 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index a5b805a4f..1cea2f8cc 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 9a9a0fee0..eac823013 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 4da007dc9..9fcc76b7e 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index ad0b9fda2..e21b6bcc4 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index ab186cf1d..67f31b924 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index c0ecb22e1..57305e83a 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 5110aeb7b..2d4478dd1 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 71315e2d9..0b66f6f22 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 5f3dae87d..3d6b45017 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 29f699321..321e3c29a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 11abb3ae6..196d80b08 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 5428f42df..787e361b1 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index db6b261f0..7ee0d9e09 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,2 @@ acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.33.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index fc36aeca6..07c4e5301 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 345acfc06..dc2ea5c99 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.33.0' +__version__ = '0.34.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ad7abfd08..ac72dfac9 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.33.0" +LE_AUTO_VERSION="0.34.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 4de4b17216e153a82ef51ee9d0a18a5ac009edaf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 3 Apr 2019 15:16:43 -0700 Subject: Fix typo in changelog. (#6910) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30cb4d4de..fb32ba8fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ More details about these changes can be found on our GitHub repo. * Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. -* CLI flag `--http-port` has been added for Nginx plugin exclusively, and replaces +* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while setting up a new SSL vhost. By default the HTTPS port is 443. -- cgit v1.2.3 From 2cf216122b5e1d0d67cce56cede348791452bb21 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 3 Apr 2019 15:17:25 -0700 Subject: Correct changelog to mention acme changes. (#6909) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb32ba8fd..7d7f0e786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ 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-nginx -- cgit v1.2.3 From c2d9ea1f610ee431cfcd0d0557cfd88ac78fc08b Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 4 Apr 2019 19:46:46 +0200 Subject: Protect certbot-auto against non numerical version release in some RPM distributions (#6913) Fixes #6912 Bash evaluate all condition in a predicate statement, eg. `"$SOMEVAR" = "test" -a "$ANOTHERVAR" = "test2"`, even if it is not necessary, for instance if the first condition is false in the example here. As a consequence, on non-Fedora distributions, an evaluation of the distribution version could be done on non numeric value, eg. `"6.7" -eq "29"`, making certbot-auto failing in this case. This PR fixes that, by evaluating the version on RPM distributions only if we are on Fedora. Otherwise, version will be "0". --- letsencrypt-auto-source/letsencrypt-auto | 5 ++++- letsencrypt-auto-source/letsencrypt-auto.template | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ac72dfac9..1958a5195 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -750,7 +750,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 0d3312968..a5a29c483 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -325,7 +325,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" -- cgit v1.2.3 From 30eafba99780ccf1b1df8d12ac5b6bee18204cd9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 11:08:07 -0700 Subject: Update changelog about #6912 fix. (#6914) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7f0e786..8070040ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* A bug causing certbot-auto to print warnings or crash on some RHEL based + systems has been resolved. 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 -- cgit v1.2.3 From 7c7715743c07feb4e664cea08d70becd3bbf38a4 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 11:38:30 -0700 Subject: Prepare for the 0.33.1 release. (#6915) The changelog should still say - master because it will be fixed up automatically by the release script at https://github.com/certbot/certbot/blob/master/tools/_release.sh#L69. * Protect certbot-auto against non numerical version release in some RPM distributions (#6913) Fixes #6912 Bash evaluate all condition in a predicate statement, eg. `"$SOMEVAR" = "test" -a "$ANOTHERVAR" = "test2"`, even if it is not necessary, for instance if the first condition is false in the example here. As a consequence, on non-Fedora distributions, an evaluation of the distribution version could be done on non numeric value, eg. `"6.7" -eq "29"`, making certbot-auto failing in this case. This PR fixes that, by evaluating the version on RPM distributions only if we are on Fedora. Otherwise, version will be "0". (cherry picked from commit c2d9ea1f610ee431cfcd0d0557cfd88ac78fc08b) * Update changelog about #6912 fix. (#6914) (cherry picked from commit 30eafba99780ccf1b1df8d12ac5b6bee18204cd9) * cleanup changelog --- CHANGELOG.md | 13 +++++++++++++ letsencrypt-auto-source/letsencrypt-auto | 5 ++++- letsencrypt-auto-source/letsencrypt-auto.template | 5 ++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d720861fa..f8095364a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.33.1 - master + +### Fixed + +* A bug causing certbot-auto to print warnings or crash on some RHEL based + systems has been resolved. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + ## 0.33.0 - 2019-04-03 ### Added diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ad7abfd08..608bb1ee4 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -750,7 +750,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 0d3312968..a5a29c483 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -325,7 +325,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" -- cgit v1.2.3 From 6590875a1a0aa539737d9026dd1995edab27bc98 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 13:30:38 -0700 Subject: mattermost > irc (#6916) --- docs/contributing.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 264db630f..b69b5c43f 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -369,9 +369,11 @@ Asking for help =============== If you have any questions while working on a Certbot issue, don't hesitate to -ask for help! You can do this in the #letsencrypt-dev IRC channel on Freenode. -If you don't already have an IRC client set up, we recommend you join using -`Riot `_. +ask for help! You can do this in the Certbot channel in EFF's Mattermost +instance for its open source projects. To join, `create an account +`_ +and then visit the `Certbot channel +`_. Updating certbot-auto and letsencrypt-auto ========================================== -- cgit v1.2.3 From 45869f8315ccafc41d8e086bfb81c657a662aa86 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 15:02:08 -0700 Subject: Update changelog for 0.33.1 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8095364a..ed191674d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.33.1 - master +## 0.33.1 - 2019-04-04 ### Fixed -- cgit v1.2.3 From c32b57607f79db1aab35ba6682d8bc947dc5a21e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 15:24:43 -0700 Subject: Release 0.33.1 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 31 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 31 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 +++++------ letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++--------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 ++++++++-------- 26 files changed, 87 insertions(+), 81 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 1b9189e67..2dd226c8a 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.33.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index faa750039..a52ef1008 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index ad7abfd08..d60bdbc70 100755 --- a/certbot-auto +++ b/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.33.0" +LE_AUTO_VERSION="0.33.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -750,7 +750,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" @@ -1215,18 +1218,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.0 \ - --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ - --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 -acme==0.33.0 \ - --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ - --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e -certbot-apache==0.33.0 \ - --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ - --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d -certbot-nginx==0.33.0 \ - --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ - --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 +certbot==0.33.1 \ + --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ + --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 +acme==0.33.1 \ + --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ + --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 +certbot-apache==0.33.1 \ + --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ + --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a +certbot-nginx==0.33.1 \ + --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ + --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index d7cd17953..aba3a1601 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 6cfb24fa3..82b20f9e9 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index dff377593..1063a3e71 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index a5b805a4f..289e5b29a 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 9a9a0fee0..1b9e926a0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 4da007dc9..dc17ab805 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index ad0b9fda2..134cf206b 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index ab186cf1d..3b79d4183 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index c0ecb22e1..64c74142a 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 5110aeb7b..0a7152fd4 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 71315e2d9..2d647e972 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 5f3dae87d..ad9558f8f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 29f699321..aa36fdd06 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 11abb3ae6..20cd292d7 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 5428f42df..f3d3adcaa 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.0' +version = '0.33.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index fc36aeca6..0fd70965b 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.0' +version = '0.33.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 345acfc06..d4d846b9d 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.33.0' +__version__ = '0.33.1' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 89354625b..e0979b989 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.33.0 + "". (default: CertbotACMEClient/0.33.1 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index ad7abfd08..d60bdbc70 100755 --- a/letsencrypt-auto +++ b/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.33.0" +LE_AUTO_VERSION="0.33.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -750,7 +750,10 @@ elif [ -f /etc/redhat-release ]; then DeterminePythonVersion "NOCRASH" # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + RPM_DIST_VERSION=0 + if [ "$RPM_DIST_NAME" = "fedora" ]; then + RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + fi if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" @@ -1215,18 +1218,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.0 \ - --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ - --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 -acme==0.33.0 \ - --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ - --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e -certbot-apache==0.33.0 \ - --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ - --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d -certbot-nginx==0.33.0 \ - --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ - --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 +certbot==0.33.1 \ + --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ + --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 +acme==0.33.1 \ + --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ + --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 +certbot-apache==0.33.1 \ + --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ + --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a +certbot-nginx==0.33.1 \ + --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ + --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index f9e2b891d..9e55d6cbb 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlylEcIACgkQTRfJlc2X -dfJB6Qf/WNi6T6T0SL6dpdh4wyO7R70PJMA9BwOoqjUKM27/gF/j7MHFcURXK9Xj -Iva/RM6Nv7IhbCvtAkUfMue0pOFQf9aoSdpVTsFEZZJ/JpRqZBQcXWODu00hq6+G -13js56P2i2BTgjnwN8O2Yd6PXXxjvWuOsCfBJOS5gS8aepHkMnrZ+y/8rbNseGb/ -7w/5n5EcVNm5ahTJv7VpFI1Yz4K9rICn+NLe7ULeSm3bhw8Hxg6pfKsHBqlCMzU/ -eHI4/SQaTx75zZiyT8aa5EIECSfomHOs4JBJ3kpgpXPeSJ1s0wKpFLQK5pDbaPBR -oZaDeWNXXnBPHcjmlIpptK3lOpshQg== -=ddnT +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlymhBYACgkQTRfJlc2X +dfKmDAf/bkoGkWpxgzKjfd7BELnvhZduQ5Y30P2+Kq43jnop56zjZrt53tRsKeOc +Rat2Rq3e/rozlo5ie939iF2UPIX8fzEQ/IIyk4Om17dJ9ld25hteX7HWJThUX9+t +OtKA0c7jw7nSrCmWjKtGhZoTe2nsMqAtp0LV7kZ7T7Ex0HAxjrYu48wA2h6lgloe +65rXyBDVHdVc3FvevUiHKYkt+SONyWuRZpeQ8xn6YSQNDwYzCub3ro1h55GYfOK2 +65eklH1xVo7TvvR0Wo7l1/hIiK8Gz6ZX5dqDaxHT817zO1cqB4HhkHAl2O3q7TCo +JIo1jxMzlttRGJaegwnMTi20KyimyA== +=8Gjd -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 608bb1ee4..d60bdbc70 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.33.0" +LE_AUTO_VERSION="0.33.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1218,18 +1218,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.0 \ - --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ - --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 -acme==0.33.0 \ - --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ - --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e -certbot-apache==0.33.0 \ - --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ - --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d -certbot-nginx==0.33.0 \ - --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ - --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 +certbot==0.33.1 \ + --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ + --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 +acme==0.33.1 \ + --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ + --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 +certbot-apache==0.33.1 \ + --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ + --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a +certbot-nginx==0.33.1 \ + --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ + --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index f8343c1e5..96adf9078 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 3d35291f0..cb79bccd7 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.33.0 \ - --hash=sha256:37a7e833e2179c7aa0c34a11fbf661cf569d09e6f37eed6ffb805822d6068f3c \ - --hash=sha256:25da67cb7f14db5fb5867d8aa3efbf0405fd63e07feb7e39f2a6f86fc0c83846 -acme==0.33.0 \ - --hash=sha256:070a7a464b20316dc1fc84eb200357a2c9589b868e63307b04a7f76696c75667 \ - --hash=sha256:9d5e6a2f200d42bbf2cafd7767724e9c58ac230763095b0640b4a16d955cb63e -certbot-apache==0.33.0 \ - --hash=sha256:cf94a1d2bafbcd434f209c04802b1da43397e7cdce80c5e253e90ed77f7d4d95 \ - --hash=sha256:406764e0b31f7c764ac721a3e308902aefe169fc2ed229c06141fafc2173059d -certbot-nginx==0.33.0 \ - --hash=sha256:96a621cf0a3124ac03c8153c4b31cded06bf45775a0084a5f9ee5226c3b799fd \ - --hash=sha256:1469539412dfc06b8009ca3f509256cada5793694f3146b2e634a520361d42d5 +certbot==0.33.1 \ + --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ + --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 +acme==0.33.1 \ + --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ + --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 +certbot-apache==0.33.1 \ + --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ + --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a +certbot-nginx==0.33.1 \ + --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ + --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff -- cgit v1.2.3 From ae9c57d68cf5ad9e7fadbaaa39cd2d92dd3c5cec Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 15:24:44 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed191674d..2f7582a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.34.0 - 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. + ## 0.33.1 - 2019-04-04 ### Fixed -- cgit v1.2.3 From e63ceb8dd2b4980e888c4acdab55fb0f0261333e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 15:24:45 -0700 Subject: Bump version to 0.34.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 2dd226c8a..11e4f3372 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.33.1' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index a52ef1008..978f7752e 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index aba3a1601..926a5e7b4 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 82b20f9e9..878ecd5f9 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 1063a3e71..e2462ac64 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 289e5b29a..1cea2f8cc 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1b9e926a0..eac823013 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index dc17ab805..9fcc76b7e 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 134cf206b..e21b6bcc4 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 3b79d4183..67f31b924 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 64c74142a..57305e83a 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 0a7152fd4..2d4478dd1 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 2d647e972..0b66f6f22 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index ad9558f8f..3d6b45017 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index aa36fdd06..321e3c29a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 20cd292d7..196d80b08 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index f3d3adcaa..787e361b1 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.33.1' +version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index db6b261f0..b0b1ca6f5 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,2 @@ acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.33.1 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 0fd70965b..07c4e5301 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.33.1' +version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index d4d846b9d..dc2ea5c99 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.33.1' +__version__ = '0.34.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index d60bdbc70..5ce0f7b86 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.33.1" +LE_AUTO_VERSION="0.34.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From f2b071f8f40e985aa717af1ac72dad209cc85a99 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 4 Apr 2019 23:54:43 -0700 Subject: Don't search for plugins once for each config item (#6917) --- certbot/storage.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/certbot/storage.py b/certbot/storage.py index d0bc36c08..48587ba40 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -238,16 +238,17 @@ def _write_live_readme_to(readme_path, is_base_dir=False): "certificates.\n".format(prefix=prefix)) -def _relevant(option): +def _relevant(namespaces, option): """ Is this option one that could be restored for future renewal purposes? + + :param namespaces: plugin namespaces for configuration options + :type namespaces: `list` of `str` :param str option: the name of the option :rtype: bool """ from certbot import renewal - plugins = plugins_disco.PluginsRegistry.find_all() - namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] return (option in renewal.CONFIG_ITEMS or any(option.startswith(namespace) for namespace in namespaces)) @@ -262,10 +263,13 @@ def relevant_values(all_values): :rtype dict: """ + plugins = plugins_disco.PluginsRegistry.find_all() + namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] + rv = dict( (option, value) for option, value in six.iteritems(all_values) - if _relevant(option) and cli.option_was_set(option, value)) + if _relevant(namespaces, option) and cli.option_was_set(option, value)) # We always save the server value to help with forward compatibility # and behavioral consistency when versions of Certbot with different # server defaults are used. -- cgit v1.2.3 From 7d58e67fd6ece1456bb8190f56a08dfd36b2a25b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 5 Apr 2019 13:38:37 -0700 Subject: Move fixing oldest reqs to avoid merge conflicts. (#6921) When releasing 0.33.1 and resolving merge conflicts between the candidate-0.33.1 branch and master, I had merge conflicts in the local-oldest-requirements.txt files. This is because the point release branch does not contain modifications to these files that landed in master because it happens later in the release script in the commit bumping version numbers which is not included in the point release branch. I think having to resolve these merge conflicts is unnecessary and even a slight problem because it means that the "oldest" tests on the point release branch may still be using the latest version of certain components when they actually should be using an older version. I fixed this by moving this code earlier in the script so the local-oldest-requirements.txt files are updated at the same time as the setup.py files. --- tools/_release.sh | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tools/_release.sh b/tools/_release.sh index d75a0f487..7751f15b9 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -75,8 +75,15 @@ for pkg_dir in $SUBPKGS_NO_CERTBOT certbot-compatibility-test . do sed -i 's/\.dev0//' "$pkg_dir/setup.py" git add "$pkg_dir/setup.py" -done + if [ -f "$pkg_dir/local-oldest-requirements.txt" ]; then + sed -i "s/-e acme\[dev\]/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e acme/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e \.\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e \./certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + git add "$pkg_dir/local-oldest-requirements.txt" + fi +done SetVersion() { ver="$1" @@ -265,16 +272,6 @@ if [ "$RELEASE_BRANCH" = candidate-"$version" ] ; then SetVersion "$nextversion".dev0 letsencrypt-auto-source/build.py git add letsencrypt-auto-source/letsencrypt-auto - for pkg_dir in $SUBPKGS_NO_CERTBOT . - do - if [ -f "$pkg_dir/local-oldest-requirements.txt" ]; then - sed -i "s/-e acme\[dev\]/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e acme/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \.\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \./certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - git add "$pkg_dir/local-oldest-requirements.txt" - fi - done git diff git commit -m "Bump version to $nextversion" fi -- cgit v1.2.3 From aec29c2f1d1eab81c3c44b7bb589eb34ae8d0c91 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 5 Apr 2019 13:39:39 -0700 Subject: Remove amazon linux test farm targets. (#6822) --- tests/letstest/targets.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index c1a28af98..0a11f5dcb 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -44,16 +44,6 @@ targets: # - [ apt-get, install, -y, curl ] #----------------------------------------------------------------------------- # Other Redhat Distros - - ami: ami-60b6c60a - name: amazonlinux-2015.09.1 - type: centos - virt: hvm - user: ec2-user - - ami: ami-0d4cfd66 - name: amazonlinux-2015.03.1 - type: centos - virt: hvm - user: ec2-user - ami: ami-a8d369c0 name: RHEL7 type: centos -- cgit v1.2.3 From 157d1ea0d85e46ef8198eedfd03229b8008dc664 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 5 Apr 2019 13:42:30 -0700 Subject: Don't run pip tools/venv.py (#6923) It won't work. Instead, follow the instructions at the top of this document to set up a virtual environment and activate it. --- docs/contributing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index b69b5c43f..0319160e6 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -353,8 +353,8 @@ Steps: 1. Write your code! 2. Make sure your environment is set up properly and that you're in your - virtualenv. You can do this by running ``pip tools/venv.py``. - (this is a **very important** step) + virtualenv. You can do this by following the instructions in the + :ref:`Getting Started ` section. 3. Run ``tox -e lint`` to check for pylint errors. Fix any errors. 4. Run ``tox --skip-missing-interpreters`` to run the entire test suite including coverage. The ``--skip-missing-interpreters`` argument ignores -- cgit v1.2.3 From 944d0e05c854e51c0de6bdd44bc00af39e8db672 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 5 Apr 2019 15:01:09 -0700 Subject: Use venv over virtualenv in venv3 (#6922) Fixes #6861. _venv_common.py is no longer executable. The reason for this is the venv creation logic is now different between Python 2 and Python 3. We could add code that branches on the Python version running the script, but I personally think that's unnecessary. --setuptools and --no-site-packages is no longer passed to virtualenv either. These flags were made noops in virtualenv 1.10 and 1.7 respectively, but all of CentOS 6, 7, Debian 8+, and Ubuntu 14.04+ have new enough versions of virtualenv where these flags are no longer necessary. They are not even accepted as flags to Python 3's venv module. Use of VENV_ARGS from test_sdists.sh was also removed because that environment variable hasn't done anything in a while. I ran test farm tests on test_apache2.sh and test_sdists.sh with these changes and they passed. * Fixes #6861. * _venv_common is no longer executable. --- tests/letstest/scripts/test_apache2.sh | 2 +- tests/letstest/scripts/test_sdists.sh | 4 +- tools/_venv_common.py | 72 ++++++++++++++++++++++++++-------- tools/venv.py | 46 ++++++++-------------- tools/venv3.py | 48 +++++++++-------------- 5 files changed, 94 insertions(+), 78 deletions(-) mode change 100755 => 100644 tools/_venv_common.py diff --git a/tests/letstest/scripts/test_apache2.sh b/tests/letstest/scripts/test_apache2.sh index d24de2458..c52578003 100755 --- a/tests/letstest/scripts/test_apache2.sh +++ b/tests/letstest/scripts/test_apache2.sh @@ -45,7 +45,7 @@ if [ $? -ne 0 ] ; then exit 1 fi -python tools/_venv_common.py -e acme[dev] -e .[dev,docs] -e certbot-apache +python tools/venv.py -e acme[dev] -e .[dev,docs] -e certbot-apache sudo venv/bin/certbot -v --debug --text --agree-dev-preview --agree-tos \ --renew-by-default --redirect --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index 260a0acfb..a9177f690 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -4,13 +4,11 @@ cd letsencrypt ./certbot-auto --os-packages-only -n --debug PLUGINS="certbot-apache certbot-nginx" -PYTHON=$(command -v python2.7 || command -v python27 || command -v python2 || command -v python) TEMP_DIR=$(mktemp -d) VERSION=$(letsencrypt-auto-source/version.py) -export VENV_ARGS="-p $PYTHON" # setup venv -tools/_venv_common.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt +tools/venv.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt . ./venv/bin/activate # pytest is needed to run tests on some of our packages so we install a pinned version here. tools/pip_install.py pytest diff --git a/tools/_venv_common.py b/tools/_venv_common.py old mode 100755 new mode 100644 index 09383b4c0..cb19d583c --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -19,7 +19,30 @@ import time import subprocess import sys import re -import shlex + +REQUIREMENTS = [ + '-e acme[dev]', + '-e .[dev,docs]', + '-e certbot-apache', + '-e certbot-dns-cloudflare', + '-e certbot-dns-cloudxns', + '-e certbot-dns-digitalocean', + '-e certbot-dns-dnsimple', + '-e certbot-dns-dnsmadeeasy', + '-e certbot-dns-gehirn', + '-e certbot-dns-google', + '-e certbot-dns-linode', + '-e certbot-dns-luadns', + '-e certbot-dns-nsone', + '-e certbot-dns-ovh', + '-e certbot-dns-rfc2136', + '-e certbot-dns-route53', + '-e certbot-dns-sakuracloud', + '-e certbot-nginx', + '-e certbot-postfix', + '-e letshelp-certbot', + '-e certbot-compatibility-test', +] VERSION_PATTERN = re.compile(r'^(\d+)\.(\d+).*$') @@ -120,16 +143,22 @@ def get_venv_python_path(venv_path): .format(venv_path))) -def main(venv_name, venv_args, args): - """Creates a virtual environment and installs packages. +def prepare_venv_path(venv_name): + """Determines the venv path and prepares it for use. + + This function cleans up any Python eggs in the current working directory + and ensures the venv path is available for use. The path used is the + VENV_NAME environment variable if it is set and venv_name otherwise. If + there is already a directory at the desired path, the existing directory is + renamed by appending a timestamp to the directory name. :param str venv_name: The name or path at where the virtual - environment should be created. - :param str venv_args: Command line arguments for virtualenv - :param str args: Command line arguments that should be given to pip - to install packages - """ + environment should be created if VENV_NAME isn't set. + :returns: path where the virtual environment should be created + :rtype: str + + """ for path in glob.glob('*.egg-info'): if os.path.isdir(path): shutil.rmtree(path) @@ -145,15 +174,30 @@ def main(venv_name, venv_args, args): if os.path.isdir(venv_name): os.rename(venv_name, '{0}.{1}.bak'.format(venv_name, int(time.time()))) - command = [sys.executable, '-m', 'virtualenv', '--no-site-packages', '--setuptools', venv_name] - command.extend(shlex.split(venv_args)) - subprocess_with_print(command) + return venv_name + + +def install_packages(venv_name, pip_args=None): + """Installs packages in the given venv. + + If pip_args is given, they are the arguments given to pip, + otherwise, REQUIREMENTS is used. + + :param str venv_name: The name or path at where the virtual + environment should be created. + :param pip_args: Command line arguments that should be given to + pip to install packages + :type pip_args: `list` of `str` + + """ + if not pip_args: + pip_args = REQUIREMENTS # Using the python executable from venv, we ensure to execute following commands in this venv. py_venv = get_venv_python_path(venv_name) subprocess_with_print([py_venv, os.path.abspath('letsencrypt-auto-source/pieces/pipstrap.py')]) command = [py_venv, os.path.abspath('tools/pip_install.py')] - command.extend(args) + command.extend(pip_args) subprocess_with_print(command) if os.path.isdir(os.path.join(venv_name, 'bin')): @@ -171,7 +215,3 @@ def main(venv_name, venv_args, args): print('---------------------------------------------------------------------------') else: raise ValueError('Error, directory {0} is not a valid venv.'.format(venv_name)) - - -if __name__ == '__main__': - main('venv', '', sys.argv[1:]) diff --git a/tools/venv.py b/tools/venv.py index 93b012e76..3fe8e05fb 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -1,41 +1,29 @@ #!/usr/bin/env python # Developer virtualenv setup for Certbot client import os +import sys import _venv_common -REQUIREMENTS = [ - '-e acme[dev]', - '-e .[dev,docs]', - '-e certbot-apache', - '-e certbot-dns-cloudflare', - '-e certbot-dns-cloudxns', - '-e certbot-dns-digitalocean', - '-e certbot-dns-dnsimple', - '-e certbot-dns-dnsmadeeasy', - '-e certbot-dns-gehirn', - '-e certbot-dns-google', - '-e certbot-dns-linode', - '-e certbot-dns-luadns', - '-e certbot-dns-nsone', - '-e certbot-dns-ovh', - '-e certbot-dns-rfc2136', - '-e certbot-dns-route53', - '-e certbot-dns-sakuracloud', - '-e certbot-nginx', - '-e certbot-postfix', - '-e letshelp-certbot', - '-e certbot-compatibility-test', -] - - -def main(): +def create_venv(venv_path): + """Create a Python 2 virtual environment at venv_path. + + :param str venv_path: path where the venv should be created + + """ + python2 = _venv_common.find_python_executable(2) + command = [sys.executable, '-m', 'virtualenv', '--python', python2, venv_path] + _venv_common.subprocess_with_print(command) + + +def main(pip_args=None): if os.name == 'nt': raise ValueError('Certbot for Windows is not supported on Python 2.x.') - venv_args = '--python "{0}"'.format(_venv_common.find_python_executable(2)) - _venv_common.main('venv', venv_args, REQUIREMENTS) + venv_path = _venv_common.prepare_venv_path('venv') + create_venv(venv_path) + _venv_common.install_packages(venv_path, pip_args) if __name__ == '__main__': - main() + main(sys.argv[1:]) diff --git a/tools/venv3.py b/tools/venv3.py index b837baf70..dc56a322e 100755 --- a/tools/venv3.py +++ b/tools/venv3.py @@ -1,36 +1,26 @@ #!/usr/bin/env python3 # Developer virtualenv setup for Certbot client +import sys + import _venv_common -REQUIREMENTS = [ - '-e acme[dev]', - '-e .[dev,docs]', - '-e certbot-apache', - '-e certbot-dns-cloudflare', - '-e certbot-dns-cloudxns', - '-e certbot-dns-digitalocean', - '-e certbot-dns-dnsimple', - '-e certbot-dns-dnsmadeeasy', - '-e certbot-dns-gehirn', - '-e certbot-dns-google', - '-e certbot-dns-linode', - '-e certbot-dns-luadns', - '-e certbot-dns-nsone', - '-e certbot-dns-ovh', - '-e certbot-dns-rfc2136', - '-e certbot-dns-route53', - '-e certbot-dns-sakuracloud', - '-e certbot-nginx', - '-e certbot-postfix', - '-e letshelp-certbot', - '-e certbot-compatibility-test', -] - - -def main(): - venv_args = '--python "{0}"'.format(_venv_common.find_python_executable(3)) - _venv_common.main('venv3', venv_args, REQUIREMENTS) + +def create_venv(venv_path): + """Create a Python 3 virtual environment at venv_path. + + :param str venv_path: path where the venv should be created + + """ + python3 = _venv_common.find_python_executable(3) + command = [python3, '-m', 'venv', venv_path] + _venv_common.subprocess_with_print(command) + + +def main(pip_args=None): + venv_path = _venv_common.prepare_venv_path('venv3') + create_venv(venv_path) + _venv_common.install_packages(venv_path, pip_args) if __name__ == '__main__': - main() + main(sys.argv[1:]) -- cgit v1.2.3 From 9c312a38820589eee12c9eb26bb76617dba10742 Mon Sep 17 00:00:00 2001 From: kaduk Date: Sun, 7 Apr 2019 15:20:03 -0500 Subject: Fix typo in comment ("upstreqm") (#6926) Spell "upstream" correctly. --- letsencrypt-auto-source/letsencrypt-auto | 2 +- letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 5ce0f7b86..b940c71a3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -534,7 +534,7 @@ BootstrapSuseCommon() { # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv # is a source package, and python2-virtualenv must be used instead. # Also currently python2-setuptools is not a dependency of python2-virtualenv, - # while it should be. Installing it explicitly until upstreqm fix. + # while it should be. Installing it explicitly until upstream fix. OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" fi diff --git a/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh b/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh index ac66119c3..7fa28ce50 100755 --- a/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh @@ -20,7 +20,7 @@ BootstrapSuseCommon() { # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv # is a source package, and python2-virtualenv must be used instead. # Also currently python2-setuptools is not a dependency of python2-virtualenv, - # while it should be. Installing it explicitly until upstreqm fix. + # while it should be. Installing it explicitly until upstream fix. OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" fi -- cgit v1.2.3 From c77159a30c0933d40c36c763a824c1d7998d5840 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 8 Apr 2019 12:51:52 -0700 Subject: Update the lexicon version used in tests/Docker. (#6929) This will resolve problems with certbot-dns-dnsimple in Docker. --- tools/dev_constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 539791a69..be2d0925f 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -14,7 +14,7 @@ botocore==1.12.36 cloudflare==1.5.1 coverage==4.4.2 decorator==4.1.2 -dns-lexicon==3.0.8 +dns-lexicon==3.2.1 dnspython==1.15.0 docutils==0.12 execnet==1.5.0 -- cgit v1.2.3 From 04152c21b5726f9bf05ec4820f95b9ebe2423c64 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 2 Apr 2019 22:48:22 +0200 Subject: Update to Pylint 1.9.4 and corrections --- .pylintrc | 7 +--- acme/acme/challenges.py | 17 ++------ acme/acme/challenges_test.py | 2 +- acme/acme/client.py | 18 ++++---- acme/acme/client_test.py | 17 ++++---- acme/acme/crypto_util.py | 1 - acme/acme/jws_test.py | 2 +- acme/acme/messages.py | 2 +- acme/acme/standalone.py | 6 +-- acme/acme/standalone_test.py | 12 ++---- certbot-apache/certbot_apache/configurator.py | 8 ++-- certbot-apache/certbot_apache/override_centos.py | 12 +++--- certbot-apache/certbot_apache/override_debian.py | 1 - .../certbot_apache/tests/autohsts_test.py | 5 ++- .../certbot_apache/tests/configurator_test.py | 1 - .../configurators/nginx/common.py | 5 ++- .../certbot_dns_gehirn/dns_gehirn.py | 2 +- .../certbot_dns_linode/dns_linode.py | 2 +- .../certbot_dns_sakuracloud/dns_sakuracloud.py | 2 +- certbot-nginx/certbot_nginx/configurator.py | 4 +- certbot-nginx/certbot_nginx/constants.py | 3 +- certbot-nginx/certbot_nginx/parser.py | 5 +-- certbot-nginx/certbot_nginx/parser_obj.py | 48 ++++++++++++---------- certbot-nginx/certbot_nginx/tests/util.py | 8 ++-- certbot-postfix/certbot_postfix/installer.py | 7 +--- certbot-postfix/certbot_postfix/postconf.py | 18 ++++---- .../certbot_postfix/tests/installer_test.py | 24 ++++++----- .../certbot_postfix/tests/postconf_test.py | 4 +- certbot-postfix/certbot_postfix/util.py | 2 +- certbot/account.py | 5 +-- certbot/cli.py | 7 ++-- certbot/configuration.py | 2 +- certbot/display/util.py | 12 ++---- certbot/main.py | 23 +++++++---- certbot/ocsp.py | 8 ++-- certbot/plugins/dns_common.py | 2 +- certbot/plugins/dns_common_lexicon.py | 1 + certbot/plugins/manual_test.py | 2 +- certbot/plugins/selection_test.py | 4 +- certbot/plugins/standalone_test.py | 2 +- certbot/plugins/util.py | 9 ++-- certbot/plugins/webroot.py | 2 +- certbot/reverter.py | 6 +-- certbot/storage.py | 1 - certbot/tests/auth_handler_test.py | 6 +-- certbot/tests/cert_manager_test.py | 2 +- certbot/tests/client_test.py | 7 ++-- certbot/tests/display/completer_test.py | 8 ++-- certbot/tests/display/util_test.py | 2 +- certbot/tests/error_handler_test.py | 3 -- certbot/tests/main_test.py | 11 +++-- certbot/tests/util.py | 14 +++---- certbot/tests/util_test.py | 8 ++-- certbot/util.py | 2 - setup.py | 4 +- .../live/a.encryption-example.com/cert.pem | 0 .../live/a.encryption-example.com/chain.pem | 0 .../live/a.encryption-example.com/fullchain.pem | 0 .../live/a.encryption-example.com/privkey.pem | 0 .../live/b.encryption-example.com/cert.pem | 0 .../live/b.encryption-example.com/chain.pem | 0 .../live/b.encryption-example.com/fullchain.pem | 0 .../live/b.encryption-example.com/privkey.pem | 0 tools/dev_constraints.txt | 11 ++++- 64 files changed, 193 insertions(+), 216 deletions(-) mode change 100644 => 120000 tests/integration/sample-config/live/a.encryption-example.com/cert.pem mode change 100644 => 120000 tests/integration/sample-config/live/a.encryption-example.com/chain.pem mode change 100644 => 120000 tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem mode change 100644 => 120000 tests/integration/sample-config/live/a.encryption-example.com/privkey.pem mode change 100644 => 120000 tests/integration/sample-config/live/b.encryption-example.com/cert.pem mode change 100644 => 120000 tests/integration/sample-config/live/b.encryption-example.com/chain.pem mode change 100644 => 120000 tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem mode change 100644 => 120000 tests/integration/sample-config/live/b.encryption-example.com/privkey.pem diff --git a/.pylintrc b/.pylintrc index 80dc16913..0284c68a0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -189,9 +189,6 @@ init-import=no # not used). dummy-variables-rgx=(unused)?_.*|dummy -# A regular expression matching ignored argument variable names -ignored-argument-names=(unused)?_.*|dummy - # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= @@ -254,7 +251,7 @@ ignored-modules=pkg_resources,confargparse,argparse,six.moves,six.moves.urllib # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). -ignored-classes=SQLObject +ignored-classes=Field,Header,JWS,closing # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. @@ -307,7 +304,7 @@ max-args=6 # Argument names that match this expression will be ignored. Default to name # with leading underscore -ignored-argument-names=_.* +ignored-argument-names=(unused)?_.*|dummy # Maximum number of locals for function / method body max-locals=15 diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index a63c60cfa..01298d46f 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -20,11 +20,8 @@ from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) -# pylint: disable=too-few-public-methods - - class Challenge(jose.TypedJSONObjectWithFields): - # _fields_to_partial_json | pylint: disable=abstract-method + # _fields_to_partial_json """ACME challenge.""" TYPES = {} # type: dict @@ -38,7 +35,7 @@ class Challenge(jose.TypedJSONObjectWithFields): class ChallengeResponse(jose.TypedJSONObjectWithFields): - # _fields_to_partial_json | pylint: disable=abstract-method + # _fields_to_partial_json """ACME challenge response.""" TYPES = {} # type: dict resource_type = 'challenge' @@ -165,7 +162,6 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): @six.add_metaclass(abc.ABCMeta) class KeyAuthorizationChallenge(_TokenChallenge): - # pylint: disable=too-many-ancestors """Challenge based on Key Authorization. :param response_cls: Subclass of `KeyAuthorizationChallengeResponse` @@ -234,7 +230,7 @@ class DNS01Response(KeyAuthorizationChallengeResponse): """ACME dns-01 challenge response.""" typ = "dns-01" - def simple_verify(self, chall, domain, account_public_key): + def simple_verify(self, chall, domain, account_public_key): # pylint: disable=unused-argument """Simple verify. This method no longer checks DNS records and is a simple wrapper @@ -250,7 +246,6 @@ class DNS01Response(KeyAuthorizationChallengeResponse): :rtype: bool """ - # pylint: disable=unused-argument verified = self.verify(chall, account_public_key) if not verified: logger.debug("Verification of key authorization in response failed") @@ -455,7 +450,6 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse): kwargs.setdefault("port", self.PORT) kwargs["name"] = self.z_domain # TODO: try different methods? - # pylint: disable=protected-access return crypto_util.probe_sni(**kwargs) def verify_cert(self, cert): @@ -516,9 +510,6 @@ class TLSSNI01(KeyAuthorizationChallenge): # boulder#962, ietf-wg-acme#22 #n = jose.Field("n", encoder=int, decoder=int) - def __init__(self, *args, **kwargs): - super(TLSSNI01, self).__init__(*args, **kwargs) - def validation(self, account_key, **kwargs): """Generate validation. @@ -560,7 +551,7 @@ class TLSALPN01(KeyAuthorizationChallenge): raise NotImplementedError() -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class DNS(_TokenChallenge): """ACME "dns" challenge.""" typ = "dns" diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 3b3c5e65e..f5f914005 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -6,7 +6,7 @@ import mock import OpenSSL import requests -from six.moves.urllib import parse as urllib_parse # pylint: disable=import-error,relative-import +from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import from acme import errors from acme import test_util diff --git a/acme/acme/client.py b/acme/acme/client.py index faabad367..5cad0acbe 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -137,7 +137,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), uri=response.headers.get('Location', uri)) - if identifier is not None and authzr.body.identifier != identifier: # pylint: disable=no-member + if identifier is not None and authzr.body.identifier != identifier: raise errors.UnexpectedUpdate(authzr) return authzr @@ -669,7 +669,7 @@ class ClientV2(ClientBase): response = self._post(self.directory['newOrder'], order) body = messages.Order.from_json(response.json()) authorizations = [] - for url in body.authorizations: + for url in body.authorizations: # pylint: disable=not-an-iterable authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url)) return messages.OrderResource( body=body, @@ -775,10 +775,7 @@ class ClientV2(ClientBase): 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 + return hasattr(self.directory, 'meta') and self.directory.meta.external_account_required def _post_as_get(self, *args, **kwargs): """ @@ -794,7 +791,7 @@ class ClientV2(ClientBase): # 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 + return self._post(*new_args, **kwargs) except messages.Error as error: if error.code == 'malformed': logger.debug('Error during a POST-as-GET request, ' @@ -915,7 +912,7 @@ class BackwardsCompatibleClientV2(object): 'certificate, please rerun the command for a new one.') cert = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode() # pylint: disable=no-member + OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode() chain = crypto_util.dump_pyopenssl_chain(chain).decode() return orderr.update(fullchain_pem=(cert + chain)) @@ -945,8 +942,7 @@ class BackwardsCompatibleClientV2(object): 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() + return self.client.external_account_required() class ClientNetwork(object): # pylint: disable=too-many-instance-attributes @@ -1167,7 +1163,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes if self.REPLAY_NONCE_HEADER in response.headers: nonce = response.headers[self.REPLAY_NONCE_HEADER] try: - decoded_nonce = jws.Header._fields['nonce'].decode(nonce) # pylint: disable=no-member + decoded_nonce = jws.Header._fields['nonce'].decode(nonce) except jose.DeserializationError as error: raise errors.BadNonce(nonce, error) logger.debug('Storing nonce: %s', nonce) diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index f4e34a8d3..5b2703701 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -358,7 +358,6 @@ class ClientTest(ClientTestBase): def test_register(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member self.response.status_code = http_client.CREATED self.response.json.return_value = self.regr.body.to_json() self.response.headers['Location'] = self.regr.uri @@ -371,7 +370,6 @@ class ClientTest(ClientTestBase): def test_update_registration(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member self.response.headers['Location'] = self.regr.uri self.response.json.return_value = self.regr.body.to_json() self.assertEqual(self.regr, self.client.update_registration(self.regr)) @@ -448,7 +446,7 @@ class ClientTest(ClientTestBase): def test_answer_challenge(self): self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() # pylint: disable=no-member + self.response.json.return_value = self.challr.body.to_json() chall_response = challenges.DNSResponse(validation=None) @@ -456,7 +454,7 @@ class ClientTest(ClientTestBase): # TODO: split here and separate test self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, - self.challr.body.update(uri='foo'), chall_response) # pylint: disable=no-member + self.challr.body.update(uri='foo'), chall_response) def test_answer_challenge_missing_next(self): self.assertRaises( @@ -538,7 +536,7 @@ class ClientTest(ClientTestBase): self.client.retry_after(response=self.response, default=10)) def test_poll(self): - self.response.json.return_value = self.authzr.body.to_json() # pylint: disable=no-member + self.response.json.return_value = self.authzr.body.to_json() self.assertEqual((self.authzr, self.response), self.client.poll(self.authzr)) @@ -768,7 +766,7 @@ class ClientV2Test(ClientTestBase): def test_new_account(self): self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() # pylint: disable=no-member + self.response.json.return_value = self.regr.body.to_json() self.response.headers['Location'] = self.regr.uri self.assertEqual(self.regr, self.client.new_account(self.new_reg)) @@ -823,7 +821,7 @@ class ClientV2Test(ClientTestBase): def test_poll_authorizations_failure(self): deadline = datetime.datetime(9999, 9, 9) - challb = self.challr.body.update(status=messages.STATUS_INVALID, # pylint: disable=no-member + challb = self.challr.body.update(status=messages.STATUS_INVALID, error=messages.Error.with_code('unauthorized')) authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) self.response.json.return_value = authz.to_json() @@ -872,7 +870,6 @@ class ClientV2Test(ClientTestBase): def test_update_registration(self): # "Instance of 'Field' has no to_json/update member" bug: - # pylint: disable=no-member self.response.headers['Location'] = self.regr.uri self.response.json.return_value = self.regr.body.to_json() self.assertEqual(self.regr, self.client.update_registration(self.regr)) @@ -964,7 +961,7 @@ class ClientNetworkTest(unittest.TestCase): MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", acme_version=1) jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) # pylint: disable=no-member + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) self.assertEqual(jws.signature.combined.nonce, b'Tg') def test_wrap_in_jws_v2(self): @@ -974,7 +971,7 @@ class ClientNetworkTest(unittest.TestCase): MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", acme_version=2) jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) # pylint: disable=no-member + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) self.assertEqual(jws.signature.combined.nonce, b'Tg') self.assertEqual(jws.signature.combined.kid, u'acct-uri') self.assertEqual(jws.signature.combined.url, u'url') diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index d68454858..6a319d94e 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -134,7 +134,6 @@ def probe_sni(name, host, port=443, timeout=300, socket_kwargs = {'source_address': source_address} try: - # pylint: disable=star-args logger.debug( "Attempting to connect to %s:%d%s.", host, port, " from {0}:{1}".format( diff --git a/acme/acme/jws_test.py b/acme/acme/jws_test.py index 62b46b528..aa3ccb700 100644 --- a/acme/acme/jws_test.py +++ b/acme/acme/jws_test.py @@ -24,7 +24,7 @@ class HeaderTest(unittest.TestCase): def test_nonce_decoder(self): from acme.jws import Header - nonce_field = Header._fields['nonce'] # pylint: disable=no-member + nonce_field = Header._fields['nonce'] self.assertRaises( jose.DeserializationError, nonce_field.decode, self.wrong_nonce) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index a27e20bf3..14ad011f6 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -1,6 +1,6 @@ """ACME protocol messages.""" -import six import json +import six try: from collections.abc import Hashable # pylint: disable=no-name-in-module except ImportError: # pragma: no cover diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index fecbaa98a..844960b2f 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -83,7 +83,7 @@ class BaseDualNetworkedServers(object): kwargs["ipv6"] = ip_version new_address = (server_address[0],) + (port,) + server_address[2:] new_args = (new_address,) + remaining_args - server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args + server = ServerClass(*new_args, **kwargs) logger.debug( "Successfully bound to %s:%s using %s", new_address[0], new_address[1], "IPv6" if ip_version else "IPv4") @@ -91,8 +91,8 @@ class BaseDualNetworkedServers(object): if self.servers: # Already bound using IPv6. logger.debug( - "Certbot wasn't able to bind to %s:%s using %s, this " + - "is often expected due to the dual stack nature of " + + "Certbot wasn't able to bind to %s:%s using %s, this " + "is often expected due to the dual stack nature of " "IPv6 socket implementations.", new_address[0], new_address[1], "IPv6" if ip_version else "IPv4") diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 90e1af37f..86ceaa316 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -31,14 +31,14 @@ class TLSServerTest(unittest.TestCase): from acme.standalone import TLSServer server = TLSServer( ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) - server.server_close() # pylint: disable=no-member + server.server_close() def test_ipv6(self): if socket.has_ipv6: from acme.standalone import TLSServer server = TLSServer( ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True) - server.server_close() # pylint: disable=no-member + server.server_close() class TLSSNI01ServerTest(unittest.TestCase): @@ -52,12 +52,11 @@ class TLSSNI01ServerTest(unittest.TestCase): )} from acme.standalone import TLSSNI01Server self.server = TLSSNI01Server(('localhost', 0), certs=self.certs) - # pylint: disable=no-member self.thread = threading.Thread(target=self.server.serve_forever) self.thread.start() def tearDown(self): - self.server.shutdown() # pylint: disable=no-member + self.server.shutdown() self.thread.join() def test_it(self): @@ -80,13 +79,12 @@ class HTTP01ServerTest(unittest.TestCase): from acme.standalone import HTTP01Server self.server = HTTP01Server(('', 0), resources=self.resources) - # pylint: disable=no-member self.port = self.server.socket.getsockname()[1] self.thread = threading.Thread(target=self.server.serve_forever) self.thread.start() def tearDown(self): - self.server.shutdown() # pylint: disable=no-member + self.server.shutdown() self.thread.join() def test_index(self): @@ -139,7 +137,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase): # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. # We use the corresponding value (41) instead. level = getattr(socket, "IPPROTO_IPV6", 41) - # pylint: disable=no-member self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) try: self.server_bind() @@ -212,7 +209,6 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): from acme.standalone import HTTP01DualNetworkedServers self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) - # pylint: disable=no-member self.port = self.servers.getsocknames()[0][1] self.servers.serve_forever() diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index b2b614d8f..b59bc0460 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -276,8 +276,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): util.lock_dir_until_exit(self.option("server_root")) except (OSError, errors.LockError): logger.debug("Encountered error:", exc_info=True) - raise errors.PluginError( - "Unable to lock %s", self.option("server_root")) + raise errors.PluginError("Unable to lock {0}".format(self.option("server_root"))) self._prepared = True def _verify_exe_availability(self, exe): @@ -1191,8 +1190,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if fp.endswith(".conf"): return fp[:-(len(".conf"))] + self.option("le_vhost_ext") - else: - return fp + self.option("le_vhost_ext") + return fp + self.option("le_vhost_ext") def _sift_rewrite_rule(self, line): """Decides whether a line should be copied to a SSL vhost. @@ -2133,7 +2131,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): vhost.enabled = True return - def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument + def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument """Enables module in Apache. Both enables and reloads Apache so module is active. diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index 29ea16dd9..f0c7e7367 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -1,17 +1,16 @@ """ Distribution specific override class for CentOS family (RHEL, Fedora) """ import logging -import pkg_resources - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +import pkg_resources import zope.interface -from certbot import interfaces - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot_apache import apache_util from certbot_apache import configurator from certbot_apache import parser + +from certbot import interfaces from certbot.errors import MisconfigurationError logger = logging.getLogger(__name__) @@ -55,7 +54,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): self.aug, self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) - def _deploy_cert(self, *args, **kwargs): + def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ """ Override _deploy_cert in order to ensure that the Apache configuration has "LoadModule ssl_module..." before parsing the VirtualHost configuration @@ -65,7 +64,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator): if self.version < (2, 4, 0): self._deploy_loadmodule_ssl_if_needed() - def _deploy_loadmodule_ssl_if_needed(self): """ Add "LoadModule ssl_module " to main httpd.conf if diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index b0f0d2f67..f4bdd2bc9 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -84,7 +84,6 @@ class DebianConfigurator(configurator.ApacheConfigurator): return None def enable_mod(self, mod_name, temp=False): - # pylint: disable=unused-argument """Enables module in Apache. Both enables and reloads Apache so module is active. diff --git a/certbot-apache/certbot_apache/tests/autohsts_test.py b/certbot-apache/certbot_apache/tests/autohsts_test.py index bf92a13ff..2d22df289 100644 --- a/certbot-apache/certbot_apache/tests/autohsts_test.py +++ b/certbot-apache/certbot_apache/tests/autohsts_test.py @@ -35,8 +35,9 @@ class AutoHSTSTest(util.ApacheTest): pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' for head in header_path: if re.search(pat, self.config.parser.aug.get(head).lower()): - return self.config.parser.aug.get(head.replace("arg[3]", - "arg[4]")) + return self.config.parser.aug.get( + head.replace("arg[3]", "arg[4]")) + return None # pragma: no cover @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index ca45fcc0d..7842eaaac 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -31,7 +31,6 @@ from certbot_apache.tests import util class MultipleVhostsTest(util.ApacheTest): """Test two standard well-configured HTTP vhosts.""" - def setUp(self): # pylint: disable=arguments-differ super(MultipleVhostsTest, self).setUp() diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 9caa8fc7e..3207cf88a 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -5,6 +5,8 @@ import subprocess import zope.interface +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module + from certbot import configuration from certbot_nginx import configurator from certbot_nginx import constants @@ -68,7 +70,7 @@ def _get_server_root(config): def _get_names(config): """Returns all and testable domain names in config""" - all_names = set() + all_names = set() # type: Set[str] for root, _dirs, files in os.walk(config): for this_file in files: update_names = _get_server_names(root, this_file) @@ -76,6 +78,7 @@ def _get_names(config): non_ip_names = set(n for n in all_names if not util.IP_REGEX.match(n)) return all_names, non_ip_names + def _get_server_names(root, filename): """Returns all names in a config file path""" all_names = set() diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py index edf530072..e64e62da9 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py @@ -83,5 +83,5 @@ class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): - return # Expected errors when zone name guess is wrong + return None # Expected errors when zone name guess is wrong return super(_GehirnLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/dns_linode.py index 4e0500fa0..c2097a7d6 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/dns_linode.py @@ -73,4 +73,4 @@ class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): if not str(e).startswith('Domain not found'): return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' .format(domain_name, e)) - + return None diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py index 7fd6d3ef5..d6e20894d 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py @@ -86,5 +86,5 @@ class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient): def _handle_http_error(self, e, domain_name): if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): - return # Expected errors when zone name guess is wrong + return None # Expected errors when zone name guess is wrong return super(_SakuraCloudLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 4ed907712..6bf82088b 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -166,7 +166,6 @@ class NginxConfigurator(common.Installer): # Entry point in main.py for installing cert def deploy_cert(self, domain, cert_path, key_path, chain_path=None, fullchain_path=None): - # pylint: disable=unused-argument """Deploys certificate to specified virtual host. .. note:: Aborts if the vhost is missing ssl_certificate or @@ -187,8 +186,7 @@ class NginxConfigurator(common.Installer): for vhost in vhosts: self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) - def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): - # pylint: disable=unused-argument + def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): # pylint: disable=unused-argument """ Helper function for deploy_cert() that handles the actual deployment this exists because we might want to do multiple deployments per diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index d749b6989..41716db0f 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -1,7 +1,8 @@ """nginx plugin constants.""" -import pkg_resources import platform +import pkg_resources + FREEBSD_DARWIN_SERVER_ROOT = "/usr/local/etc/nginx" LINUX_SERVER_ROOT = "/etc/nginx" diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py index b53157c7a..49f02b741 100644 --- a/certbot-nginx/certbot_nginx/parser.py +++ b/certbot-nginx/certbot_nginx/parser.py @@ -83,8 +83,7 @@ class NginxParser(object): """ if not os.path.isabs(path): return os.path.normpath(os.path.join(self.root, path)) - else: - return os.path.normpath(path) + return os.path.normpath(path) def _build_addr_to_ssl(self): """Builds a map from address to whether it listens on ssl in any server block @@ -395,7 +394,7 @@ class NginxParser(object): addr.default = False addr.ipv6only = False for directive in enclosing_block[new_vhost.path[-1]][1]: - if len(directive) > 0 and directive[0] == 'listen': + if directive and directive[0] == 'listen': # Exclude one-time use parameters which will cause an error if repeated. # https://nginx.org/en/docs/http/ngx_http_core_module.html#listen exclude = set(('default_server', 'default', 'setfib', 'fastopen', 'backlog', diff --git a/certbot-nginx/certbot_nginx/parser_obj.py b/certbot-nginx/certbot_nginx/parser_obj.py index f01cb2fd3..71e8c6088 100644 --- a/certbot-nginx/certbot_nginx/parser_obj.py +++ b/certbot-nginx/certbot_nginx/parser_obj.py @@ -13,6 +13,7 @@ logger = logging.getLogger(__name__) COMMENT = " managed by Certbot" COMMENT_BLOCK = ["#", COMMENT] + class Parsable(object): """ Abstract base class for "Parsable" objects whose underlying representation is a tree of lists. @@ -112,6 +113,7 @@ class Parsable(object): """ return [elem.dump(include_spaces) for elem in self._data] + class Statements(Parsable): """ A group or list of "Statements". A Statement is either a Block or a Sentence. @@ -142,24 +144,23 @@ class Statements(Parsable): if self.parent is not None: self._trailing_whitespace = "\n" + self.parent.get_tabs() - def parse(self, parse_this, add_spaces=False): + def parse(self, raw_list, add_spaces=False): """ Parses a list of statements. - Expects all elements in `parse_this` to be parseable by `type(self).parsing_hooks`, - with an optional whitespace string at the last index of `parse_this`. + Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`, + with an optional whitespace string at the last index of `raw_list`. """ - if not isinstance(parse_this, list): + if not isinstance(raw_list, list): raise errors.MisconfigurationError("Statements parsing expects a list!") # If there's a trailing whitespace in the list of statements, keep track of it. - if len(parse_this) > 0 and isinstance(parse_this[-1], six.string_types) \ - and parse_this[-1].isspace(): - self._trailing_whitespace = parse_this[-1] - parse_this = parse_this[:-1] - self._data = [parse_raw(elem, self, add_spaces) for elem in parse_this] + if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace(): + self._trailing_whitespace = raw_list[-1] + raw_list = raw_list[:-1] + self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list] def get_tabs(self): """ Takes a guess at the tabbing of all contained Statements by retrieving the tabbing of the first Statement.""" - if len(self._data) > 0: + if self._data: return self._data[0].get_tabs() return "" @@ -179,6 +180,7 @@ class Statements(Parsable): # ======== End overridden functions + def _space_list(list_): """ Inserts whitespace between adjacent non-whitespace tokens. """ spaced_statement = [] # type: List[str] @@ -188,6 +190,7 @@ def _space_list(list_): spaced_statement.insert(0, " ") return spaced_statement + class Sentence(Parsable): """ A list of words. Non-whitespace words are typically separated with whitespace tokens. """ @@ -205,15 +208,15 @@ class Sentence(Parsable): return isinstance(lists, list) and len(lists) > 0 and \ all([isinstance(elem, six.string_types) for elem in lists]) - def parse(self, parse_this, add_spaces=False): + def parse(self, raw_list, add_spaces=False): """ Parses a list of string types into this object. If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens.""" if add_spaces: - parse_this = _space_list(parse_this) - if not isinstance(parse_this, list) or \ - any([not isinstance(elem, six.string_types) for elem in parse_this]): + raw_list = _space_list(raw_list) + if not isinstance(raw_list, list) or \ + any([not isinstance(elem, six.string_types) for elem in raw_list]): raise errors.MisconfigurationError("Sentence parsing expects a list of string types.") - self._data = parse_this + self._data = raw_list def iterate(self, expanded=False, match=None): """ Simply yields itself. """ @@ -255,6 +258,7 @@ class Sentence(Parsable): def __contains__(self, word): return word in self.words + class Block(Parsable): """ Any sort of bloc, denoted by a block name and curly braces, like so: The parsed block: @@ -297,26 +301,26 @@ class Block(Parsable): for elem in self.contents.iterate(expanded, match): yield elem - def parse(self, parse_this, add_spaces=False): + def parse(self, raw_list, add_spaces=False): """ Parses a list that resembles a block. The assumptions that this routine makes are: - 1. the first element of `parse_this` is a valid Sentence. - 2. the second element of `parse_this` is a valid Statement. + 1. the first element of `raw_list` is a valid Sentence. + 2. the second element of `raw_list` is a valid Statement. If add_spaces is set, we call it recursively on `names` and `contents`, and add an extra trailing space to `names` (to separate the block's opening bracket and the block name). """ - if not Block.should_parse(parse_this): + if not Block.should_parse(raw_list): raise errors.MisconfigurationError("Block parsing expects a list of length 2. " "First element should be a list of string types (the bloc names), " "and second should be another list of statements (the bloc content).") self.names = Sentence(self) if add_spaces: - parse_this[0].append(" ") - self.names.parse(parse_this[0], add_spaces) + raw_list[0].append(" ") + self.names.parse(raw_list[0], add_spaces) self.contents = Statements(self) - self.contents.parse(parse_this[1], add_spaces) + self.contents.parse(raw_list[1], add_spaces) self._data = [self.names, self.contents] def get_tabs(self): diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index 92399cc7a..2c5f6f39a 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -1,21 +1,19 @@ """Common utilities for certbot_nginx.""" import copy import os -import pkg_resources +import shutil import tempfile import unittest -import shutil import warnings import josepy as jose import mock +import pkg_resources import zope.component from certbot import configuration - -from certbot.tests import util as test_util - from certbot.plugins import common +from certbot.tests import util as test_util from certbot_nginx import configurator from certbot_nginx import nginxparser diff --git a/certbot-postfix/certbot_postfix/installer.py b/certbot-postfix/certbot_postfix/installer.py index 9ba92ef8f..93afd4de9 100644 --- a/certbot-postfix/certbot_postfix/installer.py +++ b/certbot-postfix/certbot_postfix/installer.py @@ -191,7 +191,7 @@ class Installer(plugins_common.Installer): "subset of configuration parameters.") def deploy_cert(self, domain, cert_path, - key_path, chain_path, fullchain_path): + key_path, chain_path, fullchain_path): # pylint: disable=unused-argument """Configure the Postfix SMTP server to use the given TLS cert. :param str domain: domain to deploy certificate file @@ -204,7 +204,6 @@ class Installer(plugins_common.Installer): :raises .PluginError: when cert cannot be deployed """ - # pylint: disable=unused-argument if self._tls_enabled: return self._tls_enabled = True @@ -223,10 +222,9 @@ class Installer(plugins_common.Installer): self.postconf.set("smtpd_tls_dh1024_param_file", self.ssl_dhparams) self._confirm_changes() - def enhance(self, domain, enhancement, options=None): + def enhance(self, domain, enhancement, options=None): # pylint: disable=unused-argument """Raises an exception since this installer doesn't support any enhancements. """ - # pylint: disable=unused-argument raise errors.PluginError( "Unsupported enhancement: {0}".format(enhancement)) @@ -285,4 +283,3 @@ class Installer(plugins_common.Installer): :raises .PluginError: when server cannot be restarted """ self.postfix.restart() - diff --git a/certbot-postfix/certbot_postfix/postconf.py b/certbot-postfix/certbot_postfix/postconf.py index 466e0e63e..efa208597 100644 --- a/certbot-postfix/certbot_postfix/postconf.py +++ b/certbot-postfix/certbot_postfix/postconf.py @@ -8,6 +8,7 @@ from certbot_postfix import util from acme.magic_typing import Dict, List, Tuple # pylint: enable=unused-import, no-name-in-module + class ConfigMain(util.PostfixUtilBase): """A parser for Postfix's main.cf file.""" @@ -91,18 +92,18 @@ class ConfigMain(util.PostfixUtilBase): with a value in acceptable_overrides. """ if name not in self._db: - raise KeyError("Parameter name %s is not a valid Postfix parameter name.", name) + raise KeyError("Parameter name {0} is not a valid Postfix parameter name.".format(name)) # Check to see if this parameter is overridden by master. overrides = self.get_master_overrides(name) if not self._ignore_master_overrides and overrides is not None: util.report_master_overrides(name, overrides, acceptable_overrides) if value != self._db[name]: - # _db contains the "original" state of parameters. We only care about - # writes if they cause a delta from the original state. + # _db contains the "original" state of parameters. We only care about + # writes if they cause a delta from the original state. self._updated[name] = value elif name in self._updated: - # If this write reverts a previously updated parameter back to the - # original DB's state, we don't have to keep track of it in _updated. + # If this write reverts a previously updated parameter back to the + # original DB's state, we don't have to keep track of it in _updated. del self._updated[name] def flush(self): @@ -110,7 +111,7 @@ class ConfigMain(util.PostfixUtilBase): :raises error.PluginError: When flush to main.cf fails for some reason. """ - if len(self._updated) == 0: + if not self._updated: return args = ['-e'] for name, value in six.iteritems(self._updated): @@ -118,7 +119,7 @@ class ConfigMain(util.PostfixUtilBase): try: self._get_output(args) except IOError as e: - raise errors.PluginError("Unable to save to Postfix config: %v", e) + raise errors.PluginError("Unable to save to Postfix config: {0}".format(e)) for name, value in six.iteritems(self._updated): self._db[name] = value self._updated = {} @@ -130,6 +131,7 @@ class ConfigMain(util.PostfixUtilBase): """ return self._updated + def _parse_main_output(output): """Parses the raw output from Postconf about main.cf. @@ -148,5 +150,3 @@ def _parse_main_output(output): for line in output.splitlines(): name, _, value = line.partition(" =") yield name, value.strip() - - diff --git a/certbot-postfix/certbot_postfix/tests/installer_test.py b/certbot-postfix/certbot_postfix/tests/installer_test.py index 37b78bdca..8222ccb12 100644 --- a/certbot-postfix/certbot_postfix/tests/installer_test.py +++ b/certbot-postfix/certbot_postfix/tests/installer_test.py @@ -1,20 +1,18 @@ """Tests for certbot_postfix.installer.""" -from contextlib import contextmanager import copy import functools import os -import pkg_resources -import six import unittest +from contextlib import contextmanager import mock +import pkg_resources +import six +from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.tests import util as certbot_test_util -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Tuple, Union -# pylint: enable=unused-import, no-name-in-module DEFAULT_MAIN_CF = { "smtpd_tls_cert_file": "", @@ -127,7 +125,7 @@ class InstallerTest(certbot_test_util.ConfigTestCase): with create_installer(self.config) as installer: installer.prepare() installer.restart() - self.assertEqual(installer.postfix.restart.call_count, 1) + self.assertEqual(installer.postfix.restart.call_count, 1) # pylint: disable=no-member def test_add_parser_arguments(self): options = set(("ctl", "config-dir", "config-utility", @@ -269,7 +267,7 @@ class InstallerTest(certbot_test_util.ConfigTestCase): installer.prepare() installer.deploy_cert("example.com", "cert_path", "key_path", "chain_path", "fullchain_path") - for param in more_secure.keys(): + for param in more_secure: self.assertFalse(param in installer.postconf.get_changes()) def test_enhance(self): @@ -284,16 +282,20 @@ class InstallerTest(certbot_test_util.ConfigTestCase): installer.prepare() self.assertEqual(installer.supported_enhancements(), []) + @contextmanager -def create_installer(config, main_cf=DEFAULT_MAIN_CF): -# pylint: disable=dangerous-default-value +def create_installer(config, main_cf=None): """Creates a Postfix installer with calls to `postconf` and `postfix` mocked out. In particular, creates a ConfigMain object that does regular things, but seeds it with values from `main_cf` and `master_cf` dicts. """ + if main_cf is None: + main_cf = DEFAULT_MAIN_CF + from certbot_postfix.postconf import ConfigMain from certbot_postfix import installer + def _mock_init_postconf(postconf, executable, ignore_master_overrides=False, config_dir=None): # pylint: disable=protected-access,unused-argument postconf._ignore_master_overrides = ignore_master_overrides @@ -309,6 +311,6 @@ def create_installer(config, main_cf=DEFAULT_MAIN_CF): return_value=mock.Mock()): yield installer.Installer(config, "postfix") + if __name__ == "__main__": unittest.main() # pragma: no cover - diff --git a/certbot-postfix/certbot_postfix/tests/postconf_test.py b/certbot-postfix/certbot_postfix/tests/postconf_test.py index 01a43773d..c042093d0 100644 --- a/certbot-postfix/certbot_postfix/tests/postconf_test.py +++ b/certbot-postfix/certbot_postfix/tests/postconf_test.py @@ -1,10 +1,12 @@ """Tests for certbot_postfix.postconf.""" -import mock import unittest +import mock + from certbot import errors + class PostConfTest(unittest.TestCase): """Tests for certbot_postfix.util.PostConf.""" def setUp(self): diff --git a/certbot-postfix/certbot_postfix/util.py b/certbot-postfix/certbot_postfix/util.py index f06989903..86a892140 100644 --- a/certbot-postfix/certbot_postfix/util.py +++ b/certbot-postfix/certbot_postfix/util.py @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) COMMAND = "postfix" + class PostfixUtilBase(object): """A base class for wrapping Postfix command line utilities.""" @@ -289,4 +290,3 @@ def _has_acceptable_tls_versions(parameter_string): if "!" + bad_version not in supported_version_list: return False return True - diff --git a/certbot/account.py b/certbot/account.py index 313e82836..418eb417e 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -110,7 +110,6 @@ class AccountMemoryStorage(interfaces.AccountStorage): return list(six.itervalues(self.accounts)) def save(self, account, client): - # pylint: disable=unused-argument if account.id in self.accounts: logger.debug("Overwriting account: %s", account.id) self.accounts[account.id] = account @@ -244,8 +243,8 @@ class AccountFileStorage(interfaces.AccountStorage): def load(self, account_id): return self._load_for_server_path(account_id, self.config.server_path) - def save(self, account, acme): - self._save(account, acme, regr_only=False) + def save(self, account, client): + self._save(account, client, regr_only=False) def save_regr(self, account, acme): """Save the registration resource. diff --git a/certbot/cli.py b/certbot/cli.py index 0266d26f8..93c81648b 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -312,9 +312,8 @@ def config_help(name, hidden=False): # pylint: disable=no-member if hidden: return argparse.SUPPRESS - else: - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute - return field.__doc__ + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter + return field.__doc__ class HelpfulArgumentGroup(object): @@ -568,7 +567,7 @@ class HelpfulArgumentParser(object): apache_doc = "(the certbot apache plugin is not installed)" usage = SHORT_USAGE - if help_arg == True: + if help_arg is True: self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) sys.exit(0) elif help_arg in self.COMMANDS_TOPICS: diff --git a/certbot/configuration.py b/certbot/configuration.py index 2e7e39e28..b5d5bb1e6 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -3,7 +3,7 @@ import copy import os import zope.interface -from six.moves.urllib import parse # pylint: disable=import-error +from six.moves.urllib import parse # pylint: disable=relative-import from certbot import constants from certbot import errors diff --git a/certbot/display/util.py b/certbot/display/util.py index 675097c08..6e078137f 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -122,10 +122,9 @@ class FileDisplay(object): else: logger.debug("Not pausing for user confirmation") - def menu(self, message, choices, ok_label=None, cancel_label=None, - help_label=None, default=None, + def menu(self, message, choices, ok_label=None, cancel_label=None, # pylint: disable=unused-argument + help_label=None, default=None, # pylint: disable=unused-argument cli_flag=None, force_interactive=False, **unused_kwargs): - # pylint: disable=unused-argument """Display a menu. .. todo:: This doesn't enable the help label/button (I wasn't sold on @@ -227,7 +226,6 @@ class FileDisplay(object): def checklist(self, message, tags, default=None, cli_flag=None, force_interactive=False, **unused_kwargs): - # pylint: disable=unused-argument """Display a checklist. :param str message: Message to display to user @@ -467,8 +465,7 @@ class NoninteractiveDisplay(object): msg += "\n\n(You can set this with the {0} flag)".format(cli_flag) raise errors.MissingCommandlineFlag(msg) - def notification(self, message, pause=False, wrap=True, **unused_kwargs): - # pylint: disable=unused-argument + def notification(self, message, pause=False, wrap=True, **unused_kwargs): # pylint: disable=unused-argument """Displays a notification without waiting for user acceptance. :param str message: Message to display to stdout @@ -523,9 +520,8 @@ class NoninteractiveDisplay(object): self._interaction_fail(message, cli_flag) return OK, default - def yesno(self, message, yes_label=None, no_label=None, + def yesno(self, message, yes_label=None, no_label=None, # pylint: disable=unused-argument default=None, cli_flag=None, **unused_kwargs): - # pylint: disable=unused-argument """Decide Yes or No, without asking anybody :param str message: question for the user diff --git a/certbot/main.py b/certbot/main.py index 4bee5f003..570004b2b 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -176,7 +176,6 @@ def _handle_subset_cert_request(config, domains, cert): raise errors.Error(USER_CANCELLED) -# pylint: disable=inconsistent-return-statements def _handle_identical_cert_request(config, lineage): """Figure out what to do if a lineage has the same names as a previously obtained one @@ -224,7 +223,8 @@ def _handle_identical_cert_request(config, lineage): return "reinstall", lineage elif response[1] == 1: return "renew", lineage - assert False, "This is imporssible" + raise AssertionError('This is impossible') + def _find_lineage_for_domains(config, domains): """Determine whether there are duplicated names and how to handle @@ -501,6 +501,7 @@ def _determine_account(config): raise errors.Error( "Registration cannot proceed without accepting " "Terms of Service.") + return None account_storage = account.AccountFileStorage(config) acme = None @@ -679,7 +680,7 @@ def register(config, unused_plugins): account_storage = account.AccountFileStorage(config) accounts = account_storage.find_all() - if len(accounts) > 0: + if accounts: # 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) @@ -688,7 +689,7 @@ def register(config, unused_plugins): "unsupported.") # _determine_account will register an account _determine_account(config) - return + return None def update_account(config, unused_plugins): @@ -711,7 +712,7 @@ def update_account(config, unused_plugins): reporter_util = zope.component.getUtility(interfaces.IReporter) add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY) - if len(accounts) == 0: + if not accounts: return "Could not find an existing account to update." if config.email is None: if config.register_unsafely_without_email: @@ -762,6 +763,7 @@ def _install_cert(config, le_client, domains, lineage=None): path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path) le_client.enhance_config(domains, path_provider.chain_path) + def install(config, plugins): """Install a previously obtained cert in a server. @@ -813,13 +815,14 @@ def install(config, plugins): raise errors.ConfigurationError("Path to certificate or key was not defined. " "If your certificate is managed by Certbot, please use --cert-name " "to define which certificate you would like to install.") - return None if enhancements.are_requested(config): # In the case where we don't have certname, we have errored out already lineage = cert_manager.lineage_for_certname(config, config.certname) enhancements.enable(lineage, domains, installer, config) + return None + def _populate_from_certname(config): """Helper function for install to populate missing config values from lineage defined by --cert-name.""" @@ -882,6 +885,7 @@ def plugins_cmd(config, plugins): logger.debug("Prepared plugins: %s", available) notify(str(available)) + def enhance(config, plugins): """Add security enhancements to existing configuration @@ -938,6 +942,8 @@ def enhance(config, plugins): if enhancements.are_requested(config): enhancements.enable(lineage, domains, installer, config) + return None + def rollback(config, plugins): """Rollback server configuration changes made during install. @@ -1041,7 +1047,8 @@ def certificates(config, unused_plugins): """ cert_manager.certificates(config) -def revoke(config, unused_plugins): # TODO: coop with renewal config +# TODO: coop with renewal config +def revoke(config, unused_plugins): """Revoke a previously obtained certificate. :param config: Configuration object @@ -1334,6 +1341,8 @@ def main(cli_args=None): :raises errors.Error: error if plugin command is not supported """ + if not cli_args: + cli_args = sys.argv[1:] log.pre_arg_parse_setup() diff --git a/certbot/ocsp.py b/certbot/ocsp.py index bc63c40f9..68f73a821 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -69,8 +69,7 @@ class RevocationChecker(object): if self.use_openssl_binary: return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) - else: - return _check_ocsp_cryptography(cert_path, chain_path, url) + return _check_ocsp_cryptography(cert_path, chain_path, url) def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): # type: (str, str, str, str) -> bool @@ -121,9 +120,8 @@ def _determine_ocsp_server(cert_path): if host: return url, host - else: - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None def _check_ocsp_cryptography(cert_path, chain_path, url): diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py index 8498c182a..61878a7fd 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/plugins/dns_common.py @@ -37,7 +37,7 @@ class DNSAuthenticator(common.Plugin): help='The number of seconds to wait for DNS to propagate before asking the ACME server ' 'to verify the DNS record.') - def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use + def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use return [challenges.DNS01] def prepare(self): # pylint: disable=missing-docstring diff --git a/certbot/plugins/dns_common_lexicon.py b/certbot/plugins/dns_common_lexicon.py index 5b50cc285..2c82db030 100644 --- a/certbot/plugins/dns_common_lexicon.py +++ b/certbot/plugins/dns_common_lexicon.py @@ -110,6 +110,7 @@ class LexiconClient(object): if not str(e).startswith('No domain found'): return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' .format(domain_name, e)) + return None def build_lexicon_config(lexicon_provider_name, lexicon_options, provider_options): diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index 56444cd61..5c869f68e 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -1,10 +1,10 @@ """Tests for certbot.plugins.manual""" import os import unittest +import sys import six import mock -import sys from acme import challenges diff --git a/certbot/plugins/selection_test.py b/certbot/plugins/selection_test.py index 5f8e42516..7ebc2b53b 100644 --- a/certbot/plugins/selection_test.py +++ b/certbot/plugins/selection_test.py @@ -6,10 +6,10 @@ import unittest import mock import zope.component +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + from certbot import errors from certbot import interfaces - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot.display import util as display_util from certbot.plugins.disco import PluginsRegistry from certbot.tests import util as test_util diff --git a/certbot/plugins/standalone_test.py b/certbot/plugins/standalone_test.py index b2a7c32d4..e3e0c1f10 100644 --- a/certbot/plugins/standalone_test.py +++ b/certbot/plugins/standalone_test.py @@ -1,8 +1,8 @@ """Tests for certbot.plugins.standalone.""" import socket -import unittest # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi from socket import errno as socket_errors # type: ignore +import unittest import josepy as jose import mock diff --git a/certbot/plugins/util.py b/certbot/plugins/util.py index 3d042aa12..aac39a579 100644 --- a/certbot/plugins/util.py +++ b/certbot/plugins/util.py @@ -50,8 +50,7 @@ def path_surgery(cmd): if util.exe_exists(cmd): return True - else: - expanded = " expanded" if any(added) else "" - logger.debug("Failed to find executable %s in%s PATH: %s", cmd, - expanded, path) - return False + expanded = " expanded" if any(added) else "" + logger.debug("Failed to find executable %s in%s PATH: %s", cmd, + expanded, path) + return False diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index 529094705..205f31fba 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -225,7 +225,7 @@ to serve all files under specified web root ({0}).""" self.performed[root_path].remove(achall) not_removed = [] # type: List[str] - while len(self._created_dirs) > 0: + while self._created_dirs: path = self._created_dirs.pop() try: os.rmdir(path) diff --git a/certbot/reverter.py b/certbot/reverter.py index 2c9751ec1..764cdf2c0 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -317,7 +317,7 @@ class Reverter(object): # It is strongly advised to set newline = '' on Python 3 with CSV, # and it fixes problems on Windows. kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} - with open(filepath, 'r', **kwargs) as csvfile: # type: ignore # pylint: disable=star-args + with open(filepath, 'r', **kwargs) as csvfile: # type: ignore csvreader = csv.reader(csvfile) for command in reversed(list(csvreader)): try: @@ -418,9 +418,9 @@ class Reverter(object): kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} try: if os.path.isfile(commands_fp): - command_file = open(commands_fp, "a", **kwargs) # type: ignore # pylint: disable=star-args + command_file = open(commands_fp, "a", **kwargs) # type: ignore else: - command_file = open(commands_fp, "w", **kwargs) # type: ignore # pylint: disable=star-args + command_file = open(commands_fp, "w", **kwargs) # type: ignore csvwriter = csv.writer(command_file) csvwriter.writerow(command) diff --git a/certbot/storage.py b/certbot/storage.py index 0b0b1fefa..345b4a744 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -6,7 +6,6 @@ import os import re import shutil import stat -import shutil import configobj import parsedatetime diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 353c34da2..9b2c84879 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -457,17 +457,17 @@ class ReportFailedAuthzrsTest(unittest.TestCase): # Prevent future regressions if the error type changes self.assertTrue(kwargs["error"].description is not None) - http_01 = messages.ChallengeBody(**kwargs) # pylint: disable=star-args + http_01 = messages.ChallengeBody(**kwargs) kwargs["chall"] = acme_util.TLSSNI01 - tls_sni_01 = messages.ChallengeBody(**kwargs) # pylint: disable=star-args + tls_sni_01 = messages.ChallengeBody(**kwargs) self.authzr1 = mock.MagicMock() self.authzr1.body.identifier.value = 'example.com' self.authzr1.body.challenges = [http_01, tls_sni_01] kwargs["error"] = messages.Error(typ="dnssec", detail="detail") - tls_sni_01_diff = messages.ChallengeBody(**kwargs) # pylint: disable=star-args + tls_sni_01_diff = messages.ChallengeBody(**kwargs) self.authzr2 = mock.MagicMock() self.authzr2.body.identifier.value = 'foo.bar' diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 7a55dc1c6..3c212ebe1 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -229,7 +229,7 @@ class CertificatesTest(BaseCertManagerTest): # pylint: disable=protected-access out = get_report() self.assertTrue('1 hour(s)' in out or '2 hour(s)' in out) - self.assertTrue('VALID' in out and not 'INVALID' in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.target_expiry += datetime.timedelta(days=1) # pylint: disable=protected-access diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 5a04eef15..e338905d2 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -7,14 +7,13 @@ import unittest import mock +from josepy import interfaces + +import certbot.tests.util as test_util from certbot import account from certbot import errors 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") diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 455bf5e1e..34ed11e69 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -1,9 +1,9 @@ """Test certbot.display.completer.""" import os try: - import readline # pylint: disable=import-error + import readline # pylint: disable=import-error except ImportError: - import certbot.display.dummy_readline as readline # type: ignore + import certbot.display.dummy_readline as readline # type: ignore import string import sys import unittest @@ -12,7 +12,8 @@ import mock from six.moves import reload_module # pylint: disable=import-error from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -import certbot.tests.util as test_util +import certbot.tests.util as test_util # pylint: disable=ungrouped-imports + class CompleterTest(test_util.TempDirTestCase): """Test certbot.display.completer.Completer.""" @@ -100,5 +101,6 @@ def enable_tab_completion(unused_command): command = 'bind ^I rl_complete' if libedit else 'tab: complete' readline.parse_and_bind(command) + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index c24af3156..138bfe628 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -312,7 +312,7 @@ class FileOutputDisplayTest(unittest.TestCase): def test_methods_take_force_interactive(self): # Every IDisplay method implemented by FileDisplay must take # force_interactive to prevent workflow regressions. - for name in interfaces.IDisplay.names(): # pylint: disable=no-member + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,no-value-for-parameter if six.PY2: getargspec = inspect.getargspec # pylint: disable=no-member else: diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index bc6d4fe3f..1626580fc 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -148,9 +148,6 @@ class ExitHandlerTest(ErrorHandlerTest): **self.init_kwargs) func.assert_called_once_with() - def test_bad_recovery_with_signal(self): - super(ExitHandlerTest, self).test_bad_recovery_with_signal() - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index ba1799f32..7ff8b7452 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -153,8 +153,7 @@ class CertonlyTest(unittest.TestCase): mock_find_cert.return_value = False, None self._call('certonly --webroot -d example.com'.split()) - def _assert_no_pause(self, message, pause=True): - # pylint: disable=unused-argument + def _assert_no_pause(self, message, pause=True): # pylint: disable=unused-argument self.assertFalse(pause) @mock.patch('certbot.cert_manager.lineage_for_certname') @@ -533,14 +532,14 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met if mockisfile: orig_open = os.path.isfile + def mock_isfile(fn, *args, **kwargs): # pylint: disable=unused-argument """Mock os.path.isfile()""" if (fn.endswith("cert") or - fn.endswith("chain") or - fn.endswith("privkey")): + fn.endswith("chain") or + fn.endswith("privkey")): return True - else: - return orig_open(fn) + return orig_open(fn) with mock.patch("os.path.isfile") as mock_if: mock_if.side_effect = mock_isfile diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 7bfa98357..29d108d20 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -7,26 +7,26 @@ import logging import os import shutil import stat +import sys import tempfile import unittest -import sys from multiprocessing import Process, Event -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -import mock import OpenSSL import josepy as jose +import mock +import pkg_resources import six from six.moves import reload_module # pylint: disable=import-error +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from certbot import configuration from certbot import constants from certbot import interfaces -from certbot import storage -from certbot import configuration from certbot import lock +from certbot import storage from certbot import util - from certbot.display import util as display_util diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index c8cb89f4b..853c78499 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -312,10 +312,10 @@ class SafelyRemoveTest(test_util.TempDirTestCase): # no error, yay! self.assertFalse(os.path.exists(self.path)) - @mock.patch("certbot.util.os.remove") - def test_other_error_passthrough(self, mock_remove): - mock_remove.side_effect = OSError - self.assertRaises(OSError, self._call) + def test_other_error_passthrough(self): + with mock.patch("certbot.util.os.remove") as mock_remove: + mock_remove.side_effect = OSError + self.assertRaises(OSError, self._call) class SafeEmailTest(unittest.TestCase): diff --git a/certbot/util.py b/certbot/util.py index 14e315f1f..ec8be3150 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -13,7 +13,6 @@ import platform import re import socket import subprocess -from collections import OrderedDict import configargparse import six @@ -218,7 +217,6 @@ def safe_open(path, mode="w", chmod=None, buffering=None): defaults if ``None``. """ - # pylint: disable=star-args open_args = () # type: Union[Tuple[()], Tuple[int]] if chmod is not None: open_args = (chmod,) diff --git a/setup.py b/setup.py index f3aab96bb..00b1076ae 100644 --- a/setup.py +++ b/setup.py @@ -52,13 +52,13 @@ install_requires = [ ] dev_extras = [ - 'astroid', + 'astroid==1.6.5', 'coverage', 'ipdb', 'pytest', 'pytest-cov', 'pytest-xdist', - 'pylint>=1.8.4', + 'pylint==1.9.4', 'tox', 'twine', 'wheel', diff --git a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem deleted file mode 100644 index 79b6abdf9..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem new file mode 120000 index 000000000..79b6abdf9 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem deleted file mode 100644 index 2d6b30420..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d6b30420 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem deleted file mode 100644 index b801ef735..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..b801ef735 --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem deleted file mode 100644 index 74e20c5ff..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem new file mode 120000 index 000000000..74e20c5ff --- /dev/null +++ b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem deleted file mode 100644 index 41b06370e..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem new file mode 120000 index 000000000..41b06370e --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem deleted file mode 100644 index 2d3e18bec..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d3e18bec --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem deleted file mode 100644 index 3a08c1432..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..3a08c1432 --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem deleted file mode 100644 index 182aa6d78..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem new file mode 120000 index 000000000..182aa6d78 --- /dev/null +++ b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 539791a69..99ec3664a 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -5,13 +5,15 @@ alabaster==0.7.10 apipkg==1.4 asn1crypto==0.22.0 -astroid==1.3.5 +astroid==1.6.5 attrs==17.3.0 Babel==2.5.1 +backports.functools-lru-cache==1.5 backports.shutil-get-terminal-size==1.0.0 boto3==1.9.36 botocore==1.12.36 cloudflare==1.5.1 +configparser==3.7.4 coverage==4.4.2 decorator==4.1.2 dns-lexicon==3.0.8 @@ -26,12 +28,15 @@ imagesize==0.7.1 ipdb==0.10.2 ipython==5.5.0 ipython-genutils==0.2.0 +isort==4.2.5 Jinja2==2.9.6 jmespath==0.9.3 josepy==1.1.0 +lazy-object-proxy==1.3.1 logger==1.4 logilab-common==1.4.1 MarkupSafe==1.0 +mccabe==0.6.1 mypy==0.600 ndg-httpsclient==0.3.2 oauth2client==2.0.0 @@ -46,7 +51,7 @@ py==1.4.34 pyasn1==0.1.9 pyasn1-modules==0.0.10 Pygments==2.2.0 -pylint==1.4.2 +pylint==1.9.4 pytest==3.2.5 pytest-cov==2.5.1 pytest-forked==0.2 @@ -62,6 +67,7 @@ rsa==3.4.2 s3transfer==0.1.11 scandir==1.6 simplegeneric==0.8.1 +singledispatch==3.4.0.3 snowballstemmer==1.2.1 Sphinx==1.7.5 sphinx-rtd-theme==0.2.4 @@ -76,3 +82,4 @@ typing==3.6.4 uritemplate==0.6 virtualenv==15.1.0 wcwidth==0.1.7 +wrapt==1.11.1 -- cgit v1.2.3 From 6249cd0237308c29d387b41187bfc5a1f54140cb Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 9 Apr 2019 07:10:19 -0700 Subject: Use VIRTUALENV_NO_DOWNLOAD in tools/venv.py. (#6931) --- tools/venv.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/venv.py b/tools/venv.py index 3fe8e05fb..4e205c3a5 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -13,7 +13,10 @@ def create_venv(venv_path): """ python2 = _venv_common.find_python_executable(2) command = [sys.executable, '-m', 'virtualenv', '--python', python2, venv_path] - _venv_common.subprocess_with_print(command) + + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + _venv_common.subprocess_with_print(command, environ) def main(pip_args=None): -- cgit v1.2.3 From fb5974b8c38b7f7c2de016140ef9f1c2c20e4dfd Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 9 Apr 2019 20:43:26 +0200 Subject: Improve codecov report integration to CI in Certbot (#6934) So, we observed lately several inconsistencies in how Codecov behave toward the CI pipeline for PRs in Certbot. One example is #6888. The most annoying thing is that the build of PR is **temporary** marked as failed, until all coverage are run. The correction on the latter is done in two PRs. This is the first part. TL;DR This PR separates the Codecov report in two: one for coverage executed on Windows, one for Linux. This is the correct way to do regarding our current CI pipeline. Actions are required by a GitHub administrator of Certbot once this PR is merged. Complete explanation So the failure stated in the introduction is essentially due to several things interacting together: * AppVeyor generates a coverage report for Windows, that have a coverage value a little lower than on Linux (96%) * Travis generates a coverage report for Linux. Its coverage is higher, and slowly decrease as more specific Windows code is added to Certbot, that cannot be tested on Travis * Since AppVeyor saw its capacity increasing, it finishes its coverage job before the one from Travis * Certbot GitHub repo is configured to require the coverage pipeline to succeed (in whatever that means) to success the overall PR build So here the suite of events: 1) PR is issued. GitHub expect three pipeline to succeed: AppVeyor CI, Travis CI and Codecov (displayed in the PR page) 2) Codecov receive first the report of AppVeyor coverage. It is 96%. It is a failure for now, because coverage in master (AppVeyor+Travis) is 98.6%. 3) GitHub is reported of the failure on Codecov, so fail the PR build 4) Codecov receive then the report of Travis coverage. It is 98%. It merges it with the report from AppVeyor, leading to the 98.6%. The failure becomes a success. 5) GitHub is reported of the success on Codecov, so, nevermind, the PR build is a success finally! So we have a CI flow that change its mind. Great. This is because of 2) and 4), and we could expect that Codecov should handle that. This is not the case: it is somewhat misleading, because Codecov adverts a lot about its capability to merge reports, including from different CI. But it is about the final state, not about the transient state, while reports are progressively received. Two things to things that a transient state is existing, with a result that can change: * first, from Codecov doc itself, explaining that reports should not be trusted during the CI pipeline execution: https://docs.codecov.io/docs/ci-service-relationship#section-checking-ci-status * second, is an example of transient state of `cryptography` project, this is advert by Codecov to be a reference of the implementation: ![image](https://user-images.githubusercontent.com/9728851/55796456-5b1c8480-5aca-11e9-9628-41b83fba1bde.png) As you can see above, build state of `cryptography` is failing after the first report is received, and until all coverage reports from Travis are received. So, what can we do about it? Thing is, we are aggregating coverage from very two unrelated sources (two different OS systems), and Codecov has something for that. This is flags: https://docs.codecov.io/docs/flags Flags allow to flag coverage material depending on any logic you apply to the command that uploaded the coverage report (eg. `codecov -F a_flag`). Then, several logics can be applied on it, for instance having in Codecov UI the capability to filter the coverage other a flag, having status of build for each flag and ... having a report for a specific flag. So: 1) I modified Travis and AppVeyor to send their report under a specific flag: `linux` or `windows` 2) I created a project specific `.codecov.yml` configuration in Certbot repository, to instruct Codecov to push two separate reports on GitHub build: one for Linux, one for Windows. Each report can be validated against its specific coverage from the `master` branch (more on this just after) With all of this, now the GitHub is succeeding, because each coverage is validated independently. I think it is the good approach, because it solves the specific issue here, and because it reflects the logic behind: merging coverage from different OS architectures does not make much sense. It would be a long-term problem, because as I said at the beginning, coverages will slowly decrease as more platform specific code is added in Certbot. Now, it is not finished. Two things need to be done: an administrator action, and a second PR Administrator action Certbot GitHub as a a branch protection rule (Settings > Branches > Branch protection rules). It needs to be changed. Indeed this rule is expecting the full coverage report (named `codecov/project`) to be valid on a PR. It needs to be changed to expect two coverage reports: `codecov/project/linux` and `codecov/project/windows`. The `codecov/project` needs to be removed. This can be done once this PR is merged, and the specific coverage reports have been generated on master. Second PR Once this PR is merged and administrative actions have been done. I will make a new PR modifying `.codecov.yml` with two things: * disable the faulty full coverage report, that is not required anymore by GitHub branch protection rules * modify the `linux` and `windows` reports to validate against the relevant coverage calculated from `master` (indeed, in this PR it is a fixed ratio rule, since the coverage to compare on master is the full coverage one, significantly higher) * Tag reports * Set per-project codecov configuration --- .codecov.yml | 13 +++++++++++++ .travis.yml | 2 +- appveyor.yml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..5118fe7b3 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + linux: + flags: linux + target: 98.48 + threshold: 0.1 + base: auto + windows: + flags: windows + target: 96.84 + threshold: 0.1 + base: auto diff --git a/.travis.yml b/.travis.yml index cabd3cf73..b23164a19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -224,7 +224,7 @@ script: - tox - '[ -z "${BOULDER_INTEGRATION+x}" ] || (tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)' -after_success: '[ "$TOXENV" == "py27-cover" ] && codecov' +after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' notifications: email: false diff --git a/appveyor.yml b/appveyor.yml index 12d882973..ed3e87c6c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,4 +37,4 @@ test_script: - tox on_success: - - if exist .coverage codecov + - if exist .coverage codecov -F windows -- cgit v1.2.3 From 278cc8feef4338ce243ca8f2dc6bd8a684589408 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 9 Apr 2019 21:47:53 +0200 Subject: Disable default aggregated report. Reactivate auto-validation of reports against base branch. (#6939) Following #6934, this PR finalize two things, as explained in #6934: disable the default aggregated report validate linux and windows reports against the PR base branch --- .codecov.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 5118fe7b3..55fac56c8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,13 +1,14 @@ coverage: status: project: + default: off linux: flags: linux - target: 98.48 + target: auto threshold: 0.1 base: auto windows: flags: windows - target: 96.84 + target: auto threshold: 0.1 base: auto -- cgit v1.2.3 From 3381bc6616bdf7f2523c2d472ba12749bfea64bf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 9 Apr 2019 13:39:41 -0700 Subject: Add --disable-pip-version-check to pip calls. (#6938) --- tools/pip_install.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tools/pip_install.py b/tools/pip_install.py index 15dc2f0c0..68268e298 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -74,6 +74,12 @@ def call_with_print(command, cwd=None): subprocess.check_call(command, shell=True, cwd=cwd or os.getcwd()) +def pip_install_with_print(args_str): + command = '"{0}" -m pip install --disable-pip-version-check {1}'.format(sys.executable, + args_str) + call_with_print(command) + + def main(args): tools_path = find_tools_path() working_dir = tempfile.mkdtemp() @@ -89,8 +95,7 @@ def main(args): if os.environ.get('CERTBOT_NO_PIN') == '1': # With unpinned dependencies, there is no constraint - call_with_print('"{0}" -m pip install {1}' - .format(sys.executable, ' '.join(args))) + pip_install_with_print(' '.join(args)) else: # Otherwise, we merge requirements to build the constraints and pin dependencies requirements = None @@ -101,11 +106,11 @@ def main(args): merge_requirements(tools_path, requirements, test_constraints, all_constraints) if requirements: - call_with_print('"{0}" -m pip install --constraint "{1}" --requirement "{2}"' - .format(sys.executable, all_constraints, requirements)) + pip_install_with_print('--constraint "{0}" --requirement "{1}"' + .format(all_constraints, requirements)) - call_with_print('"{0}" -m pip install --constraint "{1}" {2}' - .format(sys.executable, all_constraints, ' '.join(args))) + pip_install_with_print('--constraint "{0}" {1}' + .format(all_constraints, ' '.join(args))) finally: if os.environ.get('TRAVIS'): print('travis_fold:end:install_certbot_deps') -- cgit v1.2.3 From b0285438ccea137574035397d07111a7b6082029 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 10 Apr 2019 18:24:32 -0700 Subject: Move venv symlink check out of leauto_upgrades. (#6830) * Move venv symlink check out of leauto_upgrades. * Add back double venv check. --- tests/letstest/scripts/test_leauto_upgrades.sh | 22 ++++++++++------------ .../test_letsencrypt_auto_certonly_standalone.sh | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index e08a0710c..ddcca39cf 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -79,12 +79,11 @@ if [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; echo "Certbot shouldn't have updated to a new version!" exit 1 fi - if [ -d "/opt/eff.org" ]; then - echo "New directory shouldn't have been created!" - exit 1 - fi - # Create a 2nd venv at the new path to ensure we properly handle this case - export VENV_PATH="/opt/eff.org/certbot/venv" + # Create a 2nd venv at the old path to ensure we properly handle the (unlikely) case of two separate virtual environments below. + HOME=${HOME:-~root} + XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share} + OLD_VENV_PATH="$XDG_DATA_HOME/letsencrypt" + export VENV_PATH="$OLD_VENV_PATH" if ! sudo -E ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo second installation appeared to fail exit 1 @@ -117,11 +116,10 @@ if [ "$RUN_PYTHON3_TESTS" = 1 ]; then echo "Python3 wasn't used in venv!" exit 1 fi -fi -echo upgrade appeared to be successful -if [ "$(tools/readlink.py ${XDG_DATA_HOME:-~/.local/share}/letsencrypt)" != "/opt/eff.org/certbot/venv" ]; then - echo symlink from old venv path not properly created! - exit 1 + if [ "$(tools/readlink.py $OLD_VENV_PATH)" != "/opt/eff.org/certbot/venv" ]; then + echo symlink from old venv path not properly created! + exit 1 + fi fi -echo symlink properly created +echo upgrade appeared to be successful diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 081ff3829..901d01e4a 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -11,12 +11,26 @@ set -eo pipefail cd letsencrypt export PATH="$PWD/letsencrypt-auto-source:$PATH" letsencrypt-auto --os-packages-only --debug --version + +# Create a venv-like layout at the old virtual environment path to test that a +# symlink is properly created when letsencrypt-auto runs. +HOME=${HOME:-~root} +XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share} +OLD_VENV_BIN="$XDG_DATA_HOME/letsencrypt/bin" +mkdir -p "$OLD_VENV_BIN" +touch "$OLD_VENV_BIN/letsencrypt" + letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \ --text --agree-dev-preview --agree-tos \ --renew-by-default --redirect \ --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL +if [ "$(tools/readlink.py ${XDG_DATA_HOME:-~/.local/share}/letsencrypt)" != "/opt/eff.org/certbot/venv" ]; then + echo symlink from old venv path not properly created! + exit 1 +fi + if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBCOMMAND]"; then echo "letsencrypt-auto not included in help output!" exit 1 -- cgit v1.2.3 From d5ea9f44865e8b42297d7511e21e9647684dd2e7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 11 Apr 2019 14:16:25 -0700 Subject: Add reminder to local-oldest-requirements.txt. (#6943) --- certbot-apache/local-oldest-requirements.txt | 1 + certbot-dns-cloudflare/local-oldest-requirements.txt | 1 + certbot-dns-cloudxns/local-oldest-requirements.txt | 1 + certbot-dns-digitalocean/local-oldest-requirements.txt | 1 + certbot-dns-dnsimple/local-oldest-requirements.txt | 1 + certbot-dns-dnsmadeeasy/local-oldest-requirements.txt | 1 + certbot-dns-gehirn/local-oldest-requirements.txt | 1 + certbot-dns-google/local-oldest-requirements.txt | 1 + certbot-dns-linode/local-oldest-requirements.txt | 1 + certbot-dns-luadns/local-oldest-requirements.txt | 1 + certbot-dns-nsone/local-oldest-requirements.txt | 1 + certbot-dns-ovh/local-oldest-requirements.txt | 1 + certbot-dns-rfc2136/local-oldest-requirements.txt | 1 + certbot-dns-route53/local-oldest-requirements.txt | 1 + certbot-dns-sakuracloud/local-oldest-requirements.txt | 1 + certbot-nginx/local-oldest-requirements.txt | 1 + certbot-postfix/local-oldest-requirements.txt | 1 + local-oldest-requirements.txt | 1 + 18 files changed, 18 insertions(+) diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index fd8869f7c..da4df38e1 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.25.0 certbot[dev]==0.26.0 diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 8368d266e..ab4ff195f 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.21.1 certbot[dev]==0.21.1 diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 8368d266e..ab4ff195f 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.21.1 certbot[dev]==0.21.1 diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 8368d266e..ab4ff195f 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.21.1 certbot[dev]==0.21.1 diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index b7e6072af..379345762 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,3 +1,4 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 8368d266e..ab4ff195f 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.21.1 certbot[dev]==0.21.1 diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 4e4aadbd8..ca12653b0 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.25.0 certbot[dev]==0.21.1 diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 45b6b2291..9525206e8 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 certbot[dev]==0.31.0 diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 7ee0d9e09..3a59d83ab 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 certbot[dev]==0.33.0 diff --git a/certbot-postfix/local-oldest-requirements.txt b/certbot-postfix/local-oldest-requirements.txt index bc0cdbf00..27652f94e 100644 --- a/certbot-postfix/local-oldest-requirements.txt +++ b/certbot-postfix/local-oldest-requirements.txt @@ -1,2 +1,3 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.25.0 certbot[dev]==0.23.0 diff --git a/local-oldest-requirements.txt b/local-oldest-requirements.txt index d582d5c65..e2a804e1b 100644 --- a/local-oldest-requirements.txt +++ b/local-oldest-requirements.txt @@ -1 +1,2 @@ +# Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -- cgit v1.2.3 From 2b1c77c1ca79d89f1ad433fd5a3f0f6159b73e29 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 12 Apr 2019 03:07:36 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 2 (#6821) * Second part: integration tests for certbot core * Specific coverages * Add comments * Improve names * Suspend fail-under until complete coverage * Implement a minimal functional example * Update certbot-ci/certbot_integration_tests/certbot_tests/conftest.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/certbot_tests/context.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/certbot_tests/context.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Fist set of corrections after review * Fix test and test deploy hook flag * Improve an assertion, remove conftest * Add a test to cover all assertions. Remove the CSR logic for now * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/utils/misc.py Co-Authored-By: adferrand * Some corrections * Add the http-01 test to complete coverage * Add a comment. * Make single requirements * Update certbot-ci/certbot_integration_tests/certbot_tests/context.py Co-Authored-By: adferrand * Revert "Some corrections" This reverts commit 6f20a060e5cd1913c94eebd4e4b67714a245a4ac. # Conflicts: # certbot-ci/certbot_integration_tests/certbot_tests/context.py # certbot-ci/certbot_integration_tests/certbot_tests/test_main.py * Clean join * Update certbot-ci/certbot_integration_tests/certbot_tests/context.py Co-Authored-By: adferrand * Update certbot-ci/certbot_integration_tests/certbot_tests/context.py Co-Authored-By: adferrand * Change assertion name * Rewrite http auth hook as real python scripts * Correct output in some OS * Try a direct execution * Fix shebang * Correct a script * Update certbot config * Call explicitly with python, to be cross platform compatible * Avoid infinite loops. Improve documentation. * Fix syntax --- certbot-ci/certbot_integration_tests/.coveragerc | 2 +- .../certbot_tests/__init__.py | 5 + .../certbot_tests/assertions.py | 75 +++++++++ .../certbot_tests/context.py | 134 ++++++++++++++- .../certbot_tests/test_main.py | 106 +++++++++--- certbot-ci/certbot_integration_tests/utils/misc.py | 182 ++++++++++++++++++++- certbot-ci/setup.py | 10 +- tools/dev_constraints.txt | 1 + tox.ini | 2 +- 9 files changed, 475 insertions(+), 42 deletions(-) create mode 100644 certbot-ci/certbot_integration_tests/certbot_tests/assertions.py diff --git a/certbot-ci/certbot_integration_tests/.coveragerc b/certbot-ci/certbot_integration_tests/.coveragerc index de36d4e02..00929eda2 100644 --- a/certbot-ci/certbot_integration_tests/.coveragerc +++ b/certbot-ci/certbot_integration_tests/.coveragerc @@ -5,4 +5,4 @@ disable_warnings = module-not-imported,no-data-collected [report] # Exclude unit tests in coverage during integration tests. -omit = **/*_test.py,**/tests/*,**/certbot_nginx/parser_obj.py +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/parser_obj.py diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py b/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py index e69de29bb..60c2fcdd8 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/__init__.py @@ -0,0 +1,5 @@ +import pytest + +# Custom assertions defined in the following package need to be registered to be properly +# displayed in a pytest report when they are failing. +pytest.register_assert_rewrite('certbot_integration_tests.certbot_tests.assertions') diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py new file mode 100644 index 000000000..cf1a4792d --- /dev/null +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -0,0 +1,75 @@ +"""This module contains advanced assertions for the certbot integration tests.""" +import os +import grp + + +def assert_hook_execution(probe_path, probe_content): + """ + Assert that a certbot hook has been executed + :param probe_path: path to the file that received the hook output + :param probe_content: content expected when the hook is executed + """ + with open(probe_path, 'r') as file: + lines = file.readlines() + + assert '{0}{1}'.format(probe_content, os.linesep) in lines + + +def assert_save_renew_hook(config_dir, lineage): + """ + Assert that the renew hook configuration of a lineage has been saved. + :param config_dir: location of the certbot configuration + :param lineage: lineage domain name + """ + assert os.path.isfile(os.path.join(config_dir, 'renewal/{0}.conf'.format(lineage))) + + +def assert_cert_count_for_lineage(config_dir, lineage, count): + """ + Assert the number of certificates generated for a lineage. + :param config_dir: location of the certbot configuration + :param lineage: lineage domain name + :param count: number of expected certificates + """ + archive_dir = os.path.join(config_dir, 'archive') + lineage_dir = os.path.join(archive_dir, lineage) + certs = [file for file in os.listdir(lineage_dir) if file.startswith('cert')] + assert len(certs) == count + + +def assert_equals_permissions(file1, file2, mask): + """ + Assert that permissions on two files are identical in respect to a given umask. + :param file1: first file path to compare + :param file2: second file path to compare + :param mask: 3-octal representation of a POSIX umask under which the two files mode + should match (eg. 0o074 will test RWX on group and R on world) + """ + mode_file1 = os.stat(file1).st_mode & mask + mode_file2 = os.stat(file2).st_mode & mask + + assert mode_file1 == mode_file2 + + +def assert_equals_group_owner(file1, file2): + """ + Assert that two files have the same group owner. + :param file1: first file path to compare + :param file2: second file path to compare + :return: + """ + group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0] + group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0] + + assert group_owner_file1 == group_owner_file2 + + +def assert_world_permissions(file, mode): + """ + Assert that a file has the expected world permission. + :param file: file path to check + :param mode: world permissions mode expected + """ + mode_file_all = os.stat(file).st_mode & 0o007 + + assert mode_file_all == mode diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index 9045cd37d..570286737 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -1,16 +1,138 @@ +"""Module to handle the context of integration tests.""" +import os +import shutil +import subprocess +import sys +import tempfile +from distutils.version import LooseVersion + +from certbot_integration_tests.utils import misc + + class IntegrationTestsContext(object): """General fixture describing a certbot integration tests context""" def __init__(self, request): self.request = request + if hasattr(request.config, 'slaveinput'): # Worker node self.worker_id = request.config.slaveinput['slaveid'] - self.acme_xdist = request.config.slaveinput['acme_xdist'] + acme_xdist = request.config.slaveinput['acme_xdist'] else: # Primary node self.worker_id = 'primary' - self.acme_xdist = request.config.acme_xdist - self.directory_url = self.acme_xdist['directory_url'] - self.tls_alpn_01_port = self.acme_xdist['https_port'][self.worker_id] - self.http_01_port = self.acme_xdist['http_port'][self.worker_id] + acme_xdist = request.config.acme_xdist + + self.acme_server =acme_xdist['acme_server'] + self.directory_url = acme_xdist['directory_url'] + self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id] + self.http_01_port = acme_xdist['http_port'][self.worker_id] + # Challtestsrv REST API, that exposes entrypoints to register new DNS entries, + # is listening on challtestsrv_port. + self.challtestsrv_port = acme_xdist['challtestsrv_port'] + + # Certbot version does not depend on the test context. But getting its value requires + # calling certbot from a subprocess. Since it will be called a lot of times through + # _common_test_no_force_renew, we cache its value as a member of the fixture context. + self.certbot_version = misc.get_certbot_version() + + self.workspace = tempfile.mkdtemp() + self.config_dir = os.path.join(self.workspace, 'conf') + self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1] + + self.manual_dns_auth_hook = ( + '{0} -c "import os; import requests; import json; ' + "assert not os.environ.get('CERTBOT_DOMAIN').startswith('fail'); " + "data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))," + "'value':os.environ.get('CERTBOT_VALIDATION')}}; " + "request = requests.post('http://localhost:{1}/set-txt', data=json.dumps(data)); " + "request.raise_for_status(); " + '"' + ).format(sys.executable, self.challtestsrv_port) + self.manual_dns_cleanup_hook = ( + '{0} -c "import os; import requests; import json; ' + "data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))}}; " + "request = requests.post('http://localhost:{1}/clear-txt', data=json.dumps(data)); " + "request.raise_for_status(); " + '"' + ).format(sys.executable, self.challtestsrv_port) def cleanup(self): - pass + """Cleanup the integration test context.""" + shutil.rmtree(self.workspace) + + def _common_test_no_force_renew(self, args): + """ + Base command to execute certbot in a distributed integration test context, + not renewing certificates by default. + """ + new_environ = os.environ.copy() + new_environ['TMPDIR'] = self.workspace + + additional_args = [] + if self.certbot_version >= LooseVersion('0.30.0'): + additional_args.append('--no-random-sleep-on-renew') + + command = [ + 'certbot', + '--server', self.directory_url, + '--no-verify-ssl', + '--http-01-port', str(self.http_01_port), + '--https-port', str(self.tls_alpn_01_port), + '--manual-public-ip-logging-ok', + '--config-dir', self.config_dir, + '--work-dir', os.path.join(self.workspace, 'work'), + '--logs-dir', os.path.join(self.workspace, 'logs'), + '--non-interactive', + '--no-redirect', + '--agree-tos', + '--register-unsafely-without-email', + '--debug', + '-vv' + ] + + command.extend(args) + command.extend(additional_args) + + print('Invoke command:\n{0}'.format(subprocess.list2cmdline(command))) + return subprocess.check_output(command, universal_newlines=True, + cwd=self.workspace, env=new_environ) + + def _common_test(self, args): + """ + Base command to execute certbot in a distributed integration test context, + renewing certificates by default. + """ + command = ['--renew-by-default'] + command.extend(args) + return self._common_test_no_force_renew(command) + + def certbot_no_force_renew(self, args): + """ + Execute certbot with given args, not renewing certificates by default. + :param args: args to pass to certbot + :return: output of certbot execution + """ + command = ['--authenticator', 'standalone', '--installer', 'null'] + command.extend(args) + return self._common_test_no_force_renew(command) + + def certbot(self, args): + """ + Execute certbot with given args, renewing certificates by default. + :param args: args to pass to certbot + :return: output of certbot execution + """ + command = ['--renew-by-default'] + command.extend(args) + return self.certbot_no_force_renew(command) + + def get_domain(self, subdomain='le'): + """ + Generate a certificate domain name suitable for distributed certbot integration tests. + This is a requirement to let the distribution know how to redirect the challenge check + from the ACME server to the relevant pytest-xdist worker. This resolution is done by + appending the pytest worker id to the subdomain, using this pattern: + {subdomain}.{worker_id}.wtf + :param subdomain: the subdomain to use in the generated domain (default 'le') + :return: the well-formed domain suitable for redirection on + """ + return '{0}.{1}.wtf'.format(subdomain, self.worker_id) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 5b0981b36..700e6d514 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -1,9 +1,16 @@ -import requests -import urllib3 +"""Module executing integration tests against certbot core.""" +from __future__ import print_function +import os +import shutil +from os.path import join import pytest - from certbot_integration_tests.certbot_tests import context as certbot_context +from certbot_integration_tests.certbot_tests.assertions import ( + assert_hook_execution, assert_save_renew_hook, assert_cert_count_for_lineage, + assert_world_permissions, assert_equals_group_owner, assert_equals_permissions, +) +from certbot_integration_tests.utils import misc @pytest.fixture() @@ -16,25 +23,78 @@ def context(request): integration_test_context.cleanup() -def test_hello_1(context): - assert context.http_01_port - assert context.tls_alpn_01_port - try: - response = requests.get(context.directory_url, verify=False) - response.raise_for_status() - assert response.json() - response.close() - except urllib3.exceptions.InsecureRequestWarning: - pass +def test_manual_http_auth(context): + """Test the HTTP-01 challenge using manual plugin.""" + with misc.create_http_server(context.http_01_port) as webroot,\ + misc.manual_http_hooks(webroot, context.http_01_port) as scripts: + certname = context.get_domain() + context.certbot([ + 'certonly', '-a', 'manual', '-d', certname, + '--cert-name', certname, + '--manual-auth-hook', scripts[0], + '--manual-cleanup-hook', scripts[1], + '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), + '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + ]) -def test_hello_2(context): - assert context.http_01_port - assert context.tls_alpn_01_port - try: - response = requests.get(context.directory_url, verify=False) - response.raise_for_status() - assert response.json() - response.close() - except urllib3.exceptions.InsecureRequestWarning: - pass + assert_hook_execution(context.hook_probe, 'deploy') + assert_save_renew_hook(context.config_dir, certname) + + +def test_manual_dns_auth(context): + """Test the DNS-01 challenge using manual plugin.""" + certname = context.get_domain('dns') + context.certbot([ + '-a', 'manual', '-d', certname, '--preferred-challenges', 'dns', + 'run', '--cert-name', certname, + '--manual-auth-hook', context.manual_dns_auth_hook, + '--manual-cleanup-hook', context.manual_dns_cleanup_hook, + '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), + '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + ]) + + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'renew') + assert_save_renew_hook(context.config_dir, certname) + + +def test_renew_files_permissions(context): + """Test certificate file permissions upon renewal""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + assert_world_permissions( + join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0) + + # Force renew. Assert certificate renewal and proper permissions. + # We assert certificate renewal and proper permissions. + context.certbot(['renew']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + assert_world_permissions( + join(context.config_dir, 'archive', certname, '/privkey2.pem'), 0) + assert_equals_group_owner( + join(context.config_dir, 'archive', certname, 'privkey1.pem'), + join(context.config_dir, 'archive', certname, 'privkey2.pem')) + assert_equals_permissions( + join(context.config_dir, 'archive', certname, 'privkey1.pem'), + join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + + +def test_renew_with_hook_scripts(context): + """Test certificate renewal with script hooks.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + # Force renew. Assert certificate renewal and hook scripts execution. + misc.generate_test_file_hooks(context.config_dir, context.hook_probe) + context.certbot(['renew']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + assert_hook_execution(context.hook_probe, 'deploy') diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index a3b134788..17c0e74f6 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -2,11 +2,21 @@ Misc module contains stateless functions that could be used during pytest execution, or outside during setup/teardown of the integration tests environment. """ +import contextlib +import errno +import multiprocessing import os +import shutil +import stat +import subprocess +import sys +import tempfile import time -import contextlib +from distutils.version import LooseVersion import requests +from OpenSSL import crypto +from six.moves import socketserver, SimpleHTTPServer def check_until_timeout(url): @@ -30,16 +40,172 @@ def check_until_timeout(url): raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url)) +class GracefulTCPServer(socketserver.TCPServer): + """ + This subclass of TCPServer allows graceful reuse of an address that has + just been released by another instance of TCPServer. + """ + allow_reuse_address = True + + @contextlib.contextmanager -def execute_in_given_cwd(cwd): +def create_http_server(port): """ - Context manager that will execute any command in the given cwd after entering context, - and restore current cwd when context is destroyed. - :param str cwd: the path to use as the temporary current workspace for python execution + Setup and start an HTTP server for the given TCP port. + This server stays active for the lifetime of the context, and is automatically + stopped with context exit, while its temporary webroot is deleted. + :param int port: the TCP port to use + :return str: the temporary webroot attached to this server """ current_cwd = os.getcwd() + webroot = tempfile.mkdtemp() + + def run(): + GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever() + + process = multiprocessing.Process(target=run) + try: - os.chdir(cwd) - yield + # SimpleHTTPServer is designed to serve files from the current working directory at the + # time it starts. So we temporarily change the cwd to our crafted webroot before launch. + try: + os.chdir(webroot) + process.start() + finally: + os.chdir(current_cwd) + + check_until_timeout('http://localhost:{0}/'.format(port)) + + yield webroot finally: - os.chdir(current_cwd) + try: + if process.is_alive(): + process.terminate() + process.join() # Block until process is effectively terminated + finally: + shutil.rmtree(webroot) + + +def list_renewal_hooks_dirs(config_dir): + """ + Find and return paths of all hook directories for the given certbot config directory + :param str config_dir: path to the certbot config directory + :return str[]: list of path to the standard hooks directory for this certbot instance + """ + renewal_hooks_root = os.path.join(config_dir, 'renewal-hooks') + return [os.path.join(renewal_hooks_root, item) for item in ['pre', 'deploy', 'post']] + + +def generate_test_file_hooks(config_dir, hook_probe): + """ + Create a suite of certbot hook scripts and put them in the relevant hook directory + for the given certbot configuration directory. These scripts, when executed, will write + specific verbs in the given hook_probe file to allow asserting they have effectively + been executed. The deploy hook also checks that the renewal environment variables are set. + :param str config_dir: current certbot config directory + :param hook_probe: path to the hook probe to test hook scripts execution + """ + if sys.platform == 'win32': + extension = 'bat' + else: + extension = 'sh' + + renewal_hooks_dirs = list_renewal_hooks_dirs(config_dir) + renewal_deploy_hook_path = os.path.join(renewal_hooks_dirs[1], 'hook.sh') + + for hook_dir in renewal_hooks_dirs: + # We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of + # the hierarchy already exists. It is not the case of os.makedirs. Python 3 has an + # optional parameter `exists_ok` to not fail on existing dir, but Python 2.7 does not. + # So we pass through a try except pass for it. To be removed with dropped support on py27. + try: + os.makedirs(hook_dir) + except OSError as error: + if error.errno != errno.EEXIST: + raise + hook_path = os.path.join(hook_dir, 'hook.{0}'.format(extension)) + if extension == 'sh': + data = '''\ +#!/bin/bash -xe +if [ "$0" = "{0}" ]; then + if [ -z "$RENEWED_DOMAINS" -o -z "$RENEWED_LINEAGE" ]; then + echo "Environment variables not properly set!" >&2 + exit 1 + fi +fi +echo $(basename $(dirname "$0")) >> "{1}"\ +'''.format(renewal_deploy_hook_path, hook_probe) + else: + # TODO: Write the equivalent bat file for Windows + data = '''\ + +''' + with open(hook_path, 'w') as file: + file.write(data) + os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IEXEC) + + +@contextlib.contextmanager +def manual_http_hooks(http_server_root, http_port): + """ + Generate suitable http-01 hooks command for test purpose in the given HTTP + server webroot directory. These hooks command use temporary python scripts + that are deleted upon context exit. + :param str http_server_root: path to the HTTP server configured to serve http-01 challenges + :param int http_port: HTTP port that the HTTP server listen on + :return (str, str): a tuple containing the authentication hook and cleanup hook commands + """ + tempdir = tempfile.mkdtemp() + try: + auth_script_path = os.path.join(tempdir, 'auth.py') + with open(auth_script_path, 'w') as file_h: + file_h.write('''\ +#!/usr/bin/env python +import os +import requests +import time +import sys +challenge_dir = os.path.join('{0}', '.well-known', 'acme-challenge') +os.makedirs(challenge_dir) +challenge_file = os.path.join(challenge_dir, os.environ.get('CERTBOT_TOKEN')) +with open(challenge_file, 'w') as file_h: + file_h.write(os.environ.get('CERTBOT_VALIDATION')) +url = 'http://localhost:{1}/.well-known/acme-challenge/' + os.environ.get('CERTBOT_TOKEN') +for _ in range(0, 10): + time.sleep(1) + try: + if request.get(url).status_code == 200: + sys.exit(0) + except requests.exceptions.ConnectionError: + pass +raise ValueError('Error, url did not respond after 10 attempts: {{0}}'.format(url)) +'''.format(http_server_root, http_port)) + os.chmod(auth_script_path, 0o755) + + cleanup_script_path = os.path.join(tempdir, 'cleanup.py') + with open(cleanup_script_path, 'w') as file_h: + file_h.write('''\ +#!/usr/bin/env python +import os +import shutil +well_known = os.path.join('{0}', '.well-known') +shutil.rmtree(well_known) +'''.format(http_server_root)) + os.chmod(cleanup_script_path, 0o755) + + yield ('{0} {1}'.format(sys.executable, auth_script_path), + '{0} {1}'.format(sys.executable, cleanup_script_path)) + finally: + shutil.rmtree(tempdir) + + +def get_certbot_version(): + """ + Find the version of the certbot available in PATH. + :return str: the certbot version + """ + output = subprocess.check_output(['certbot', '--version'], + universal_newlines=True, stderr=subprocess.STDOUT) + # Typical response is: output = 'certbot 0.31.0.dev0' + version_str = output.split(' ')[1].strip() + return LooseVersion(version_str) diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 595bba69e..0dbcf1aa1 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -5,13 +5,17 @@ from setuptools import find_packages version = '0.32.0.dev0' install_requires = [ + 'coverage', + 'cryptography', + 'pyopenssl', 'pytest', 'pytest-cov', - 'pytest-xdist', + 'pytest-rerunfailures==4.2', 'pytest-sugar', - 'coverage', - 'requests', + 'pytest-xdist', 'pyyaml', + 'requests', + 'six', ] setup( diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 3ff655faa..cc0e54185 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -57,6 +57,7 @@ pytest-cov==2.5.1 pytest-forked==0.2 pytest-xdist==1.22.5 pytest-sugar==0.9.2 +pytest-rerunfailures==4.2 python-dateutil==2.6.1 python-digitalocean==1.11 PyYAML==3.13 diff --git a/tox.ini b/tox.ini index 2c5fe0644..02fa68dcf 100644 --- a/tox.ini +++ b/tox.ini @@ -259,6 +259,6 @@ commands = --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ -W 'ignore:Unverified HTTPS request' - coverage report --fail-under=65 --show-missing + coverage report --include 'certbot/*' --show-missing passenv = DOCKER_* -- cgit v1.2.3 From 3a2e9ff1fa48112ead871de4f6865f47b6245455 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 12 Apr 2019 19:40:51 +0300 Subject: Try to restart httpd on Fedora if config check fails (#6941) This PR adds a step to Apache plugin config_test when run on Fedora. Because Fedora now creates self signed certificate and related key material upon first startup of httpd. This was causing issues for users who run certbot-auto or install certbot (and mod_ssl) and run Certbot directly after. Fixes: #6828 * Try to restart httpd on Fedora if config check fails * Update CHANGELOG.md --- CHANGELOG.md | 7 ++- certbot-apache/certbot_apache/override_centos.py | 39 +++++++++++++++- certbot-apache/certbot_apache/tests/centos_test.py | 52 ++++++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34838bdc3..e05808edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Apache plugin now tries to restart httpd on Fedora using systemctl if a + configuration test error is detected. This has to be done due to the way + Fedora now generates the self signed certificate files upon first + restart. ### Fixed @@ -20,7 +23,7 @@ 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-apache More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index f0c7e7367..a42e1ac9c 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -4,14 +4,17 @@ import logging import pkg_resources import zope.interface +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.errors import MisconfigurationError + from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot_apache import apache_util from certbot_apache import configurator from certbot_apache import parser -from certbot import interfaces -from certbot.errors import MisconfigurationError logger = logging.getLogger(__name__) @@ -40,6 +43,38 @@ class CentOSConfigurator(configurator.ApacheConfigurator): "certbot_apache", "centos-options-ssl-apache.conf") ) + def config_test(self): + """ + Override config_test to mitigate configtest error in vanilla installation + of mod_ssl in Fedora. The error is caused by non-existent self-signed + certificates referenced by the configuration, that would be autogenerated + during the first (re)start of httpd. + """ + + os_info = util.get_os_info() + fedora = os_info[0].lower() == "fedora" + + try: + super(CentOSConfigurator, self).config_test() + except errors.MisconfigurationError: + if fedora: + self._try_restart_fedora() + else: + raise + + def _try_restart_fedora(self): + """ + Tries to restart httpd using systemctl to generate the self signed keypair. + """ + + try: + util.run_script(['systemctl', 'restart', 'httpd']) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + # Finish with actual config check to see if systemctl restart helped + super(CentOSConfigurator, self).config_test() + def _prepare_options(self): """ Override the options dictionary initialization in order to support diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index a27916c32..c5a51c749 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -30,6 +30,58 @@ def get_vh_truth(temp_dir, config_name): ] return vh_truth +class FedoraRestartTest(util.ApacheTest): + """Tests for Fedora specific self-signed certificate override""" + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(FedoraRestartTest, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def _run_fedora_test(self): + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ["fedora"] + self.config.config_test() + + def test_non_fedora_error(self): + c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + mock_test.side_effect = errors.MisconfigurationError + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ["not_fedora"] + self.assertRaises(errors.MisconfigurationError, + self.config.config_test) + + def test_fedora_restart_error(self): + c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + with mock.patch("certbot.util.run_script") as mock_run: + mock_run.side_effect = errors.SubprocessError + self.assertRaises(errors.MisconfigurationError, + self._run_fedora_test) + + def test_fedora_restart(self): + c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + with mock.patch("certbot.util.run_script") as mock_run: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + self._run_fedora_test() + self.assertEqual(mock_test.call_count, 2) + self.assertEqual(mock_run.call_args[0][0], + ['systemctl', 'restart', 'httpd']) + + class MultipleVhostsTestCentOS(util.ApacheTest): """Multiple vhost tests for CentOS / RHEL family of distros""" -- cgit v1.2.3 From 9c54f3dec8c25d3d674022eaaeb0dec1025ee10b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 12 Apr 2019 12:33:17 -0700 Subject: Add back used sys import. (#6954) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 00b1076ae..09ffe8cb5 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ import codecs import os import re +import sys from setuptools import find_packages, setup from setuptools.command.test import test as TestCommand -- cgit v1.2.3 From d5de24d9fcab7d0c60174478bcfab8418f8ce94b Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 12 Apr 2019 22:32:52 +0200 Subject: [Windows] Security model for files permissions - STEP 2 (#6895) This PR is the second part of #6497 to ease the integration, following the new plan propose by @bmw here: #6497 (comment) This PR creates the module certbot.compat.os, that delegates everything to os, and that will be the safeguard against problematic methods of the standard module. On top of that, a quality check wrapper is called in the lint tox environment. This wrapper calls pylint and ensures that standard os module is no used directly in the certbot codebase. Finally local oldest requirements are updated to ensure that tests will take the new logic when running. * Add executable permissions * Add the delegate certbot.compat.os module, add check coding style to enforce usage of certbot.compat.os instead of standard os * Load certbot.compat.os instead of os * Move existing compat test * Update local oldest requirements * Import sys * Update account_test.py * Update os.py * Update os.py * Update local oldest requirements * Implement the new linter_plugin * Fix local oldest for nginx * Remove check coding style * Update linter_plugin.py * Add several comments * Update the setup.py * Add documentation * Update acme dependencies * Update certbot/compat/os.py * Update docs/contributing.rst * Update linter_plugin.py * Handle os.path. Simplify checker. * Add a comment to a reference implementation * Update changelog * Fix module registering * Update docs/contributing.rst * Update config and changelog --- .pylintrc | 2 +- CHANGELOG.md | 18 +++++ certbot-apache/certbot_apache/apache_util.py | 3 +- certbot-apache/certbot_apache/configurator.py | 4 +- certbot-apache/certbot_apache/display_ops.py | 6 +- certbot-apache/certbot_apache/http_01.py | 5 +- certbot-apache/certbot_apache/override_debian.py | 5 +- certbot-apache/certbot_apache/parser.py | 3 +- .../tests/augeas_configurator_test.py | 2 +- .../certbot_apache/tests/centos6_test.py | 6 +- certbot-apache/certbot_apache/tests/centos_test.py | 3 +- .../certbot_apache/tests/complex_parsing_test.py | 2 +- .../certbot_apache/tests/configurator_test.py | 6 +- certbot-apache/certbot_apache/tests/debian_test.py | 2 +- certbot-apache/certbot_apache/tests/gentoo_test.py | 5 +- .../certbot_apache/tests/http_01_test.py | 4 +- certbot-apache/certbot_apache/tests/parser_test.py | 2 +- certbot-apache/certbot_apache/tests/util.py | 4 +- certbot-apache/docs/conf.py | 2 +- certbot-apache/local-oldest-requirements.txt | 4 +- certbot-apache/setup.py | 4 +- .../certbot_dns_cloudflare/dns_cloudflare_test.py | 2 +- .../local-oldest-requirements.txt | 4 +- certbot-dns-cloudflare/setup.py | 4 +- .../certbot_dns_cloudxns/dns_cloudxns_test.py | 2 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 2 +- .../dns_digitalocean_test.py | 2 +- .../local-oldest-requirements.txt | 4 +- certbot-dns-digitalocean/setup.py | 4 +- .../certbot_dns_dnsimple/dns_dnsimple_test.py | 2 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 2 +- .../dns_dnsmadeeasy_test.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- .../certbot_dns_gehirn/dns_gehirn_test.py | 2 +- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 2 +- .../certbot_dns_google/dns_google_test.py | 2 +- certbot-dns-google/local-oldest-requirements.txt | 4 +- certbot-dns-google/setup.py | 4 +- .../certbot_dns_linode/dns_linode_test.py | 2 +- certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 2 +- .../certbot_dns_luadns/dns_luadns_test.py | 2 +- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 2 +- .../certbot_dns_nsone/dns_nsone_test.py | 2 +- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py | 2 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 2 +- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 2 +- certbot-dns-rfc2136/local-oldest-requirements.txt | 4 +- certbot-dns-rfc2136/setup.py | 4 +- .../certbot_dns_route53/dns_route53_test.py | 2 +- certbot-dns-route53/local-oldest-requirements.txt | 4 +- certbot-dns-route53/setup.py | 4 +- .../dns_sakuracloud_test.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/certbot_nginx/configurator.py | 2 +- certbot-nginx/certbot_nginx/http_01.py | 2 +- certbot-nginx/certbot_nginx/parser.py | 2 +- .../certbot_nginx/tests/configurator_test.py | 5 +- certbot-nginx/certbot_nginx/tests/parser_test.py | 5 +- certbot-nginx/certbot_nginx/tests/util.py | 2 +- certbot-nginx/docs/conf.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot-postfix/certbot_postfix/installer.py | 14 ++-- .../certbot_postfix/tests/installer_test.py | 4 +- certbot-postfix/docs/conf.py | 2 +- certbot-postfix/local-oldest-requirements.txt | 4 +- certbot-postfix/setup.py | 4 +- certbot/account.py | 2 +- certbot/cert_manager.py | 2 +- certbot/cli.py | 11 ++- certbot/client.py | 2 +- certbot/compat/misc.py | 8 +-- certbot/compat/os.py | 31 +++++++++ certbot/configuration.py | 2 +- certbot/constants.py | 2 +- certbot/crypto_util.py | 2 +- certbot/display/ops.py | 2 +- certbot/display/util.py | 2 +- certbot/error_handler.py | 2 +- certbot/hooks.py | 7 +- certbot/lock.py | 5 +- certbot/log.py | 2 +- certbot/main.py | 2 +- certbot/plugins/common.py | 4 +- certbot/plugins/common_test.py | 5 +- certbot/plugins/dns_common.py | 3 +- certbot/plugins/dns_common_test.py | 2 +- certbot/plugins/dns_test_common.py | 4 +- certbot/plugins/manual.py | 5 +- certbot/plugins/manual_test.py | 7 +- certbot/plugins/selection.py | 3 +- certbot/plugins/selection_test.py | 2 +- certbot/plugins/storage.py | 3 +- certbot/plugins/storage_test.py | 5 +- certbot/plugins/util.py | 2 +- certbot/plugins/util_test.py | 4 +- certbot/plugins/webroot.py | 5 +- certbot/plugins/webroot_test.py | 4 +- certbot/renewal.py | 15 ++--- certbot/reverter.py | 4 +- certbot/storage.py | 2 +- certbot/tests/account_test.py | 4 +- certbot/tests/cert_manager_test.py | 4 +- certbot/tests/cli_test.py | 10 ++- certbot/tests/client_test.py | 4 +- certbot/tests/compat/__init__.py | 0 certbot/tests/compat/compat_test.py | 21 ++++++ certbot/tests/compat_test.py | 22 ------ certbot/tests/configuration_test.py | 2 +- certbot/tests/crypto_util_test.py | 5 +- certbot/tests/display/completer_test.py | 5 +- certbot/tests/display/ops_test.py | 9 +-- certbot/tests/error_handler_test.py | 4 +- certbot/tests/hook_test.py | 4 +- certbot/tests/lock_test.py | 2 +- certbot/tests/log_test.py | 3 +- certbot/tests/main_test.py | 2 +- certbot/tests/reverter_test.py | 2 +- certbot/tests/storage_test.py | 2 +- certbot/tests/util.py | 2 +- certbot/tests/util_test.py | 2 +- certbot/util.py | 2 +- docs/contributing.rst | 17 +++++ linter_plugin.py | 78 ++++++++++++++-------- 134 files changed, 349 insertions(+), 268 deletions(-) create mode 100644 certbot/compat/os.py create mode 100644 certbot/tests/compat/__init__.py create mode 100644 certbot/tests/compat/compat_test.py delete mode 100644 certbot/tests/compat_test.py diff --git a/.pylintrc b/.pylintrc index 0284c68a0..bd4eab11a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -22,7 +22,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=linter_plugin [MESSAGES CONTROL] diff --git a/CHANGELOG.md b/CHANGELOG.md index e05808edc..89da42c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). configuration test error is detected. This has to be done due to the way Fedora now generates the self signed certificate files upon first restart. +* Updated Certbot and its plugins to improve the handling of file system permissions + on Windows as a step towards adding proper Windows support to Certbot. ### Fixed @@ -23,7 +25,23 @@ 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 * certbot-apache +* certbot-dns-cloudflare +* certbot-dns-cloudxns +* certbot-dns-digitalocean +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-google +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-rfc2136 +* certbot-dns-route53 +* certbot-dns-sakuracloud +* certbot-nginx More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index 62342004f..7a2ecf49b 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,8 +1,9 @@ """ Utility functions for certbot-apache plugin """ import binascii -import os from certbot import util +from certbot.compat import os + def get_mod_deps(mod_name): """Get known module dependencies. diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index b59bc0460..9174143a4 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -3,7 +3,6 @@ import copy import fnmatch import logging -import os import re import socket import time @@ -17,13 +16,14 @@ import zope.component import zope.interface from acme import challenges -from acme.magic_typing import Any, DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import +from certbot.compat import os from certbot.plugins import common from certbot.plugins.util import path_surgery from certbot.plugins.enhancements import AutoHSTSEnhancement diff --git a/certbot-apache/certbot_apache/display_ops.py b/certbot-apache/certbot_apache/display_ops.py index 9e036bbcd..2639eabd1 100644 --- a/certbot-apache/certbot_apache/display_ops.py +++ b/certbot-apache/certbot_apache/display_ops.py @@ -1,14 +1,12 @@ """Contains UI methods for Apache operations.""" import logging -import os import zope.component +import certbot.display.util as display_util from certbot import errors from certbot import interfaces - -import certbot.display.util as display_util - +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py index 962433415..1d1f99919 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/http_01.py @@ -1,15 +1,18 @@ """A class that performs HTTP-01 challenges for Apache""" import logging -import os from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module + from certbot import errors +from certbot.compat import os from certbot.plugins import common + from certbot_apache.obj import VirtualHost # pylint: disable=unused-import from certbot_apache.parser import get_aug_path logger = logging.getLogger(__name__) + class ApacheHttp01(common.TLSSNI01): """Class that performs HTTP-01 challenges within the Apache configurator.""" diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index f4bdd2bc9..5706ce760 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -1,19 +1,20 @@ """ Distribution specific override class for Debian family (Ubuntu/Debian) """ import logging -import os -import pkg_resources +import pkg_resources import zope.interface from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import os from certbot_apache import apache_util from certbot_apache import configurator logger = logging.getLogger(__name__) + @zope.interface.provider(interfaces.IPluginFactory) class DebianConfigurator(configurator.ApacheConfigurator): """Debian specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 1e6f956b1..95b0930a0 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -2,7 +2,6 @@ import copy import fnmatch import logging -import os import re import subprocess import sys @@ -10,7 +9,9 @@ import sys import six from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module + from certbot import errors +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py b/certbot-apache/certbot_apache/tests/augeas_configurator_test.py index c121ecdf3..bfe3b7f16 100644 --- a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py +++ b/certbot-apache/certbot_apache/tests/augeas_configurator_test.py @@ -1,11 +1,11 @@ """Test for certbot_apache.augeas_configurator.""" -import os import shutil import unittest import mock from certbot import errors +from certbot.compat import os from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/certbot_apache/tests/centos6_test.py index ea8a85ed7..0d093aca8 100644 --- a/certbot-apache/certbot_apache/tests/centos6_test.py +++ b/certbot-apache/certbot_apache/tests/centos6_test.py @@ -1,12 +1,14 @@ """Test for certbot_apache.configurator for CentOS 6 overrides""" -import os import unittest +from certbot.compat import os +from certbot.errors import MisconfigurationError + from certbot_apache import obj from certbot_apache import override_centos from certbot_apache import parser from certbot_apache.tests import util -from certbot.errors import MisconfigurationError + def get_vh_truth(temp_dir, config_name): """Return the ground truth for the specified directory.""" diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index c5a51c749..a0c1636b0 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -1,15 +1,16 @@ """Test for certbot_apache.configurator for Centos overrides""" -import os import unittest import mock from certbot import errors +from certbot.compat import os from certbot_apache import obj from certbot_apache import override_centos from certbot_apache.tests import util + def get_vh_truth(temp_dir, config_name): """Return the ground truth for the specified directory.""" prefix = os.path.join( diff --git a/certbot-apache/certbot_apache/tests/complex_parsing_test.py b/certbot-apache/certbot_apache/tests/complex_parsing_test.py index a296fb0eb..8712eb5bf 100644 --- a/certbot-apache/certbot_apache/tests/complex_parsing_test.py +++ b/certbot-apache/certbot_apache/tests/complex_parsing_test.py @@ -1,9 +1,9 @@ """Tests for certbot_apache.parser.""" -import os import shutil import unittest from certbot import errors +from certbot.compat import os from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 7842eaaac..5b2884eb2 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1,7 +1,6 @@ # pylint: disable=too-many-public-methods,too-many-lines """Test for certbot_apache.configurator.""" import copy -import os import shutil import socket import tempfile @@ -16,15 +15,14 @@ from acme import challenges from certbot import achallenges from certbot import crypto_util from certbot import errors - +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as certbot_util from certbot_apache import apache_util from certbot_apache import constants -from certbot_apache import parser from certbot_apache import obj - +from certbot_apache import parser from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py index bb1d64278..f1d5843a5 100644 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ b/certbot-apache/certbot_apache/tests/debian_test.py @@ -1,11 +1,11 @@ """Test for certbot_apache.configurator for Debian overrides""" -import os import shutil import unittest import mock from certbot import errors +from certbot.compat import os from certbot_apache import apache_util from certbot_apache import obj diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py index f61165a3d..dd01e9170 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/certbot_apache/tests/gentoo_test.py @@ -1,15 +1,16 @@ """Test for certbot_apache.configurator for Gentoo overrides""" -import os import unittest import mock from certbot import errors +from certbot.compat import os -from certbot_apache import override_gentoo from certbot_apache import obj +from certbot_apache import override_gentoo from certbot_apache.tests import util + def get_vh_truth(temp_dir, config_name): """Return the ground truth for the specified directory.""" prefix = os.path.join( diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py index 7a950ad7f..a6af68037 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/certbot_apache/tests/http_01_test.py @@ -1,5 +1,4 @@ """Test for certbot_apache.http_01.""" -import os import unittest import mock @@ -8,8 +7,9 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import achallenges from certbot import errors - +from certbot.compat import os from certbot.tests import acme_util + from certbot_apache.parser import get_aug_path from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index dd12e4a49..ef4412a58 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -1,5 +1,4 @@ """Tests for certbot_apache.parser.""" -import os import shutil import unittest @@ -7,6 +6,7 @@ import augeas import mock from certbot import errors +from certbot.compat import os from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 02de6ada4..2ac51540f 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -1,5 +1,4 @@ """Common utilities for certbot_apache.""" -import os import shutil import sys import unittest @@ -9,10 +8,9 @@ import josepy as jose import mock import zope.component +from certbot.compat import os from certbot.display import util as display_util - from certbot.plugins import common - from certbot.tests import util as test_util from certbot_apache import configurator diff --git a/certbot-apache/docs/conf.py b/certbot-apache/docs/conf.py index d2fe15581..5375fd2b8 100644 --- a/certbot-apache/docs/conf.py +++ b/certbot-apache/docs/conf.py @@ -13,7 +13,7 @@ # serve to show the default. import sys -import os +from certbot.compat import os import shlex import mock diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da4df38e1..da509406e 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.25.0 -certbot[dev]==0.26.0 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 978f7752e..deb688fd2 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -9,8 +9,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.25.0', - 'certbot>=0.26.0', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py index e60d6ff8b..4b9419ca8 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py @@ -1,12 +1,12 @@ """Tests for certbot_dns_cloudflare.dns_cloudflare.""" -import os import unittest import CloudFlare import mock from certbot import errors +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index ab4ff195f..da509406e 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.21.1 -certbot[dev]==0.21.1 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 878ecd5f9..971ce7be8 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -7,8 +7,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.21.1', - 'certbot>=0.21.1', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py index c9bad23ab..6bc1e1f79 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_cloudxns.dns_cloudxns.""" -import os import unittest import mock from requests.exceptions import HTTPError, RequestException +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e2462ac64..6af7bb6e7 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py index 0e2043f50..3cb49e9fb 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py @@ -1,12 +1,12 @@ """Tests for certbot_dns_digitalocean.dns_digitalocean.""" -import os import unittest import digitalocean import mock from certbot import errors +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index ab4ff195f..da509406e 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.21.1 -certbot[dev]==0.21.1 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 1cea2f8cc..81803d7da 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -7,8 +7,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.21.1', - 'certbot>=0.21.1', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py index d8f3a23ea..d84bf71ed 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_dnsimple.dns_dnsimple.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index eac823013..4512f9fc0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py index 44a777e1b..f0901664c 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_dnsmadeeasy.dns_dnsmadeeasy.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 9fcc76b7e..51c6637a9 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py index b771c103e..5a591392b 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_gehirn.dns_gehirn.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index e21b6bcc4..deb5c442d 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -7,7 +7,7 @@ version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/certbot_dns_google/dns_google_test.py b/certbot-dns-google/certbot_dns_google/dns_google_test.py index 2b081885b..288357bc1 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google_test.py +++ b/certbot-dns-google/certbot_dns_google/dns_google_test.py @@ -1,6 +1,5 @@ """Tests for certbot_dns_google.dns_google.""" -import os import unittest import mock @@ -10,6 +9,7 @@ from googleapiclient.http import HttpMock from httplib2 import ServerNotFoundError from certbot import errors +from certbot.compat import os from certbot.errors import PluginError from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index ab4ff195f..da509406e 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.21.1 -certbot[dev]==0.21.1 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 67f31b924..176c74968 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -7,8 +7,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.21.1', - 'certbot>=0.21.1', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', # 1.5 is the first version that supports oauth2client>=2.0 'google-api-python-client>=1.5', 'mock', diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py index 2a0ee49f7..c1a4e0ec0 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py +++ b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py @@ -1,10 +1,10 @@ """Tests for certbot_dns_linode.dns_linode.""" -import os import unittest import mock +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 57305e83a..e43ab8de9 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -6,7 +6,7 @@ version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py index bf77e03e4..73cef6521 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py +++ b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_luadns.dns_luadns.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 2d4478dd1..ef77e4143 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py index 56668dd01..b2db2f603 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py +++ b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_nsone.dns_nsone.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 0b66f6f22..7bb7fbbff 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py index f2a10485d..b48a85055 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py +++ b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_ovh.dns_ovh.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 379345762..ed5aa6c87 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 3d6b45017..9a05e69cc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -8,7 +8,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index 30afdbec9..2eb0517f6 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -1,6 +1,5 @@ """Tests for certbot_dns_rfc2136.dns_rfc2136.""" -import os import unittest import dns.flags @@ -9,6 +8,7 @@ import dns.tsig import mock from certbot import errors +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index ab4ff195f..da509406e 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.21.1 -certbot[dev]==0.21.1 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 321e3c29a..8e1d37650 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -7,8 +7,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.21.1', - 'certbot>=0.21.1', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py index 71326c2af..36c391690 100644 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py +++ b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py @@ -1,12 +1,12 @@ """Tests for certbot_dns_route53.dns_route53.Authenticator""" -import os import unittest import mock from botocore.exceptions import NoCredentialsError, ClientError from certbot import errors +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index ca12653b0..da509406e 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.25.0 -certbot[dev]==0.21.1 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 196d80b08..787d4a555 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -6,8 +6,8 @@ version = '0.34.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.25.0', - 'certbot>=0.21.1', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py index 1d9282f9a..10abc29e2 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py @@ -1,11 +1,11 @@ """Tests for certbot_dns_sakuracloud.dns_sakuracloud.""" -import os import unittest import mock from requests.exceptions import HTTPError +from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.plugins.dns_test_common import DOMAIN diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 9525206e8..2b3ba9f32 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.31.0 +-e .[dev] diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 787e361b1..286b13ee9 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -7,7 +7,7 @@ version = '0.34.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.31.0', + 'certbot>=0.34.0.dev0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 6bf82088b..6da00e513 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -1,6 +1,5 @@ """Nginx Configuration""" import logging -import os import re import socket import subprocess @@ -20,6 +19,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.plugins import common from certbot_nginx import constants diff --git a/certbot-nginx/certbot_nginx/http_01.py b/certbot-nginx/certbot_nginx/http_01.py index 2e897a8ac..70147a433 100644 --- a/certbot-nginx/certbot_nginx/http_01.py +++ b/certbot-nginx/certbot_nginx/http_01.py @@ -1,12 +1,12 @@ """A class that performs HTTP-01 challenges for Nginx""" import logging -import os from acme import challenges from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors +from certbot.compat import os from certbot.plugins import common from certbot_nginx import obj diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py index 49f02b741..d50606fc5 100644 --- a/certbot-nginx/certbot_nginx/parser.py +++ b/certbot-nginx/certbot_nginx/parser.py @@ -3,13 +3,13 @@ import copy import functools import glob import logging -import os import re import pyparsing import six from certbot import errors +from certbot.compat import os from certbot_nginx import obj from certbot_nginx import nginxparser diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 08e4a56ae..6c3f4f0cf 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -1,17 +1,16 @@ # pylint: disable=too-many-public-methods """Test for certbot_nginx.configurator.""" -import os import unittest -import mock import OpenSSL - +import mock from acme import challenges from acme import messages from certbot import achallenges from certbot import crypto_util from certbot import errors +from certbot.compat import os from certbot.tests import util as certbot_test_util from certbot_nginx import constants diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index 126eb2a38..97c542532 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -1,17 +1,18 @@ """Tests for certbot_nginx.parser.""" import glob -import os import re import shutil import unittest +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + from certbot import errors +from certbot.compat import os from certbot_nginx import nginxparser from certbot_nginx import obj from certbot_nginx import parser from certbot_nginx.tests import util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index 2c5f6f39a..5476333e0 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -1,6 +1,5 @@ """Common utilities for certbot_nginx.""" import copy -import os import shutil import tempfile import unittest @@ -12,6 +11,7 @@ import pkg_resources import zope.component from certbot import configuration +from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util diff --git a/certbot-nginx/docs/conf.py b/certbot-nginx/docs/conf.py index 167abb4fb..d606b6292 100644 --- a/certbot-nginx/docs/conf.py +++ b/certbot-nginx/docs/conf.py @@ -13,7 +13,7 @@ # serve to show the default. import sys -import os +from certbot.compat import os import shlex diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 3a59d83ab..da509406e 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.33.0 +-e .[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 07c4e5301..a6da1d851 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '0.34.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.33.0', + 'certbot>=0.34.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot-postfix/certbot_postfix/installer.py b/certbot-postfix/certbot_postfix/installer.py index 93afd4de9..b5e4df5ec 100644 --- a/certbot-postfix/certbot_postfix/installer.py +++ b/certbot-postfix/certbot_postfix/installer.py @@ -1,24 +1,24 @@ """certbot installer plugin for postfix.""" import logging -import os -import zope.interface -import zope.component import six +import zope.component +import zope.interface + +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Callable, Dict, List +# pylint: enable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util as certbot_util +from certbot.compat import os from certbot.plugins import common as plugins_common from certbot_postfix import constants from certbot_postfix import postconf from certbot_postfix import util -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Dict, List -# pylint: enable=unused-import, no-name-in-module - logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IInstaller) diff --git a/certbot-postfix/certbot_postfix/tests/installer_test.py b/certbot-postfix/certbot_postfix/tests/installer_test.py index 8222ccb12..a24643379 100644 --- a/certbot-postfix/certbot_postfix/tests/installer_test.py +++ b/certbot-postfix/certbot_postfix/tests/installer_test.py @@ -1,16 +1,16 @@ """Tests for certbot_postfix.installer.""" import copy import functools -import os import unittest from contextlib import contextmanager import mock import pkg_resources import six -from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import,no-name-in-module from certbot import errors +from certbot.compat import os from certbot.tests import util as certbot_test_util diff --git a/certbot-postfix/docs/conf.py b/certbot-postfix/docs/conf.py index 51d99aab5..a2f1a8092 100644 --- a/certbot-postfix/docs/conf.py +++ b/certbot-postfix/docs/conf.py @@ -12,7 +12,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os +from certbot.compat import os # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-postfix/local-oldest-requirements.txt b/certbot-postfix/local-oldest-requirements.txt index 27652f94e..da509406e 100644 --- a/certbot-postfix/local-oldest-requirements.txt +++ b/certbot-postfix/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.25.0 -certbot[dev]==0.23.0 +acme[dev]==0.29.0 +-e .[dev] diff --git a/certbot-postfix/setup.py b/certbot-postfix/setup.py index 0ff2908df..e708863b2 100644 --- a/certbot-postfix/setup.py +++ b/certbot-postfix/setup.py @@ -5,8 +5,8 @@ from setuptools import find_packages version = '0.26.0.dev0' install_requires = [ - 'acme>=0.25.0', - 'certbot>=0.23.0', + 'acme>=0.29.0', + 'certbot>=0.34.0.dev0', 'setuptools', 'six', 'zope.component', diff --git a/certbot/account.py b/certbot/account.py index 418eb417e..bf5c131db 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -3,7 +3,6 @@ import datetime import functools import hashlib import logging -import os import shutil import socket @@ -22,6 +21,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import misc +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index 5c102beb4..ab929b597 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -1,7 +1,6 @@ """Tools for managing certificates.""" import datetime import logging -import os import re import traceback @@ -17,6 +16,7 @@ from certbot import ocsp from certbot import storage from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.display import util as display_util logger = logging.getLogger(__name__) diff --git a/certbot/cli.py b/certbot/cli.py index 93c81648b..96f58caf7 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1,19 +1,17 @@ """Certbot command line argument & config processing.""" # pylint: disable=too-many-lines from __future__ import print_function + import argparse import copy import glob -import logging import logging.handlers -import os import sys import configargparse import six import zope.component import zope.interface - from zope.interface import interfaces as zope_interfaces from acme import challenges @@ -22,18 +20,17 @@ from acme.magic_typing import Any, Dict, Optional # pylint: enable=unused-import, no-name-in-module import certbot - +import certbot.plugins.enhancements as enhancements +import certbot.plugins.selection as plugin_selection from certbot import constants from certbot import crypto_util from certbot import errors from certbot import hooks from certbot import interfaces from certbot import util - +from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import disco as plugins_disco -import certbot.plugins.enhancements as enhancements -import certbot.plugins.selection as plugin_selection logger = logging.getLogger(__name__) diff --git a/certbot/client.py b/certbot/client.py index 3cc073c03..5ec3c4d92 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -1,7 +1,6 @@ """Certbot client API.""" import datetime import logging -import os import platform import OpenSSL @@ -32,6 +31,7 @@ from certbot import reverter from certbot import storage from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.display import enhancements from certbot.display import ops as display_ops from certbot.plugins import selection as plugin_selection diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 3ea4a7908..2f4ba0c6b 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -2,14 +2,14 @@ This compat module handles various platform specific calls that do not fall into one particular category. """ -import os -import select -import sys -import errno import ctypes +import errno +import select import stat +import sys from certbot import errors +from certbot.compat import os UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ 'certificates', 'enhance', 'revoke', 'delete', diff --git a/certbot/compat/os.py b/certbot/compat/os.py new file mode 100644 index 000000000..0112fbc73 --- /dev/null +++ b/certbot/compat/os.py @@ -0,0 +1,31 @@ +""" +This compat modules is a wrapper of the core os module that forbids usage of specific operations +(eg. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. +This module is intended to replace standard os module throughout certbot projects (except acme). +""" +from __future__ import absolute_import + +# First round of wrapping: we import statically all public attributes exposed by the os module +# This allows in particular to have pylint, mypy, IDEs be aware that most of os members are +# available in certbot.compat.os. +from os import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden + +# Second round of wrapping: we import dynamically all attributes from the os module that have not +# yet been imported by the first round (static import). This covers in particular the case of +# specific python 3.x versions where not all public attributes are in the special __all__ of os, +# and so not in `from os import *`. +import os as std_os # pylint: disable=os-module-forbidden +import sys as std_sys +ourselves = std_sys.modules[__name__] +for attribute in dir(std_os): + # Check if the attribute does not already exist in our module. It could be internal attributes + # of the module (__name__, __doc__), or attributes from standard os already imported with + # `from os import *`. + if not hasattr(ourselves, attribute): + setattr(ourselves, attribute, getattr(std_os, attribute)) + +# Similar to os.path, allow certbot.compat.os.path to behave as a module +std_sys.modules[__name__ + '.path'] = path + +# Clean all remaining importables that are not from the core os module. +del ourselves, std_os, std_sys diff --git a/certbot/configuration.py b/certbot/configuration.py index b5d5bb1e6..cc9cb2d98 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -1,6 +1,5 @@ """Certbot user-supplied configuration.""" import copy -import os import zope.interface from six.moves.urllib import parse # pylint: disable=relative-import @@ -10,6 +9,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import misc +from certbot.compat import os @zope.interface.implementer(interfaces.IConfig) diff --git a/certbot/constants.py b/certbot/constants.py index 3b2f7a2d9..c23effe2d 100644 --- a/certbot/constants.py +++ b/certbot/constants.py @@ -1,12 +1,12 @@ """Certbot constants.""" import logging -import os import pkg_resources from acme import challenges from certbot.compat import misc +from certbot.compat import os SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" """Setuptools entry point group name for plugins.""" diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index f976372c5..281a76668 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -6,7 +6,6 @@ """ import hashlib import logging -import os import warnings import pyrfc3339 @@ -30,6 +29,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import misc +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/display/ops.py b/certbot/display/ops.py index 3624a4727..b5f3655fe 100644 --- a/certbot/display/ops.py +++ b/certbot/display/ops.py @@ -1,6 +1,5 @@ """Contains UI methods for LE user operations.""" import logging -import os import zope.component @@ -8,6 +7,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.display import util as display_util logger = logging.getLogger(__name__) diff --git a/certbot/display/util.py b/certbot/display/util.py index 6e078137f..91f3bc33c 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -1,6 +1,5 @@ """Certbot display.""" import logging -import os import sys import textwrap @@ -10,6 +9,7 @@ from certbot import constants from certbot import errors from certbot import interfaces from certbot.compat import misc +from certbot.compat import os from certbot.display import completer logger = logging.getLogger(__name__) diff --git a/certbot/error_handler.py b/certbot/error_handler.py index 2114fbbed..1a570e48e 100644 --- a/certbot/error_handler.py +++ b/certbot/error_handler.py @@ -1,7 +1,6 @@ """Registers functions to be called if an exception or signal occurs.""" import functools import logging -import os import signal import traceback @@ -10,6 +9,7 @@ from acme.magic_typing import Any, Callable, Dict, List, Union # pylint: enable=unused-import, no-name-in-module from certbot import errors +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/hooks.py b/certbot/hooks.py index 7d2e42fcd..7de846ae4 100644 --- a/certbot/hooks.py +++ b/certbot/hooks.py @@ -2,14 +2,13 @@ from __future__ import print_function import logging -import os - from subprocess import Popen, PIPE -from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module + from certbot import errors from certbot import util - +from certbot.compat import os from certbot.plugins import util as plug_util logger = logging.getLogger(__name__) diff --git a/certbot/lock.py b/certbot/lock.py index 760a12b8f..fad8a5175 100644 --- a/certbot/lock.py +++ b/certbot/lock.py @@ -1,7 +1,6 @@ """Implements file locks compatible with Linux and Windows for locking files and directories.""" import errno import logging -import os try: import fcntl # pylint: disable=import-error except ImportError: @@ -10,8 +9,10 @@ except ImportError: else: POSIX_MODE = True +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module + from certbot import errors -from acme.magic_typing import Optional, Callable # pylint: disable=unused-import, no-name-in-module +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/log.py b/certbot/log.py index 84911c1c0..bf444de07 100644 --- a/certbot/log.py +++ b/certbot/log.py @@ -17,7 +17,6 @@ from __future__ import print_function import functools import logging import logging.handlers -import os import sys import tempfile import traceback @@ -28,6 +27,7 @@ from certbot import constants from certbot import errors from certbot import util from certbot.compat import misc +from certbot.compat import os # Logging format CLI_FMT = "%(message)s" diff --git a/certbot/main.py b/certbot/main.py index 570004b2b..5365cd591 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -4,7 +4,6 @@ from __future__ import print_function import functools import logging.handlers -import os import sys import configobj @@ -33,6 +32,7 @@ from certbot import storage from certbot import updater from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.display import util as display_util, ops as display_ops from certbot.plugins import disco as plugins_disco from certbot.plugins import enhancements diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 78684193b..3dd9534db 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -1,6 +1,5 @@ """Plugin common functions.""" import logging -import os import re import shutil import tempfile @@ -12,6 +11,7 @@ import zope.interface from josepy import util as jose_util from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + from certbot import achallenges # pylint: disable=unused-import from certbot import constants from certbot import crypto_util @@ -19,7 +19,7 @@ from certbot import errors from certbot import interfaces from certbot import reverter from certbot import util - +from certbot.compat import os from certbot.plugins.storage import PluginStorage logger = logging.getLogger(__name__) diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 103a12499..bce8f833a 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -1,20 +1,19 @@ """Tests for certbot.plugins.common.""" import functools -import os import shutil import tempfile import unittest +import OpenSSL import josepy as jose import mock -import OpenSSL from acme import challenges from certbot import achallenges from certbot import crypto_util from certbot import errors - +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py index 61878a7fd..9be2868b0 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/plugins/dns_common.py @@ -2,16 +2,17 @@ import abc import logging -import os import stat from time import sleep import configobj import zope.interface + from acme import challenges from certbot import errors from certbot import interfaces +from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util from certbot.plugins import common diff --git a/certbot/plugins/dns_common_test.py b/certbot/plugins/dns_common_test.py index 62c99d579..6741ff8e5 100644 --- a/certbot/plugins/dns_common_test.py +++ b/certbot/plugins/dns_common_test.py @@ -2,12 +2,12 @@ import collections import logging -import os import unittest import mock from certbot import errors +from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import dns_common from certbot.plugins import dns_test_common diff --git a/certbot/plugins/dns_test_common.py b/certbot/plugins/dns_test_common.py index 54b656b20..7f57b9431 100644 --- a/certbot/plugins/dns_test_common.py +++ b/certbot/plugins/dns_test_common.py @@ -1,14 +1,14 @@ """Base test class for DNS authenticators.""" -import os - import configobj import josepy as jose import mock import six + from acme import challenges from certbot import achallenges +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/plugins/manual.py b/certbot/plugins/manual.py index b4d20478a..4bb11de3f 100644 --- a/certbot/plugins/manual.py +++ b/certbot/plugins/manual.py @@ -1,6 +1,4 @@ """Manual authenticator plugin""" -import os - import zope.component import zope.interface @@ -8,10 +6,11 @@ from acme import challenges from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import -from certbot import interfaces from certbot import errors from certbot import hooks +from certbot import interfaces from certbot import reverter +from certbot.compat import os from certbot.plugins import common diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index 5c869f68e..10dbe73c9 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -1,15 +1,14 @@ """Tests for certbot.plugins.manual""" -import os import unittest import sys -import six import mock +import six from acme import challenges from certbot import errors - +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util @@ -73,7 +72,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.config.manual_public_ip_logging_ok = True self.config.manual_auth_hook = ( '{0} -c "from __future__ import print_function;' - 'import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' + 'from certbot.compat import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' .format(sys.executable)) diff --git a/certbot/plugins/selection.py b/certbot/plugins/selection.py index 0b321d713..39a6b01fc 100644 --- a/certbot/plugins/selection.py +++ b/certbot/plugins/selection.py @@ -1,7 +1,6 @@ """Decide which plugins to use for authentication & installation""" from __future__ import print_function -import os import logging import six @@ -9,7 +8,7 @@ import zope.component from certbot import errors from certbot import interfaces - +from certbot.compat import os from certbot.display import util as display_util logger = logging.getLogger(__name__) diff --git a/certbot/plugins/selection_test.py b/certbot/plugins/selection_test.py index 7ebc2b53b..a2a171c1d 100644 --- a/certbot/plugins/selection_test.py +++ b/certbot/plugins/selection_test.py @@ -1,5 +1,4 @@ """Tests for letsencrypt.plugins.selection""" -import os import sys import unittest @@ -10,6 +9,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import errors from certbot import interfaces +from certbot.compat import os from certbot.display import util as display_util from certbot.plugins.disco import PluginsRegistry from certbot.tests import util as test_util diff --git a/certbot/plugins/storage.py b/certbot/plugins/storage.py index ae3ca1889..51350802c 100644 --- a/certbot/plugins/storage.py +++ b/certbot/plugins/storage.py @@ -1,10 +1,11 @@ """Plugin storage class.""" import json import logging -import os from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module + from certbot import errors +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py index 4b5af5c22..2fa2c0345 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/plugins/storage_test.py @@ -1,14 +1,15 @@ """Tests for certbot.plugins.storage.PluginStorage""" import json -import os import unittest import mock from certbot import errors +from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util + class PluginStorageTest(test_util.ConfigTestCase): """Test for certbot.plugins.storage.PluginStorage""" @@ -71,7 +72,7 @@ class PluginStorageTest(test_util.ConfigTestCase): def test_save_errors_unable_to_write_file(self): mock_open = mock.mock_open() mock_open.side_effect = IOError - with mock.patch("os.open", mock_open): + with mock.patch("certbot.compat.os.open", mock_open): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access self.plugin.storage._initialized = True # pylint: disable=protected-access diff --git a/certbot/plugins/util.py b/certbot/plugins/util.py index aac39a579..61f811280 100644 --- a/certbot/plugins/util.py +++ b/certbot/plugins/util.py @@ -1,8 +1,8 @@ """Plugin utilities.""" import logging -import os from certbot import util +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/plugins/util_test.py b/certbot/plugins/util_test.py index 8ecd380b8..6ec6f62f4 100644 --- a/certbot/plugins/util_test.py +++ b/certbot/plugins/util_test.py @@ -1,9 +1,11 @@ """Tests for certbot.plugins.util.""" -import os import unittest import mock +from certbot.compat import os + + class GetPrefixTest(unittest.TestCase): """Tests for certbot.plugins.get_prefixes.""" def test_get_prefix(self): diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index 205f31fba..1c94b34d3 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -4,7 +4,6 @@ import collections import errno import json import logging -import os import six import zope.component @@ -19,12 +18,12 @@ from certbot import achallenges # pylint: disable=unused-import from certbot import cli from certbot import errors from certbot import interfaces -from certbot.display import util as display_util +from certbot.compat import os from certbot.display import ops +from certbot.display import util as display_util from certbot.plugins import common from certbot.plugins import util - logger = logging.getLogger(__name__) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index a67ddbb83..3a902b91f 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -5,7 +5,6 @@ from __future__ import print_function import argparse import errno import json -import os import shutil import tempfile import unittest @@ -19,6 +18,7 @@ from acme import challenges from certbot import achallenges from certbot import errors from certbot.compat import misc +from certbot.compat import os from certbot.display import util as display_util from certbot.tests import acme_util from certbot.tests import util as test_util @@ -242,7 +242,7 @@ class AuthenticatorTest(unittest.TestCase): os.rmdir(leftover_path) - @mock.patch('os.rmdir') + @mock.patch('certbot.compat.os.rmdir') def test_cleanup_failure(self, mock_rmdir): self.auth.prepare() self.auth.perform([self.achall]) diff --git a/certbot/renewal.py b/certbot/renewal.py index 9da0ec596..f932269b6 100644 --- a/certbot/renewal.py +++ b/certbot/renewal.py @@ -1,30 +1,29 @@ """Functionality for autorenewal and associated juggling of configurations""" from __future__ import print_function + import copy import itertools import logging -import os -import traceback +import random import sys import time -import random +import traceback +import OpenSSL import six import zope.component -import OpenSSL - from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import cli from certbot import crypto_util from certbot import errors -from certbot import interfaces -from certbot import util from certbot import hooks +from certbot import interfaces from certbot import storage from certbot import updater - +from certbot import util +from certbot.compat import os from certbot.plugins import disco as plugins_disco logger = logging.getLogger(__name__) diff --git a/certbot/reverter.py b/certbot/reverter.py index 764cdf2c0..c7992e73c 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -2,7 +2,6 @@ import csv import glob import logging -import os import shutil import sys import time @@ -11,11 +10,12 @@ import traceback import six import zope.component -from certbot.compat import misc from certbot import constants from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import misc +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/storage.py b/certbot/storage.py index 05faf81cb..01e012920 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -2,7 +2,6 @@ import datetime import glob import logging -import os import re import shutil import stat @@ -20,6 +19,7 @@ from certbot import error_handler from certbot import errors from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.plugins import common as plugins_common from certbot.plugins import disco as plugins_disco diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index fb0205f9c..24a092cc8 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -1,7 +1,6 @@ """Tests for certbot.account.""" import datetime import json -import os import shutil import stat import unittest @@ -15,6 +14,7 @@ from acme import messages import certbot.tests.util as test_util from certbot import errors from certbot.compat import misc +from certbot.compat import os KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -233,7 +233,7 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self._set_server('https://acme-v02.api.letsencrypt.org/directory') self.assertEqual([self.acc], self.storage.find_all()) - @mock.patch('os.rmdir') + @mock.patch('certbot.compat.os.rmdir') def test_corrupted_account(self, mock_rmdir): # pylint: disable=protected-access self._set_server('https://acme-staging.api.letsencrypt.org/directory') diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 3c212ebe1..08d7282a7 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -1,7 +1,6 @@ """Tests for certbot.cert_manager.""" # pylint: disable=protected-access -import os import re import shutil import tempfile @@ -12,10 +11,9 @@ import mock from certbot import configuration from certbot import errors - +from certbot.compat import os from certbot.display import util as display_util from certbot.storage import ALL_FOUR - from certbot.tests import storage_test from certbot.tests import util as test_util diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index ebba0b0c0..592bd1be7 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -1,10 +1,9 @@ """Tests for certbot.cli.""" import argparse -import unittest -import os -import tempfile import copy import sys +import tempfile +import unittest import mock import six @@ -12,13 +11,12 @@ from six.moves import reload_module # pylint: disable=import-error from acme import challenges +import certbot.tests.util as test_util from certbot import cli from certbot import constants from certbot import errors +from certbot.compat import os from certbot.plugins import disco - -import certbot.tests.util as test_util - from certbot.tests.util import TempDirTestCase PLUGINS = disco.PluginsRegistry.find_all() diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index e338905d2..711346dbb 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -1,5 +1,4 @@ """Tests for certbot.client.""" -import os import platform import shutil import tempfile @@ -12,6 +11,7 @@ from josepy import interfaces import certbot.tests.util as test_util from certbot import account from certbot import errors +from certbot.compat import os from certbot import util KEY = test_util.load_vector("rsa512_key.pem") @@ -323,7 +323,7 @@ class ClientTest(ClientTestCommon): self.eg_order.fullchain_pem) @mock.patch("certbot.client.crypto_util") - @mock.patch("os.remove") + @mock.patch("certbot.compat.os.remove") def test_obtain_certificate_partial_success(self, mock_remove, mock_crypto_util): csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN) key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN) diff --git a/certbot/tests/compat/__init__.py b/certbot/tests/compat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot/tests/compat/compat_test.py b/certbot/tests/compat/compat_test.py new file mode 100644 index 000000000..832c557f6 --- /dev/null +++ b/certbot/tests/compat/compat_test.py @@ -0,0 +1,21 @@ +"""Tests for certbot.compat.""" +import certbot.tests.util as test_util +from certbot.compat import misc +from certbot.compat import os + + +class OsReplaceTest(test_util.TempDirTestCase): + """Test to ensure consistent behavior of os_rename method""" + + def test_os_rename_to_existing_file(self): + """Ensure that os_rename will effectively rename src into dst for all platforms.""" + src = os.path.join(self.tempdir, 'src') + dst = os.path.join(self.tempdir, 'dst') + open(src, 'w').close() + open(dst, 'w').close() + + # On Windows, a direct call to os.rename will fail because dst already exists. + misc.os_rename(src, dst) + + self.assertFalse(os.path.exists(src)) + self.assertTrue(os.path.exists(dst)) diff --git a/certbot/tests/compat_test.py b/certbot/tests/compat_test.py deleted file mode 100644 index ffbba24fd..000000000 --- a/certbot/tests/compat_test.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Tests for certbot.compat.""" -import os - -import certbot.tests.util as test_util -from certbot.compat import misc - - -class OsReplaceTest(test_util.TempDirTestCase): - """Test to ensure consistent behavior of os_rename method""" - - def test_os_rename_to_existing_file(self): - """Ensure that os_rename will effectively rename src into dst for all platforms.""" - src = os.path.join(self.tempdir, 'src') - dst = os.path.join(self.tempdir, 'dst') - open(src, 'w').close() - open(dst, 'w').close() - - # On Windows, a direct call to os.rename will fail because dst already exists. - misc.os_rename(src, dst) - - self.assertFalse(os.path.exists(src)) - self.assertTrue(os.path.exists(dst)) diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index a4d32a57f..aa07a580f 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -1,5 +1,4 @@ """Tests for certbot.configuration.""" -import os import unittest import mock @@ -7,6 +6,7 @@ import mock from certbot import constants from certbot import errors from certbot.compat import misc +from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index c2f5fa25d..5c1a07f8d 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -1,17 +1,16 @@ """Tests for certbot.crypto_util.""" import logging -import os import unittest import OpenSSL import mock import zope.component +import certbot.tests.util as test_util from certbot import errors from certbot import interfaces from certbot import util -import certbot.tests.util as test_util - +from certbot.compat import os RSA256_KEY = test_util.load_vector('rsa256_key.pem') RSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem') diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 34ed11e69..bbbc50c49 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -1,5 +1,4 @@ """Test certbot.display.completer.""" -import os try: import readline # pylint: disable=import-error except ImportError: @@ -11,7 +10,9 @@ import unittest import mock from six.moves import reload_module # pylint: disable=import-error -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import,no-name-in-module + +from certbot.compat import os # pylint: disable=ungrouped-imports import certbot.tests.util as test_util # pylint: disable=ungrouped-imports diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index d499c026b..95334a9d3 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -1,6 +1,5 @@ # coding=utf-8 """Test certbot.display.ops.""" -import os import sys import unittest @@ -10,14 +9,12 @@ import zope.component from acme import messages +import certbot.tests.util as test_util from certbot import account from certbot import errors - -from certbot.display import util as display_util +from certbot.compat import os from certbot.display import ops - -import certbot.tests.util as test_util - +from certbot.display import util as display_util KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index 1626580fc..cc805a9e6 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -1,15 +1,17 @@ """Tests for certbot.error_handler.""" import contextlib -import os import signal import sys import unittest import mock + # pylint: disable=unused-import, no-name-in-module from acme.magic_typing import Callable, Dict, Union # pylint: enable=unused-import, no-name-in-module +from certbot.compat import os + def get_signals(signums): """Get the handlers for an iterable of signums.""" diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 90f639958..2a3742aa2 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -1,12 +1,12 @@ """Tests for certbot.hooks.""" -import os import stat import unittest import mock - from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + from certbot import errors +from certbot.compat import os from certbot.tests import util diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index d2e61e386..fb3a8fedf 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -1,7 +1,6 @@ """Tests for certbot.lock.""" import functools import multiprocessing -import os import unittest try: import fcntl # pylint: disable=import-error,unused-import @@ -13,6 +12,7 @@ else: import mock from certbot import errors +from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index f55affcfc..d203635a2 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -1,13 +1,13 @@ """Tests for certbot.log.""" import logging import logging.handlers -import os import sys import time import unittest import mock import six + from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module @@ -15,6 +15,7 @@ from certbot import constants from certbot import errors from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 7ff8b7452..bdc42a62f 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -6,7 +6,6 @@ from __future__ import print_function import datetime import itertools import json -import os import shutil import sys import tempfile @@ -33,6 +32,7 @@ from certbot import main from certbot import updater from certbot import util from certbot.compat import misc +from certbot.compat import os from certbot.plugins import disco from certbot.plugins import enhancements from certbot.plugins import manual diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 18e698444..f90708a69 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -1,7 +1,6 @@ """Test certbot.reverter.""" import csv import logging -import os import shutil import tempfile import unittest @@ -10,6 +9,7 @@ import mock import six from certbot import errors +from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index a6577deb3..13c09395d 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -1,7 +1,6 @@ """Tests for certbot.storage.""" # pylint disable=protected-access import datetime -import os import shutil import stat import unittest @@ -15,6 +14,7 @@ import certbot import certbot.tests.util as test_util from certbot import errors from certbot.compat import misc +from certbot.compat import os from certbot.storage import ALL_FOUR CERT = test_util.load_cert('cert_512.pem') diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 29d108d20..49ff6731b 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -4,7 +4,6 @@ """ import logging -import os import shutil import stat import sys @@ -27,6 +26,7 @@ from certbot import interfaces from certbot import lock from certbot import storage from certbot import util +from certbot.compat import os from certbot.display import util as display_util diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 853c78499..6e231b74d 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -1,7 +1,6 @@ """Tests for certbot.util.""" import argparse import errno -import os import unittest import mock @@ -11,6 +10,7 @@ from six.moves import reload_module # pylint: disable=import-error import certbot.tests.util as test_util from certbot import errors from certbot.compat import misc +from certbot.compat import os class RunScriptTest(unittest.TestCase): diff --git a/certbot/util.py b/certbot/util.py index ec8be3150..e15d02779 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -8,7 +8,6 @@ from collections import OrderedDict import distutils.version # pylint: disable=import-error,no-name-in-module import errno import logging -import os import platform import re import socket @@ -23,6 +22,7 @@ from certbot import constants from certbot import errors from certbot import lock from certbot.compat import misc +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/docs/contributing.rst b/docs/contributing.rst index 0319160e6..582f14599 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -312,6 +312,23 @@ Please: .. _PEP 8 - Style Guide for Python Code: https://www.python.org/dev/peps/pep-0008 +Use ``certbot.compat.os`` instead of ``os`` +=========================================== + + +Python's standard library ``os`` module lacks full support for several Windows +security features about file permissions (eg. DACLs). However several files +handled by Certbot (eg. private keys) need strongly restricted access +on both Linux and Windows. + +To help with this, the ``certbot.compat.os`` module wraps the standard +``os`` module, and forbids usage of methods that lack support for these Windows +security features. + +As a developer, when working on Certbot or its plugins, you must use ``certbot.compat.os`` +in every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of +``import os``). Otherwise the tests will fail when your PR is submitted. + Mypy type annotations ===================== diff --git a/linter_plugin.py b/linter_plugin.py index 85896a36b..e870fda3a 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -1,31 +1,51 @@ -"""Certbot ACME PyLint plugin. - +"""Certbot PyLint plugin. http://docs.pylint.org/plugins.html - """ -from astroid import MANAGER -from astroid import nodes - - -def register(unused_linter): - """Register this module as PyLint plugin.""" - pass - - -def _transform(cls): - # fix the "no-member" error on instances of - # letsencrypt.acme.util.ImmutableMap subclasses (instance - # attributes are initialized dynamically based on __slots__) - - # TODO: this is too broad and applies to any tested class... - - #if cls.slots() is not None: - # for slot in cls.slots(): - # cls.locals[slot.value] = [nodes.EmptyNode()] - - if cls.name == 'JSONObjectWithFields': - # _fields is magically introduced by JSONObjectWithFieldsMeta - cls.locals['_fields'] = [nodes.EmptyNode()] - - -MANAGER.register_transform(nodes.Class, _transform) +# The built-in ImportChecker of Pylint does a similar job to ForbidStandardOsModule to detect +# deprecated modules. You can check its behavior as a reference to what is coded here. +# See https://github.com/PyCQA/pylint/blob/b20a2984c94e2946669d727dbda78735882bf50a/pylint/checkers/imports.py#L287 +from pylint.checkers import BaseChecker +from pylint.interfaces import IAstroidChecker + + +# Modules in theses packages can import the os module. +WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', 'lock_test'] + + +class ForbidStandardOsModule(BaseChecker): + """ + This checker ensures that standard os module (and submodules) is not imported by certbot + modules. Otherwise a 'os-module-forbidden' error will be registered for the faulty lines. + """ + __implements__ = IAstroidChecker + + name = 'forbid-os-module' + msgs = { + 'E5001': ( + 'Forbidden use of os module, certbot.compat.os must be used instead', + 'os-module-forbidden', + 'Some methods from the standard os module cannot be used for security reasons on Windows: ' + 'the safe wrapper certbot.compat.os must be used instead in Certbot.' + ) + } + priority = -1 + + def visit_import(self, node): + os_used = any(name for name in node.names if name[0] == 'os' or name[0].startswith('os.')) + if os_used and not _check_disabled(node): + self.add_message('os-module-forbidden', node=node) + + def visit_importfrom(self, node): + if node.modname == 'os' or node.modname.startswith('os.') and not _check_disabled(node): + self.add_message('os-module-forbidden', node=node) + + +def register(linter): + """Pylint hook to auto-register this linter""" + linter.register_checker(ForbidStandardOsModule(linter)) + + +def _check_disabled(node): + module = node.root() + return any(package for package in WHITELIST_PACKAGES + if module.name.startswith(package + '.') or module.name == package) -- cgit v1.2.3 From de84688844196915134eda2d3bb09f00f3bfb5e7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 12 Apr 2019 14:08:45 -0700 Subject: Remove slash from path. (#6957) --- certbot-ci/certbot_integration_tests/certbot_tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 700e6d514..7320128de 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -76,7 +76,7 @@ def test_renew_files_permissions(context): assert_cert_count_for_lineage(context.config_dir, certname, 2) assert_world_permissions( - join(context.config_dir, 'archive', certname, '/privkey2.pem'), 0) + join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0) assert_equals_group_owner( join(context.config_dir, 'archive', certname, 'privkey1.pem'), join(context.config_dir, 'archive', certname, 'privkey2.pem')) -- cgit v1.2.3 From d7610c1ae73699cbac7d3433d8ff286d201ea018 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 12 Apr 2019 14:44:43 -0700 Subject: Update Fedora AMI (#6956) * Update Fedora AMI to Fedora 28. * Update initial version in test_leauto_upgrades. --- tests/letstest/scripts/test_leauto_upgrades.sh | 9 ++------- tests/letstest/targets.yaml | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index ddcca39cf..d565aa268 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -15,13 +15,8 @@ if ! command -v git ; then exit 1 fi fi -# 0.17.0 is the oldest version of letsencrypt-auto that has precompiled -# cryptography and the tagged commit is in master. 0.16.0 was the first version -# to use precompiled cryptography, but the release PR was squashed losing the -# commit. We want to use a precompiled version of cryptography for stability. -# Previous versions that have to compile against OpenSSL on installation -# started failing on newer distros with newer versions of OpenSSL. -INITIAL_VERSION="0.17.0" +# 0.18.0 is the oldest version of letsencrypt-auto that works on Fedora 26+. +INITIAL_VERSION="0.18.0" git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 0a11f5dcb..d784071c2 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -49,8 +49,8 @@ targets: type: centos virt: hvm user: ec2-user - - ami: ami-518bfb3b - name: fedora23 + - ami: ami-5c69df23 + name: fedora28 type: centos virt: hvm user: fedora -- cgit v1.2.3 From 3f0dc7c81c60aa204e50fad3e415843829539121 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 15 Apr 2019 23:59:45 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3a (#6946) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering on L185-222 in tests/certbot-boulder-integration.sh. * Add tests * Correct some assertions --- .../certbot_tests/assertions.py | 5 +- .../certbot_tests/test_main.py | 85 +++++++++++++++++++--- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py index cf1a4792d..b82c0b5f0 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -15,13 +15,14 @@ def assert_hook_execution(probe_path, probe_content): assert '{0}{1}'.format(probe_content, os.linesep) in lines -def assert_save_renew_hook(config_dir, lineage): +def assert_saved_renew_hook(config_dir, lineage): """ Assert that the renew hook configuration of a lineage has been saved. :param config_dir: location of the certbot configuration :param lineage: lineage domain name """ - assert os.path.isfile(os.path.join(config_dir, 'renewal/{0}.conf'.format(lineage))) + with open(os.path.join(config_dir, 'renewal', '{0}.conf'.format(lineage))) as file_h: + assert 'renew_hook' in file_h.read() def assert_cert_count_for_lineage(config_dir, lineage, count): diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 7320128de..4c7d77a6d 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -1,13 +1,14 @@ """Module executing integration tests against certbot core.""" from __future__ import print_function + import os -import shutil +import subprocess from os.path import join import pytest from certbot_integration_tests.certbot_tests import context as certbot_context from certbot_integration_tests.certbot_tests.assertions import ( - assert_hook_execution, assert_save_renew_hook, assert_cert_count_for_lineage, + assert_hook_execution, assert_saved_renew_hook, assert_cert_count_for_lineage, assert_world_permissions, assert_equals_group_owner, assert_equals_permissions, ) from certbot_integration_tests.utils import misc @@ -23,6 +24,72 @@ def context(request): integration_test_context.cleanup() +def test_basic_commands(context): + """Test simple commands on Certbot CLI.""" + # TMPDIR env variable is set to workspace for the certbot subprocess. + # So tempdir module will create any temporary files/dirs in workspace, + # and its content can be tested to check correct certbot cleanup. + initial_count_tmpfiles = len(os.listdir(context.workspace)) + + context.certbot(['--help']) + context.certbot(['--help', 'all']) + context.certbot(['--version']) + + with pytest.raises(subprocess.CalledProcessError): + context.certbot(['--csr']) + + new_count_tmpfiles = len(os.listdir(context.workspace)) + assert initial_count_tmpfiles == new_count_tmpfiles + + +def test_hook_dirs_creation(context): + """Test all hooks directory are created during Certbot startup.""" + context.certbot(['register']) + + for hook_dir in misc.list_renewal_hooks_dirs(context.config_dir): + assert os.path.isdir(hook_dir) + + +def test_registration_override(context): + """Test correct register/unregister, and registration override.""" + context.certbot(['register']) + context.certbot(['unregister']) + context.certbot(['register', '--email', 'ex1@domain.org,ex2@domain.org']) + + # TODO: When `certbot register --update-registration` is fully deprecated, + # delete the two following deprecated uses + context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org']) + context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org,ex2@domain.org']) + + context.certbot(['update_account', '--email', 'example@domain.org']) + context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org']) + + +def test_prepare_plugins(context): + """Test that plugins are correctly instantiated and displayed.""" + output = context.certbot(['plugins', '--init', '--prepare']) + + assert 'webroot' in output + + +def test_http_01(context): + """Test the HTTP-01 challenge using standalone plugin.""" + # We start a server listening on the port for the + # TLS-SNI challenge to prevent regressions in #3601. + with misc.create_http_server(context.tls_alpn_01_port): + certname = context.get_domain('le2') + context.certbot([ + '--domains', certname, '--preferred-challenges', 'http-01', 'run', + '--cert-name', certname, + '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), + '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + ]) + + assert_hook_execution(context.hook_probe, 'deploy') + assert_saved_renew_hook(context.config_dir, certname) + + def test_manual_http_auth(context): """Test the HTTP-01 challenge using manual plugin.""" with misc.create_http_server(context.http_01_port) as webroot,\ @@ -36,11 +103,12 @@ def test_manual_http_auth(context): '--manual-cleanup-hook', scripts[1], '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) ]) - assert_hook_execution(context.hook_probe, 'deploy') - assert_save_renew_hook(context.config_dir, certname) + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'renew') + assert_saved_renew_hook(context.config_dir, certname) def test_manual_dns_auth(context): @@ -58,11 +126,11 @@ def test_manual_dns_auth(context): with pytest.raises(AssertionError): assert_hook_execution(context.hook_probe, 'renew') - assert_save_renew_hook(context.config_dir, certname) + assert_saved_renew_hook(context.config_dir, certname) def test_renew_files_permissions(context): - """Test certificate file permissions upon renewal""" + """Test proper certificate file permissions upon renewal""" certname = context.get_domain('renew') context.certbot(['-d', certname]) @@ -70,8 +138,6 @@ def test_renew_files_permissions(context): assert_world_permissions( join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0) - # Force renew. Assert certificate renewal and proper permissions. - # We assert certificate renewal and proper permissions. context.certbot(['renew']) assert_cert_count_for_lineage(context.config_dir, certname, 2) @@ -92,7 +158,6 @@ def test_renew_with_hook_scripts(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - # Force renew. Assert certificate renewal and hook scripts execution. misc.generate_test_file_hooks(context.config_dir, context.hook_probe) context.certbot(['renew']) -- cgit v1.2.3 From 471f8aecc014293386ee906f65aa93a55f9605b3 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 16 Apr 2019 00:04:22 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3b (#6947) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering on L268-282 in tests/certbot-boulder-integration.sh. Previous lines are covered with existing tests, or by #6946. * Add tests * Fix CSR generation * Add dependency --- .../certbot_tests/test_main.py | 32 ++++++++++++++ certbot-ci/certbot_integration_tests/utils/misc.py | 51 ++++++++++++++++++++++ certbot-ci/setup.py | 1 + 3 files changed, 84 insertions(+) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 4c7d77a6d..31fb1a68e 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -129,6 +129,38 @@ def test_manual_dns_auth(context): assert_saved_renew_hook(context.config_dir, certname) +def test_certonly(context): + """Test the certonly verb on certbot.""" + context.certbot(['certonly', '--cert-name', 'newname', '-d', context.get_domain('newname')]) + + +def test_auth_and_install_with_csr(context): + """Test certificate issuance and install using an existing CSR.""" + certname = context.get_domain('le3') + key_path = join(context.workspace, 'key.pem') + csr_path = join(context.workspace, 'csr.der') + + misc.generate_csr([certname], key_path, csr_path) + + cert_path = join(context.workspace, 'csr', 'cert.pem') + chain_path = join(context.workspace, 'csr', 'chain.pem') + + context.certbot([ + 'auth', '--csr', csr_path, + '--cert-path', cert_path, + '--chain-path', chain_path + ]) + + print(misc.read_certificate(cert_path)) + print(misc.read_certificate(chain_path)) + + context.certbot([ + '--domains', certname, 'install', + '--cert-path', cert_path, + '--key-path', key_path + ]) + + def test_renew_files_permissions(context): """Test proper certificate file permissions upon renewal""" certname = context.get_domain('renew') diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 17c0e74f6..76383ded1 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -16,8 +16,17 @@ from distutils.version import LooseVersion import requests from OpenSSL import crypto +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption from six.moves import socketserver, SimpleHTTPServer +from acme import crypto_util + + +RSA_KEY_TYPE = 'rsa' +ECDSA_KEY_TYPE = 'ecdsa' + def check_until_timeout(url): """ @@ -209,3 +218,45 @@ def get_certbot_version(): # Typical response is: output = 'certbot 0.31.0.dev0' version_str = output.split(' ')[1].strip() return LooseVersion(version_str) + + +def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE): + """ + Generate a private key, and a CSR for the given domains using this key. + :param domains: the domain names to include in the CSR + :type domains: `list` of `str` + :param str key_path: path to the private key that will be generated + :param str csr_path: path to the CSR that will be generated + :param str key_type: type of the key (misc.RSA_KEY_TYPE or misc.ECDSA_KEY_TYPE) + """ + if key_type == RSA_KEY_TYPE: + key = crypto.PKey() + key.generate_key(crypto.TYPE_RSA, 2048) + elif key_type == ECDSA_KEY_TYPE: + key = ec.generate_private_key(ec.SECP384R1(), default_backend()) + key = key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=NoEncryption()) + key = crypto.load_privatekey(crypto.FILETYPE_PEM, key) + else: + raise ValueError('Invalid key type: {0}'.format(key_type)) + + key_bytes = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) + with open(key_path, 'wb') as file: + file.write(key_bytes) + + csr_bytes = crypto_util.make_csr(key_bytes, domains) + with open(csr_path, 'wb') as file: + file.write(csr_bytes) + + +def read_certificate(cert_path): + """ + Load the certificate from the provided path, and return a human readable version of it (TEXT mode). + :param str cert_path: the path to the certificate + :returns: the TEXT version of the certificate, as it would be displayed by openssl binary + """ + with open(cert_path, 'rb') as file: + data = file.read() + + cert = crypto.load_certificate(crypto.FILETYPE_PEM, data) + return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert).decode('utf-8') diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 0dbcf1aa1..852d2481c 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -5,6 +5,7 @@ from setuptools import find_packages version = '0.32.0.dev0' install_requires = [ + 'acme', 'coverage', 'cryptography', 'pyopenssl', -- cgit v1.2.3 From b73c551f14a3eea9e3f396e5996fb23388d32312 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 16 Apr 2019 00:09:57 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3c (#6948) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering about renew, on L283-396 in tests/certbot-boulder-integration.sh (by including existing test_renew_files_permissions and test_renew_with_hook_scripts). Previous lines are covered with existing tests, or by #6946 and #6947. * Add tests * Correct assertion about world permission --- .../certbot_tests/test_main.py | 154 +++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 31fb1a68e..be10e5efe 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -160,6 +160,10 @@ def test_auth_and_install_with_csr(context): '--key-path', key_path ]) + context.certbot(['renew', '--cert-name', certname, '--authenticator', 'manual']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + def test_renew_files_permissions(context): """Test proper certificate file permissions upon renewal""" @@ -195,3 +199,153 @@ def test_renew_with_hook_scripts(context): assert_cert_count_for_lineage(context.config_dir, certname, 2) assert_hook_execution(context.hook_probe, 'deploy') + + +def test_renew_files_propagate_permissions(context): + """Test proper certificate renewal with custom permissions propagated on private key.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + os.chmod(join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0o444) + context.certbot(['renew']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + assert_world_permissions( + join(context.config_dir, 'archive', certname, 'privkey2.pem'), 4) + assert_equals_permissions( + join(context.config_dir, 'archive', certname, 'privkey1.pem'), + join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + + +def test_graceful_renew_it_is_not_time(context): + """Test graceful renew is not done when it is not due time.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + context.certbot_no_force_renew([ + 'renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'deploy') + + +def test_graceful_renew_it_is_time(context): + """Test graceful renew is done when it is due time.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'r') as file: + lines = file.readlines() + lines.insert(4, 'renew_before_expiry = 100 years{0}'.format(os.linesep)) + with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'w') as file: + file.writelines(lines) + + context.certbot_no_force_renew([ + 'renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)]) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + assert_hook_execution(context.hook_probe, 'deploy') + + +def test_renew_with_changed_private_key_complexity(context): + """Test proper renew with updated private key complexity.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname, '--rsa-key-size', '4096']) + + key1 = join(context.config_dir, 'archive', certname, 'privkey1.pem') + assert os.stat(key1).st_size > 3000 # 4096 bits keys takes more than 3000 bytes + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + context.certbot(['renew']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + key2 = join(context.config_dir, 'archive', certname, 'privkey2.pem') + assert os.stat(key2).st_size > 3000 + + context.certbot(['renew', '--rsa-key-size', '2048']) + + assert_cert_count_for_lineage(context.config_dir, certname, 3) + key3 = join(context.config_dir, 'archive', certname, 'privkey3.pem') + assert os.stat(key3).st_size < 1800 # 2048 bits keys takes less than 1800 bytes + + +def test_renew_ignoring_directory_hooks(context): + """Test hooks are ignored during renewal with relevant CLI flag.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + misc.generate_test_file_hooks(context.config_dir, context.hook_probe) + context.certbot(['renew', '--no-directory-hooks']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'deploy') + + +def test_renew_empty_hook_scripts(context): + """Test proper renew with empty hook scripts.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + misc.generate_test_file_hooks(context.config_dir, context.hook_probe) + for hook_dir in misc.list_renewal_hooks_dirs(context.config_dir): + shutil.rmtree(hook_dir) + os.makedirs(join(hook_dir, 'dir')) + open(join(hook_dir, 'file'), 'w').close() + context.certbot(['renew']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + + +def test_renew_hook_override(context): + """Test correct hook override on renew.""" + certname = context.get_domain('override') + context.certbot([ + 'certonly', '-d', certname, + '--preferred-challenges', 'http-01', + '--pre-hook', 'echo pre >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo post >> "{0}"'.format(context.hook_probe), + '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + ]) + + assert_hook_execution(context.hook_probe, 'pre') + assert_hook_execution(context.hook_probe, 'post') + assert_hook_execution(context.hook_probe, 'deploy') + + # Now we override all previous hooks during next renew. + open(context.hook_probe, 'w').close() + context.certbot([ + 'renew', '--cert-name', certname, + '--pre-hook', 'echo pre-override >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo post-override >> "{0}"'.format(context.hook_probe), + '--deploy-hook', 'echo deploy-override >> "{0}"'.format(context.hook_probe) + ]) + + assert_hook_execution(context.hook_probe, 'pre-override') + assert_hook_execution(context.hook_probe, 'post-override') + assert_hook_execution(context.hook_probe, 'deploy-override') + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'pre') + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'post') + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'deploy') + + # Expect that this renew will reuse new hooks registered in the previous renew. + open(context.hook_probe, 'w').close() + context.certbot(['renew', '--cert-name', certname]) + + assert_hook_execution(context.hook_probe, 'pre-override') + assert_hook_execution(context.hook_probe, 'post-override') + assert_hook_execution(context.hook_probe, 'deploy-override') -- cgit v1.2.3 From 6bdc6435eb9e2c3e4f770b1d65b22fb4843dd379 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 16 Apr 2019 01:18:24 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3d (#6949) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering checks on L397-429 in tests/certbot-boulder-integration.sh. Previous lines are covered with existing tests, or by #6946, #6947 and #6948. * Add tests * Change a variable name * Fix merge errors from git --- .../certbot_tests/test_main.py | 53 ++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index be10e5efe..1671cadf9 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -128,6 +128,10 @@ def test_manual_dns_auth(context): assert_hook_execution(context.hook_probe, 'renew') assert_saved_renew_hook(context.config_dir, certname) + context.certbot(['renew', '--cert-name', certname, '--authenticator', 'manual']) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + def test_certonly(context): """Test the certonly verb on certbot.""" @@ -160,10 +164,6 @@ def test_auth_and_install_with_csr(context): '--key-path', key_path ]) - context.certbot(['renew', '--cert-name', certname, '--authenticator', 'manual']) - - assert_cert_count_for_lineage(context.config_dir, certname, 2) - def test_renew_files_permissions(context): """Test proper certificate file permissions upon renewal""" @@ -349,3 +349,48 @@ def test_renew_hook_override(context): assert_hook_execution(context.hook_probe, 'pre-override') assert_hook_execution(context.hook_probe, 'post-override') assert_hook_execution(context.hook_probe, 'deploy-override') + + +def test_invalid_domain_with_dns_challenge(context): + """Test certificate issuance failure with DNS-01 challenge.""" + # Manual dns auth hooks from misc are designed to fail if the domain contains 'fail-*'. + domains = ','.join([context.get_domain('dns1'), context.get_domain('fail-dns1')]) + context.certbot([ + '-a', 'manual', '-d', domains, + '--allow-subset-of-names', + '--preferred-challenges', 'dns', + '--manual-auth-hook', context.manual_dns_auth_hook, + '--manual-cleanup-hook', context.manual_dns_cleanup_hook + ]) + + output = context.certbot(['certificates']) + + assert context.get_domain('fail-dns1') not in output + + +def test_reuse_key(context): + """Test various scenarios where a key is reused.""" + certname = context.get_domain('reusekey') + context.certbot(['--domains', certname, '--reuse-key']) + context.certbot(['renew', '--cert-name', certname]) + + with open(join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname), 'r') as file: + privkey1 = file.read() + with open(join(context.config_dir, 'archive/{0}/privkey2.pem').format(certname), 'r') as file: + privkey2 = file.read() + assert privkey1 == privkey2 + + context.certbot(['--cert-name', certname, '--domains', certname, '--force-renewal']) + + with open(join(context.config_dir, 'archive/{0}/privkey3.pem').format(certname), 'r') as file: + privkey3 = file.read() + assert privkey2 != privkey3 + + with open(join(context.config_dir, 'archive/{0}/cert1.pem').format(certname), 'r') as file: + cert1 = file.read() + with open(join(context.config_dir, 'archive/{0}/cert2.pem').format(certname), 'r') as file: + cert2 = file.read() + with open(join(context.config_dir, 'archive/{0}/cert3.pem').format(certname), 'r') as file: + cert3 = file.read() + + assert len({cert1, cert2, cert3}) == 3 \ No newline at end of file -- cgit v1.2.3 From 298b1db36b29d9b581f1271036ee3052bd245e09 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 16 Apr 2019 01:42:06 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3f (#6952) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering checks on L448-530 in tests/certbot-boulder-integration.sh. Previous lines are covered with existing tests, or by #6946, #6947, #6948, #6949, #6951. * Add tests * Normalize paths * Fix merge error in git --- .../certbot_tests/test_main.py | 124 ++++++++++++++++++++- 1 file changed, 121 insertions(+), 3 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 1671cadf9..9b99b1747 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -2,8 +2,10 @@ from __future__ import print_function import os +import re +import shutil import subprocess -from os.path import join +from os.path import join, exists import pytest from certbot_integration_tests.certbot_tests import context as certbot_context @@ -350,7 +352,7 @@ def test_renew_hook_override(context): assert_hook_execution(context.hook_probe, 'post-override') assert_hook_execution(context.hook_probe, 'deploy-override') - + def test_invalid_domain_with_dns_challenge(context): """Test certificate issuance failure with DNS-01 challenge.""" # Manual dns auth hooks from misc are designed to fail if the domain contains 'fail-*'. @@ -393,4 +395,120 @@ def test_reuse_key(context): with open(join(context.config_dir, 'archive/{0}/cert3.pem').format(certname), 'r') as file: cert3 = file.read() - assert len({cert1, cert2, cert3}) == 3 \ No newline at end of file + assert len({cert1, cert2, cert3}) == 3 + + +def test_revoke_simple(context): + """Test various scenarios that revokes a certificate.""" + # Default action after revoke is to delete the certificate. + certname = context.get_domain() + cert_path = join(context.config_dir, 'live', certname, 'cert.pem') + context.certbot(['-d', certname]) + context.certbot(['revoke', '--cert-path', cert_path, '--delete-after-revoke']) + + assert not exists(cert_path) + + # Check default deletion is overridden. + certname = context.get_domain('le1') + cert_path = join(context.config_dir, 'live', certname, 'cert.pem') + context.certbot(['-d', certname]) + context.certbot(['revoke', '--cert-path', cert_path, '--no-delete-after-revoke']) + + assert exists(cert_path) + + context.certbot(['delete', '--cert-name', certname]) + + assert not exists(join(context.config_dir, 'archive', certname)) + assert not exists(join(context.config_dir, 'live', certname)) + assert not exists(join(context.config_dir, 'renewal', '{0}.conf'.format(certname))) + + certname = context.get_domain('le2') + key_path = join(context.config_dir, 'live', certname, 'privkey.pem') + cert_path = join(context.config_dir, 'live', certname, 'cert.pem') + context.certbot(['-d', certname]) + context.certbot(['revoke', '--cert-path', cert_path, '--key-path', key_path]) + + +def test_revoke_and_unregister(context): + """Test revoke with a reason then unregister.""" + cert1 = context.get_domain('le1') + cert2 = context.get_domain('le2') + cert3 = context.get_domain('le3') + + cert_path1 = join(context.config_dir, 'live', cert1, 'cert.pem') + key_path2 = join(context.config_dir, 'live', cert2, 'privkey.pem') + cert_path2 = join(context.config_dir, 'live', cert2, 'cert.pem') + + context.certbot(['-d', cert1]) + context.certbot(['-d', cert2]) + context.certbot(['-d', cert3]) + + context.certbot(['revoke', '--cert-path', cert_path1, + '--reason', 'cessationOfOperation']) + context.certbot(['revoke', '--cert-path', cert_path2, '--key-path', key_path2, + '--reason', 'keyCompromise']) + + context.certbot(['unregister']) + + output = context.certbot(['certificates']) + + assert cert1 not in output + assert cert2 not in output + assert cert3 in output + + +def test_revoke_mutual_exclusive_flags(context): + """Test --cert-path and --cert-name cannot be used during revoke.""" + cert = context.get_domain('le1') + context.certbot(['-d', cert]) + with pytest.raises(subprocess.CalledProcessError) as error: + context.certbot([ + 'revoke', '--cert-name', cert, + '--cert-path', join(context.config_dir, 'live', cert, 'fullchain.pem') + ]) + assert 'Exactly one of --cert-path or --cert-name must be specified' in error.out + + +def test_revoke_multiple_lineages(context): + """Test revoke does not delete certs if multiple lineages share the same dir.""" + cert1 = context.get_domain('le1') + context.certbot(['-d', cert1]) + + assert os.path.isfile(join(context.config_dir, 'renewal', '{0}.conf'.format(cert1))) + + cert2 = context.get_domain('le2') + context.certbot(['-d', cert2]) + + # Copy over renewal configuration of cert1 into renewal configuration of cert2. + with open(join(context.config_dir, 'renewal', '{0}.conf'.format(cert2)), 'r') as file: + data = file.read() + + data = re.sub('archive_dir = .*\n', + 'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1)), + data) + + with open(join(context.config_dir, 'renewal', '{0}.conf'.format(cert2)), 'w') as file: + file.write(data) + + output = context.certbot([ + 'revoke', '--cert-path', join(context.config_dir, 'live', cert1, 'cert.pem') + ]) + + assert 'Not deleting revoked certs due to overlapping archive dirs' in output + + +def test_wildcard_certificates(context): + """Test wildcard certificate issuance.""" + if context.acme_server == 'boulder-v1': + pytest.skip('Wildcard certificates are not supported on ACME v1') + + certname = context.get_domain('wild') + + context.certbot([ + '-a', 'manual', '-d', '*.{0},{0}'.format(certname), + '--preferred-challenge', 'dns', + '--manual-auth-hook', context.manual_dns_auth_hook, + '--manual-cleanup-hook', context.manual_dns_cleanup_hook + ]) + + assert exists(join(context.config_dir, 'live', certname, 'fullchain.pem')) -- cgit v1.2.3 From 410e74c4a1252ae377a0d4c5085849e88d4dfb95 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 16 Apr 2019 02:39:38 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3g (#6953) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering checks on L531 to the end on tests/certbot-boulder-integration.sh. Previous lines are covered with existing tests, or by #6946, #6947, #6948, #6949, #6951, #6952. * Add tests * Add load resource * Separate OCSP in two tests * Copy new asset * Load the asset * Add coverage limit --- certbot-ci/MANIFEST.in | 1 + .../48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json | 1 + .../private_key.json | 1 + .../48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json | 1 + .../archive/a.encryption-example.com/cert1.pem | 29 +++++++++++ .../archive/a.encryption-example.com/chain1.pem | 27 +++++++++++ .../a.encryption-example.com/fullchain1.pem | 56 ++++++++++++++++++++++ .../archive/a.encryption-example.com/privkey1.pem | 28 +++++++++++ .../archive/b.encryption-example.com/cert1.pem | 29 +++++++++++ .../archive/b.encryption-example.com/chain1.pem | 27 +++++++++++ .../b.encryption-example.com/fullchain1.pem | 56 ++++++++++++++++++++++ .../archive/b.encryption-example.com/privkey1.pem | 28 +++++++++++ .../assets/sample-config/csr/0000_csr-certbot.pem | 16 +++++++ .../assets/sample-config/csr/0001_csr-certbot.pem | 16 +++++++ .../assets/sample-config/csr/0002_csr-certbot.pem | 17 +++++++ .../assets/sample-config/csr/0003_csr-certbot.pem | 17 +++++++ .../assets/sample-config/keys/0000_key-certbot.pem | 28 +++++++++++ .../assets/sample-config/keys/0001_key-certbot.pem | 28 +++++++++++ .../assets/sample-config/keys/0002_key-certbot.pem | 28 +++++++++++ .../assets/sample-config/keys/0003_key-certbot.pem | 28 +++++++++++ .../live/a.encryption-example.com/README | 10 ++++ .../live/a.encryption-example.com/cert.pem | 1 + .../live/a.encryption-example.com/chain.pem | 1 + .../live/a.encryption-example.com/fullchain.pem | 1 + .../live/a.encryption-example.com/privkey.pem | 1 + .../live/b.encryption-example.com/README | 10 ++++ .../live/b.encryption-example.com/cert.pem | 1 + .../live/b.encryption-example.com/chain.pem | 1 + .../live/b.encryption-example.com/fullchain.pem | 1 + .../live/b.encryption-example.com/privkey.pem | 1 + .../assets/sample-config/options-ssl-apache.conf | 22 +++++++++ .../renewal/a.encryption-example.com.conf | 15 ++++++ .../renewal/b.encryption-example.com.conf | 15 ++++++ .../certbot_tests/test_main.py | 32 +++++++++++++ certbot-ci/certbot_integration_tests/utils/misc.py | 14 ++++++ tox.ini | 2 +- 36 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 certbot-ci/MANIFEST.in create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/cert1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/chain1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/fullchain1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/privkey1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/cert1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/chain1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/fullchain1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/privkey1.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/csr/0000_csr-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/csr/0001_csr-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/csr/0002_csr-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/csr/0003_csr-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/keys/0000_key-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/keys/0001_key-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/keys/0002_key-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/keys/0003_key-certbot.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/README create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/cert.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/chain.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/fullchain.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/privkey.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/README create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/cert.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/chain.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/fullchain.pem create mode 120000 certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/privkey.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/options-ssl-apache.conf create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/renewal/a.encryption-example.com.conf create mode 100644 certbot-ci/certbot_integration_tests/assets/sample-config/renewal/b.encryption-example.com.conf diff --git a/certbot-ci/MANIFEST.in b/certbot-ci/MANIFEST.in new file mode 100644 index 000000000..7a18def43 --- /dev/null +++ b/certbot-ci/MANIFEST.in @@ -0,0 +1 @@ +recursive-include certbot_integration_tests/assets * diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json new file mode 100644 index 000000000..6fe0b47f3 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json @@ -0,0 +1 @@ +{"creation_host": "ec2-52-91-193-99.compute-1.amazonaws.com", "creation_dt": "2016-12-23T02:08:32Z"} \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json new file mode 100644 index 000000000..0affb573d --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json @@ -0,0 +1 @@ +{"e": "AQAB", "d": "W410Wny96RO4qJ207KGQ3RSn0KAwqb93JBMHWU1yS9H3fN_2eCpFYdMLNFI9t1__nW1okeUioEfvMN_YW-G9krw97kVdZ63MfbeJCf35Onc8VZhAnk_3V8MtS26Of8ml0tTYhlQ65nuzhvHbY7aP-Uk260oDN-AbCCVhu5G4CQiMY6sdtCc8YkB6gK7SK874oWU7ogvAIPtNtEI-AXDUBYNAfoh34s1r2fE6mJSX4UYtzWB2hTUisvZdVL5JUInvxpCQFttk1cwWLFwwb6d2ERCbseeudvGJ6fkYiJ-EYxfHKOQK2kxPeOlLFMwGYQ0khDxTNajxQ1Asl43r7wgAeQ", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw", "q": "0xdfHMMKYWHPE1UoQ10niDI7rnCM9vmPo4JpCOCYZf51KPNJgNaPCw62Q0Y-ZQfCBifypQyf291d0_2C_Rif0WMg07Y-Ypv8SpPK77vLV12GoAoAX2Xy3AJAz1gDBcyUzDtRlrzgCZja9YqIDVzMatkdPJXaBrBu5B-sXv4wGa0", "p": "7pl5xe_400Sn6PdN_F6KLWHFROVd7379WPWGHYmnvOvXx7DmrMjDsTOmhNRlrv7jPemVqMzp1FGsubGBizEMFGyCET30bUgH6ZU7Cmgv-2JKKN1FZnm1QTepZ7kjAT_qRCI6nvN6J0SIX197QOSz3hMmP7UYQXQ32QcVKdCksps", "kty": "RSA", "qi": "zG60VpLZjgR0o7dTeEP-HjbtxHUedyZLGe4FIPyWrPRl28anebkMUGzibpB8z5ohRsqHU2i4tmDq2NMvshISqkpk8t5PLiIcQgU46HQ24SCv7lunkVPKYU1n2uXVVfttrBP4c3UkjYzda1bcIVp6cJHanm_JuWI5nxy9ebVQJiw", "dp": "kRIBx0aj7Jh22x_aa9JzgypKDhzDY4W7tmX5-GWk9ioTVZgKeQ3MZiZ4XZTiimbxdchbNXn5xh0uvuzdTesxZA2he6hGwFcmcHBKqIY2fksBuhznQGpJuXCFcMpRLUZWQrzpFZIGOG_j1tEwGIG1lxXfkKakK8_k0PEMfhMcwHc", "dq": "AsoSRa0GHBdQxy6e45T9ir0vMLToB_NwRHbasHVXTjG4lpvwYrVzGnBNVEI_XNJna_FnMWsjSaJ5NO3qpzGGGxw2ONX1qRPql4mwas6Od08TElZPfvM37FRTSuoc0BzN8ozuHRHN3BKbAheciKCrStYnnr9ULDZ0oKsSegbd19k"} \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json new file mode 100644 index 000000000..fdd2df7da --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json @@ -0,0 +1 @@ +{"body": {"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", "key": {"e": "AQAB", "kty": "RSA", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw"}}, "uri": "https://acme-staging.api.letsencrypt.org/acme/reg/566631", "new_authzr_uri": "https://acme-staging.api.letsencrypt.org/acme/new-authz", "terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"} \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/cert1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/cert1.pem new file mode 100644 index 000000000..80739dd3f --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/cert1.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco +hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR +WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM +FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca +nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny +Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q +lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR +omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J +/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH +FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY +Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC +4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/chain1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/chain1.pem new file mode 100644 index 000000000..29a54e2a1 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/chain1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/fullchain1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/fullchain1.pem new file mode 100644 index 000000000..ba245d213 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/fullchain1.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco +hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR +WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM +FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca +nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny +Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q +lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR +omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J +/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH +FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY +Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC +4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/privkey1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/privkey1.pem new file mode 100644 index 000000000..b3059cb47 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/a.encryption-example.com/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj +8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 +FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH +g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 +/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN +fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg +PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY +JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo +fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc +xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj +bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D +WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 +mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu +wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 +enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO +iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x +nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL +W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 +q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R +zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz +h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 +Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS +xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy +YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY +o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl +wkqKc3PndJHrR40mswUOHbs= +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/cert1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/cert1.pem new file mode 100644 index 000000000..0c1c6b5ef --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/cert1.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR +FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f +s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim ++144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi +Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl +eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 +XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u +SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw +U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT +0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ +omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV +ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/chain1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/chain1.pem new file mode 100644 index 000000000..29a54e2a1 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/chain1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/fullchain1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/fullchain1.pem new file mode 100644 index 000000000..705cca6c3 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/fullchain1.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw +MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l +eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR +FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f +s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim ++144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi +Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl +eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 +XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU +RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L +IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z +dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j +ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu +Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG +CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy +eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv +bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp +biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh +dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u +SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw +U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT +0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ +omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV +ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/privkey1.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/privkey1.pem new file mode 100644 index 000000000..c43af4f50 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/archive/b.encryption-example.com/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh +DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 +Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt +YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv +Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 +npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 +HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX +Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l +/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r +m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK +tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj +xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n +9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf +Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN +cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT ++kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P +JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F +NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ +IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv +JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc +Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY +EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT +xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe +vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj +t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar +Bwhs4S84KEJFT0LAC3YeRQ== +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0000_csr-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0000_csr-certbot.pem new file mode 100644 index 000000000..16d73ffde --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0000_csr-certbot.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIChjCCAW4CAQIwFzEVMBMGA1UEAwwMaXMuaXNub3Qub3JnMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7nsHOCTvvQlRYXpI5xE7AggqTVmM8lGi18Y2 +gVlr3WYAS7higHRJjWroAmZ2Bx9IRfHOxwhVWm/hlc/u4w0IYlRnArg6suXrgtn+ +6Ea0WDUCiKEiKvQqD0kaI936hpydU/dY70UZnpKSyi0kiCrLzCkIaXS8HJdLOIXB +Q4FMVqjppYjUejMgrabthq1QTqU0S4MxwS1oj67VqaAkedGWxFgFQ2kIFV0/WL13 +Xs0SCTYyN96KK1Q2CF63HoN79zc+TVslg32DDU5UF7sVVvlkoHcl0OgR9l4jfou5 +HwmatMjXPI+0bWVxmw6iC6tbK7Dx+ytYIodhEOL52Youzy/lLwIDAQABoCowKAYJ +KoZIhvcNAQkOMRswGTAXBgNVHREEEDAOggxpcy5pc25vdC5vcmcwDQYJKoZIhvcN +AQELBQADggEBAAJsLiylvGq64wxVt8EBeXRB4ycBzC5J/pyOWMP9oexW1o3XPhCC ++0tIQVGk7wJMe3+WiPMVsn4pGOUGDaPvfC7ijlvipzaYyLEfnr+J7pukhYbzNHmu +XL5lbTJ0hTCfqUjmi1yE4M/v2eX5yNaEHsZExZ1NbtwutE/Tx5iSqt7kxbIoFqmF +7Tne2JHjt945+/l9yvqaIcEFOmblS0OxY9EjxgJdhKCKbhD/ZoYaVVisc52h/2/M +jtzvzZr1rZCvFnuQxGDco5vYe3u7uJ9tQHLCMpoIorT3kX3yTdgnWxst6XBVUY/P +Q6O18obG4ALoP/ESzvTauQIwFVGfal/jqyI= +-----END CERTIFICATE REQUEST----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0001_csr-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0001_csr-certbot.pem new file mode 100644 index 000000000..452bc45cd --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0001_csr-certbot.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICgDCCAWgCAQIwFDESMBAGA1UEAwwJaXNub3Qub3JnMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAsEAy7rdPsYFFt9VsK9NZy+W9nbsYGmvIaMSyJkEg +Xe2P0MmnWG/hn6F1bLPm85uS5oQsOWDpwVz31tKhoWhUDbRzPWP5Ur2NnHY92Whz +5tP4ir4vEEDuB9etQ8+wZ7+3z9q1VhPcgDdYyouQVB0QejJ1yUBiVPr289bW//ln +kj9DFxn4oufoJ4ELSZSZgWFM92EGKMMy1zD2bJH87mI0Gs0pIOEo+QMJ8TvVEbau ++aFaTANslqRAF5LaWcrPgvHor7cK5w/4bVBZCmY2QYKqlYwZiRPpwg3Ii6B9Q8kz +rDkGSDjwsazca4api57cza13XkRl7KvyZbwTwlFBud+ydwIDAQABoCcwJQYJKoZI +hvcNAQkOMRgwFjAUBgNVHREEDTALgglpc25vdC5vcmcwDQYJKoZIhvcNAQELBQAD +ggEBAB3vniZw2ML6E9jrMY8DtQjPDDNr1BqOGzyOaJipqpGZSRvhTA44DAAjdFpS +5BLrnXniPIZGG4/6WorLTEDBnlFcLinUg7GDT2DpauQa+4PLxFi13hE1TuSVOp9A +08YXhzALvZxMIjQ/tVhAp0+PkGEWU2wI0SmDvUUTJqMwSJYgXkf/vBS34/koKywV +gPDod5AbLuhYgKiQYwDZ0dd69leT0REmizuaHtA6tW3mBgewSKotwqY3fHmhHV8o +YLSVhImz4jJjK3LjmcdXuBxqE0z+p6n/+lSGG8RR/E8pix4OAkVAP6nyt/loW1BX +ZzWOuSHozGN5UJSL248vLFWrsV8= +-----END CERTIFICATE REQUEST----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0002_csr-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0002_csr-certbot.pem new file mode 100644 index 000000000..2ee44b3fd --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0002_csr-certbot.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYS5lbmNyeXB0aW9uLWV4YW1wbGUuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrPRxyiGwKrJY/Aavv1r +yP4k7cepumEtPOGg0dgX59byCXIzIGY4zJmNzy/wTdTcpgRe65FbNH5u9RcFws6F +KiAiJ9kM2IwoQXIM1wOUbTzN70ZOPaFNDKN63Zm49Y264ni7lQwW+htpx4OohO6o +SpSuMIGLkKYRezCtXeXYFVKI5w9/6X5lHzXEbupyMZ8sbphBZxqexhiivP+RJG3B +iMBPg1p5Rg1dV4fWW5ZNaFt53ymYp8FV+/44SbBTICFITV49+fIKPfvCjX6epDPf +sgk0SkjRE1vGsTtkKADjRCsVWpz1d69zKkWL0N+1VbMM0G133tCWYe4ioD0pvcL9 +7wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhhLmVuY3J5cHRp +b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJyKJHdUwR9BOKYJarUy +P8mqu6UBUt8faSu6o3EUeDHbnUgxGAVwB5TJV0+JwIjPFQFRofHE8CFhUvi0W0YJ +BsGVqblnJzz80NkUX9uwjBAGKaDxXqXDOctkQSAOJxM/rvD2uJLmlokibDDm7mnS +DX8SUVAPgORDGlVTGATjvmA3YeH05gHRFgRDWFP5DOZs99fx4957HrXhsIxew98s +Felupgswnouyq3crrgcjY0qo3Pc5gjUcuwaT2cjtvzi93f/ImDt6f1sdSSJB00wk +34lbs/Z+0G8bH1dqYIZzkwNgq7rolhDYh3WRgTlfkgkV7FlkQGm8qn5uoQvaXaaS +ShM= +-----END CERTIFICATE REQUEST----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0003_csr-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0003_csr-certbot.pem new file mode 100644 index 000000000..2a50dc33d --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/csr/0003_csr-certbot.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYi5lbmNyeXB0aW9uLWV4YW1wbGUuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYDq1ZEUB9jI4Qz0e0lc +ri0J3W0YXZBmuUkcQvc8nMUeie/tlo+jpWStqvW4uKczgBW7H9+z29yxt15u7ngN +emPUiWXOMqze2NK0crjqW4yqPyB/7fItkghWc3hjGQi13VYG2Kb7XjiS7WBQuwae +cea4J59fPqqqQvMVmwSWo7s1j5MvgXib8vSUONixT75eBsdP2CJPDSMCLxLIfwhr +ghiLjKecy4e5LXV77I62rXqQtbVF2fGtimV673PQNT7peWvW1uV5KTLNtZ6UOB2y +Jx87FnPmluYAtcStvjvZOJ1wccrpuwxWFH8/XNAr1h1z60Lfd3RcJFGA+h243xdF +9wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhiLmVuY3J5cHRp +b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACDw8/zjFaIdp4aqyrzT +fzaqAnoXZt3+0JDPLANy3DLCJmK2TQMyItg/Oid5NEQ45UluXv811IMCcONyVmrD +19W3XErhTJOJMgpjg4GLBRRFhLm+uTIcbv/xEeUgOYbslsqwi2gHECe1Vsj/Ahbo +QXXqcDg1cXe6VTQhX+Nw5q30t/oCmkJWcUVHBON2nbOujRz1+z6AjVl1dM+CYDRq +bsKn7m3biYS7lx7/ApIuhJQsghcmccCtWrH5GsOUsJUgiANv5u+QZgGaajkCRKYV +fD/u8qTPfKb/+lTxtDrfFOGH+mbZKbKf2/ibneYcql8fFQWiapbudI2cMk8yDxA9 +2Tw= +-----END CERTIFICATE REQUEST----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0000_key-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0000_key-certbot.pem new file mode 100644 index 000000000..9a018c41e --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0000_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDuewc4JO+9CVFh +ekjnETsCCCpNWYzyUaLXxjaBWWvdZgBLuGKAdEmNaugCZnYHH0hF8c7HCFVab+GV +z+7jDQhiVGcCuDqy5euC2f7oRrRYNQKIoSIq9CoPSRoj3fqGnJ1T91jvRRmekpLK +LSSIKsvMKQhpdLwcl0s4hcFDgUxWqOmliNR6MyCtpu2GrVBOpTRLgzHBLWiPrtWp +oCR50ZbEWAVDaQgVXT9YvXdezRIJNjI33oorVDYIXrceg3v3Nz5NWyWDfYMNTlQX +uxVW+WSgdyXQ6BH2XiN+i7kfCZq0yNc8j7RtZXGbDqILq1srsPH7K1gih2EQ4vnZ +ii7PL+UvAgMBAAECggEBAIX9jeLXrfNSRu0z3b4mCjdsCwiGphCIGayOa5VlfptY +chYZNQ7jR2gzhsPCedIqm1rhL8LYRcyYS/D2cUwUyH8m2PHIPQLC9/3/KZ+sCiv9 +LL1De4USxobsFcnNMLNtT2Ab+1YERw63X85EauAu226MJ3PI6OBPiS3qyNl6zj9p +do9SyzsNFEGtDk+ndWf3keoHBKLge4DP1lA3Jt42wSUxVv9U5SLvFpMQm8PqbqrK +4ofXcgxMFIJHDDGXsoDI7LOOsV6ncBVlui0ELM/QWBb5x1605VxqEDRL+h/wMp5Y +JIc6HbgcERmtHmyFlHHNtjAXxeulJVDJQDekd/irJ5ECgYEA/WQJ4LwkkA/Yhf2W +WYJtD8LuwzRnvGs3R+rgx3+hOeO4TFZD5fzObZVRSwWQO2jbOtBJOaRLUsUngcJQ +DXr/FGf1rnGhLmNeLE+jN9FS73wBhEXViFZ/fzhVibGbc7u45Y5REykZj8HtUHP5 +hBKR2Nx94WDiv1MBgcKrRk6yI50CgYEA8O+vWcMzEdPtonHl8UgTa8/c5g/RBBvS +plB8mVsmM/E5CNwnetZM32cg7dC7yNaZzn3qF6w+LdE2vw3j5VbqvuVUvsRgvYcJ +3kMbHsbsxkRw+HVWZGgEtWNzuYQUL0xN+xzIZDWkbtuaihqYAy4voYNAM08BTNcE +POQEMIGxcDsCgYEAg+TLo3grS/WDjhM2bHcQT9D2uRMRIClqx/uBbzaG9HwNFWcd +xpv102KSwwstTU9CNfXu95sGPhozez5qrumj1rpaTqgE7wF4JnZ5jfdeRRv2KiSz +hlkH2m+3TontUauYDZ0rpF6TWJnn7iW/7jhARHJY77SfslkBgsqSnnEeFp0CgYEA +7FsFVvZRzCRt01UOsPL28mWYmyxa7D/rFvKQONUdFgmG3PUz2aIPCX2e5Q1GmlBD +1Djbg1uaJ9I8dZJHxbzNTnWk+/ujt2mYuax1F20n65xKgsKA/MC6FcM5TH2QW5Hs +UfI7d2rUI1hVMzPBeiU93qDmQy825E1uP9mjbn5cNe8CgYAsBpJgS1LkDruyWmjG +ZTzdHGciA1O3gUArLQmyUfJlPS3Hgwn7wnBBihtGZDHmjJ7734+PQ9ioCnO9Pb+K +8Cp29vJ85lka7o7I48OeScLmczgEUYOPCrbkkKJdKaG6gn5CKpRBVYDlhbWjVZ51 +4uda/BQ1hqHh8WmxK6x21qC9JQ== +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0001_key-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0001_key-certbot.pem new file mode 100644 index 000000000..a3a7faf55 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0001_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwQDLut0+xgUW3 +1Wwr01nL5b2duxgaa8hoxLImQSBd7Y/QyadYb+GfoXVss+bzm5LmhCw5YOnBXPfW +0qGhaFQNtHM9Y/lSvY2cdj3ZaHPm0/iKvi8QQO4H161Dz7Bnv7fP2rVWE9yAN1jK +i5BUHRB6MnXJQGJU+vbz1tb/+WeSP0MXGfii5+gngQtJlJmBYUz3YQYowzLXMPZs +kfzuYjQazSkg4Sj5AwnxO9URtq75oVpMA2yWpEAXktpZys+C8eivtwrnD/htUFkK +ZjZBgqqVjBmJE+nCDciLoH1DyTOsOQZIOPCxrNxrhqmLntzNrXdeRGXsq/JlvBPC +UUG537J3AgMBAAECggEBAJoZR27X72GvORmmDFG1FInlcIf8EPLo0exoLaqsvnPh +RSCzbxEvoQFE1boZARB1MVdCsLfqN/bMJhU5TAAni3YAE9HVGyRwfuQRrbnsTYnA +Q0prRhLb8kIBHIhxijbrtPaSroF4FA42VfehVqt0TffJLpqrJE5QrqI7cPeVRCzk +laLyi2rjZBhN6l1OxFSIOrEDlcowlPUMORbmNDMbq/dLu5riVO/kP2x70K1IiANI +NZzVhMwkktYj3Ku2altRLcyRrC3Bs46w2QF6wiC88/LMapt79um65P/SgcCgyOYE +oxJywZwMnyw8ut1Y+KS8B7AdzqWmj7Q9wr0xbW6+4eECgYEA6sNrMGZVRUFRPAcr +m3y5fkM/WJ8tAkT3hI2/noljv3k8iameTy/B/y3p+aM8/6Oa/gdO/SWtfKPednkf +CIh/3J5tJ1yvK7wHEEU6r6qxVKr2FLCMfSXoGx+E+r9qPF8WdV+55beVgO86UqA5 +y9a6DhNA+Xt4jDJc+rbpga0pj60CgYEAwDHDV0lR7jVT6iiU6VhAu1gM/SBVqXE/ +VSfmGihgaO4pJ9OgfqusKbraNONc+oBub7B4T3sSnF/I0mSUclD6brmG99OWLIg8 +L6/ed+bLPRO0iTvKRLbyBLom1Totfh/X6iQ2Zci40vLIS7kbYDban16ca+iSm+0B +41RV4q6+vzMCgYBLoxiW6HGStZ+xonHHT+EHsCzppac/su64c18IeiV8HFiH1fFe +e/mZ+LYIqzJM/u5B6CLn5srFfJqBOzbnbescLqLmarM5eQQhltx4mps1tzs/oT4y +WBM3IembTC6zMsOun1/qhkKR3wHAe0UDyrP5MvTdLI3DRbq1QFdtY1gfpQKBgEgg +pNGWJ5RBGSvwbOohf7GPOtioEN3VLVJ09crtSjk23+Uda8b+AE9s20Ur6pHsLwXl +cVFKu9JJtCEZNAiu0T1KjRdmpZ4yxnuTAed3iuByC7fQ43jkO3GAtuAgxD/oDWzG +iE+sg4hPKtIYNujlzSgwJn3su1CfIq1A0jaPI/C3AoGAHGTBtsXdR1goFvcxwA+n +l2bAs/InoED5nj26a//JuONgtGlm//QKCxIgjjktpeZm8sfsaYeR+rwIUODWRX/e +LUF85a70SaH+FZRXBRS2d/zaNxO4F37nE5fwO+VAurSb7El7yOyCepK22iSHMYdl +xak78KZKv3HXW5yrfA+dc2Y= +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0002_key-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0002_key-certbot.pem new file mode 100644 index 000000000..b3059cb47 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0002_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj +8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 +FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH +g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 +/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN +fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg +PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY +JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo +fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc +xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj +bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D +WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 +mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu +wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 +enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO +iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x +nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL +W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 +q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R +zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz +h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 +Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS +xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy +YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY +o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl +wkqKc3PndJHrR40mswUOHbs= +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0003_key-certbot.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0003_key-certbot.pem new file mode 100644 index 000000000..c43af4f50 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/keys/0003_key-certbot.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh +DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 +Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt +YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv +Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 +npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 +HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX +Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l +/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r +m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK +tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj +xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n +9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf +Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN +cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT ++kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P +JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F +NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ +IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv +JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc +Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY +EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT +xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe +vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj +t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar +Bwhs4S84KEJFT0LAC3YeRQ== +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/README b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/README new file mode 100644 index 000000000..15194ae3a --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/README @@ -0,0 +1,10 @@ +This directory contains your keys and certificates. + +`privkey.pem` : the private key for your certificate. +`fullchain.pem`: the certificate file used in most server software. +`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. +`cert.pem` : will break many server configurations, and should not be used + without reading further documentation (see link below). + +We recommend not moving these files. For more information, see the Certbot +User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/cert.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/cert.pem new file mode 120000 index 000000000..79b6abdf9 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/chain.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d6b30420 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/fullchain.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..b801ef735 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/privkey.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/privkey.pem new file mode 120000 index 000000000..74e20c5ff --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/a.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/README b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/README new file mode 100644 index 000000000..15194ae3a --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/README @@ -0,0 +1,10 @@ +This directory contains your keys and certificates. + +`privkey.pem` : the private key for your certificate. +`fullchain.pem`: the certificate file used in most server software. +`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. +`cert.pem` : will break many server configurations, and should not be used + without reading further documentation (see link below). + +We recommend not moving these files. For more information, see the Certbot +User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/cert.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/cert.pem new file mode 120000 index 000000000..41b06370e --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/cert.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/chain.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/chain.pem new file mode 120000 index 000000000..2d3e18bec --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/chain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/fullchain.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/fullchain.pem new file mode 120000 index 000000000..3a08c1432 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/fullchain.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/privkey.pem b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/privkey.pem new file mode 120000 index 000000000..182aa6d78 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/live/b.encryption-example.com/privkey.pem @@ -0,0 +1 @@ +../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/options-ssl-apache.conf b/certbot-ci/certbot_integration_tests/assets/sample-config/options-ssl-apache.conf new file mode 100644 index 000000000..ec07a4ba3 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/options-ssl-apache.conf @@ -0,0 +1,22 @@ +# Baseline setting to Include for SSL sites + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/a.encryption-example.com.conf b/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/a.encryption-example.com.conf new file mode 100644 index 000000000..4455137b4 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/a.encryption-example.com.conf @@ -0,0 +1,15 @@ +# renew_before_expiry = 30 days +version = 0.10.0.dev0 +archive_dir = sample-config/archive/a.encryption-example.com +cert = sample-config/live/a.encryption-example.com/cert.pem +privkey = sample-config/live/a.encryption-example.com/privkey.pem +chain = sample-config/live/a.encryption-example.com/chain.pem +fullchain = sample-config/live/a.encryption-example.com/fullchain.pem + +# Options used in the renewal process +[renewalparams] +authenticator = apache +installer = apache +account = 48d6b9e8d767eccf7e4d877d6ffa81e3 +config_dir = sample-config +server = https://acme-staging.api.letsencrypt.org/directory diff --git a/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/b.encryption-example.com.conf b/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/b.encryption-example.com.conf new file mode 100644 index 000000000..58d8a13d9 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/sample-config/renewal/b.encryption-example.com.conf @@ -0,0 +1,15 @@ +# renew_before_expiry = 30 days +version = 0.10.0.dev0 +archive_dir = sample-config/archive/b.encryption-example.com +cert = sample-config/live/b.encryption-example.com/cert.pem +privkey = sample-config/live/b.encryption-example.com/privkey.pem +chain = sample-config/live/b.encryption-example.com/chain.pem +fullchain = sample-config/live/b.encryption-example.com/fullchain.pem + +# Options used in the renewal process +[renewalparams] +authenticator = apache +installer = apache +account = 48d6b9e8d767eccf7e4d877d6ffa81e3 +config_dir = sample-config +server = https://acme-staging.api.letsencrypt.org/directory diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 9b99b1747..f00cbd0c9 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -512,3 +512,35 @@ def test_wildcard_certificates(context): ]) assert exists(join(context.config_dir, 'live', certname, 'fullchain.pem')) + + +def test_ocsp_status_stale(context): + """Test retrieval of OCSP statuses for staled config""" + sample_data_path = misc.load_sample_data_path(context.workspace) + output = context.certbot(['certificates', '--config-dir', sample_data_path]) + + assert output.count('TEST_CERT') == 2, ('Did not find two test certs as expected ({0})' + .format(output.count('TEST_CERT'))) + assert output.count('EXPIRED') == 2, ('Did not find two expired certs as expected ({0})' + .format(output.count('EXPIRED'))) + + +def test_ocsp_status_live(context): + """Test retrieval of OCSP statuses for live config""" + if context.acme_server == 'pebble': + pytest.skip('Pebble does not support OCSP status requests.') + + # OSCP 1: Check live certificate OCSP status (VALID) + cert = context.get_domain('ocsp-check') + context.certbot(['--domains', cert]) + output = context.certbot(['certificates']) + + assert output.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert) + assert output.count('EXPIRED') == 0, 'Did not expect {0} to be EXPIRED'.format(cert) + + # OSCP 2: Check live certificate OCSP status (REVOKED) + context.certbot(['revoke', '--cert-name', cert, '--no-delete-after-revoke']) + output = context.certbot(['certificates']) + + assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert) + assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert) diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 76383ded1..e5ea659ec 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -14,6 +14,7 @@ import tempfile import time from distutils.version import LooseVersion +import pkg_resources import requests from OpenSSL import crypto from cryptography.hazmat.backends import default_backend @@ -260,3 +261,16 @@ def read_certificate(cert_path): cert = crypto.load_certificate(crypto.FILETYPE_PEM, data) return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert).decode('utf-8') + + +def load_sample_data_path(workspace): + """ + Load the certbot configuration example designed to make OCSP tests, and return its path + :param str workspace: current test workspace directory path + :returns: the path to the loaded sample data directory + :rtype: str + """ + original = pkg_resources.resource_filename('certbot_integration_tests', 'assets/sample-config') + copied = os.path.join(workspace, 'sample-config') + shutil.copytree(original, copied, symlinks=True) + return copied diff --git a/tox.ini b/tox.ini index 02fa68dcf..a396c3227 100644 --- a/tox.ini +++ b/tox.ini @@ -259,6 +259,6 @@ commands = --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ -W 'ignore:Unverified HTTPS request' - coverage report --include 'certbot/*' --show-missing + coverage report --include 'certbot/*' --show-missing --fail-under=56 passenv = DOCKER_* -- cgit v1.2.3 From 24eb299a9b30e32c9041b22c4d1688ec8032c2bf Mon Sep 17 00:00:00 2001 From: Jeremy Gillula Date: Tue, 16 Apr 2019 16:27:22 -0700 Subject: Added a link to the EFF Public Projects Code of Conduct to the readme. --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index f55581268..0f26901d7 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,8 @@ Contributing If you'd like to contribute to this project please read `Developer Guide `_. +This project is governed by `EFF's Public Projects Code of Conduct `_. + .. _installation: Installation -- cgit v1.2.3 From 7e5dcaa38397438969eb46f1fc8c04046afa7261 Mon Sep 17 00:00:00 2001 From: Jeremy Gillula Date: Tue, 16 Apr 2019 16:28:32 -0700 Subject: Adding the EFF Public Projects Code of Conduct to the contributing guide --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d740b7d89..07187eb59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,3 +33,5 @@ started. In particular, we recommend you read these sections - [Finding issues to work on](https://certbot.eff.org/docs/contributing.html#find-issues-to-work-on) - [Coding style](https://certbot.eff.org/docs/contributing.html#coding-style) - [Submitting a pull request](https://certbot.eff.org/docs/contributing.html#submitting-a-pull-request) + - [EFF's Public Projects Code of Conduct](https://www.eff.org/pages/eppcode) + -- cgit v1.2.3 From 0ee1002edc301ed8c268e9ec2b9ba4a5e3715747 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 17 Apr 2019 20:44:50 +0300 Subject: Clarify certbot-auto installation instructions (#6969) --- docs/install.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index eae40c1f0..3ae7fa397 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -71,8 +71,10 @@ from your web server OS and putting others in a python virtual environment. You download and run it as follows:: user@webserver:~$ wget https://dl.eff.org/certbot-auto - user@webserver:~$ chmod a+x ./certbot-auto - user@webserver:~$ ./certbot-auto --help + user@webserver:~$ sudo mv certbot-auto /usr/local/bin/certbot-auto + user@webserver:~$ sudo chown root /usr/local/bin/certbot-auto + user@webserver:~$ chmod 0755 /usr/local/bin/certbot-auto + user@webserver:~$ /usr/local/bin/certbot-auto --help To check the integrity of the ``certbot-auto`` script, you can use these steps:: @@ -80,7 +82,7 @@ you can use these steps:: user@webserver:~$ wget -N https://dl.eff.org/certbot-auto.asc user@webserver:~$ gpg2 --keyserver pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2 - user@webserver:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc certbot-auto + user@webserver:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto @@ -106,7 +108,7 @@ the same command line flags and arguments. For more information, see For full command line help, you can type:: - ./certbot-auto --help all + /usr/local/bin/certbot-auto --help all Problems with Python virtual environment ---------------------------------------- -- cgit v1.2.3 From f5d0d4241f790fa5890e100637bba9def0448ba2 Mon Sep 17 00:00:00 2001 From: Jeremy Gillula Date: Wed, 17 Apr 2019 11:36:26 -0700 Subject: Added a CODE_OF_CONDUCT.md file so Github doesn't complain --- CODE_OF_CONDUCT.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..142b31c93 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +This project is governed by [EFF's Public Projects Code of Conduct](https://www.eff.org/pages/eppcode). \ No newline at end of file -- cgit v1.2.3 From a58ad220028f446489d19f9441d091dcb23fffca Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 18 Apr 2019 00:24:39 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 3e (#6951) Following #6821, this PR continues to convert certbot integration tests into certbot-ci. This PR add tests covering checks on L430-447 in tests/certbot-boulder-integration.sh. Previous lines are covered with existing tests, or by #6946, #6947, #6948, #6949. * Add tests * Change param * Increase coverage min to 64% * Disable OCSP Must-Staple test for Pebble --- .../certbot_tests/test_main.py | 31 ++++++++++++++++++++-- tox.ini | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index f00cbd0c9..748b7699e 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -396,8 +396,35 @@ def test_reuse_key(context): cert3 = file.read() assert len({cert1, cert2, cert3}) == 3 - - + + +def test_ecdsa(context): + """Test certificate issuance with ECDSA key.""" + key_path = join(context.workspace, 'privkey-p384.pem') + csr_path = join(context.workspace, 'csr-p384.der') + cert_path = join(context.workspace, 'cert-p384.pem') + chain_path = join(context.workspace, 'chain-p384.pem') + + misc.generate_csr([context.get_domain('ecdsa')], key_path, csr_path, key_type=misc.ECDSA_KEY_TYPE) + context.certbot(['auth', '--csr', csr_path, '--cert-path', cert_path, '--chain-path', chain_path]) + + certificate = misc.read_certificate(cert_path) + assert 'ASN1 OID: secp384r1' in certificate + + +def test_ocsp_must_staple(context): + """Test that OCSP Must-Staple is correctly set in the generated certificate.""" + if context.acme_server == 'pebble': + pytest.skip('Pebble does not support OCSP Must-Staple.') + + certname = context.get_domain('must-staple') + context.certbot(['auth', '--must-staple', '--domains', certname]) + + certificate = misc.read_certificate(join(context.config_dir, + 'live/{0}/cert.pem').format(certname)) + assert 'status_request' in certificate or '1.3.6.1.5.5.7.1.24' in certificate + + def test_revoke_simple(context): """Test various scenarios that revokes a certificate.""" # Default action after revoke is to delete the certificate. diff --git a/tox.ini b/tox.ini index a396c3227..03a965544 100644 --- a/tox.ini +++ b/tox.ini @@ -259,6 +259,6 @@ commands = --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ -W 'ignore:Unverified HTTPS request' - coverage report --include 'certbot/*' --show-missing --fail-under=56 + coverage report --include 'certbot/*' --show-missing --fail-under=64 passenv = DOCKER_* -- cgit v1.2.3 From a817e4f0ec3ca833c48e040e3fd36e9c4faea132 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 22 Apr 2019 09:14:20 -0700 Subject: There's no need to use certbot-auto here. (#6970) I came across this when looking through our docs for other references to certbot-auto. For the README changes, I deleted a bunch of duplicated and outdated instructions in favor of pointing people to https://certbot.eff.org. --- README.rst | 38 +++++--------------------------------- docs/using.rst | 2 +- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index f55581268..799507605 100644 --- a/README.rst +++ b/README.rst @@ -30,43 +30,15 @@ If you'd like to contribute to this project please read `Developer Guide .. _installation: -Installation ------------- - -The easiest way to install Certbot is by visiting `certbot.eff.org`_, where you can -find the correct installation instructions for many web server and OS combinations. -For more information, see `Get Certbot `_. - -.. _certbot.eff.org: https://certbot.eff.org/ - How to run the client --------------------- -In many cases, you can just run ``certbot-auto`` or ``certbot``, and the -client will guide you through the process of obtaining and installing certs -interactively. - -For full command line help, you can type:: - - ./certbot-auto --help all - - -You can also tell it exactly what you want it to do from the command line. -For instance, if you want to obtain a cert for ``example.com``, -``www.example.com``, and ``other.example.net``, using the Apache plugin to both -obtain and install the certs, you could do this:: - - ./certbot-auto --apache -d example.com -d www.example.com -d other.example.net - -(The first time you run the command, it will make an account, and ask for an -email and agreement to the Let's Encrypt Subscriber Agreement; you can -automate those with ``--email`` and ``--agree-tos``) - -If you want to use a webserver that doesn't have full plugin support yet, you -can still use "standalone" or "webroot" plugins to obtain a certificate:: - - ./certbot-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net +The easiest way to install and run Certbot is by visiting `certbot.eff.org`_, +where you can find the correct instructions for many web server and OS +combinations. For more information, see `Get Certbot +`_. +.. _certbot.eff.org: https://certbot.eff.org/ Understanding the client in more depth -------------------------------------- diff --git a/docs/using.rst b/docs/using.rst index d2799f7dc..d398c1c4d 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -923,7 +923,7 @@ files that can be found in ``/etc/letsencrypt/renewal``. By default no cli.ini file is created, after creating one it is possible to specify the location of this configuration file with -``certbot-auto --config cli.ini`` (or shorter ``-c cli.ini``). An +``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An example configuration file is shown below: .. include:: ../examples/cli.ini -- cgit v1.2.3 From 2812f054a3e9bf87a799c9d153393a9d3593fed0 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 22 Apr 2019 15:23:26 -0700 Subject: Update urllib3 to 1.24.2 (#6977) * Update urllib3 to 1.24.2 * Run build.py * Update changelog --- CHANGELOG.md | 7 ++++--- letsencrypt-auto-source/letsencrypt-auto | 6 +++--- letsencrypt-auto-source/pieces/dependency-requirements.txt | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89da42c80..ac27aa772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Apache plugin now tries to restart httpd on Fedora using systemctl if a configuration test error is detected. This has to be done due to the way - Fedora now generates the self signed certificate files upon first + Fedora now generates the self signed certificate files upon first restart. * Updated Certbot and its plugins to improve the handling of file system permissions on Windows as a step towards adding proper Windows support to Certbot. +* Updated urllib3 to 1.24.2 in certbot-auto. ### Fixed @@ -84,8 +85,8 @@ More details about these changes can be found on our GitHub repo. * Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 is installed. We fixed a bug in Certbot causing it to interpret timestamps in the OCSP response as being in the local timezone rather than UTC. -* Issue causing the default CentOS 6 TLS configuration to ignore some of the - HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main +* Issue causing the default CentOS 6 TLS configuration to ignore some of the + HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main http.conf for this environment where possible. Despite us having broken lockstep, we are continuing to release new versions of diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index b940c71a3..15305da39 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1138,9 +1138,9 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.1 \ - --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ - --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 +urllib3==1.24.2 \ + --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ + --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 8ee8fa5ea..48c2afd93 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -121,9 +121,9 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.1 \ - --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ - --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 +urllib3==1.24.2 \ + --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ + --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -- cgit v1.2.3 From 618e0562a071151452198d60b9cfd7c0c9a41f5e Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 23 Apr 2019 22:29:48 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 4 (#6958) This PR is the part 4 to implement #6541. It adds the integration tests for the nginx certbot plugin, and corresponds to the certbot-ci translation of certbot-nginx/tests/boulder-integration.sh that is executed for each PR. As with certbot core tests, tests are written in Python, and executed by pytest, against a dynamic Boulder/Pebble instance setup. Tests are parallelized, of course, and a specific IntegrationTestsContext class, extended the one from certbot core tests, is crafter for these specific tests: its main goal is to setup a specific nginx instance for the current test. On top of that, I use the test parametrization feature of Pytest, to drastically reduce the size of the actual code: indeed, the 6 tests from the original bash script share the same logic. So using a parametrization, one unique test is written, that is then executed 6 times against 6 different sets of parameters. Note that the module integration_tests.nginx_tests.nginx_config do the same, but in Python, than certbot-nginx/tests/boulder-integration.conf.sh. The latter will be removed in a future PR, with all other bash scripts. * Add nginx tests * Distribute the other_port * Load a pre-generated key/cert for nginx config * Correct preload, remove a test, simplify a variable * Integrate assertion directly in the test function * Check process is not terminated * Add spaces in the nginx config * Add comments * Use indirection * Allow external cert * Add coverage threshold for certbot-nginx --- .../assets/nginx_cert.pem | 32 ++++++ .../certbot_integration_tests/assets/nginx_key.pem | 52 +++++++++ .../certbot_tests/context.py | 1 + .../nginx_tests/context.py | 53 +++++++++ .../nginx_tests/nginx_config.py | 126 +++++++++++++++++++++ .../nginx_tests/test_main.py | 43 ++++++- .../certbot_integration_tests/utils/acme_server.py | 2 + tox.ini | 1 + 8 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 certbot-ci/certbot_integration_tests/assets/nginx_cert.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/nginx_key.pem create mode 100644 certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem b/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem new file mode 100644 index 000000000..5aae58f25 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIUR3wbM8qFE68f8NxfciHhUjR1GeUwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbmdpbngud3RmMCAX +DTE5MDQxODIwMDUwM1oYDzIyOTMwMTMwMjAwNTAzWjBZMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMRIwEAYDVQQDDAluZ2lueC53dGYwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQC/W+yxYE0PWJOS4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OC +HyRm0TEhl6FPUp9tD9YeEHloUZNjHEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf +0go+aVsWelDhapFl8fccw4tWwijVZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+ +rOTruzRzOzlbgKv5WRb4+BxxWonHLkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMH +ZlZvUT6aWRnzFWjAnQGiJpVIw7l9r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXH +aKIR82/xwkcZfFk3sCAM0IcZc8B2SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtE +X5qYlgXRaM2raOn31lxcerk5pjgagQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4 +QHtoO0nyP0RJqxvwX/H98513JTkeqFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquu +k9anv56yvxo77qZ8r80l3z8eMyDA+UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFH +JIUozHoinAg/9lBC/W80fcbILks+Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/ +Shb6R6sWr1YtzUFUc5EH2g9pMriaqT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQAB +o1MwUTAdBgNVHQ4EFgQU46gJeu9ZOfTQ6c4vfbWbSLUpEMowHwYDVR0jBBgwFoAU +46gJeu9ZOfTQ6c4vfbWbSLUpEMowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAgEAcnfkXDUTsEGs0MleegkGbRCVy72a3U7tv1KVTLB8qLPc3tpPJJoT +D4PbOuw9+yIE+HetZTZooOpaZoorLQdiwAEjlQ44RVuXSHSARQ8KW9ZZeiWN/Qvl +Ip4xJ/cHxcKTFKSc/99o8M+kmPKEXF9SUMfKPc5jXarNxCsnA3VriYqJ+CnYEox2 +duNUEe3A9Y2d8ZxjmscBqlcXpk1kFwsCRT5UYVoUYwyjYznLkO5A+GJ0ZnMyRMQp +obUiB34hUrNgyOaBvizk+pNh9EV4rEBPRQwhy4vDMco4AjQcwLWQAQ9G4GSt/E+Q +62XdVDa6CAuOvBCudDPki7kEqNLbj1tMY1K/gsbgb6TYA/xTOVulAnqm4OEZ2svJ +0Jqw3BzMfRTaxsNU6jxm8WehVL15GjoJUzfs7Te+l7Vm/QNc1Dv2pmEhVfBibwMa +YxUZ8ClQtQ1lsOpne97Og0p/Cm93kKELNBLTjzXtpXGGPPYisAyNwe0Hadq8SiOd +pXeNwXa5vHOXHv8xBENzBvFJ3TRN2GmMlHBp/eOfVUx/huNSpcnh2gO3fn5EbMj7 +43IaR133JW5yWbneYAMJOEAMdEB5EthRmEDtLVA7kLqLc/ywFTQ4VbS2b+PsOr5O +501nzt0OTMMEz+UafvGXj5OPJBhe26RtnYXzVwwLfto/F5udM5zglWo= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_key.pem b/certbot-ci/certbot_integration_tests/assets/nginx_key.pem new file mode 100644 index 000000000..1f768f079 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/nginx_key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC/W+yxYE0PWJOS +4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OCHyRm0TEhl6FPUp9tD9YeEHloUZNj +HEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf0go+aVsWelDhapFl8fccw4tWwijV +ZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+rOTruzRzOzlbgKv5WRb4+BxxWonH +LkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMHZlZvUT6aWRnzFWjAnQGiJpVIw7l9 +r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXHaKIR82/xwkcZfFk3sCAM0IcZc8B2 +SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtEX5qYlgXRaM2raOn31lxcerk5pjga +gQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4QHtoO0nyP0RJqxvwX/H98513JTke +qFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquuk9anv56yvxo77qZ8r80l3z8eMyDA ++UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFHJIUozHoinAg/9lBC/W80fcbILks+ +Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/Shb6R6sWr1YtzUFUc5EH2g9pMria +qT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQABAoICAAGGL+pxw+tdXz+KQPgmiUnn +aRSrqbUIugIw9Pst67HWjBqUxSkiKl4PSH7mAEjrdY2e1KvEodLs42mkrf04ShAx +0pArfFX8Sx7KrZgLOonGOPPQM+YmfCJnIGybaM2C1cmkFb3K6O81+LFKbr1ZHAYf +SrE2XnufS6cdmItTBMvPPTk6lieqpOAjy5UnYZuS+Muxo/czsrZMbFCD08rOpyiE +kXf94TMCJ2R0UetA7LPxe9N0TzLd485bLU55azV+dCkklwC9oe7EcFPJ9BNEdWdB +UlRcMvxMGdwct+L3QTaEb2QlTwi5kqDl+XxJeduAHA3Pf1Haz1iqjVvj01PvT1di +Cs0+ZeFBsa+BfiGDe9ONwuSQljda1CuI+vDv5bGUExulOSG1dHJ7RK9PBaXFaR/b +/9tRBwAw1Erm7s1JIkjda5Oc46jFb3HzDaZYB1n5hUmEIrYM8HhUOGITyVT3hxDO +AWlaV3aveQ0MmMXLptVXDgbjPGbWDGMLD9d5vUE9R7IyOLeXOmjthYlCH2rj378M +r2PkgX2tD0A/yoEZ8XCFdtBWSVajLdL0/gkm7sKosMABBy3yrSCxbHeq5TFuTAXA +hOdypX4NOZkA6WJU+hn3GkQyIScLqSrvGRA9kzHGoEWVZDKkB9DXg+dmTARZDWXD +mCnHkJo6+FcbhUpXniuZAoIBAQDmE94vvdstB+HEtXxN1uNDY7H8gPc/BUonU6a9 +G5YOIbjByCfEDcXF8AUWekc6lc8DNG3ydx0dnb2ZAkxmdlsaD8GLqHGILzlSsOwR +sez8nR4+4n9vYMfx9Qal8Ren5xEP9Z9sJcNqbKVGta1WFtQzrgYbpVXXf/Luv0xS +YoVK8KaEACciD6XX4wmajrAXPPQgThvqQtXuTn/AxWsUDg1DK0tw1VRUuOJuJwpw +f6qocM9AyqUNvdeVyjFx8Slag34ZI7fmxPtHX/e6opTg3zVXab1Ow8AMICOHMRL6 +m5/+wnWa9xMoKI4kYfk/QFqeTccnLDlwi6kQM8WRfbwr9AyPAoIBAQDU60wrX6Lm +0vIfngv1/4j/w+AGAwjvxiuJ7Q7LwQ2fGsZGOIfMK/lpBxCn543kGbQT+KQKNOjO ++EywObftnJ6Y2+om2NoLkCnCiptsfr5WlN8pxtIPQu2iu5xXA67WpQv4Nc4769PM +wJGVW3pmPKi6H0QjjqYAZd1NAXdN9Au14zZVh3KBWoz82kTHWKSL6Ld1UClG728W +k/moyCFFMMGTXX/LVliQzDVLM6L5jbAOaG317qAuxZIqFJ9NLwHFW9uH/i1S6Qfp ++lOmOfVYKu1O/qh1DUBQfuJkR1XIn2ifZEjxOsxeTmWu1LXpyoZy526JRu49pk8Z +DdEu+w7hsdNjAoIBAD1YWsub8Y6GJXpPcX9HpnzXXiOXN1VEUcs+kJyneFD4SMzS +U1gA3BS0tIaTv94tB28xUYdunwLAhkb/x+Mh95RxUwert+m5va0Ao1DsgeWw9tmJ +hrTptyYaUNV5/Pa1s2Tv9rvdLcd4hHDgDAGCQL4uzk4cvVCiOuHRe8YTorqig6N6 +bvSz+2IelPbyyJzJkcXzTZoei+/oWkPJ340PWhXou0qwdrXIPgdkvXHVeGlE+t2p +qmyJi6vSp3Bb/sy1dq+5SFVtfBpBykmnA88ZdJ2EAge4RcJ150MqoIbVa8l/i9/v +tNnmRlAJF233+LFwx4L4VbBebIt3YlwyjDOj9J0CggEAIknKOGnsV/O8ni7bikAe +leG7X/x5IfPt6wZMDbAHO4oaSBCufcjPH4TNv9xgU014XIb8E9C1dS8zWmXRIujH ++aHgsWTWqGoM75FWukAm8taCob2s8lw63KwN301uiI6HwO8ZSTkPILgaOc1DhtdZ +7K9AT+GXBhVhcBc+WUVl5WKzy05GuGIWtlmIHfo+dXGCqdfA7fV9FEu8NtwTz4qs +gcja3aoIFTltk7C7HCkfIxLaMnK9RQr4IOK1TL63MEs8rUfXkLSKW7m+YtSOmCZB +lSkZg9AgfVYRq0h5nhddx91kicSISN+jLGaA7Sd6Q2LVwDG2CCOSNVyuRTyVBu+W +NQKCAQAWN6vB6oToNIoBLdOThm0HD07cNHcrnBjtaKsYsQDgqbr2m8LRCRzNRML4 +jG0IAOWpuCiEGsgUPxywiI1Ufvyq7ZSNT1QQNzCR47NM3Ve6S2abrQkMIk9VJ+za +CB9c1BH92GokoRxqswb/BiMttG2EIP8L8/pSRYEcVnaaxAkf9QOhEwj4LJPGX0mS +t7kWIUVHPdFJ67F25dYr3mUHgyV+QJupQICkkkgY3nBOU1fS42vAugaxqH0wAP3T +53FlpY3NuE7+kYC3FjfcBer99F1pOac3X9jxhk26w9dr2/QNA33xhDXHKYvoLUCG +RPQylahJByU7IrtQzSCf/RE7q4v0 +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index 570286737..c82793d3d 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -25,6 +25,7 @@ class IntegrationTestsContext(object): self.directory_url = acme_xdist['directory_url'] self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id] self.http_01_port = acme_xdist['http_port'][self.worker_id] + self.other_port = acme_xdist['other_port'][self.worker_id] # Challtestsrv REST API, that exposes entrypoints to register new DNS entries, # is listening on challtestsrv_port. self.challtestsrv_port = acme_xdist['challtestsrv_port'] diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py index 6d7d6012b..3da8a7dd9 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/context.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -1,5 +1,58 @@ +import os +import subprocess + from certbot_integration_tests.certbot_tests import context as certbot_context +from certbot_integration_tests.utils import misc +from certbot_integration_tests.nginx_tests import nginx_config as config class IntegrationTestsContext(certbot_context.IntegrationTestsContext): """General fixture describing a certbot-nginx integration tests context""" + def __init__(self, request): + super(IntegrationTestsContext, self).__init__(request) + + self.nginx_root = os.path.join(self.workspace, 'nginx') + os.mkdir(self.nginx_root) + + self.webroot = os.path.join(self.nginx_root, 'webroot') + os.mkdir(self.webroot) + with open(os.path.join(self.webroot, 'index.html'), 'w') as file_handler: + file_handler.write('Hello World!') + + self.nginx_config_path = os.path.join(self.nginx_root, 'nginx.conf') + self.nginx_config = None + + default_server = request.param['default_server'] + self.process = self._start_nginx(default_server) + + def cleanup(self): + self._stop_nginx() + super(IntegrationTestsContext, self).cleanup() + + def certbot_test_nginx(self, args): + """ + Main command to execute certbot using the nginx plugin. + :param list args: list of arguments to pass to nginx + """ + command = ['--authenticator', 'nginx', '--installer', 'nginx', + '--nginx-server-root', self.nginx_root] + command.extend(args) + return self._common_test(command) + + def _start_nginx(self, default_server): + self.nginx_config = config.construct_nginx_config( + self.nginx_root, self.webroot, self.http_01_port, self.tls_alpn_01_port, + self.other_port, default_server, wtf_prefix=self.worker_id) + with open(self.nginx_config_path, 'w') as file: + file.write(self.nginx_config) + + process = subprocess.Popen(['nginx', '-c', self.nginx_config_path, '-g', 'daemon off;']) + + assert process.poll() is None + misc.check_until_timeout('http://localhost:{0}'.format(self.http_01_port)) + return process + + def _stop_nginx(self): + assert self.process.poll() is None + self.process.terminate() + self.process.wait() diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py new file mode 100644 index 000000000..a6305c3cc --- /dev/null +++ b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py @@ -0,0 +1,126 @@ +"""General purpose nginx test configuration generator.""" +import getpass + +import pkg_resources + + +def construct_nginx_config(nginx_root, nginx_webroot, http_port, https_port, other_port, + default_server, key_path=None, cert_path=None, wtf_prefix='le'): + """ + This method returns a full nginx configuration suitable for integration tests. + :param str nginx_root: nginx root configuration path + :param str nginx_webroot: nginx webroot path + :param int http_port: HTTP port to listen on + :param int https_port: HTTPS port to listen on + :param int other_port: other HTTP port to listen on + :param bool default_server: True to set a default server in nginx config, False otherwise + :param str key_path: the path to a SSL key + :param str cert_path: the path to a SSL certificate + :param str wtf_prefix: the prefix to use in all domains handled by this nginx config + :return: a string containing the full nginx configuration + :rtype: str + """ + key_path = key_path if key_path \ + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_key.pem') + cert_path = cert_path if cert_path \ + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_cert.pem') + return '''\ +# This error log will be written regardless of server scope error_log +# definitions, so we have to set this here in the main scope. +# +# Even doing this, Nginx will still try to create the default error file, and +# log a non-fatal error when it fails. After that things will work, however. +error_log {nginx_root}/error.log; + +# The pidfile will be written to /var/run unless this is set. +pid {nginx_root}/nginx.pid; + +user {user}; +worker_processes 1; + +events {{ + worker_connections 1024; +}} + +http {{ + # Set an array of temp, cache and log file options that will otherwise default to + # restricted locations accessible only to root. + client_body_temp_path {nginx_root}/client_body; + fastcgi_temp_path {nginx_root}/fastcgi_temp; + proxy_temp_path {nginx_root}/proxy_temp; + #scgi_temp_path {nginx_root}/scgi_temp; + #uwsgi_temp_path {nginx_root}/uwsgi_temp; + access_log {nginx_root}/error.log; + + # This should be turned off in a Virtualbox VM, as it can cause some + # interesting issues with data corruption in delivered files. + sendfile off; + + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + #include /etc/nginx/mime.types; + index index.html index.htm index.php; + + log_format main '$remote_addr - $remote_user [$time_local] $status ' + '"$request" $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + default_type application/octet-stream; + + server {{ + # IPv4. + listen {http_port} {default_server}; + # IPv6. + listen [::]:{http_port} {default_server}; + server_name nginx.{wtf_prefix}.wtf nginx2.{wtf_prefix}.wtf; + + root {nginx_webroot}; + + location / {{ + # First attempt to serve request as file, then as directory, then fall + # back to index.html. + try_files $uri $uri/ /index.html; + }} + }} + + server {{ + listen {http_port}; + listen [::]:{http_port}; + server_name nginx3.{wtf_prefix}.wtf; + + root {nginx_webroot}; + + location /.well-known/ {{ + return 404; + }} + + return 301 https://$host$request_uri; + }} + + server {{ + listen {other_port}; + listen [::]:{other_port}; + server_name nginx4.{wtf_prefix}.wtf nginx5.{wtf_prefix}.wtf; + }} + + server {{ + listen {http_port}; + listen [::]:{http_port}; + listen {https_port} ssl; + listen [::]:{https_port} ssl; + if ($scheme != "https") {{ + return 301 https://$host$request_uri; + }} + server_name nginx6.{wtf_prefix}.wtf nginx7.{wtf_prefix}.wtf; + + ssl_certificate {cert_path}; + ssl_certificate_key {key_path}; + }} +}} +'''.format(nginx_root=nginx_root, nginx_webroot=nginx_webroot, user=getpass.getuser(), + http_port=http_port, https_port=https_port, other_port=other_port, + default_server='default_server' if default_server else '', wtf_prefix=wtf_prefix, + key_path=key_path, cert_path=cert_path) diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py index 472e5e7b7..176bb220a 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py @@ -1,3 +1,7 @@ +"""Module executing integration tests against certbot with nginx plugin.""" +import os +import ssl + import pytest from certbot_integration_tests.nginx_tests import context as nginx_context @@ -13,5 +17,40 @@ def context(request): integration_test_context.cleanup() -def test_hello(context): - print(context.directory_url) +@pytest.mark.parametrize('certname_pattern, params, context', [ + ('nginx.{0}.wtf', ['run'], {'default_server': True}), + ('nginx2.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': True}), + # Overlapping location block and server-block-level return 301 + ('nginx3.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': True}), + # No matching server block; default_server exists + ('nginx4.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': True}), + # No matching server block; default_server does not exist + ('nginx5.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': False}), + # Multiple domains, mix of matching and not + ('nginx6.{0}.wtf,nginx7.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': False}), +], indirect=['context']) +def test_certificate_deployment(certname_pattern, params, context): + """ + Test various scenarios to deploy a certificate to nginx using certbot. + """ + domains = certname_pattern.format(context.worker_id) + command = ['--domains', domains] + command.extend(params) + context.certbot_test_nginx(command) + + lineage = domains.split(',')[0] + server_cert = ssl.get_server_certificate(('localhost', context.tls_alpn_01_port)) + with open(os.path.join(context.workspace, 'conf/live/{0}/cert.pem'.format(lineage)), 'r') as file: + certbot_cert = file.read() + + assert server_cert == certbot_cert + + command = ['--authenticator', 'nginx', '--installer', 'nginx', + '--nginx-server-root', context.nginx_root, + 'rollback', '--checkpoints', '1'] + context._common_test_no_force_renew(command) + + with open(context.nginx_config_path, 'r') as file_h: + current_nginx_config = file_h.read() + + assert context.nginx_config == current_nginx_config diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index 33ef05194..80516aea1 100644 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -57,6 +57,8 @@ def _construct_acme_xdist(acme_server, nodes): in zip(nodes, range(5200, 5200 + len(nodes)))} acme_xdist['https_port'] = {node: port for (node, port) in zip(nodes, range(5100, 5100 + len(nodes)))} + acme_xdist['other_port'] = {node: port for (node, port) + in zip(nodes, range(5300, 5300 + len(nodes)))} return acme_xdist diff --git a/tox.ini b/tox.ini index 03a965544..1087a1969 100644 --- a/tox.ini +++ b/tox.ini @@ -260,5 +260,6 @@ commands = --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ -W 'ignore:Unverified HTTPS request' coverage report --include 'certbot/*' --show-missing --fail-under=64 + coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 passenv = DOCKER_* -- cgit v1.2.3 From 9dd2990e5939edf891831439d62907d1769dac34 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 24 Apr 2019 00:10:15 +0200 Subject: Remove keyAuthorization fallback dump in challenges response (#6975) Fixes #6974. This PR removes the fallback that consists in retrying to send the keyAuthorization field during a challenge request in case of malformed request. * Remove keyAuthorization fallback dump in challenges response * Correct import * Add changelog entry --- CHANGELOG.md | 4 ++++ acme/acme/challenges.py | 17 +---------------- acme/acme/challenges_test.py | 8 -------- acme/acme/client.py | 19 +------------------ acme/acme/client_test.py | 28 ---------------------------- 5 files changed, 6 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac27aa772..afc8b8b21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Updated Certbot and its plugins to improve the handling of file system permissions on Windows as a step towards adding proper Windows support to Certbot. * Updated urllib3 to 1.24.2 in certbot-auto. +* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response + with a `keyAuthorization` if sending the response without this field caused a + `malformed` error to be received from the ACME server. ### Fixed @@ -26,6 +29,7 @@ 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 diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 01298d46f..78991608a 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -107,10 +107,6 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): key_authorization = jose.Field("keyAuthorization") thumbprint_hash_function = hashes.SHA256 - def __init__(self, *args, **kwargs): - super(KeyAuthorizationChallengeResponse, self).__init__(*args, **kwargs) - self._dump_authorization_key(False) - def verify(self, chall, account_public_key): """Verify the key authorization. @@ -143,20 +139,9 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): return True - def _dump_authorization_key(self, dump): - # type: (bool) -> None - """ - Set if keyAuthorization is dumped in the JSON representation of this ChallengeResponse. - NB: This method is declared as private because it will eventually be removed. - :param bool dump: True to dump the keyAuthorization, False otherwise - """ - object.__setattr__(self, '_dump_auth_key', dump) - def to_partial_json(self): jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json() - if not self._dump_auth_key: # pylint: disable=no-member - jobj.pop('keyAuthorization', None) - + jobj.pop('keyAuthorization', None) return jobj diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index f5f914005..9d3a92fa5 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -95,8 +95,6 @@ class DNS01ResponseTest(unittest.TestCase): def test_to_partial_json(self): self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, self.msg.to_partial_json()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import DNS01Response @@ -169,8 +167,6 @@ class HTTP01ResponseTest(unittest.TestCase): def test_to_partial_json(self): self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, self.msg.to_partial_json()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import HTTP01Response @@ -292,8 +288,6 @@ class TLSSNI01ResponseTest(unittest.TestCase): def test_to_partial_json(self): self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, self.response.to_partial_json()) - self.response._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.response.to_partial_json()) def test_from_json(self): from acme.challenges import TLSSNI01Response @@ -428,8 +422,6 @@ class TLSALPN01ResponseTest(unittest.TestCase): def test_to_partial_json(self): self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, self.msg.to_partial_json()) - self.msg._dump_authorization_key(True) # pylint: disable=protected-access - self.assertEqual(self.jmsg, self.msg.to_partial_json()) def test_from_json(self): from acme.challenges import TLSALPN01Response diff --git a/acme/acme/client.py b/acme/acme/client.py index 5cad0acbe..a41787756 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -17,7 +17,6 @@ import requests from requests.adapters import HTTPAdapter from requests_toolbelt.adapters.source import SourceAddressAdapter -from acme import challenges from acme import crypto_util from acme import errors from acme import jws @@ -156,23 +155,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes :raises .UnexpectedUpdate: """ - # Because sending keyAuthorization in a response challenge has been removed from the ACME - # spec, it is not included in the KeyAuthorizationResponseChallenge JSON by default. - # However as a migration path, we temporarily expect a malformed error from the server, - # and fallback by resending the challenge response with the keyAuthorization field. - # TODO: Remove this fallback for Certbot 0.34.0 - try: - response = self._post(challb.uri, response) - except messages.Error as error: - if (error.code == 'malformed' - and isinstance(response, challenges.KeyAuthorizationChallengeResponse)): - logger.debug('Error while responding to a challenge without keyAuthorization ' - 'in the JWS, your ACME CA server may not support it:\n%s', error) - logger.debug('Retrying request with keyAuthorization set.') - response._dump_authorization_key(True) # pylint: disable=protected-access - response = self._post(challb.uri, response) - else: - raise + response = self._post(challb.uri, response) try: authzr_uri = response.links['up']['url'] except KeyError: diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index 5b2703701..406201751 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -461,34 +461,6 @@ class ClientTest(ClientTestBase): errors.ClientError, self.client.answer_challenge, self.challr.body, challenges.DNSResponse(validation=None)) - def test_answer_challenge_key_authorization_fallback(self): - self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() - - def _wrapper_post(url, obj, *args, **kwargs): # pylint: disable=unused-argument - """ - Simulate an old ACME CA server, that would respond a 'malformed' - error if keyAuthorization is missing. - """ - jobj = obj.to_partial_json() - if 'keyAuthorization' not in jobj: - raise messages.Error.with_code('malformed') - return self.response - self.net.post.side_effect = _wrapper_post - - # This challenge response is of type KeyAuthorizationChallengeResponse, so the fallback - # should be triggered, and avoid an exception. - http_chall_response = challenges.HTTP01Response(key_authorization='test', - resource=mock.MagicMock()) - self.client.answer_challenge(self.challr.body, http_chall_response) - - # This challenge response is not of type KeyAuthorizationChallengeResponse, so the fallback - # should not be triggered, leading to an exception. - dns_chall_response = challenges.DNSResponse(validation=None) - self.assertRaises( - errors.Error, self.client.answer_challenge, - self.challr.body, dns_chall_response) - def test_retry_after_date(self): self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' self.assertEqual( -- cgit v1.2.3 From 333ea90d1b1348933aa6e586472bb62e182bfebc Mon Sep 17 00:00:00 2001 From: Trinopoty Biswas Date: Wed, 24 Apr 2019 20:41:42 +0000 Subject: Added support for linode version 4 tokens (#6588) * certbot-dns-linode : Added support for linode version 4 tokens * certbot-dns-linode : Added credentials ini option to override automatic api version detection * certbot-dns-linode : Added clearer messages and documentation based on review * certbot-dns-linode : Added check for empty 'linode_version' config instead of missing * certbot-dns-linode : Fix rebase on master * certbot-dns-linode : Updated local-oldest-requirements.txt * Updated CHANGELOG to indicate Linode v4 API key support --- CHANGELOG.md | 2 + certbot-dns-linode/certbot_dns_linode/__init__.py | 4 +- .../certbot_dns_linode/dns_linode.py | 46 +++++++-- .../certbot_dns_linode/dns_linode_test.py | 103 ++++++++++++++++++++- certbot-dns-linode/local-oldest-requirements.txt | 1 + certbot-dns-linode/setup.py | 2 +- 6 files changed, 146 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc8b8b21..c1af9ffb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response with a `keyAuthorization` if sending the response without this field caused a `malformed` error to be received from the ACME server. +* Linode DNS plugin now supports api keys created from their new panel + at [cloud.linode.com](https://cloud.linode.com) ### Fixed diff --git a/certbot-dns-linode/certbot_dns_linode/__init__.py b/certbot-dns-linode/certbot_dns_linode/__init__.py index 0a6ccec61..107781a13 100644 --- a/certbot-dns-linode/certbot_dns_linode/__init__.py +++ b/certbot-dns-linode/certbot_dns_linode/__init__.py @@ -27,7 +27,8 @@ Credentials Use of this plugin requires a configuration file containing Linode API credentials, obtained from your Linode account's `Applications & API -Tokens page `_. +Tokens page (legacy) `_ or `Applications +& API Tokens page (new) `_. .. code-block:: ini :name: credentials.ini @@ -35,6 +36,7 @@ Tokens page `_. # Linode API credentials used by Certbot dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64 + dns_linode_version = [|3|4] The path to this file can be provided interactively or using the ``--dns-linode-credentials`` command-line argument. Certbot records the path diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/dns_linode.py index c2097a7d6..507ad5e53 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/dns_linode.py @@ -1,8 +1,10 @@ """DNS Authenticator for Linode.""" import logging +import re import zope.interface from lexicon.providers import linode +from lexicon.providers import linode4 from certbot import errors from certbot import interfaces @@ -12,6 +14,7 @@ from certbot.plugins import dns_common_lexicon logger = logging.getLogger(__name__) API_KEY_URL = 'https://manager.linode.com/profile/api' +API_KEY_URL_V4 = 'https://cloud.linode.com/profile/tokens' @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) @@ -41,7 +44,8 @@ class Authenticator(dns_common.DNSAuthenticator): 'credentials', 'Linode credentials INI file', { - 'key': 'API key for Linode account, obtained from {0}'.format(API_KEY_URL) + 'key': 'API key for Linode account, obtained from {0} or {1}' + .format(API_KEY_URL, API_KEY_URL_V4) } ) @@ -52,7 +56,23 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_linode_client().del_txt_record(domain, validation_name, validation) def _get_linode_client(self): - return _LinodeLexiconClient(self.credentials.conf('key')) + api_key = self.credentials.conf('key') + api_version = self.credentials.conf('version') + if api_version == '': + api_version = None + + if not api_version: + api_version = 3 + + # Match for v4 api key + regex_v4 = re.compile('^[0-9a-f]{64}$') + regex_match = regex_v4.match(api_key) + if regex_match: + api_version = 4 + else: + api_version = int(api_version) + + return _LinodeLexiconClient(api_key, api_version) class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): @@ -60,14 +80,26 @@ class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): Encapsulates all communication with the Linode API. """ - def __init__(self, api_key): + def __init__(self, api_key, api_version): super(_LinodeLexiconClient, self).__init__() - config = dns_common_lexicon.build_lexicon_config('linode', {}, { - 'auth_token': api_key, - }) + self.api_version = api_version + + if api_version == 3: + config = dns_common_lexicon.build_lexicon_config('linode', {}, { + 'auth_token': api_key, + }) + + self.provider = linode.Provider(config) + elif api_version == 4: + config = dns_common_lexicon.build_lexicon_config('linode4', {}, { + 'auth_token': api_key, + }) - self.provider = linode.Provider(config) + self.provider = linode4.Provider(config) + else: + raise errors.PluginError('Invalid api version specified: {0}. (Supported: 3, 4)' + .format(api_version)) def _handle_general_error(self, e, domain_name): if not str(e).startswith('Domain not found'): diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py index c1a4e0ec0..153f8b51d 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py +++ b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py @@ -4,12 +4,16 @@ import unittest import mock +from certbot import errors from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util +from certbot_dns_linode.dns_linode import Authenticator TOKEN = 'a-token' +TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64' +TOKEN_V4 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest): @@ -17,8 +21,6 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_linode.dns_linode import Authenticator - path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"linode_key": TOKEN}, path) @@ -31,6 +33,89 @@ class AuthenticatorTest(test_util.TempDirTestCase, # _get_linode_client | pylint: disable=protected-access self.auth._get_linode_client = mock.MagicMock(return_value=self.mock_client) + # pylint: disable=protected-access + def test_api_version_3_detection(self): + path = os.path.join(self.tempdir, 'file_3_auto.ini') + dns_test_common.write({"linode_key": TOKEN_V3}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_detection(self): + path = os.path.join(self.tempdir, 'file_4_auto.ini') + dns_test_common.write({"linode_key": TOKEN_V4}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_3_detection_empty_version(self): + path = os.path.join(self.tempdir, 'file_3_auto_empty.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": ""}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_detection_empty_version(self): + path = os.path.join(self.tempdir, 'file_4_auto_empty.ini') + dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": ""}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_3_manual(self): + path = os.path.join(self.tempdir, 'file_3_manual.ini') + dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": 3}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_manual(self): + path = os.path.join(self.tempdir, 'file_4_manual.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 4}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_error(self): + path = os.path.join(self.tempdir, 'file_version_error.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 5}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + self.assertRaises(errors.PluginError, auth._get_linode_client) + class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): DOMAIN_NOT_FOUND = Exception('Domain not found') @@ -38,7 +123,19 @@ class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex def setUp(self): from certbot_dns_linode.dns_linode import _LinodeLexiconClient - self.client = _LinodeLexiconClient(TOKEN) + self.client = _LinodeLexiconClient(TOKEN, 3) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + +class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + DOMAIN_NOT_FOUND = Exception('Domain not found') + + def setUp(self): + from certbot_dns_linode.dns_linode import _LinodeLexiconClient + + self.client = _LinodeLexiconClient(TOKEN, 4) self.provider_mock = mock.MagicMock() self.client.provider = self.provider_mock diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 2b3ba9f32..d48a789bb 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,3 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -e .[dev] +dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index e43ab8de9..771e09381 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -7,7 +7,7 @@ version = '0.34.0.dev0' install_requires = [ 'acme>=0.31.0', 'certbot>=0.34.0.dev0', - 'dns-lexicon>=2.2.1', + 'dns-lexicon>=2.2.3', 'mock', 'setuptools', 'zope.interface', -- cgit v1.2.3 From 463d0894078cce837fb3eb03a51d6d4ea2d890a1 Mon Sep 17 00:00:00 2001 From: Tim White Date: Fri, 29 Mar 2019 10:15:41 +1000 Subject: Detect private DNS zones in Google and skip them until we get to a public zone --- certbot-dns-google/certbot_dns_google/dns_google.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/certbot-dns-google/certbot_dns_google/dns_google.py b/certbot-dns-google/certbot_dns_google/dns_google.py index 6144acac3..b722a38cf 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/dns_google.py @@ -274,10 +274,11 @@ class _GoogleClient(object): raise errors.PluginError('Encountered error finding managed zone: {0}' .format(e)) - if zones: - zone_id = zones[0]['id'] - logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) - return zone_id + for zone in zones: + zone_id = zone['id'] + if 'privateVisibilityConfig' not in zone: + logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) + return zone_id raise errors.PluginError('Unable to determine managed zone for {0} using zone names: {1}.' .format(domain, zone_dns_name_guesses)) -- cgit v1.2.3 From 352218510a0bd22385b30d4f6d1d16cc2fd23c87 Mon Sep 17 00:00:00 2001 From: Tim White Date: Fri, 29 Mar 2019 10:20:43 +1000 Subject: Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1af9ffb3..f76b67654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fixed Google DNS Challenge issues when private zones exist 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 -- cgit v1.2.3 From c99079fb0ac308ebd363bb0296e1524e8a5d77d4 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 26 Apr 2019 12:43:09 -0700 Subject: Warn install users that future versions of certbot will automatically redirect (#6976) First step of #6960. * Warn install users that future versions of certbot will automatically redirect * Only warn when the user declines or auto-declines redirect * Unit tests * Update changelog --- CHANGELOG.md | 3 +++ certbot/client.py | 5 +++++ certbot/tests/client_test.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1af9ffb3..d80f1baa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). `malformed` error to be received from the ACME server. * Linode DNS plugin now supports api keys created from their new panel at [cloud.linode.com](https://cloud.linode.com) +* Adding a warning noting that future versions of Certbot will automatically configure the + webserver so that all requests redirect to secure HTTPS access. You can control this + behavior and disable this warning with the --redirect and --no-redirect flags. ### Fixed diff --git a/certbot/client.py b/certbot/client.py index 5ec3c4d92..3c4a894ae 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -549,6 +549,11 @@ class Client(object): if ask_redirect: if config_name == "redirect" and config_value is None: config_value = enhancements.ask(enhancement_name) + if not config_value: + logger.warning("Future versions of Certbot will automatically " + "configure the webserver so that all requests redirect to secure " + "HTTPS access. You can control this behavior and disable this " + "warning with the --redirect and --no-redirect flags.") if config_value: self.apply_enhancement(domains, enhancement_name, option) enhanced = True diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 711346dbb..844758ad5 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -564,6 +564,21 @@ class EnhanceConfigTest(ClientTestCommon): self.assertEqual(mock_log.warning.call_args[0][1], 'redirect') + @mock.patch("certbot.client.logger") + def test_config_set_no_warning_redirect(self, mock_log): + self.config.redirect = False + self._test_with_already_existing() + self.assertFalse(mock_log.warning.called) + + @mock.patch("certbot.client.enhancements.ask") + @mock.patch("certbot.client.logger") + def test_warn_redirect(self, mock_log, mock_ask): + self.config.redirect = None + mock_ask.return_value = False + self._test_with_already_existing() + self.assertTrue(mock_log.warning.called) + self.assertTrue("disable" in mock_log.warning.call_args[0][0]) + def test_no_ask_hsts(self): self.config.hsts = True self._test_with_all_supported() -- cgit v1.2.3 From a1dc63a0a226b296c6679c1787cfe60c69b7e943 Mon Sep 17 00:00:00 2001 From: Ricky Grassmuck Date: Sun, 28 Apr 2019 21:45:27 -0500 Subject: Allow algorithm in certbot_dns_rfc2136's config to be case insensitive Update dns_rfc2136_test to use a mixed-case test value in the valid algorithm test. --- certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py | 2 +- certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index f985c9bf4..2061374e0 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -57,7 +57,7 @@ class Authenticator(dns_common.DNSAuthenticator): def _validate_algorithm(self, credentials): algorithm = credentials.conf('algorithm') if algorithm: - if not self.ALGORITHMS.get(algorithm): + if not self.ALGORITHMS.get(algorithm.upper()): raise errors.PluginError("Unknown algorithm: {0}.".format(algorithm)) def _setup_credentials(self): diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index 2eb0517f6..d800f1ec7 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -64,7 +64,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def test_valid_algorithm_passes(self): config = VALID_CONFIG.copy() - config["rfc2136_algorithm"] = "HMAC-SHA512" + config["rfc2136_algorithm"] = "HMAC-sha512" dns_test_common.write(config, self.config.rfc2136_credentials) self.auth.perform([self.achall]) -- cgit v1.2.3 From 1aa111f94169b83dfe8da6c01390421e463e7a97 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 29 Apr 2019 21:59:45 -0700 Subject: Fix typo and add instructions for changing a single dependency (#6978) * Fix typo and add instructions for changing a single dependency * Only mention installing hashin --- letsencrypt-auto-source/rebuild_dependencies.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index 22c89fae6..7096e226c 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -217,7 +217,12 @@ def _write_requirements(dest_file, requirements, conflicts): # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \\ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ''') -- cgit v1.2.3 From b41a992545c3ae4b57bd52eb8598f31cb6da23d5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 30 Apr 2019 09:42:58 -0700 Subject: Use archive.org instead of ietf.org directly. (#7004) Fixes the failing website builds at https://travis-ci.com/certbot/website/builds/110049706. --- docs/ciphers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ciphers.rst b/docs/ciphers.rst index b748dd87a..c3d6abc42 100644 --- a/docs/ciphers.rst +++ b/docs/ciphers.rst @@ -286,7 +286,7 @@ https://weakdh.org/sysadmin.html These lists may have been derived from Mozilla's recommendations. One of the authors clarified his view of the priorities for various changes as a result of the research at -https://www.ietf.org/mail-archive/web/tls/current/msg16496.html +https://web.archive.org/web/20150526022820/https://www.ietf.org/mail-archive/web/tls/current/msg16496.html In particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn't clear from the group's original recommendations). -- cgit v1.2.3 From d1330efe41c4939098fd37c5746c68919fe166b7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 30 Apr 2019 10:45:03 -0700 Subject: Print warning when certbot-auto has insecure permissions. (#6995) This PR attempts to better inform people about the problem identified at https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/. I was hesitant to add the flag --no-permissions-check, however, if there's some obscure distro out there (or custom user setup) that has a strange users and groups, I didn't want us to either: Have to put out a bug fix release Refuse to fix the problem and let them deal with warnings on every run * add check_permissions.py * Update letsencrypt-auto.template. * build letsencrypt-auto * Add test_permissions_warnings to auto_test * Allow uid/gid < 1000. * Add --no-permissions-check to Certbot. * Add --no-permissions-check to certbot-auto. * Add test farm test that letsencrypt-auto is quiet. As a bonus, this new test will catch problems like the one that the caused 0.33.1 point release. * Update CHANGELOG about permissions check. * Update permissions comment. * Fix symlink handling. * Use a better default in auto_test.py. --- CHANGELOG.md | 6 + certbot/cli.py | 5 + certbot/constants.py | 1 + certbot/tests/cli_test.py | 4 + letsencrypt-auto-source/letsencrypt-auto | 112 +++++++++++++++++- letsencrypt-auto-source/letsencrypt-auto.template | 31 ++++- .../pieces/check_permissions.py | 81 +++++++++++++ letsencrypt-auto-source/tests/auto_test.py | 130 ++++++++++++++++++--- .../test_letsencrypt_auto_certonly_standalone.sh | 14 ++- 9 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 letsencrypt-auto-source/pieces/check_permissions.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d80f1baa5..275913bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Adding a warning noting that future versions of Certbot will automatically configure the webserver so that all requests redirect to secure HTTPS access. You can control this behavior and disable this warning with the --redirect and --no-redirect flags. +* certbot-auto now prints warnings when run as root with insecure file system + permissions. If you see these messages, you should fix the problem by + following the instructions at + https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, + however, these warnings can be disabled as necessary with the flag + --no-permissions-check. ### Fixed diff --git a/certbot/cli.py b/certbot/cli.py index 96f58caf7..866b64aa6 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1089,6 +1089,11 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis help="(certbot-auto only) prevent the certbot-auto script from" " installing OS-level dependencies (default: Prompt to install " " OS-wide dependencies, but exit if the user says 'No')") + helpful.add( + "automation", "--no-permissions-check", action="store_true", + default=flag_default("no_permissions_check"), + help="(certbot-auto only) skip the check on the file system" + " permissions of the certbot-auto script") helpful.add( ["automation", "renew", "certonly", "run"], "-q", "--quiet", dest="quiet", action="store_true", diff --git a/certbot/constants.py b/certbot/constants.py index c23effe2d..5b268e157 100644 --- a/certbot/constants.py +++ b/certbot/constants.py @@ -46,6 +46,7 @@ CLI_DEFAULTS = dict( duplicate=False, os_packages_only=False, no_self_upgrade=False, + no_permissions_check=False, no_bootstrap=False, quiet=False, staging=False, diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 592bd1be7..8259d4040 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -453,6 +453,10 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods for topic in ['all', 'plugins', 'dns-route53']: self.assertFalse('certbot-route53:auth' in self._help_output([help_flag, topic])) + def test_no_permissions_check_accepted(self): + namespace = self.parse(["--no-permissions-check"]) + self.assertTrue(namespace.no_permissions_check) + class DefaultTest(unittest.TestCase): """Tests for certbot.cli._Default.""" diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 15305da39..ce57ca682 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -45,6 +45,7 @@ Help for certbot itself cannot be provided until it is installed. -h, --help print this help -n, --non-interactive, --noninteractive run without asking for user input --no-bootstrap do not install OS dependencies + --no-permissions-check do not warn about file system permissions --no-self-upgrade do not download updates --os-packages-only install OS dependencies and exit --install-only install certbot, upgrade if needed, and exit @@ -67,6 +68,8 @@ for arg in "$@" ; do # Do not upgrade this script (also prevents client upgrades, because each # copy of the script pins a hash of the python client) NO_SELF_UPGRADE=1;; + --no-permissions-check) + NO_PERMISSIONS_CHECK=1;; --no-bootstrap) NO_BOOTSTRAP=1;; --help) @@ -172,7 +175,11 @@ SetRootAuthMechanism() { sudo) SUDO="sudo -E" ;; - '') ;; # Nothing to do for plain root method. + '') + # If we're not running with root, don't check that this script can only + # be modified by system users and groups. + NO_PERMISSIONS_CHECK=1 + ;; *) error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." exit 1 @@ -1494,6 +1501,108 @@ else exit 0 fi + DeterminePythonVersion "NOCRASH" + # Don't warn about file permissions if the user disabled the check or we + # can't find an up-to-date Python. + if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF + # --------------------------------------------------------------------------- + # If the script fails for some reason, don't break certbot-auto. + set +e + # Suppress unexpected error output and only print the script's output if it + # ran successfully. + CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_STATUS="$?" + set -e + if [ "$CHECK_PERM_STATUS" = 0 ]; then + error "$CHECK_PERM_OUT" + fi + fi + if [ "$NO_SELF_UPGRADE" != 1 ]; then TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT @@ -1650,7 +1759,6 @@ if __name__ == '__main__': UNLIKELY_EOF # --------------------------------------------------------------------------- - DeterminePythonVersion "NOCRASH" if [ "$PYVER" -lt "$MIN_PYVER" ]; then 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 diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index a5a29c483..21db0f908 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -45,6 +45,7 @@ Help for certbot itself cannot be provided until it is installed. -h, --help print this help -n, --non-interactive, --noninteractive run without asking for user input --no-bootstrap do not install OS dependencies + --no-permissions-check do not warn about file system permissions --no-self-upgrade do not download updates --os-packages-only install OS dependencies and exit --install-only install certbot, upgrade if needed, and exit @@ -67,6 +68,8 @@ for arg in "$@" ; do # Do not upgrade this script (also prevents client upgrades, because each # copy of the script pins a hash of the python client) NO_SELF_UPGRADE=1;; + --no-permissions-check) + NO_PERMISSIONS_CHECK=1;; --no-bootstrap) NO_BOOTSTRAP=1;; --help) @@ -172,7 +175,11 @@ SetRootAuthMechanism() { sudo) SUDO="sudo -E" ;; - '') ;; # Nothing to do for plain root method. + '') + # If we're not running with root, don't check that this script can only + # be modified by system users and groups. + NO_PERMISSIONS_CHECK=1 + ;; *) error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." exit 1 @@ -652,6 +659,27 @@ else exit 0 fi + DeterminePythonVersion "NOCRASH" + # Don't warn about file permissions if the user disabled the check or we + # can't find an up-to-date Python. + if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" +{{ check_permissions.py }} +UNLIKELY_EOF + # --------------------------------------------------------------------------- + # If the script fails for some reason, don't break certbot-auto. + set +e + # Suppress unexpected error output and only print the script's output if it + # ran successfully. + CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_STATUS="$?" + set -e + if [ "$CHECK_PERM_STATUS" = 0 ]; then + error "$CHECK_PERM_OUT" + fi + fi + if [ "$NO_SELF_UPGRADE" != 1 ]; then TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT @@ -660,7 +688,6 @@ else {{ fetch.py }} UNLIKELY_EOF # --------------------------------------------------------------------------- - DeterminePythonVersion "NOCRASH" if [ "$PYVER" -lt "$MIN_PYVER" ]; then 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 diff --git a/letsencrypt-auto-source/pieces/check_permissions.py b/letsencrypt-auto-source/pieces/check_permissions.py new file mode 100644 index 000000000..ba55e6d97 --- /dev/null +++ b/letsencrypt-auto-source/pieces/check_permissions.py @@ -0,0 +1,81 @@ +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/letsencrypt-auto-source/tests/auto_test.py b/letsencrypt-auto-source/tests/auto_test.py index 16c478f20..9c823fb55 100644 --- a/letsencrypt-auto-source/tests/auto_test.py +++ b/letsencrypt-auto-source/tests/auto_test.py @@ -4,13 +4,13 @@ from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from contextlib import contextmanager from functools import partial from json import dumps -from os import chmod, environ, makedirs +from os import chmod, environ, makedirs, stat from os.path import abspath, dirname, exists, join import re from shutil import copy, rmtree import socket import ssl -from stat import S_IRUSR, S_IXUSR +from stat import S_IMODE, S_IRUSR, S_IWUSR, S_IXUSR, S_IWGRP, S_IWOTH from subprocess import CalledProcessError, Popen, PIPE import sys from tempfile import mkdtemp @@ -192,7 +192,7 @@ def install_le_auto(contents, install_path): chmod(install_path, S_IRUSR | S_IXUSR) -def run_le_auto(le_auto_path, venv_dir, base_url, **kwargs): +def run_le_auto(le_auto_path, venv_dir, base_url=None, le_auto_args_str='--version', **kwargs): """Run the prebuilt version of letsencrypt-auto, returning stdout and stderr strings. @@ -201,13 +201,17 @@ def run_le_auto(le_auto_path, venv_dir, base_url, **kwargs): """ env = environ.copy() d = dict(VENV_PATH=venv_dir, - # URL to PyPI-style JSON that tell us the latest released version - # of LE: - LE_AUTO_JSON_URL=base_url + 'certbot/json', - # URL to dir containing letsencrypt-auto and letsencrypt-auto.sig: - LE_AUTO_DIR_TEMPLATE=base_url + '%s/', - # The public key corresponding to signing.key: - LE_AUTO_PUBLIC_KEY="""-----BEGIN PUBLIC KEY----- + NO_CERT_VERIFY='1', + **kwargs) + + if base_url is not None: + # URL to PyPI-style JSON that tell us the latest released version + # of LE: + d['LE_AUTO_JSON_URL'] = base_url + 'certbot/json' + # URL to dir containing letsencrypt-auto and letsencrypt-auto.sig: + d['LE_AUTO_DIR_TEMPLATE'] = base_url + '%s/' + # The public key corresponding to signing.key: + d['LE_AUTO_PUBLIC_KEY'] = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMoSzLYQ7E1sdSOkwelg tzKIh2qi3bpXuYtcfFC0XrvWig071NwIj+dZiT0OLZ2hPispEH0B7ISuuWg1ll7G hFW0VdbxL6JdGzS2ShNWkX9hE9z+j8VqwDPOBn3ZHm03qwpYkBDwQib3KqOdYbTT @@ -215,12 +219,12 @@ uUtJmmGcuk3a9Aq/sCT6DdfmTSdP5asdQYwIcaQreDrOosaS84DTWI3IU+UYJVgl LsIVPBuy9IcgHidUQ96hJnoPsDCWsHwX62495QKEarauyKQrJzFes0EY95orDM47 Z5o/NDiQB11m91yNB0MmPYY9QSbnOA9j7IaaC97AwRLuwXY+/R2ablTcxurWou68 iQIDAQAB ------END PUBLIC KEY-----""", - NO_CERT_VERIFY='1', - **kwargs) +-----END PUBLIC KEY-----""" + env.update(d) + return out_and_err( - le_auto_path + ' --version', + le_auto_path + ' ' + le_auto_args_str, shell=True, env=env) @@ -240,6 +244,12 @@ def set_le_script_version(venv_dir, version): chmod(letsencrypt_path, S_IRUSR | S_IXUSR) +def sudo_chmod(path, mode): + """Runs `sudo chmod mode path`.""" + mode = oct(mode).replace('o', '') + out_and_err(['sudo', 'chmod', mode, path]) + + class AutoTests(TestCase): """Test the major branch points of letsencrypt-auto: @@ -395,3 +405,95 @@ class AutoTests(TestCase): else: self.fail("Pip didn't detect a bad hash and stop the " "installation.") + + def test_permissions_warnings(self): + """Make sure letsencrypt-auto properly warns about permissions problems.""" + # This test assumes that only the parent of the directory containing + # letsencrypt-auto (usually /tmp) may have permissions letsencrypt-auto + # considers insecure. + with temp_paths() as (le_auto_path, venv_dir): + le_auto_path = abspath(le_auto_path) + le_auto_dir = dirname(le_auto_path) + le_auto_dir_parent = dirname(le_auto_dir) + install_le_auto(self.NEW_LE_AUTO, le_auto_path) + + run_letsencrypt_auto = partial( + run_le_auto, le_auto_path, venv_dir, + le_auto_args_str='--install-only --no-self-upgrade', + PIP_FIND_LINKS=join(tests_dir(), 'fake-letsencrypt', 'dist')) + # Run letsencrypt-auto once with current permissions to avoid + # potential problems when the script tries to write to temporary + # directories. + run_letsencrypt_auto() + + le_auto_dir_mode = stat(le_auto_dir).st_mode + le_auto_dir_parent_mode = S_IMODE(stat(le_auto_dir_parent).st_mode) + try: + # Make letsencrypt-auto happy with the current permissions + chmod(le_auto_dir, S_IRUSR | S_IXUSR) + sudo_chmod(le_auto_dir_parent, 0o755) + + self._test_permissions_warnings_about_path(le_auto_path, run_letsencrypt_auto) + self._test_permissions_warnings_about_path(le_auto_dir, run_letsencrypt_auto) + finally: + chmod(le_auto_dir, le_auto_dir_mode) + sudo_chmod(le_auto_dir_parent, le_auto_dir_parent_mode) + + def _test_permissions_warnings_about_path(self, path, run_le_auto_func): + # Test that there are no problems with the current permissions + out, _ = run_le_auto_func() + self.assertFalse('insecure permissions' in out) + + stat_result = stat(path) + original_mode = stat_result.st_mode + + # Test world permissions + chmod(path, original_mode | S_IWOTH) + out, _ = run_le_auto_func() + self.assertTrue('insecure permissions' in out) + + # Test group permissions + if stat_result.st_gid >= 1000: + chmod(path, original_mode | S_IWGRP) + out, _ = run_le_auto_func() + self.assertTrue('insecure permissions' in out) + + # Test owner permissions + if stat_result.st_uid >= 1000: + chmod(path, original_mode | S_IWUSR) + out, _ = run_le_auto_func() + self.assertTrue('insecure permissions' in out) + + # Test that permissions were properly restored + chmod(path, original_mode) + out, _ = run_le_auto_func() + self.assertFalse('insecure permissions' in out) + + def test_disabled_permissions_warnings(self): + """Make sure that letsencrypt-auto permissions warnings can be disabled.""" + with temp_paths() as (le_auto_path, venv_dir): + le_auto_path = abspath(le_auto_path) + install_le_auto(self.NEW_LE_AUTO, le_auto_path) + + le_auto_args_str='--install-only --no-self-upgrade' + pip_links=join(tests_dir(), 'fake-letsencrypt', 'dist') + out, _ = run_le_auto(le_auto_path, venv_dir, + le_auto_args_str=le_auto_args_str, + PIP_FIND_LINKS=pip_links) + self.assertTrue('insecure permissions' in out) + + # Test that warnings are disabled when the script isn't run as + # root. + out, _ = run_le_auto(le_auto_path, venv_dir, + le_auto_args_str=le_auto_args_str, + LE_AUTO_SUDO='', + PIP_FIND_LINKS=pip_links) + self.assertFalse('insecure permissions' in out) + + # Test that --no-permissions-check disables warnings. + le_auto_args_str += ' --no-permissions-check' + out, _ = run_le_auto( + le_auto_path, venv_dir, + le_auto_args_str=le_auto_args_str, + PIP_FIND_LINKS=pip_links) + self.assertFalse('insecure permissions' in out) diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 901d01e4a..035512ef7 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -9,7 +9,13 @@ set -eo pipefail #private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4) cd letsencrypt -export PATH="$PWD/letsencrypt-auto-source:$PATH" +LE_AUTO_DIR="/usr/local/bin" +LE_AUTO_PATH="$LE_AUTO_DIR/letsencrypt-auto" +sudo cp letsencrypt-auto-source/letsencrypt-auto "$LE_AUTO_PATH" +sudo chown root "$LE_AUTO_PATH" +sudo chmod 0755 "$LE_AUTO_PATH" +export PATH="$LE_AUTO_DIR:$PATH" + letsencrypt-auto --os-packages-only --debug --version # Create a venv-like layout at the old virtual environment path to test that a @@ -35,3 +41,9 @@ if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBC echo "letsencrypt-auto not included in help output!" exit 1 fi + +OUTPUT=$(letsencrypt-auto --install-only --no-self-upgrade --quiet 2>&1) +if [ -n "$OUTPUT" ]; then + echo letsencrypt-auto produced unexpected output! + exit 1 +fi -- cgit v1.2.3 From dcf89c9396f20ddb1df9126f7f9207a459df00d9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 30 Apr 2019 11:59:05 -0700 Subject: Update Lexicon dependency in dnsimple (#7008) * Add CERTBOT_OLDEST conditional to setup.py. * Unset CERTBOT_OLDEST in release script. * import os --- certbot-dns-dnsimple/setup.py | 14 +++++++++++++- tools/_release.sh | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 4512f9fc0..9088e8113 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -1,3 +1,4 @@ +import os from setuptools import setup from setuptools import find_packages @@ -9,12 +10,23 @@ version = '0.34.0.dev0' install_requires = [ 'acme>=0.31.0', 'certbot>=0.34.0.dev0', - 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', 'zope.interface', ] +# This package normally depends on dns-lexicon>=3.2.1 to address the +# problem described in https://github.com/AnalogJ/lexicon/issues/387, +# however, the fix there has been backported to older versions of +# lexicon found in various Linux distros. This conditional helps us test +# that we've maintained compatibility with these versions of lexicon +# which allows us to potentially upgrade our packages in these distros +# as necessary. +if os.environ.get('CERTBOT_OLDEST') == '1': + install_requires.append('dns-lexicon>=2.2.1') +else: + install_requires.append('dns-lexicon>=3.2.1') + docs_extras = [ 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags 'sphinx_rtd_theme', diff --git a/tools/_release.sh b/tools/_release.sh index 7751f15b9..e228bae99 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -109,6 +109,9 @@ SetVersion() { SetVersion "$version" +# Unset CERTBOT_OLDEST to prevent wheels from being built improperly due to +# conditionals like the one found in certbot-dns-dnsimple's setup.py file. +unset CERTBOT_OLDEST echo "Preparing sdists and wheels" for pkg_dir in . $SUBPKGS_NO_CERTBOT do -- cgit v1.2.3 From f0f5bb4fc0f238cead49ca4db562844e49971021 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 30 Apr 2019 13:13:37 -0700 Subject: Update test farm version of boulder to current master (#7002) Recent changes are no longer compatible with the old version of boulder used in the test farm tests. This PR updates the version of boulder used, and runs it with the new way of running boulder. A new ami was created and is used here that uses Ubuntu 18.04, so that docker-compose can be installed more properly. Removed commented-out section about rabbitmq that was already deprecated. Switched to using the public DNS resolver 8.8.8.8 for the tests because the way to find the correct local resolver changed. --- tests/letstest/multitester.py | 5 ++--- tests/letstest/scripts/boulder_config.sh | 26 +++++++++----------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index b8ae937ad..430acb634 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -98,7 +98,7 @@ PROFILE = cl_args.aws_profile # Globals #------------------------------------------------------------------------------- -BOULDER_AMI = 'ami-5f490b35' # premade shared boulder AMI 14.04LTS us-east-1 +BOULDER_AMI = 'ami-072a9534772bec854' # premade shared boulder AMI 18.04LTS us-east-1 LOGDIR = "" #points to logging / working directory # boto3/AWS api globals AWS_SESSION = None @@ -290,8 +290,7 @@ def deploy_script(scriptpath, *args): def run_boulder(): with cd('$GOPATH/src/github.com/letsencrypt/boulder'): - run('go run cmd/rabbitmq-setup/main.go -server amqp://localhost') - run('nohup ./start.py >& /dev/null < /dev/null &') + run('sudo docker-compose up -d') def config_and_launch_boulder(instance): execute(deploy_script, 'scripts/boulder_config.sh') diff --git a/tests/letstest/scripts/boulder_config.sh b/tests/letstest/scripts/boulder_config.sh index 1ef63ca10..b99bbabbe 100755 --- a/tests/letstest/scripts/boulder_config.sh +++ b/tests/letstest/scripts/boulder_config.sh @@ -1,32 +1,24 @@ #!/bin/bash -x # Configures and Launches Boulder Server installed on -# us-east-1 ami-5f490b35 bouldertestserver (boulder commit 8b433f54dab) +# us-east-1 ami-072a9534772bec854 bouldertestserver3 (boulder commit b24fe7c3ea4) # fetch instance data from EC2 metadata service public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname) public_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-ipv4) private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4) -# get local DNS resolver for VPC -resolver_ip=$(grep nameserver /etc/resolv.conf |cut -d" " -f2 |head -1) +# set to public DNS resolver +resolver_ip=8.8.8.8 resolver=$resolver_ip':53' # modifies integration testing boulder setup for local AWS VPC network # connections instead of localhost cd $GOPATH/src/github.com/letsencrypt/boulder -# configure boulder to receive outside connection on 4000 -sed -i '/listenAddress/ s/127.0.0.1:4000/'$private_ip':4000/' ./test/boulder-config.json -sed -i '/baseURL/ s/127.0.0.1:4000/'$private_ip':4000/' ./test/boulder-config.json # change test ports to real -sed -i '/httpPort/ s/5002/80/' ./test/boulder-config.json -sed -i '/httpsPort/ s/5001/443/' ./test/boulder-config.json -sed -i '/tlsPort/ s/5001/443/' ./test/boulder-config.json -# set local dns resolver -sed -i '/dnsResolver/ s/127.0.0.1:8053/'$resolver'/' ./test/boulder-config.json - -# start rabbitMQ -#go run cmd/rabbitmq-setup/main.go -server amqp://localhost -# start acme services -#nohup ./start.py >& /dev/null < /dev/null & -#./start.py +sed -i '/httpPort/ s/5002/80/' ./test/config/va.json +sed -i '/httpsPort/ s/5001/443/' ./test/config/va.json +sed -i '/tlsPort/ s/5001/443/' ./test/config/va.json +# set dns resolver +sed -i 's/"127.0.0.1:8053",/"'$resolver'"/' ./test/config/va.json +sed -i 's/"127.0.0.1:8054"//' ./test/config/va.json -- cgit v1.2.3 From 3900e56b524514d6670e7d52eb731b4dccf721da Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 30 Apr 2019 13:16:47 -0700 Subject: Update Debian Jessie AMI to continue being able to use apt (#7003) Fixes #6907. --- tests/letstest/targets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index d784071c2..d1eba43de 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -33,7 +33,7 @@ targets: type: ubuntu virt: hvm user: admin - - ami: ami-116d857a + - ami: ami-077bf3962f29d3fa4 name: debian8.1 type: ubuntu virt: hvm -- cgit v1.2.3 From b0d960f102c998d8231c0ee48952b488f10864ac Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 1 May 2019 00:37:23 +0200 Subject: Send a POST-as-GET request to query registration in ACME v2 (#6993) * Send a post-as-get request to query registration * Add changelog * Add comments. Add again a line. * Prepare code for future PR about post-as-get --- CHANGELOG.md | 2 ++ acme/acme/client.py | 29 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 275913bbd..404956d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, however, these warnings can be disabled as necessary with the flag --no-permissions-check. +* `acme` module uses now a POST-as-GET request to retrieve the registration + from an ACME v2 server ### Fixed diff --git a/acme/acme/client.py b/acme/acme/client.py index a41787756..5a8fd88ae 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -123,15 +123,6 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes """ return self.update_registration(regr, update={'status': 'deactivated'}) - def query_registration(self, regr): - """Query server about registration. - - :param messages.RegistrationResource: Existing Registration - Resource. - - """ - return self._send_recv_regr(regr, messages.UpdateRegistration()) - def _authzr_from_response(self, response, identifier=None, uri=None): authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), @@ -276,6 +267,15 @@ class Client(ClientBase): # pylint: disable=no-member return self._regr_from_response(response) + def query_registration(self, regr): + """Query server about registration. + + :param messages.RegistrationResource: Existing Registration + Resource. + + """ + return self._send_recv_regr(regr, messages.UpdateRegistration()) + def agree_to_tos(self, regr): """Agree to the terms-of-service. @@ -603,10 +603,13 @@ class ClientV2(ClientBase): Resource. """ - self.net.account = regr - updated_regr = super(ClientV2, self).query_registration(regr) - self.net.account = updated_regr - return updated_regr + self.net.account = regr # See certbot/certbot#6258 + # ACME v2 requires to use a POST-as-GET request (POST an empty JWS) here. + # This is done by passing None instead of an empty UpdateRegistration to _post(). + response = self._post(regr.uri, None) + self.net.account = self._regr_from_response(response, uri=regr.uri, + terms_of_service=regr.terms_of_service) + return self.net.account def update_registration(self, regr, update=None): """Update registration. -- cgit v1.2.3 From de88e7d7778f968d5bcb3d7f950df44977df5e50 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 1 May 2019 02:21:10 +0200 Subject: Implements specific overrides for Fedora 29+ in Apache plugin (#6988) * Start to plug specific logic for Fedora >= 29 * Invert the logic * Implement specifics for Fedora 29 * Fix config * Add documentation * Fix parser, fix tests * Fix import * Fix lint * Use LooseVersion to be fail safe on versions comparison * Remove conditional restart on fedora override * Use parent logic * Update certbot-apache/certbot_apache/tests/fedora_test.py Co-Authored-By: adferrand * Simplify restart test * Update certbot-apache/certbot_apache/override_fedora.py Co-Authored-By: adferrand * Correct test assertion * Fix pylint errors * Revert to a direct call to systemctl --- certbot-apache/certbot_apache/entrypoint.py | 20 ++- certbot-apache/certbot_apache/override_fedora.py | 98 +++++++++++ certbot-apache/certbot_apache/tests/centos_test.py | 8 +- .../certbot_apache/tests/entrypoint_test.py | 8 +- certbot-apache/certbot_apache/tests/fedora_test.py | 194 +++++++++++++++++++++ certbot/util.py | 2 +- 6 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 certbot-apache/certbot_apache/override_fedora.py create mode 100644 certbot-apache/certbot_apache/tests/fedora_test.py diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py index 6f1443507..df7297d3e 100644 --- a/certbot-apache/certbot_apache/entrypoint.py +++ b/certbot-apache/certbot_apache/entrypoint.py @@ -1,8 +1,13 @@ """ Entry point for Apache Plugin """ +# Pylint does not like disutils.version when running inside a venv. +# See: https://github.com/PyCQA/pylint/issues/73 +from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error + from certbot import util from certbot_apache import configurator from certbot_apache import override_arch +from certbot_apache import override_fedora from certbot_apache import override_darwin from certbot_apache import override_debian from certbot_apache import override_centos @@ -16,7 +21,8 @@ OVERRIDE_CLASSES = { "ubuntu": override_debian.DebianConfigurator, "centos": override_centos.CentOSConfigurator, "centos linux": override_centos.CentOSConfigurator, - "fedora": override_centos.CentOSConfigurator, + "fedora_old": override_centos.CentOSConfigurator, + "fedora": override_fedora.FedoraConfigurator, "ol": override_centos.CentOSConfigurator, "red hat enterprise linux server": override_centos.CentOSConfigurator, "rhel": override_centos.CentOSConfigurator, @@ -27,12 +33,19 @@ OVERRIDE_CLASSES = { "suse": override_suse.OpenSUSEConfigurator, } + def get_configurator(): """ Get correct configurator class based on the OS fingerprint """ - os_info = util.get_os_info() + os_name, os_version = util.get_os_info() + os_name = os_name.lower() override_class = None + + # Special case for older Fedora versions + if os_name == 'fedora' and LooseVersion(os_version) < LooseVersion('29'): + os_name = 'fedora_old' + try: - override_class = OVERRIDE_CLASSES[os_info[0].lower()] + override_class = OVERRIDE_CLASSES[os_name] except KeyError: # OS not found in the list os_like = util.get_systemd_os_like() @@ -45,4 +58,5 @@ def get_configurator(): override_class = configurator.ApacheConfigurator return override_class + ENTRYPOINT = get_configurator() diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py new file mode 100644 index 000000000..cb0bf06d0 --- /dev/null +++ b/certbot-apache/certbot_apache/override_fedora.py @@ -0,0 +1,98 @@ +""" Distribution specific override class for Fedora 29+ """ +import pkg_resources +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot import util + +from certbot_apache import apache_util +from certbot_apache import configurator +from certbot_apache import parser + + +@zope.interface.provider(interfaces.IPluginFactory) +class FedoraConfigurator(configurator.ApacheConfigurator): + """Fedora 29+ specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/httpd", + vhost_root="/etc/httpd/conf.d", + vhost_files="*.conf", + logs_root="/var/log/httpd", + ctl="httpd", + version_cmd=['httpd', '-v'], + restart_cmd=['apachectl', 'graceful'], + restart_cmd_alt=['apachectl', 'restart'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + # TODO: eventually newest version of Fedora will need their own config + "certbot_apache", "centos-options-ssl-apache.conf") + ) + + def config_test(self): + """ + Override config_test to mitigate configtest error in vanilla installation + of mod_ssl in Fedora. The error is caused by non-existent self-signed + certificates referenced by the configuration, that would be autogenerated + during the first (re)start of httpd. + """ + try: + super(FedoraConfigurator, self).config_test() + except errors.MisconfigurationError: + self._try_restart_fedora() + + def get_parser(self): + """Initializes the ApacheParser""" + return FedoraParser( + self.aug, self.option("server_root"), self.option("vhost_root"), + self.version, configurator=self) + + def _try_restart_fedora(self): + """ + Tries to restart httpd using systemctl to generate the self signed keypair. + """ + try: + util.run_script(['systemctl', 'restart', 'httpd']) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + # Finish with actual config check to see if systemctl restart helped + super(FedoraConfigurator, self).config_test() + + def _prepare_options(self): + """ + Override the options dictionary initialization to keep using apachectl + instead of httpd and so take advantages of this new bash script in newer versions + of Fedora to restart httpd. + """ + super(FedoraConfigurator, self)._prepare_options() + self.options["restart_cmd"][0] = 'apachectl' + self.options["restart_cmd_alt"][0] = 'apachectl' + self.options["conftest_cmd"][0] = 'apachectl' + + +class FedoraParser(parser.ApacheParser): + """Fedora 29+ specific ApacheParser override class""" + def __init__(self, *args, **kwargs): + # Fedora 29+ specific configuration file for Apache + self.sysconfig_filep = "/etc/sysconfig/httpd" + super(FedoraParser, self).__init__(*args, **kwargs) + + def update_runtime_variables(self): + """ Override for update_runtime_variables for custom parsing """ + # Opportunistic, works if SELinux not enforced + super(FedoraParser, self).update_runtime_variables() + self._parse_sysconfig_var() + + def _parse_sysconfig_var(self): + """ Parses Apache CLI options from Fedora configuration file """ + defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") + for k in defines: + self.variables[k] = defines[k] diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index a0c1636b0..5d16c2b55 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -43,13 +43,14 @@ class FedoraRestartTest(util.ApacheTest): vhost_root=vhost_root) self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="fedora") + os_info="fedora_old") self.vh_truth = get_vh_truth( self.temp_dir, "centos7_apache/apache") def _run_fedora_test(self): + self.assertIsInstance(self.config, override_centos.CentOSConfigurator) with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ["fedora"] + mock_info.return_value = ["fedora", "28"] self.config.config_test() def test_non_fedora_error(self): @@ -103,8 +104,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): self.temp_dir, "centos7_apache/apache") def test_get_parser(self): - self.assertTrue(isinstance(self.config.parser, - override_centos.CentOSParser)) + self.assertIsInstance(self.config.parser, override_centos.CentOSParser) @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/certbot_apache/tests/entrypoint_test.py index 6d85b0db2..9adcd46dc 100644 --- a/certbot-apache/certbot_apache/tests/entrypoint_test.py +++ b/certbot-apache/certbot_apache/tests/entrypoint_test.py @@ -6,6 +6,7 @@ import mock from certbot_apache import configurator from certbot_apache import entrypoint + class EntryPointTest(unittest.TestCase): """Entrypoint tests""" @@ -15,7 +16,12 @@ class EntryPointTest(unittest.TestCase): with mock.patch("certbot.util.get_os_info") as mock_info: for distro in entrypoint.OVERRIDE_CLASSES: - mock_info.return_value = (distro, "whatever") + return_value = (distro, "whatever") + if distro == 'fedora_old': + return_value = ('fedora', '28') + elif distro == 'fedora': + return_value = ('fedora', '29') + mock_info.return_value = return_value self.assertEqual(entrypoint.get_configurator(), entrypoint.OVERRIDE_CLASSES[distro]) diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/certbot_apache/tests/fedora_test.py new file mode 100644 index 000000000..67533fe1d --- /dev/null +++ b/certbot-apache/certbot_apache/tests/fedora_test.py @@ -0,0 +1,194 @@ +"""Test for certbot_apache.configurator for Fedora 29+ overrides""" +import unittest + +import mock + +from certbot import errors +from certbot.compat import os + +from certbot_apache import obj +from certbot_apache import override_fedora +from certbot_apache.tests import util + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "httpd/conf.d") + + aug_pre = "/files" + prefix + # TODO: eventually, these tests should have a dedicated configuration instead + # of reusing the ones from centos_test + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "centos.example.com.conf"), + os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"), + {obj.Addr.fromstring("*:80")}, + False, True, "centos.example.com"), + obj.VirtualHost( + os.path.join(prefix, "ssl.conf"), + os.path.join(aug_pre, "ssl.conf/VirtualHost"), + {obj.Addr.fromstring("_default_:443")}, + True, True, None) + ] + return vh_truth + + +class FedoraRestartTest(util.ApacheTest): + """Tests for Fedora specific self-signed certificate override""" + + # TODO: eventually, these tests should have a dedicated configuration instead + # of reusing the ones from centos_test + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(FedoraRestartTest, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def _run_fedora_test(self): + self.assertIsInstance(self.config, override_fedora.FedoraConfigurator) + self.config.config_test() + + def test_fedora_restart_error(self): + c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + with mock.patch("certbot.util.run_script") as mock_run: + mock_run.side_effect = errors.SubprocessError + self.assertRaises(errors.MisconfigurationError, + self._run_fedora_test) + + def test_fedora_restart(self): + c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + with mock.patch("certbot.util.run_script") as mock_run: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + self._run_fedora_test() + self.assertEqual(mock_test.call_count, 2) + self.assertEqual(mock_run.call_args[0][0], + ['systemctl', 'restart', 'httpd']) + + +class MultipleVhostsTestFedora(util.ApacheTest): + """Multiple vhost tests for CentOS / RHEL family of distros""" + + _multiprocess_can_split_ = True + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(MultipleVhostsTestFedora, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def test_get_parser(self): + self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) + + @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + def test_opportunistic_httpd_runtime_parsing(self, mock_get): + define_val = ( + 'Define: TEST1\n' + 'Define: TEST2\n' + 'Define: DUMP_RUN_CFG\n' + ) + mod_val = ( + 'Loaded Modules:\n' + ' mock_module (static)\n' + ' another_module (static)\n' + ) + def mock_get_cfg(command): + """Mock httpd process stdout""" + if command == ['httpd', '-t', '-D', 'DUMP_RUN_CFG']: + return define_val + elif command == ['httpd', '-t', '-D', 'DUMP_MODULES']: + return mod_val + return "" + mock_get.side_effect = mock_get_cfg + self.config.parser.modules = set() + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("fedora", "29") + self.config.parser.update_runtime_variables() + + 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) + + @mock.patch("certbot_apache.configurator.util.run_script") + def test_get_version(self, mock_run_script): + mock_run_script.return_value = ('', None) + self.assertRaises(errors.PluginError, self.config.get_version) + self.assertEqual(mock_run_script.call_args[0][0][0], 'httpd') + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + found = 0 + + for vhost in vhs: + for centos_truth in self.vh_truth: + if vhost == centos_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 2) + + @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + def test_get_sysconfig_vars(self, mock_cfg): + """Make sure we read the sysconfig OPTIONS variable correctly""" + # Return nothing for the process calls + mock_cfg.return_value = "" + self.config.parser.sysconfig_filep = os.path.realpath( + os.path.join(self.config.parser.root, "../sysconfig/httpd")) + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("fedora", "29") + self.config.parser.update_runtime_variables() + + self.assertTrue("mock_define" in self.config.parser.variables.keys()) + self.assertTrue("mock_define_too" in self.config.parser.variables.keys()) + self.assertTrue("mock_value" in self.config.parser.variables.keys()) + self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) + self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) + self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) + + @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.assertEqual(mock_run_script.call_count, 3) + + @mock.patch("certbot_apache.configurator.util.run_script") + def test_alt_restart_errors(self, mock_run_script): + mock_run_script.side_effect = [None, + errors.SubprocessError, + errors.SubprocessError] + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/util.py b/certbot/util.py index e15d02779..66e5d2524 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -323,7 +323,7 @@ def get_os_info(filepath="/etc/os-release"): # Systemd os-release parsing might be viable os_name, os_version = get_systemd_os_info(filepath=filepath) if os_name: - return (os_name, os_version) + return os_name, os_version # Fallback to platform module return get_python_os_info() -- cgit v1.2.3 From 40481e0fdb518453f92893358a7fded0be104728 Mon Sep 17 00:00:00 2001 From: Ricky Grassmuck Date: Tue, 30 Apr 2019 20:33:05 -0500 Subject: Update CHANGELOG.md Signed-off-by: Ricky Grassmuck --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d80f1baa5..9982c710e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Adding a warning noting that future versions of Certbot will automatically configure the webserver so that all requests redirect to secure HTTPS access. You can control this behavior and disable this warning with the --redirect and --no-redirect flags. +* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to + all uppercase letters before validating. This makes the value in the config case + insensitive. ### Fixed -- cgit v1.2.3 From 2ef1c512b4977fe7dfcd95ba2d43fac423546f7d Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 1 May 2019 13:21:32 -0700 Subject: Remove unused Changelog sections --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2c882e8..69d7845e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ## 0.34.0 - master -### Added - -* - ### Changed * Apache plugin now tries to restart httpd on Fedora using systemctl if a @@ -37,10 +33,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). all uppercase letters before validating. This makes the value in the config case insensitive. -### 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: -- cgit v1.2.3 From 6ba242bc3dfeeff090dbc3a4d11efbbf12dc6f62 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 1 May 2019 13:24:21 -0700 Subject: Update changelog for 0.34.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d7845e9..82eac94cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.34.0 - master +## 0.34.0 - 2019-05-01 ### Changed -- cgit v1.2.3 From 7d28480844c1ce4cd75375c2494f20ab09d7a415 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 1 May 2019 14:07:25 -0700 Subject: Release 0.34.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 +- certbot-auto | 146 ++++++++++++++++++--- certbot-compatibility-test/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 4 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 4 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 4 +- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 4 +- certbot-dns-google/local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 4 +- certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 4 +- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 4 +- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 4 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 4 +- certbot-dns-rfc2136/local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 4 +- certbot-dns-route53/local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 4 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 4 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 6 +- letsencrypt-auto | 146 ++++++++++++++++++--- letsencrypt-auto-source/certbot-auto.asc | 16 +-- letsencrypt-auto-source/letsencrypt-auto | 26 ++-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 ++-- 42 files changed, 343 insertions(+), 123 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 11e4f3372..85e9a642a 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.0.dev0' +version = '0.34.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index deb688fd2..3161402a5 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index d60bdbc70..0d9606372 100755 --- a/certbot-auto +++ b/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.33.1" +LE_AUTO_VERSION="0.34.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -45,6 +45,7 @@ Help for certbot itself cannot be provided until it is installed. -h, --help print this help -n, --non-interactive, --noninteractive run without asking for user input --no-bootstrap do not install OS dependencies + --no-permissions-check do not warn about file system permissions --no-self-upgrade do not download updates --os-packages-only install OS dependencies and exit --install-only install certbot, upgrade if needed, and exit @@ -67,6 +68,8 @@ for arg in "$@" ; do # Do not upgrade this script (also prevents client upgrades, because each # copy of the script pins a hash of the python client) NO_SELF_UPGRADE=1;; + --no-permissions-check) + NO_PERMISSIONS_CHECK=1;; --no-bootstrap) NO_BOOTSTRAP=1;; --help) @@ -172,7 +175,11 @@ SetRootAuthMechanism() { sudo) SUDO="sudo -E" ;; - '') ;; # Nothing to do for plain root method. + '') + # If we're not running with root, don't check that this script can only + # be modified by system users and groups. + NO_PERMISSIONS_CHECK=1 + ;; *) error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." exit 1 @@ -534,7 +541,7 @@ BootstrapSuseCommon() { # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv # is a source package, and python2-virtualenv must be used instead. # Also currently python2-setuptools is not a dependency of python2-virtualenv, - # while it should be. Installing it explicitly until upstreqm fix. + # while it should be. Installing it explicitly until upstream fix. OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" fi @@ -1138,9 +1145,9 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.1 \ - --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ - --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 +urllib3==1.24.2 \ + --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ + --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 @@ -1218,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.1 \ - --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ - --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 -acme==0.33.1 \ - --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ - --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 -certbot-apache==0.33.1 \ - --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ - --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a -certbot-nginx==0.33.1 \ - --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ - --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff +certbot==0.34.0 \ + --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ + --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b +acme==0.34.0 \ + --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ + --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd +certbot-apache==0.34.0 \ + --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ + --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 +certbot-nginx==0.34.0 \ + --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ + --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1494,6 +1501,108 @@ else exit 0 fi + DeterminePythonVersion "NOCRASH" + # Don't warn about file permissions if the user disabled the check or we + # can't find an up-to-date Python. + if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF + # --------------------------------------------------------------------------- + # If the script fails for some reason, don't break certbot-auto. + set +e + # Suppress unexpected error output and only print the script's output if it + # ran successfully. + CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_STATUS="$?" + set -e + if [ "$CHECK_PERM_STATUS" = 0 ]; then + error "$CHECK_PERM_OUT" + fi + fi + if [ "$NO_SELF_UPGRADE" != 1 ]; then TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT @@ -1650,7 +1759,6 @@ if __name__ == '__main__': UNLIKELY_EOF # --------------------------------------------------------------------------- - DeterminePythonVersion "NOCRASH" if [ "$PYVER" -lt "$MIN_PYVER" ]; then 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 diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 926a5e7b4..fc03fd971 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 971ce7be8..64efd115b 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 6af7bb6e7..df79af91d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 81803d7da..3444a6f8c 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 9088e8113..588541821 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,13 +3,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 51c6637a9..4f1f9d59c 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index deb5c442d..e27d0e154 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 176c74968..fc95cc06b 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', # 1.5 is the first version that supports oauth2client>=2.0 'google-api-python-client>=1.5', 'mock', diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index d48a789bb..ff1651cf7 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 771e09381..e1238ab07 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,12 +1,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index ef77e4143..9c4c74f96 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 7bb7fbbff..8a75f6d9d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index ed5aa6c87..5472399aa 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 9a05e69cc..a4da5976f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 8e1d37650..c37660aaf 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 787d4a555..4177da095 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,13 +1,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 2b3ba9f32..c9999e87a 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 286b13ee9..3d75a0279 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0.dev0' +version = '0.34.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index da509406e..0bc9ee027 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.34.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index a6da1d851..1bf6f1825 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0.dev0' +version = '0.34.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', + 'certbot>=0.34.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/__init__.py b/certbot/__init__.py index dc2ea5c99..4157090a5 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.0.dev0' +__version__ = '0.34.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index e0979b989..da5b51d3c 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.33.1 + "". (default: CertbotACMEClient/0.34.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -171,6 +171,10 @@ automation: from installing OS-level dependencies (default: Prompt to install OS-wide dependencies, but exit if the user says 'No') + --no-permissions-check + (certbot-auto only) skip the check on the file system + permissions of the certbot-auto script (default: + False) -q, --quiet Silence all output except errors. Useful for automation via cron. Implies --non-interactive. (default: False) diff --git a/letsencrypt-auto b/letsencrypt-auto index d60bdbc70..0d9606372 100755 --- a/letsencrypt-auto +++ b/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.33.1" +LE_AUTO_VERSION="0.34.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -45,6 +45,7 @@ Help for certbot itself cannot be provided until it is installed. -h, --help print this help -n, --non-interactive, --noninteractive run without asking for user input --no-bootstrap do not install OS dependencies + --no-permissions-check do not warn about file system permissions --no-self-upgrade do not download updates --os-packages-only install OS dependencies and exit --install-only install certbot, upgrade if needed, and exit @@ -67,6 +68,8 @@ for arg in "$@" ; do # Do not upgrade this script (also prevents client upgrades, because each # copy of the script pins a hash of the python client) NO_SELF_UPGRADE=1;; + --no-permissions-check) + NO_PERMISSIONS_CHECK=1;; --no-bootstrap) NO_BOOTSTRAP=1;; --help) @@ -172,7 +175,11 @@ SetRootAuthMechanism() { sudo) SUDO="sudo -E" ;; - '') ;; # Nothing to do for plain root method. + '') + # If we're not running with root, don't check that this script can only + # be modified by system users and groups. + NO_PERMISSIONS_CHECK=1 + ;; *) error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." exit 1 @@ -534,7 +541,7 @@ BootstrapSuseCommon() { # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv # is a source package, and python2-virtualenv must be used instead. # Also currently python2-setuptools is not a dependency of python2-virtualenv, - # while it should be. Installing it explicitly until upstreqm fix. + # while it should be. Installing it explicitly until upstream fix. OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" fi @@ -1138,9 +1145,9 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.1 \ - --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ - --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 +urllib3==1.24.2 \ + --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ + --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 @@ -1218,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.1 \ - --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ - --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 -acme==0.33.1 \ - --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ - --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 -certbot-apache==0.33.1 \ - --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ - --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a -certbot-nginx==0.33.1 \ - --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ - --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff +certbot==0.34.0 \ + --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ + --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b +acme==0.34.0 \ + --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ + --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd +certbot-apache==0.34.0 \ + --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ + --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 +certbot-nginx==0.34.0 \ + --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ + --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1494,6 +1501,108 @@ else exit 0 fi + DeterminePythonVersion "NOCRASH" + # Don't warn about file permissions if the user disabled the check or we + # can't find an up-to-date Python. + if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF + # --------------------------------------------------------------------------- + # If the script fails for some reason, don't break certbot-auto. + set +e + # Suppress unexpected error output and only print the script's output if it + # ran successfully. + CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_STATUS="$?" + set -e + if [ "$CHECK_PERM_STATUS" = 0 ]; then + error "$CHECK_PERM_OUT" + fi + fi + if [ "$NO_SELF_UPGRADE" != 1 ]; then TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT @@ -1650,7 +1759,6 @@ if __name__ == '__main__': UNLIKELY_EOF # --------------------------------------------------------------------------- - DeterminePythonVersion "NOCRASH" if [ "$PYVER" -lt "$MIN_PYVER" ]; then 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 diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 9e55d6cbb..0b6fb32dc 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlymhBYACgkQTRfJlc2X -dfKmDAf/bkoGkWpxgzKjfd7BELnvhZduQ5Y30P2+Kq43jnop56zjZrt53tRsKeOc -Rat2Rq3e/rozlo5ie939iF2UPIX8fzEQ/IIyk4Om17dJ9ld25hteX7HWJThUX9+t -OtKA0c7jw7nSrCmWjKtGhZoTe2nsMqAtp0LV7kZ7T7Ex0HAxjrYu48wA2h6lgloe -65rXyBDVHdVc3FvevUiHKYkt+SONyWuRZpeQ8xn6YSQNDwYzCub3ro1h55GYfOK2 -65eklH1xVo7TvvR0Wo7l1/hIiK8Gz6ZX5dqDaxHT817zO1cqB4HhkHAl2O3q7TCo -JIo1jxMzlttRGJaegwnMTi20KyimyA== -=8Gjd +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzKCkUACgkQTRfJlc2X +dfL8xwf/Sjxb5LWkbvVem9Mc8w76D4DKECQdUdwJJCPrvgkBy2LAXYmpy4ZEBETV +p+QuUk2EuUxBNc81Wdo3PNdoA3eDd8uaxMc/GPCRxSWNH/taqL0Xk7s6Jqhx6rh+ +tQNnJoTmqgWaUwQkfJXiiwlcvIdFjdOoQgZnP3YJaNVrlIi6rd4mDJ1dU7ik2Qvz +pI78mCfHokhvq1tWUFram12z045n4/lZ9uy/auA2VFnAmUvh/18h1VSTEoWJK2vW +Xuxv59G1vtG+cC4jzenMho0oVt18hdqQPOaUstzPhS9XxFuyvYMurHusZ4fysnbQ +cUofX1hY0jmaGkMHBkfjtJfdbOQXUg== +=jqpL -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ce57ca682..0d9606372 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.0.dev0" +LE_AUTO_VERSION="0.34.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1225,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.33.1 \ - --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ - --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 -acme==0.33.1 \ - --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ - --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 -certbot-apache==0.33.1 \ - --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ - --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a -certbot-nginx==0.33.1 \ - --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ - --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff +certbot==0.34.0 \ + --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ + --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b +acme==0.34.0 \ + --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ + --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd +certbot-apache==0.34.0 \ + --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ + --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 +certbot-nginx==0.34.0 \ + --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ + --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 96adf9078..3afa861cd 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index cb79bccd7..d37b22069 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.33.1 \ - --hash=sha256:e2a08467146b7a7ed2c8ca6625b1705d93b51e89866f6ede8a8a262594c18f3f \ - --hash=sha256:d5203f32c50f3ec5a32df97e4affddbcd288a569678ecb5669adda21cd5ac3d9 -acme==0.33.1 \ - --hash=sha256:02467d4b1d246105d6d1ea01822dd9e2eea5bf3a50607523969d8e400d53c07b \ - --hash=sha256:b38cdb71d0071efe1f1190a744f8f95f3c698b76ac0f5d919bbfe3522e277a82 -certbot-apache==0.33.1 \ - --hash=sha256:0d2a463539e6396de2d374de62faba34e1fe40dd8059e3c64dcd5dabaa66887b \ - --hash=sha256:659db7335d919fee52ae707567994e13c31ed25109c94b246c60c97d21c46f3a -certbot-nginx==0.33.1 \ - --hash=sha256:df9fb86e735eb2668e070f20317e85c37952f3f612fa7f6bbc2c63784b213f28 \ - --hash=sha256:b3201eee03be74fc743c21c721d3b5586c3323db63e78b68583a6250ad680cff +certbot==0.34.0 \ + --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ + --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b +acme==0.34.0 \ + --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ + --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd +certbot-apache==0.34.0 \ + --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ + --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 +certbot-nginx==0.34.0 \ + --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ + --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 -- cgit v1.2.3 From 9734be69220168588780430880fe410cfc6a2ec8 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 1 May 2019 14:07:30 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82eac94cb..77351c84b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.35.0 - 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. + ## 0.34.0 - 2019-05-01 ### Changed -- cgit v1.2.3 From 7711da9fc21aea72325c3d730b706c12fc1ffe94 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 1 May 2019 14:07:30 -0700 Subject: Bump version to 0.35.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 85e9a642a..56a9a63f3 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 3161402a5..e14bcb3b6 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index fc03fd971..c95864e09 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 64efd115b..afdfb09e1 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index df79af91d..4883150fb 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 3444a6f8c..07d406fd9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 588541821..f781bd0a5 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 4f1f9d59c..41c1b75a5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index e27d0e154..f009df8e8 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index fc95cc06b..e1d6aeed0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index e1238ab07..ae8739d61 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 9c4c74f96..b2e14869e 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 8a75f6d9d..e839cd71d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a4da5976f..a6a52d648 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index c37660aaf..e05104e5d 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 4177da095..09cd4acd2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 3d75a0279..29f458542 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 1bf6f1825..51055ce64 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 4157090a5..abec68040 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.0' +__version__ = '0.35.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 0d9606372..4e1503715 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.0" +LE_AUTO_VERSION="0.35.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 82f64126d9a1105da17f37386e1f40fcd3e97ea6 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 2 May 2019 12:46:59 -0400 Subject: Grammar (#7013) * spelling: these * grammar: either-or * spelling: e.g. --- certbot/compat/misc.py | 4 ++-- certbot/compat/os.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 2f4ba0c6b..4f0e22078 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -31,7 +31,7 @@ def raise_for_non_administrative_windows_rights(subcommand): # Because windll exists only on a Windows runtime, and static code analysis engines # do not like at all non existent objects when run from Linux (even if we handle properly # all the cases in the code). - # So we access windll only by reflection to trick theses engines. + # So we access windll only by reflection to trick these engines. if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED: windll = getattr(ctypes, 'windll') if windll.shell32.IsUserAnAdmin() == 0: @@ -73,7 +73,7 @@ def os_rename(src, dst): raise if not hasattr(os, 'replace'): # pragma: no cover # We should never go on this line. Either we are on Linux and os.rename has succeeded, - # either we are on Windows, and only Python >= 3.4 is supported where os.replace is + # or we are on Windows, and only Python >= 3.4 is supported where os.replace is # available. raise RuntimeError('Error: tried to run os_rename on Python < 3.3. ' 'Certbot supports only Python 3.4 >= on Windows.') diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 0112fbc73..8a139a141 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -1,6 +1,6 @@ """ This compat modules is a wrapper of the core os module that forbids usage of specific operations -(eg. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. +(e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. This module is intended to replace standard os module throughout certbot projects (except acme). """ from __future__ import absolute_import -- cgit v1.2.3 From 862577fffc5ea6b29a9bb519d7b850b454d23e84 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 11:32:49 -0700 Subject: Bump initial version to 0.33.1. (#7017) We made this change locally yesterday while preparing the release. I tested this change on all AMIs currently in the test farm as well as Fedora 29 and this test passed on all instances. --- tests/letstest/scripts/test_leauto_upgrades.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index d565aa268..d5133ba38 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -15,8 +15,8 @@ if ! command -v git ; then exit 1 fi fi -# 0.18.0 is the oldest version of letsencrypt-auto that works on Fedora 26+. -INITIAL_VERSION="0.18.0" +# 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. +INITIAL_VERSION="0.33.1" git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail -- cgit v1.2.3 From e15e848474a5c180b2f22cd059c44905a11568ef Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 11:36:47 -0700 Subject: Stop certbot-auto from printing blank lines (#7016) Fixes #7012. Apparently, the previous test we had here doesn't catch the case when certbot-auto prints blank lines. (I don't yet understand why so if someone does, please let me know!) Regardless, I fixed up the test and verified it fails with the version of letsencrypt-auto in master and then fixed letsencrypt-auto so the test passes. I ran test farm tests on the changes here and they passed on all instances. * correct test * fixes #7012 --- letsencrypt-auto-source/letsencrypt-auto | 8 +++++--- letsencrypt-auto-source/letsencrypt-auto.template | 8 +++++--- .../letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 4e1503715..f9f02da62 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1593,12 +1593,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 21db0f908..bff4173d4 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -670,12 +670,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 035512ef7..0973bbc03 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -42,8 +42,8 @@ if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBC exit 1 fi -OUTPUT=$(letsencrypt-auto --install-only --no-self-upgrade --quiet 2>&1) -if [ -n "$OUTPUT" ]; then +OUTPUT_LEN=$(letsencrypt-auto --install-only --no-self-upgrade --quiet 2>&1 | wc -c) +if [ "$OUTPUT_LEN" != 0 ]; then echo letsencrypt-auto produced unexpected output! exit 1 fi -- cgit v1.2.3 From 698e52004490e99996a191764278a751aac66ac5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 11:36:47 -0700 Subject: Stop certbot-auto from printing blank lines (#7016) Fixes #7012. Apparently, the previous test we had here doesn't catch the case when certbot-auto prints blank lines. (I don't yet understand why so if someone does, please let me know!) Regardless, I fixed up the test and verified it fails with the version of letsencrypt-auto in master and then fixed letsencrypt-auto so the test passes. I ran test farm tests on the changes here and they passed on all instances. * correct test * fixes #7012 (cherry picked from commit e15e848474a5c180b2f22cd059c44905a11568ef) --- letsencrypt-auto-source/letsencrypt-auto | 8 +++++--- letsencrypt-auto-source/letsencrypt-auto.template | 8 +++++--- .../letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 0d9606372..a4feb455f 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1593,12 +1593,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 21db0f908..bff4173d4 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -670,12 +670,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 035512ef7..0973bbc03 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -42,8 +42,8 @@ if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBC exit 1 fi -OUTPUT=$(letsencrypt-auto --install-only --no-self-upgrade --quiet 2>&1) -if [ -n "$OUTPUT" ]; then +OUTPUT_LEN=$(letsencrypt-auto --install-only --no-self-upgrade --quiet 2>&1 | wc -c) +if [ "$OUTPUT_LEN" != 0 ]; then echo letsencrypt-auto produced unexpected output! exit 1 fi -- cgit v1.2.3 From 57be329058db22ad940633f5dc5c9eabfc1628cb Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 11:32:49 -0700 Subject: Bump initial version to 0.33.1. (#7017) We made this change locally yesterday while preparing the release. I tested this change on all AMIs currently in the test farm as well as Fedora 29 and this test passed on all instances. (cherry picked from commit 862577fffc5ea6b29a9bb519d7b850b454d23e84) --- tests/letstest/scripts/test_leauto_upgrades.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index d565aa268..d5133ba38 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -15,8 +15,8 @@ if ! command -v git ; then exit 1 fi fi -# 0.18.0 is the oldest version of letsencrypt-auto that works on Fedora 26+. -INITIAL_VERSION="0.18.0" +# 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. +INITIAL_VERSION="0.33.1" git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail -- cgit v1.2.3 From b19d4801c9dea2898402c5b388da4bd10b103d01 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 May 2019 23:32:02 +0200 Subject: Fix oldest tests when local dependencies are used (#7019) Fixes #7014. Using a --force-reinstall (only for oldest tests), dependencies are properly reinstalled. Since this action significantly increases the execution time of oldest tests, I split them into two parts to allow their parallel execution by Travis. We will need to find a better way to solve this in the future. An example of successful execution of oldest tests in the situation of a point release can be found here: https://travis-ci.org/adferrand/certbot/builds/527475532 * Fix for oldest requirements * Split oldest tests * Update a comment --- .travis.yml | 7 ++++++- tools/pip_install.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b23164a19..e1859cc0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,12 @@ matrix: env: TOXENV=mypy <<: *not-on-master - python: "2.7" - env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest' + env: TOXENV='py27-{acme,apache,certbot,nginx,postfix}-oldest' + sudo: required + services: docker + <<: *not-on-master + - python: "2.7" + env: TOXENV='py27-dns-oldest' sudo: required services: docker <<: *not-on-master diff --git a/tools/pip_install.py b/tools/pip_install.py index 68268e298..abc7baa91 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -99,8 +99,15 @@ def main(args): else: # Otherwise, we merge requirements to build the constraints and pin dependencies requirements = None + reinstall = False if os.environ.get('CERTBOT_OLDEST') == '1': requirements = certbot_oldest_processing(tools_path, args, test_constraints) + # We need to --force-reinstall the tested distribution when using oldest + # requirements because of an error in these tests in particular situations + # described in https://github.com/certbot/certbot/issues/7014. + # However this slows down considerably the oldest tests (5 min -> 10 min), + # so we need to find a better mitigation in the future. + reinstall = True else: certbot_normal_processing(tools_path, test_constraints) @@ -109,8 +116,8 @@ def main(args): pip_install_with_print('--constraint "{0}" --requirement "{1}"' .format(all_constraints, requirements)) - pip_install_with_print('--constraint "{0}" {1}' - .format(all_constraints, ' '.join(args))) + pip_install_with_print('--constraint "{0}" {1} {2}'.format( + all_constraints, '--force-reinstall' if reinstall else '', ' '.join(args))) finally: if os.environ.get('TRAVIS'): print('travis_fold:end:install_certbot_deps') -- cgit v1.2.3 From 0ab2bb21faec93532779c8a39434d0af18770633 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 May 2019 23:32:02 +0200 Subject: Fix oldest tests when local dependencies are used (#7019) Fixes #7014. Using a --force-reinstall (only for oldest tests), dependencies are properly reinstalled. Since this action significantly increases the execution time of oldest tests, I split them into two parts to allow their parallel execution by Travis. We will need to find a better way to solve this in the future. An example of successful execution of oldest tests in the situation of a point release can be found here: https://travis-ci.org/adferrand/certbot/builds/527475532 * Fix for oldest requirements * Split oldest tests * Update a comment (cherry picked from commit b19d4801c9dea2898402c5b388da4bd10b103d01) --- .travis.yml | 7 ++++++- tools/pip_install.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b23164a19..e1859cc0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,12 @@ matrix: env: TOXENV=mypy <<: *not-on-master - python: "2.7" - env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest' + env: TOXENV='py27-{acme,apache,certbot,nginx,postfix}-oldest' + sudo: required + services: docker + <<: *not-on-master + - python: "2.7" + env: TOXENV='py27-dns-oldest' sudo: required services: docker <<: *not-on-master diff --git a/tools/pip_install.py b/tools/pip_install.py index 68268e298..abc7baa91 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -99,8 +99,15 @@ def main(args): else: # Otherwise, we merge requirements to build the constraints and pin dependencies requirements = None + reinstall = False if os.environ.get('CERTBOT_OLDEST') == '1': requirements = certbot_oldest_processing(tools_path, args, test_constraints) + # We need to --force-reinstall the tested distribution when using oldest + # requirements because of an error in these tests in particular situations + # described in https://github.com/certbot/certbot/issues/7014. + # However this slows down considerably the oldest tests (5 min -> 10 min), + # so we need to find a better mitigation in the future. + reinstall = True else: certbot_normal_processing(tools_path, test_constraints) @@ -109,8 +116,8 @@ def main(args): pip_install_with_print('--constraint "{0}" --requirement "{1}"' .format(all_constraints, requirements)) - pip_install_with_print('--constraint "{0}" {1}' - .format(all_constraints, ' '.join(args))) + pip_install_with_print('--constraint "{0}" {1} {2}'.format( + all_constraints, '--force-reinstall' if reinstall else '', ' '.join(args))) finally: if os.environ.get('TRAVIS'): print('travis_fold:end:install_certbot_deps') -- cgit v1.2.3 From 4bf6eb2091e3190282b0e2c6540186e64bf4d846 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 14:52:36 -0700 Subject: Update changelog for 0.34.1. (#7021) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77351c84b..84f28cea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* certbot-auto no longer prints a blank line when there are no permissions + problems. 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 -- cgit v1.2.3 From 6a970f74d0fe13c739a105e20dc8892c49b677b1 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 3 May 2019 00:17:11 +0200 Subject: Try another approach (#7022) In #7019, a solution has been integrated to fix oldest tests execution in the corner cases described in #7014. However this solution was not very satisfactory, as it consists in making a --force-reinstall for all requirements on each oldest tests (apache, certbot, acme, each dns plugin ...). As a consequence, the overall execution time of these tests increased from 5 min to 10 min. In this PR I propose a more elegant solution: instead of reinstalling all dependencies, we force reinstall only the requirements themselves describe in the relevant oldest-requirements.txt files. This way only the packages that are potentially ignored by pip because they exists locally (acme, certbot, ...) are reinstalled. The result is the same than in #7019 (we are sure that all packages are really installed by pip), but the very limited number of force reinstalled package here make the impact on execution time negligible. As a consequence, I revert back also the tox environments to execute all oldest tests together. A successful execution of oldest tests using this PR material in the context of a point release can be seen here: https://travis-ci.org/adferrand/certbot/builds/527513101 --- .travis.yml | 7 +------ tools/pip_install.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1859cc0e..b23164a19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,12 +60,7 @@ matrix: env: TOXENV=mypy <<: *not-on-master - python: "2.7" - env: TOXENV='py27-{acme,apache,certbot,nginx,postfix}-oldest' - sudo: required - services: docker - <<: *not-on-master - - python: "2.7" - env: TOXENV='py27-dns-oldest' + env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest' sudo: required services: docker <<: *not-on-master diff --git a/tools/pip_install.py b/tools/pip_install.py index abc7baa91..cf0a7aee5 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -99,25 +99,25 @@ def main(args): else: # Otherwise, we merge requirements to build the constraints and pin dependencies requirements = None - reinstall = False if os.environ.get('CERTBOT_OLDEST') == '1': requirements = certbot_oldest_processing(tools_path, args, test_constraints) - # We need to --force-reinstall the tested distribution when using oldest - # requirements because of an error in these tests in particular situations - # described in https://github.com/certbot/certbot/issues/7014. - # However this slows down considerably the oldest tests (5 min -> 10 min), - # so we need to find a better mitigation in the future. - reinstall = True else: certbot_normal_processing(tools_path, test_constraints) merge_requirements(tools_path, requirements, test_constraints, all_constraints) - if requirements: + if requirements: # This branch is executed during the oldest tests + # First step, install the transitive dependencies of oldest requirements + # in respect with oldest constraints. pip_install_with_print('--constraint "{0}" --requirement "{1}"' .format(all_constraints, requirements)) - - pip_install_with_print('--constraint "{0}" {1} {2}'.format( - all_constraints, '--force-reinstall' if reinstall else '', ' '.join(args))) + # Second step, ensure that oldest requirements themselves are effectively + # installed using --force-reinstall, and avoid corner cases like the one described + # in https://github.com/certbot/certbot/issues/7014. + pip_install_with_print('--force-reinstall --no-deps --requirement "{0}"' + .format(requirements)) + + pip_install_with_print('--constraint "{0}" {1}'.format( + all_constraints, ' '.join(args))) finally: if os.environ.get('TRAVIS'): print('travis_fold:end:install_certbot_deps') -- cgit v1.2.3 From 3410b9332cf568647ef9c59a9a5d9707283a28f6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 2 May 2019 15:28:27 -0700 Subject: Update changelog for 0.34.1. (#7021) (#7023) (cherry picked from commit 4bf6eb2091e3190282b0e2c6540186e64bf4d846) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82eac94cb..44a4bdf67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.34.1 - master + +### Fixed + +* certbot-auto no longer prints a blank line when there are no permissions + problems. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + ## 0.34.0 - 2019-05-01 ### Changed -- cgit v1.2.3 From e5cdc2738d651ffdf8b0b0beffd3c5706a33b504 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 6 May 2019 13:12:42 -0700 Subject: Update changelog for 0.34.1 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a4bdf67..1d9a0b5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.34.1 - master +## 0.34.1 - 2019-05-06 ### Fixed -- cgit v1.2.3 From 2b4d6e23d570d32b77e9043d4929c135a36ec6c7 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 6 May 2019 13:28:15 -0700 Subject: Release 0.34.1 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 34 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 34 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 +++++----- letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++-------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++-------- 26 files changed, 89 insertions(+), 85 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 85e9a642a..aeee67267 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.34.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 3161402a5..b78528297 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 0d9606372..fb1ade06d 100755 --- a/certbot-auto +++ b/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.34.0" +LE_AUTO_VERSION="0.34.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1225,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.0 \ - --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ - --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b -acme==0.34.0 \ - --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ - --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd -certbot-apache==0.34.0 \ - --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ - --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 -certbot-nginx==0.34.0 \ - --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ - --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 +certbot==0.34.1 \ + --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ + --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 +acme==0.34.1 \ + --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ + --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 +certbot-apache==0.34.1 \ + --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ + --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 +certbot-nginx==0.34.1 \ + --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ + --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1593,12 +1593,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index fc03fd971..8f11feefc 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 64efd115b..cdbd5a277 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index df79af91d..e86c3e92f 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 3444a6f8c..7e4aeb2b2 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 588541821..b30a71d71 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 4f1f9d59c..d0f3c72a0 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index e27d0e154..0c1cc88a6 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index fc95cc06b..f04c1eb44 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index e1238ab07..80848b4c0 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 9c4c74f96..12151b51b 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 8a75f6d9d..0a3aba37a 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a4da5976f..9aac9e60c 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index c37660aaf..2562ba036 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 4177da095..47d38171d 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 3d75a0279..795272312 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.0' +version = '0.34.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 1bf6f1825..cc6e8c6bc 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.0' +version = '0.34.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 4157090a5..28374e474 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.0' +__version__ = '0.34.1' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index da5b51d3c..2a577a09a 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.34.0 + "". (default: CertbotACMEClient/0.34.1 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 0d9606372..fb1ade06d 100755 --- a/letsencrypt-auto +++ b/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.34.0" +LE_AUTO_VERSION="0.34.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1225,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.0 \ - --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ - --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b -acme==0.34.0 \ - --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ - --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd -certbot-apache==0.34.0 \ - --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ - --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 -certbot-nginx==0.34.0 \ - --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ - --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 +certbot==0.34.1 \ + --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ + --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 +acme==0.34.1 \ + --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ + --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 +certbot-apache==0.34.1 \ + --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ + --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 +certbot-nginx==0.34.1 \ + --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ + --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1593,12 +1593,14 @@ UNLIKELY_EOF # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e - # Suppress unexpected error output and only print the script's output if it - # ran successfully. + # Suppress unexpected error output. CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e - if [ "$CHECK_PERM_STATUS" = 0 ]; then + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then error "$CHECK_PERM_OUT" fi fi diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 0b6fb32dc..ed67415eb 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzKCkUACgkQTRfJlc2X -dfL8xwf/Sjxb5LWkbvVem9Mc8w76D4DKECQdUdwJJCPrvgkBy2LAXYmpy4ZEBETV -p+QuUk2EuUxBNc81Wdo3PNdoA3eDd8uaxMc/GPCRxSWNH/taqL0Xk7s6Jqhx6rh+ -tQNnJoTmqgWaUwQkfJXiiwlcvIdFjdOoQgZnP3YJaNVrlIi6rd4mDJ1dU7ik2Qvz -pI78mCfHokhvq1tWUFram12z045n4/lZ9uy/auA2VFnAmUvh/18h1VSTEoWJK2vW -Xuxv59G1vtG+cC4jzenMho0oVt18hdqQPOaUstzPhS9XxFuyvYMurHusZ4fysnbQ -cUofX1hY0jmaGkMHBkfjtJfdbOQXUg== -=jqpL +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzQmLAACgkQTRfJlc2X +dfL04Af9E06u0S3Q+xroaysGFPUv2Jl1Mr1FMxk8LckuOzVQDf2hPE1WR7gJ4Csg +s5wMh+inEws45QgpihbANjNvoMHJX3mzcjYkvMhwiW2q93pU6PEWjVnLV5qx79Jh +L7gatx96S+fQ/e5LDLx7cTngDLJGYjJUbOWfHVBsYwMNotTFJNMPaTx8IAQAqaLN +1LAZDsZq/EJpdE+JhR+pXJ2xhCjWmxjmsPvUVjBhlM+gTpFw2CwKhJJtmKgV/0tG +jf8Ot3ruRCNIvonB9tD6j67nStA7i6fMn9irW9rLCu9s2PXFAYPC/tB4nvKvP1wX +OUyihTSztHA/vgm3JStXkoYA4T1tBA== +=RfRc -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index a4feb455f..fb1ade06d 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.0" +LE_AUTO_VERSION="0.34.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1225,18 +1225,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.0 \ - --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ - --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b -acme==0.34.0 \ - --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ - --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd -certbot-apache==0.34.0 \ - --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ - --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 -certbot-nginx==0.34.0 \ - --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ - --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 +certbot==0.34.1 \ + --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ + --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 +acme==0.34.1 \ + --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ + --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 +certbot-apache==0.34.1 \ + --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ + --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 +certbot-nginx==0.34.1 \ + --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ + --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 3afa861cd..743210bf0 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index d37b22069..25c333281 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.34.0 \ - --hash=sha256:51dddf2cb1c50a9f8b993090890bf4858d8fadffce38bafcdf6bf585a2040317 \ - --hash=sha256:e75bdabfd9183bd9842ada42a51070f120d15982e81c490df59dde62e4df2c8b -acme==0.34.0 \ - --hash=sha256:3448024d2c274aebfb9b31b53862576d167626ce2fd1997a78d450c32a292fa3 \ - --hash=sha256:92478e58f541c5c7c527427a50650005cdede799b78f0a0a65b8093d6368bcfd -certbot-apache==0.34.0 \ - --hash=sha256:79e686f25b63dac17d771d71f791f252774da22125f3f6e0665f4cf791d516fe \ - --hash=sha256:d5ae09b4801fbac23d5acf64a5ee265108199d2852fbe743e7b6ab06fa08edf6 -certbot-nginx==0.34.0 \ - --hash=sha256:868d7dcb59bb2548cb4a2ae187db5da1bfe33aac306b1b844b96ee00a39cac52 \ - --hash=sha256:d6c728b85c523711ec0dc800f8d4ebbef192fb0ca1ec7914c173207e4aba5194 +certbot==0.34.1 \ + --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ + --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 +acme==0.34.1 \ + --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ + --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 +certbot-apache==0.34.1 \ + --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ + --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 +certbot-nginx==0.34.1 \ + --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ + --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 -- cgit v1.2.3 From 115ed0e10b2fbd4d414a39e9aa6feaca573a428f Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 6 May 2019 13:28:23 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9a0b5ae..9b695786f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.35.0 - 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. + ## 0.34.1 - 2019-05-06 ### Fixed -- cgit v1.2.3 From 0baefcae3227f6a71780a2b70cd26615becbd3ad Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 6 May 2019 13:28:23 -0700 Subject: Bump version to 0.35.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index aeee67267..56a9a63f3 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b78528297..e14bcb3b6 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8f11feefc..c95864e09 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index cdbd5a277..afdfb09e1 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e86c3e92f..4883150fb 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 7e4aeb2b2..07d406fd9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index b30a71d71..f781bd0a5 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index d0f3c72a0..41c1b75a5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 0c1cc88a6..f009df8e8 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f04c1eb44..e1d6aeed0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 80848b4c0..ae8739d61 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 12151b51b..b2e14869e 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 0a3aba37a..e839cd71d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 9aac9e60c..a6a52d648 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 2562ba036..e05104e5d 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 47d38171d..09cd4acd2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 795272312..29f458542 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index cc6e8c6bc..51055ce64 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 28374e474..abec68040 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.1' +__version__ = '0.35.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index fb1ade06d..2d3da3962 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.1" +LE_AUTO_VERSION="0.35.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 71b1b8c2d9b99f1ca42b7ae08a1ef577341ce485 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 7 May 2019 00:49:47 +0200 Subject: Fix check permissions logic (#7034) Fixes #7031 I use the same approach than in `CreateVenv()` and `CompareVersions()`: a new bash function `CheckPathPermissions()` is declared an execute a python script passed to the interpreter through stdin. This allows: * to not require the temp_dir that holds a temporary script to be executed * to reduce at the bare minimum the change to make on the order of bash command to execute (including when the temp_dir is created) * Fix check permissions logic in certbot-auto by making a temp dir useless * Update CHANGELOG.md --- CHANGELOG.md | 2 + letsencrypt-auto-source/letsencrypt-auto | 177 +++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.template | 15 +- 3 files changed, 101 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f28cea7..408118ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * certbot-auto no longer prints a blank line when there are no permissions problems. +* certbot-auto no longer writes a check_permissions.py script at the root + of the filesystem. 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 diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index f9f02da62..da3fbe30f 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -953,6 +953,95 @@ if __name__ == '__main__': UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -1505,96 +1594,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -"""Verifies certbot-auto cannot be modified by unprivileged users. - -This script takes the path to certbot-auto as its only command line -argument. It then checks that the file can only be modified by uid/gid -< 1000 and if other users can modify the file, it prints a warning with -a suggestion on how to solve the problem. - -Permissions on symlinks in the absolute path of certbot-auto are ignored -and only the canonical path to certbot-auto is checked. There could be -permissions problems due to the symlinks that are unreported by this -script, however, issues like this were not caused by our documentation -and are ignored for the sake of simplicity. - -All warnings are printed to stdout rather than stderr so all stderr -output from this script can be suppressed to avoid printing messages if -this script fails for some reason. - -""" -from __future__ import print_function - -import os -import stat -import sys - - -FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' - - -def has_safe_permissions(path): - """Returns True if the given path has secure permissions. - - The permissions are considered safe if the file is only writable by - uid/gid < 1000. - - The reason we allow more IDs than 0 is because on some systems such - as Debian, system users/groups other than uid/gid 0 are used for the - path we recommend in our instructions which is /usr/local/bin. 1000 - was chosen because on Debian 0-999 is reserved for system IDs[1] and - on RHEL either 0-499 or 0-999 is reserved depending on the - version[2][3]. Due to these differences across different OSes, this - detection isn't perfect so we only determine permissions are - insecure when we can be reasonably confident there is a problem - regardless of the underlying OS. - - [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes - [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups - [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups - - :param str path: filesystem path to check - :returns: True if the path has secure permissions, otherwise, False - :rtype: bool - - """ - # os.stat follows symlinks before obtaining information about a file. - stat_result = os.stat(path) - if stat_result.st_mode & stat.S_IWOTH: - return False - if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: - return False - if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: - return False - return True - - -def main(certbot_auto_path): - current_path = os.path.realpath(certbot_auto_path) - last_path = None - permissions_ok = True - # This loop makes use of the fact that os.path.dirname('/') == '/'. - while current_path != last_path and permissions_ok: - permissions_ok = has_safe_permissions(current_path) - last_path = current_path - current_path = os.path.dirname(current_path) - - if not permissions_ok: - print('{0} has insecure permissions!'.format(certbot_auto_path)) - print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) - - -if __name__ == '__main__': - main(sys.argv[1]) - -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index bff4173d4..c064580bd 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -501,6 +501,14 @@ CreateVenv() { UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +{{ check_permissions.py }} +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -663,15 +671,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -{{ check_permissions.py }} -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced -- cgit v1.2.3 From a754a90940f95059a7e754bb97ae86d95a9886b1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 6 May 2019 16:50:03 -0700 Subject: Fix test_leauto_upgrades.sh on CentOS 6. (#7037) --- tests/letstest/scripts/test_leauto_upgrades.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index d5133ba38..8cc1748ed 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -15,8 +15,17 @@ if ! command -v git ; then exit 1 fi fi -# 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. -INITIAL_VERSION="0.33.1" +# If we're on a RHEL 6 based system, we can be confident Python is already +# installed because the package manager is written in Python. +if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then + # 0.20.0 is the latest version of letsencrypt-auto that doesn't install + # Python 3 on RHEL 6. + INITIAL_VERSION="0.20.0" + RUN_PYTHON3_TESTS=1 +else + # 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. + INITIAL_VERSION="0.33.1" +fi git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail @@ -63,8 +72,7 @@ iQIDAQAB -----END PUBLIC KEY----- " -if [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then - RUN_PYTHON3_TESTS=1 +if [ "$RUN_PYTHON3_TESTS" = 1 ]; then if command -v python3; then echo "Didn't expect Python 3 to be installed!" exit 1 -- cgit v1.2.3 From 8ff24f60a853470762e6cf9673adf5d9c31fcfe5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 6 May 2019 16:54:33 -0700 Subject: 0.34.x check_permissions.py filesystem root (#7038) * Fix check permissions logic (#7034) Fixes #7031 I use the same approach than in `CreateVenv()` and `CompareVersions()`: a new bash function `CheckPathPermissions()` is declared an execute a python script passed to the interpreter through stdin. This allows: * to not require the temp_dir that holds a temporary script to be executed * to reduce at the bare minimum the change to make on the order of bash command to execute (including when the temp_dir is created) * Fix check permissions logic in certbot-auto by making a temp dir useless * Update CHANGELOG.md (cherry picked from commit 71b1b8c2d9b99f1ca42b7ae08a1ef577341ce485) * Fixup changelog. --- CHANGELOG.md | 13 ++ letsencrypt-auto-source/letsencrypt-auto | 177 +++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.template | 15 +- 3 files changed, 112 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9a0b5ae..f812d621f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.34.2 - master + +### Fixed + +* certbot-auto no longer writes a check_permissions.py script at the root + of the filesystem. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + ## 0.34.1 - 2019-05-06 ### Fixed diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index fb1ade06d..8e928327b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -953,6 +953,95 @@ if __name__ == '__main__': UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -1505,96 +1594,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -"""Verifies certbot-auto cannot be modified by unprivileged users. - -This script takes the path to certbot-auto as its only command line -argument. It then checks that the file can only be modified by uid/gid -< 1000 and if other users can modify the file, it prints a warning with -a suggestion on how to solve the problem. - -Permissions on symlinks in the absolute path of certbot-auto are ignored -and only the canonical path to certbot-auto is checked. There could be -permissions problems due to the symlinks that are unreported by this -script, however, issues like this were not caused by our documentation -and are ignored for the sake of simplicity. - -All warnings are printed to stdout rather than stderr so all stderr -output from this script can be suppressed to avoid printing messages if -this script fails for some reason. - -""" -from __future__ import print_function - -import os -import stat -import sys - - -FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' - - -def has_safe_permissions(path): - """Returns True if the given path has secure permissions. - - The permissions are considered safe if the file is only writable by - uid/gid < 1000. - - The reason we allow more IDs than 0 is because on some systems such - as Debian, system users/groups other than uid/gid 0 are used for the - path we recommend in our instructions which is /usr/local/bin. 1000 - was chosen because on Debian 0-999 is reserved for system IDs[1] and - on RHEL either 0-499 or 0-999 is reserved depending on the - version[2][3]. Due to these differences across different OSes, this - detection isn't perfect so we only determine permissions are - insecure when we can be reasonably confident there is a problem - regardless of the underlying OS. - - [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes - [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups - [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups - - :param str path: filesystem path to check - :returns: True if the path has secure permissions, otherwise, False - :rtype: bool - - """ - # os.stat follows symlinks before obtaining information about a file. - stat_result = os.stat(path) - if stat_result.st_mode & stat.S_IWOTH: - return False - if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: - return False - if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: - return False - return True - - -def main(certbot_auto_path): - current_path = os.path.realpath(certbot_auto_path) - last_path = None - permissions_ok = True - # This loop makes use of the fact that os.path.dirname('/') == '/'. - while current_path != last_path and permissions_ok: - permissions_ok = has_safe_permissions(current_path) - last_path = current_path - current_path = os.path.dirname(current_path) - - if not permissions_ok: - print('{0} has insecure permissions!'.format(certbot_auto_path)) - print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) - - -if __name__ == '__main__': - main(sys.argv[1]) - -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index bff4173d4..c064580bd 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -501,6 +501,14 @@ CreateVenv() { UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +{{ check_permissions.py }} +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -663,15 +671,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -{{ check_permissions.py }} -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced -- cgit v1.2.3 From 7683636684114504845bef44aab2286ca85c53e8 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 7 May 2019 12:17:33 -0700 Subject: Update changelog for 0.34.2 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f812d621f..05fdb865e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.34.2 - master +## 0.34.2 - 2019-05-07 ### Fixed -- cgit v1.2.3 From 0e95cd8cde23141ac8dd670d1d4005d9109e92c2 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 7 May 2019 12:52:28 -0700 Subject: Release 0.34.2 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 203 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 203 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 26 files changed, 259 insertions(+), 253 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index aeee67267..0372db05b 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.34.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b78528297..3522c2304 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index fb1ade06d..c5a9989c5 100755 --- a/certbot-auto +++ b/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.34.1" +LE_AUTO_VERSION="0.34.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -953,6 +953,95 @@ if __name__ == '__main__': UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -1225,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.1 \ - --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ - --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 -acme==0.34.1 \ - --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ - --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 -certbot-apache==0.34.1 \ - --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ - --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 -certbot-nginx==0.34.1 \ - --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ - --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 +certbot==0.34.2 \ + --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ + --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb +acme==0.34.2 \ + --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ + --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a +certbot-apache==0.34.2 \ + --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ + --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 +certbot-nginx==0.34.2 \ + --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ + --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1505,96 +1594,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -"""Verifies certbot-auto cannot be modified by unprivileged users. - -This script takes the path to certbot-auto as its only command line -argument. It then checks that the file can only be modified by uid/gid -< 1000 and if other users can modify the file, it prints a warning with -a suggestion on how to solve the problem. - -Permissions on symlinks in the absolute path of certbot-auto are ignored -and only the canonical path to certbot-auto is checked. There could be -permissions problems due to the symlinks that are unreported by this -script, however, issues like this were not caused by our documentation -and are ignored for the sake of simplicity. - -All warnings are printed to stdout rather than stderr so all stderr -output from this script can be suppressed to avoid printing messages if -this script fails for some reason. - -""" -from __future__ import print_function - -import os -import stat -import sys - - -FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' - - -def has_safe_permissions(path): - """Returns True if the given path has secure permissions. - - The permissions are considered safe if the file is only writable by - uid/gid < 1000. - - The reason we allow more IDs than 0 is because on some systems such - as Debian, system users/groups other than uid/gid 0 are used for the - path we recommend in our instructions which is /usr/local/bin. 1000 - was chosen because on Debian 0-999 is reserved for system IDs[1] and - on RHEL either 0-499 or 0-999 is reserved depending on the - version[2][3]. Due to these differences across different OSes, this - detection isn't perfect so we only determine permissions are - insecure when we can be reasonably confident there is a problem - regardless of the underlying OS. - - [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes - [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups - [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups - - :param str path: filesystem path to check - :returns: True if the path has secure permissions, otherwise, False - :rtype: bool - - """ - # os.stat follows symlinks before obtaining information about a file. - stat_result = os.stat(path) - if stat_result.st_mode & stat.S_IWOTH: - return False - if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: - return False - if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: - return False - return True - - -def main(certbot_auto_path): - current_path = os.path.realpath(certbot_auto_path) - last_path = None - permissions_ok = True - # This loop makes use of the fact that os.path.dirname('/') == '/'. - while current_path != last_path and permissions_ok: - permissions_ok = has_safe_permissions(current_path) - last_path = current_path - current_path = os.path.dirname(current_path) - - if not permissions_ok: - print('{0} has insecure permissions!'.format(certbot_auto_path)) - print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) - - -if __name__ == '__main__': - main(sys.argv[1]) - -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8f11feefc..049e0791c 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index cdbd5a277..d710583cf 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e86c3e92f..d3f4f56dc 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 7e4aeb2b2..5ea2f91f4 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index b30a71d71..0cf297bf4 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index d0f3c72a0..3171115b9 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 0c1cc88a6..b9283ad1f 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f04c1eb44..581cddff2 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 80848b4c0..404d0dfc4 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 12151b51b..271232bb3 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 0a3aba37a..1f967d9d2 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 9aac9e60c..00bc464c5 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 2562ba036..9afcfc537 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 47d38171d..9004e7348 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 795272312..8f48668a2 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.1' +version = '0.34.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index cc6e8c6bc..27aee8a77 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.1' +version = '0.34.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 28374e474..91c478b42 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.1' +__version__ = '0.34.2' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 2a577a09a..3e5fdc53b 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.34.1 + "". (default: CertbotACMEClient/0.34.2 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index fb1ade06d..c5a9989c5 100755 --- a/letsencrypt-auto +++ b/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.34.1" +LE_AUTO_VERSION="0.34.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -953,6 +953,95 @@ if __name__ == '__main__': UNLIKELY_EOF } +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF +} + if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. @@ -1225,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.1 \ - --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ - --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 -acme==0.34.1 \ - --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ - --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 -certbot-apache==0.34.1 \ - --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ - --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 -certbot-nginx==0.34.1 \ - --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ - --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 +certbot==0.34.2 \ + --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ + --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb +acme==0.34.2 \ + --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ + --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a +certbot-apache==0.34.2 \ + --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ + --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 +certbot-nginx==0.34.2 \ + --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ + --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1505,96 +1594,10 @@ else # Don't warn about file permissions if the user disabled the check or we # can't find an up-to-date Python. if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then - # --------------------------------------------------------------------------- - cat << "UNLIKELY_EOF" > "$TEMP_DIR/check_permissions.py" -"""Verifies certbot-auto cannot be modified by unprivileged users. - -This script takes the path to certbot-auto as its only command line -argument. It then checks that the file can only be modified by uid/gid -< 1000 and if other users can modify the file, it prints a warning with -a suggestion on how to solve the problem. - -Permissions on symlinks in the absolute path of certbot-auto are ignored -and only the canonical path to certbot-auto is checked. There could be -permissions problems due to the symlinks that are unreported by this -script, however, issues like this were not caused by our documentation -and are ignored for the sake of simplicity. - -All warnings are printed to stdout rather than stderr so all stderr -output from this script can be suppressed to avoid printing messages if -this script fails for some reason. - -""" -from __future__ import print_function - -import os -import stat -import sys - - -FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' - - -def has_safe_permissions(path): - """Returns True if the given path has secure permissions. - - The permissions are considered safe if the file is only writable by - uid/gid < 1000. - - The reason we allow more IDs than 0 is because on some systems such - as Debian, system users/groups other than uid/gid 0 are used for the - path we recommend in our instructions which is /usr/local/bin. 1000 - was chosen because on Debian 0-999 is reserved for system IDs[1] and - on RHEL either 0-499 or 0-999 is reserved depending on the - version[2][3]. Due to these differences across different OSes, this - detection isn't perfect so we only determine permissions are - insecure when we can be reasonably confident there is a problem - regardless of the underlying OS. - - [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes - [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups - [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups - - :param str path: filesystem path to check - :returns: True if the path has secure permissions, otherwise, False - :rtype: bool - - """ - # os.stat follows symlinks before obtaining information about a file. - stat_result = os.stat(path) - if stat_result.st_mode & stat.S_IWOTH: - return False - if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: - return False - if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: - return False - return True - - -def main(certbot_auto_path): - current_path = os.path.realpath(certbot_auto_path) - last_path = None - permissions_ok = True - # This loop makes use of the fact that os.path.dirname('/') == '/'. - while current_path != last_path and permissions_ok: - permissions_ok = has_safe_permissions(current_path) - last_path = current_path - current_path = os.path.dirname(current_path) - - if not permissions_ok: - print('{0} has insecure permissions!'.format(certbot_auto_path)) - print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) - - -if __name__ == '__main__': - main(sys.argv[1]) - -UNLIKELY_EOF - # --------------------------------------------------------------------------- # If the script fails for some reason, don't break certbot-auto. set +e # Suppress unexpected error output. - CHECK_PERM_OUT=$("$LE_PYTHON" "$TEMP_DIR/check_permissions.py" "$0" 2>/dev/null) + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) CHECK_PERM_STATUS="$?" set -e # Only print output if the script ran successfully and it actually produced diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index ed67415eb..54ea543e9 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzQmLAACgkQTRfJlc2X -dfL04Af9E06u0S3Q+xroaysGFPUv2Jl1Mr1FMxk8LckuOzVQDf2hPE1WR7gJ4Csg -s5wMh+inEws45QgpihbANjNvoMHJX3mzcjYkvMhwiW2q93pU6PEWjVnLV5qx79Jh -L7gatx96S+fQ/e5LDLx7cTngDLJGYjJUbOWfHVBsYwMNotTFJNMPaTx8IAQAqaLN -1LAZDsZq/EJpdE+JhR+pXJ2xhCjWmxjmsPvUVjBhlM+gTpFw2CwKhJJtmKgV/0tG -jf8Ot3ruRCNIvonB9tD6j67nStA7i6fMn9irW9rLCu9s2PXFAYPC/tB4nvKvP1wX -OUyihTSztHA/vgm3JStXkoYA4T1tBA== -=RfRc +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzR4cYACgkQTRfJlc2X +dfKDMQf/RTQ53OV2UMp/9qc7Ig8GdHG0MT8h3d2dhFtfT3aAVYGxWXPnZp68Ut2l +hL9qpoDX1VbMcG110oQp4SXGIfMfs/aUZXs6bsW1yfTHv63CT0j4oxycShZWy5vp +mMj2T/huW/yXcaHPdIGUmYyxAKr/CyZ9o3jTg5YARoaO2q5VcSII6MpBtrvlPr2r +3fNhvuQf0tjjpYec/iyR1sg/0cK/ZxdsqdSC7HpDUsxBNqwxLrXhW27KdB4GU5mI +y6ngzrg32FEj2MDkna52/HFsVroqpoIbmdB6LdVxWH2xMRW5YbE3+p2ntT+T0NBt +Us2cca3NgnM938Fo/oto4GNZU+bqaQ== +=VxSR -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 8e928327b..c5a9989c5 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.1" +LE_AUTO_VERSION="0.34.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.1 \ - --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ - --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 -acme==0.34.1 \ - --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ - --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 -certbot-apache==0.34.1 \ - --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ - --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 -certbot-nginx==0.34.1 \ - --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ - --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 +certbot==0.34.2 \ + --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ + --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb +acme==0.34.2 \ + --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ + --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a +certbot-apache==0.34.2 \ + --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ + --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 +certbot-nginx==0.34.2 \ + --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ + --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 743210bf0..fa9dabdc2 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 25c333281..3b33abb33 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.34.1 \ - --hash=sha256:84b0990e9a0d1390f80467af4b29b6f65b80f6ed3b2b32aae6baba9d968e957f \ - --hash=sha256:464f49371ed308aa17356a7152167defc342b67a8bbf8f4b8d9019788f6d4b52 -acme==0.34.1 \ - --hash=sha256:6b989576dee7b57c25e391cbe93f817961cd9307aca1c429fe9fa36c1c3c95d3 \ - --hash=sha256:7bdbdbfcec5c05834e91a2d950e964654401e0112a27afd34f5f03a5cadf23f1 -certbot-apache==0.34.1 \ - --hash=sha256:a199202d212492fca92939e8424a1b312b0959843dd46c673888275407bb341d \ - --hash=sha256:6223e61eb83ade317693e8542b480fc5ef9cd67fc54f8137a5ac13f0f75c62f7 -certbot-nginx==0.34.1 \ - --hash=sha256:c115f5f3d47aacaa67790e5628148b0074b57d0e538cf0118231e832bc410e52 \ - --hash=sha256:b92f457afa1a1c7596c2d22a6863b5917376677746996da73faa2b4e56692576 +certbot==0.34.2 \ + --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ + --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb +acme==0.34.2 \ + --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ + --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a +certbot-apache==0.34.2 \ + --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ + --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 +certbot-nginx==0.34.2 \ + --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ + --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 -- cgit v1.2.3 From c3a395e7c5b0945959915d77f895758a534658c5 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 7 May 2019 12:52:34 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05fdb865e..ac7f34a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.35.0 - 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. + ## 0.34.2 - 2019-05-07 ### Fixed -- cgit v1.2.3 From ccedde088d822aa8bf5e0e60c9349af6fc9e16b1 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 7 May 2019 12:52:34 -0700 Subject: Bump version to 0.35.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 0372db05b..56a9a63f3 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.34.2' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 3522c2304..e14bcb3b6 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 049e0791c..c95864e09 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index d710583cf..afdfb09e1 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index d3f4f56dc..4883150fb 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 5ea2f91f4..07d406fd9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 0cf297bf4..f781bd0a5 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 3171115b9..41c1b75a5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index b9283ad1f..f009df8e8 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 581cddff2..e1d6aeed0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 404d0dfc4..ae8739d61 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 271232bb3..b2e14869e 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 1f967d9d2..e839cd71d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 00bc464c5..a6a52d648 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 9afcfc537..e05104e5d 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 9004e7348..09cd4acd2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 8f48668a2..29f458542 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.34.2' +version = '0.35.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 27aee8a77..51055ce64 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.34.2' +version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 91c478b42..abec68040 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.34.2' +__version__ = '0.35.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index c5a9989c5..f063b789b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.34.2" +LE_AUTO_VERSION="0.35.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 7fe82cf1ac4c6e40ab4a21a5a8822db200cb28ad Mon Sep 17 00:00:00 2001 From: Po-Chuan Hsieh Date: Sat, 11 May 2019 14:13:37 +0800 Subject: Add FreeBSD specific paths (#6702) * Add support for FreeBSD specific paths Reference: https://svnweb.freebsd.org/ports/head/security/py-certbot/files/patch-certbot_compat.py * Add CHANGELOG.md entry * Fix linting error Pointed out by: @adferrand --- CHANGELOG.md | 1 + certbot/compat/misc.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7f34a09..e7abdfcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added +* Add FreeBSD specific paths to certbot * ### Changed diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 4f0e22078..b22fa93e8 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -129,7 +129,11 @@ LINUX_DEFAULT_FOLDERS = { 'work': '/var/lib/letsencrypt', 'logs': '/var/log/letsencrypt', } - +FREEBSD_DEFAULT_FOLDERS = { + 'config': '/usr/local/etc/letsencrypt', + 'work': '/var/db/letsencrypt', + 'logs': '/var/log/letsencrypt', +} def get_default_folder(folder_type): """ @@ -142,6 +146,10 @@ def get_default_folder(folder_type): """ if os.name != 'nt': + # Unix-like + if sys.platform.startswith('freebsd') or sys.platform.startswith('dragonfly'): + # FreeBSD specific + return FREEBSD_DEFAULT_FOLDERS[folder_type] # Linux specific return LINUX_DEFAULT_FOLDERS[folder_type] # Windows specific -- cgit v1.2.3 From 3888bc8f2ab39dca00b83deafab904cbe4aa8708 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 13 May 2019 22:55:22 +0200 Subject: Revert " Add FreeBSD specific paths (#6702)" (#7056) Revert #6702 After some discussions, we realized that changing the path for FreeBSD users, if it corresponds to the path used when Certbot is installed using ports, will break for users that installed it through certbot-auto. Indeed in this case, the path used was the one for Linux. After #6702, Certbot would not find anymore the existing config path by default. It would require, to be integrated, a proper documentation and a migration path. For now, it is preferable to revert it. This reverts commit 7fe82cf1ac4c6e40ab4a21a5a8822db200cb28ad. --- CHANGELOG.md | 1 - certbot/compat/misc.py | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7abdfcee..ac7f34a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* Add FreeBSD specific paths to certbot * ### Changed diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index b22fa93e8..4f0e22078 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -129,11 +129,7 @@ LINUX_DEFAULT_FOLDERS = { 'work': '/var/lib/letsencrypt', 'logs': '/var/log/letsencrypt', } -FREEBSD_DEFAULT_FOLDERS = { - 'config': '/usr/local/etc/letsencrypt', - 'work': '/var/db/letsencrypt', - 'logs': '/var/log/letsencrypt', -} + def get_default_folder(folder_type): """ @@ -146,10 +142,6 @@ def get_default_folder(folder_type): """ if os.name != 'nt': - # Unix-like - if sys.platform.startswith('freebsd') or sys.platform.startswith('dragonfly'): - # FreeBSD specific - return FREEBSD_DEFAULT_FOLDERS[folder_type] # Linux specific return LINUX_DEFAULT_FOLDERS[folder_type] # Windows specific -- cgit v1.2.3 From 2abe39d8a2fa01d04cfc82a4a814f2d9dd2d78b8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 May 2019 13:28:23 -0700 Subject: Add legalese around MM instance. (#7064) --- docs/contributing.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 582f14599..4a0e69f2d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -387,10 +387,23 @@ Asking for help If you have any questions while working on a Certbot issue, don't hesitate to ask for help! You can do this in the Certbot channel in EFF's Mattermost -instance for its open source projects. To join, `create an account -`_ -and then visit the `Certbot channel -`_. +instance for its open source projects as described below. + +You can get involved with several of EFF's software projects such as Certbot at +the `EFF Open Source Contributor Chat Platform +`_. +By signing up for the EFF Open Source Contributor Chat Platform, you consent to +share your personal information with the Electronic Frontier Foundation, which +is the operator and data controller for this platform. The channels will be +available both to EFF, and to other users of EFFOSCCP, who may use or disclose +information in these channels outside of EFFOSCCP. EFF will use your +information, according to the `Privacy Policy `_, +to further the mission of EFF, including hosting and moderating the discussions +on this platform. + +Use of EFFOSCCP is subject to the `EFF Code of Conduct +`_. When investigating an alleged Code of +Conduct violation, EFF may review discussion channels or direct messages. Updating certbot-auto and letsencrypt-auto ========================================== -- cgit v1.2.3 From 9a7f774706484dcec10cf5247d31bb547a5e1160 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 14 May 2019 22:56:32 +0200 Subject: [Unix] Create a framework for certbot integration tests: PART 5-FINAL (#6989) * Connect certbot-ci to travis. Remove old bash files. * Configure test-everything * Protect against import error * Remove unused ignore * Better handling of urllib3 * Correct path * Remove a warning * Correct call * Protect atexit register execution * Update docs/contributing.rst Co-Authored-By: Brad Warren * Update docs/contributing.rst Co-Authored-By: Brad Warren * Add again some bash scripts to avoid breaking to much retro-compatiblity on third party scripts * Move boulder-v1 and boulder-v2 in nightly tests * Separate oldest unit tests and oldest integration tests * Remove try/except * Test integration included in toxenv * Add a wait to avoid a transient issue on OCSP status in oldest tests * Clean travis.yml, split other tests * Remove useless config * Update .travis.yml Co-Authored-By: Brad Warren * Update tox.ini * Update tox.ini * Remove pytest-sugar * Remove empty pytest.ini, tests are working without it --- .travis.yml | 68 ++- .../certbot_tests/test_main.py | 4 + .../certbot_integration_tests/utils/acme_server.py | 1 + certbot-ci/certbot_integration_tests/utils/misc.py | 15 +- certbot-ci/setup.py | 2 - certbot-nginx/tests/boulder-integration.sh | 71 --- docs/contributing.rst | 32 +- tests/boulder-integration.sh | 16 - tests/certbot-boulder-integration.sh | 584 --------------------- tests/certbot-pebble-integration.sh | 16 - .../48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json | 1 - .../private_key.json | 1 - .../48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json | 1 - .../archive/a.encryption-example.com/cert1.pem | 29 - .../archive/a.encryption-example.com/chain1.pem | 27 - .../a.encryption-example.com/fullchain1.pem | 56 -- .../archive/a.encryption-example.com/privkey1.pem | 28 - .../archive/b.encryption-example.com/cert1.pem | 29 - .../archive/b.encryption-example.com/chain1.pem | 27 - .../b.encryption-example.com/fullchain1.pem | 56 -- .../archive/b.encryption-example.com/privkey1.pem | 28 - .../sample-config/csr/0000_csr-certbot.pem | 16 - .../sample-config/csr/0001_csr-certbot.pem | 16 - .../sample-config/csr/0002_csr-certbot.pem | 17 - .../sample-config/csr/0003_csr-certbot.pem | 17 - .../sample-config/keys/0000_key-certbot.pem | 28 - .../sample-config/keys/0001_key-certbot.pem | 28 - .../sample-config/keys/0002_key-certbot.pem | 28 - .../sample-config/keys/0003_key-certbot.pem | 28 - .../live/a.encryption-example.com/README | 10 - .../live/a.encryption-example.com/cert.pem | 1 - .../live/a.encryption-example.com/chain.pem | 1 - .../live/a.encryption-example.com/fullchain.pem | 1 - .../live/a.encryption-example.com/privkey.pem | 1 - .../live/b.encryption-example.com/README | 10 - .../live/b.encryption-example.com/cert.pem | 1 - .../live/b.encryption-example.com/chain.pem | 1 - .../live/b.encryption-example.com/fullchain.pem | 1 - .../live/b.encryption-example.com/privkey.pem | 1 - .../sample-config/options-ssl-apache.conf | 22 - .../renewal/a.encryption-example.com.conf | 15 - .../renewal/b.encryption-example.com.conf | 15 - tests/manual-dns-auth.sh | 8 - tests/manual-dns-cleanup.sh | 8 - tests/manual-http-auth.sh | 14 - tests/manual-http-cleanup.sh | 2 - tests/pebble-fetch.sh | 48 +- tests/run_http_server.py | 11 - tests/tox-boulder-integration.sh | 12 - tools/_venv_common.py | 1 + tox.ini | 33 +- 51 files changed, 106 insertions(+), 1381 deletions(-) delete mode 100755 certbot-nginx/tests/boulder-integration.sh delete mode 100755 tests/boulder-integration.sh delete mode 100755 tests/certbot-boulder-integration.sh delete mode 100755 tests/certbot-pebble-integration.sh delete mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json delete mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json delete mode 100644 tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json delete mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem delete mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem delete mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem delete mode 100644 tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem delete mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem delete mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem delete mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem delete mode 100644 tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem delete mode 100644 tests/integration/sample-config/csr/0000_csr-certbot.pem delete mode 100644 tests/integration/sample-config/csr/0001_csr-certbot.pem delete mode 100644 tests/integration/sample-config/csr/0002_csr-certbot.pem delete mode 100644 tests/integration/sample-config/csr/0003_csr-certbot.pem delete mode 100644 tests/integration/sample-config/keys/0000_key-certbot.pem delete mode 100644 tests/integration/sample-config/keys/0001_key-certbot.pem delete mode 100644 tests/integration/sample-config/keys/0002_key-certbot.pem delete mode 100644 tests/integration/sample-config/keys/0003_key-certbot.pem delete mode 100644 tests/integration/sample-config/live/a.encryption-example.com/README delete mode 120000 tests/integration/sample-config/live/a.encryption-example.com/cert.pem delete mode 120000 tests/integration/sample-config/live/a.encryption-example.com/chain.pem delete mode 120000 tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem delete mode 120000 tests/integration/sample-config/live/a.encryption-example.com/privkey.pem delete mode 100644 tests/integration/sample-config/live/b.encryption-example.com/README delete mode 120000 tests/integration/sample-config/live/b.encryption-example.com/cert.pem delete mode 120000 tests/integration/sample-config/live/b.encryption-example.com/chain.pem delete mode 120000 tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem delete mode 120000 tests/integration/sample-config/live/b.encryption-example.com/privkey.pem delete mode 100644 tests/integration/sample-config/options-ssl-apache.conf delete mode 100644 tests/integration/sample-config/renewal/a.encryption-example.com.conf delete mode 100644 tests/integration/sample-config/renewal/b.encryption-example.com.conf delete mode 100755 tests/manual-dns-auth.sh delete mode 100755 tests/manual-dns-cleanup.sh delete mode 100755 tests/manual-http-auth.sh delete mode 100755 tests/manual-http-cleanup.sh delete mode 100644 tests/run_http_server.py delete mode 100755 tests/tox-boulder-integration.sh diff --git a/.travis.yml b/.travis.yml index b23164a19..3a054b419 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ cache: - $HOME/.cache/pip before_script: - - 'if [ $TRAVIS_OS_NAME = osx ] ; then ulimit -n 1024 ; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi' + # On Travis, the fastest parallelization for integration tests has proved to be 4. + - 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi' - export TOX_TESTENV_PASSENV=TRAVIS # Only build pushes to the master branch, PRs, and branches beginning with @@ -30,12 +32,7 @@ matrix: include: # Main test suite - python: "2.7" - env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=all TOXENV=py27_install - sudo: required - services: docker - <<: *not-on-master - - python: "2.7" - env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=all TOXENV=py27_install + env: ACME_SERVER=pebble TOXENV=integration sudo: required services: docker <<: *not-on-master @@ -102,64 +99,93 @@ matrix: env: TOXENV=py37 CERTBOT_NO_PIN=1 <<: *extended-test-suite - python: "2.7" - env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest + env: ACME_SERVER=boulder-v1 TOXENV=integration + sudo: required + services: docker + <<: *extended-test-suite + - python: "2.7" + env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "2.7" - env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest + env: TOXENV=py27-certbot-oldest + <<: *extended-test-suite + - python: "2.7" + env: TOXENV=py27-nginx-oldest + <<: *extended-test-suite + - python: "2.7" + env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest sudo: required services: docker <<: *extended-test-suite - python: "2.7" - env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest + env: ACME_SERVER=boulder-v2 TOXENV=integration-certbot-oldest sudo: required services: docker <<: *extended-test-suite - python: "2.7" - env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest + env: ACME_SERVER=boulder-v1 TOXENV=integration-nginx-oldest sudo: required services: docker <<: *extended-test-suite + - python: "2.7" + env: ACME_SERVER=boulder-v2 TOXENV=integration-nginx-oldest + sudo: required + services: docker + <<: *extended-test-suite + - python: "3.4" + env: TOXENV=py34 + <<: *extended-test-suite + - python: "3.5" + env: TOXENV=py35 + <<: *extended-test-suite + - python: "3.6" + env: TOXENV=py36 + <<: *extended-test-suite + - python: "3.7" + dist: xenial + env: TOXENV=py37 + <<: *extended-test-suite - python: "3.4" - env: TOXENV=py34 BOULDER_INTEGRATION=v1 + env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.4" - env: TOXENV=py34 BOULDER_INTEGRATION=v2 + env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.5" - env: TOXENV=py35 BOULDER_INTEGRATION=v1 + env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.5" - env: TOXENV=py35 BOULDER_INTEGRATION=v2 + env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.6" - env: TOXENV=py36 BOULDER_INTEGRATION=v1 + env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.6" - env: TOXENV=py36 BOULDER_INTEGRATION=v2 + env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.7" dist: xenial - env: TOXENV=py37 BOULDER_INTEGRATION=v1 + env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.7" dist: xenial - env: TOXENV=py37 BOULDER_INTEGRATION=v2 + env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite @@ -220,9 +246,7 @@ addons: - openssl install: "$(command -v pip || command -v pip3) install codecov tox" -script: - - tox - - '[ -z "${BOULDER_INTEGRATION+x}" ] || (tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)' +script: tox after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 748b7699e..5ce19dcb8 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -5,6 +5,7 @@ import os import re import shutil import subprocess +import time from os.path import join, exists import pytest @@ -567,6 +568,9 @@ def test_ocsp_status_live(context): # OSCP 2: Check live certificate OCSP status (REVOKED) context.certbot(['revoke', '--cert-name', cert, '--no-delete-after-revoke']) + # Sometimes in oldest tests (using openssl binary and not cryptography), the OCSP status is + # not seen immediately by Certbot as invalid. Waiting few seconds solves this transient issue. + time.sleep(5) output = context.certbot(['certificates']) assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert) diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index 80516aea1..44010d899 100644 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -5,6 +5,7 @@ import atexit import os import subprocess import shutil +import stat import sys from os.path import join diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index e5ea659ec..4647a229e 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -12,6 +12,7 @@ import subprocess import sys import tempfile import time +import warnings from distutils.version import LooseVersion import pkg_resources @@ -36,8 +37,13 @@ def check_until_timeout(url): :param str url: the URL to test :raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL """ - import urllib3 - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + try: + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + except ImportError: + # Handle old versions of request with vendorized urllib3 + from requests.packages.urllib3.exceptions import InsecureRequestWarning + requests.packages.urllib3.disable_warnings(InsecureRequestWarning) for _ in range(0, 150): time.sleep(1) @@ -234,7 +240,10 @@ def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE): key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, 2048) elif key_type == ECDSA_KEY_TYPE: - key = ec.generate_private_key(ec.SECP384R1(), default_backend()) + with warnings.catch_warnings(): + # Ignore a warning on some old versions of cryptography + warnings.simplefilter('ignore', category=PendingDeprecationWarning) + key = ec.generate_private_key(ec.SECP384R1(), default_backend()) key = key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, encryption_algorithm=NoEncryption()) key = crypto.load_privatekey(crypto.FILETYPE_PEM, key) diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 852d2481c..88372bffc 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -11,8 +11,6 @@ install_requires = [ 'pyopenssl', 'pytest', 'pytest-cov', - 'pytest-rerunfailures==4.2', - 'pytest-sugar', 'pytest-xdist', 'pyyaml', 'requests', diff --git a/certbot-nginx/tests/boulder-integration.sh b/certbot-nginx/tests/boulder-integration.sh deleted file mode 100755 index fbb34a273..000000000 --- a/certbot-nginx/tests/boulder-integration.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -xe -# prerequisite: apt-get install --no-install-recommends nginx-light openssl - -. ./tests/integration/_common.sh - -export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx -nginx_root="$root/nginx" -mkdir $nginx_root - -# Generate self-signed certificate for Nginx -openssl req -new -newkey rsa:2048 -days 1 -nodes -x509 \ - -keyout $nginx_root/cert.key -out $nginx_root/cert.pem -subj "/CN=nginx.wtf" - -reload_nginx () { - original=$(./certbot-nginx/tests/boulder-integration.conf.sh $nginx_root $nginx_root/cert.key $nginx_root/cert.pem) - nginx_conf="$nginx_root/nginx.conf" - echo "$original" > $nginx_conf - - killall nginx || true - nginx -c $nginx_root/nginx.conf -} - -certbot_test_nginx () { - certbot_test \ - --authenticator nginx \ - --installer nginx \ - --nginx-server-root $nginx_root \ - "$@" -} - -test_deployment_and_rollback() { - # Arguments: certname - echo | openssl s_client -connect localhost:5001 \ - | openssl x509 -out $root/nginx.pem - diff -q $root/nginx.pem "$root/conf/live/$1/cert.pem" - - certbot_test_nginx rollback --checkpoints 9001 - diff -q <(echo "$original") $nginx_conf -} - -export default_server="default_server" -nginx -v -reload_nginx -certbot_test_nginx --domains nginx.wtf run -test_deployment_and_rollback nginx.wtf -certbot_test_nginx --domains nginx2.wtf --preferred-challenges http -test_deployment_and_rollback nginx2.wtf -# Overlapping location block and server-block-level return 301 -certbot_test_nginx --domains nginx3.wtf --preferred-challenges http -test_deployment_and_rollback nginx3.wtf -# No matching server block; default_server exists -certbot_test_nginx --domains nginx4.wtf --preferred-challenges http -test_deployment_and_rollback nginx4.wtf -# No matching server block; default_server does not exist -export default_server="" -reload_nginx -if nginx -c $nginx_root/nginx.conf -T 2>/dev/null | grep "default_server"; then - echo "Failed to remove default_server" - exit 1 -fi -certbot_test_nginx --domains nginx5.wtf --preferred-challenges http -test_deployment_and_rollback nginx5.wtf -# Mutiple domains, mix of matching and not -certbot_test_nginx --domains nginx6.wtf,nginx7.wtf --preferred-challenges http -test_deployment_and_rollback nginx6.wtf - -# note: not reached if anything above fails, hence "killall" at the -# top -nginx -c $nginx_root/nginx.conf -s stop - -coverage report --fail-under 72 --include 'certbot-nginx/*' --show-missing diff --git a/docs/contributing.rst b/docs/contributing.rst index 4a0e69f2d..eed7d1bce 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -116,36 +116,24 @@ of output can make it hard to find specific failures when they happen. .. _integration: -Integration testing with the Boulder CA -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Integration testing with the Pebble CA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generally it is sufficient to open a pull request and let Github and Travis run -integration tests for you, however, if you want to run them locally you need -Docker and docker-compose installed and working. Fetch and start Boulder, Let's -Encrypt's ACME CA software, by using: +integration tests for you. However, you may want to run them locally before submitting +your pull request. You need Docker and docker-compose installed and working. -.. code-block:: shell - - ./tests/boulder-fetch.sh - -If you have problems with Docker, you may want to try `removing all containers and -volumes`_ and making sure you have at least 1GB of memory. - -Set up a certbot_test alias that enables easily running against the local -Boulder: - -.. code-block:: shell - - export SERVER=http://localhost:4000/directory - source tests/integration/_common.sh +The tox environment `integration` will setup Pebble, the Let's Encrypt ACME CA server +for integration testing, then launch the Certbot integration tests. -Run the integration tests using: +With a user allowed to access your local Docker daemon, run: .. code-block:: shell - ./tests/boulder-integration.sh + tox -e integration -.. _removing all containers and volumes: https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes +Tests will be run using pytest. A test report and a code coverage report will be +displayed at the end of the integration tests execution. Code components and layout ========================== diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh deleted file mode 100755 index 3e16fcbbc..000000000 --- a/tests/boulder-integration.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e - -if [ "$INTEGRATION_TEST" = "certbot" ]; then - tests/certbot-boulder-integration.sh -elif [ "$INTEGRATION_TEST" = "nginx" ]; then - certbot-nginx/tests/boulder-integration.sh -else - tests/certbot-boulder-integration.sh - # Most CI systems set this variable to true. - # If the tests are running as part of CI, Nginx should be available. - if ${CI:-false} || type nginx; then - certbot-nginx/tests/boulder-integration.sh - fi -fi diff --git a/tests/certbot-boulder-integration.sh b/tests/certbot-boulder-integration.sh deleted file mode 100755 index 853712e57..000000000 --- a/tests/certbot-boulder-integration.sh +++ /dev/null @@ -1,584 +0,0 @@ -#!/bin/bash -# Simple integration test. Make sure to activate virtualenv beforehand -# (source venv/bin/activate) and that you are running Boulder test -# instance (see ./boulder-fetch.sh). -# -# Environment variables: -# SERVER: Passed as "certbot --server" argument. -# -# Note: this script is called by Boulder integration test suite! - -set -eux - -# Check that python executable is available in the PATH. Fail immediatly if not. -command -v python > /dev/null || (echo "Error, python executable is not in the PATH" && exit 1) - -. ./tests/integration/_common.sh -export PATH="$PATH:/usr/sbin" # /usr/sbin/nginx -CURRENT_DIR="$(pwd)" - -cleanup_and_exit() { - EXIT_STATUS=$? - cd $CURRENT_DIR - if SERVER_STILL_RUNNING=`ps -p $python_server_pid -o pid=` - then - echo Kill server subprocess, left running by abnormal exit - kill $SERVER_STILL_RUNNING - fi - if [ -f "$HOOK_DIRS_TEST" ]; then - rm -f "$HOOK_DIRS_TEST" - fi - exit $EXIT_STATUS -} - -trap cleanup_and_exit EXIT - -export HOOK_DIRS_TEST="$(mktemp)" -renewal_hooks_root="$config_dir/renewal-hooks" -renewal_hooks_dirs=$(echo "$renewal_hooks_root/"{pre,deploy,post}) -renewal_dir_pre_hook="$(echo $renewal_hooks_dirs | cut -f 1 -d " ")/hook.sh" -renewal_dir_deploy_hook="$(echo $renewal_hooks_dirs | cut -f 2 -d " ")/hook.sh" -renewal_dir_post_hook="$(echo $renewal_hooks_dirs | cut -f 3 -d " ")/hook.sh" - -# Creates hooks in Certbot's renewal hook directory that write to a file -CreateDirHooks() { - for hook_dir in $renewal_hooks_dirs; do - mkdir -p $hook_dir - hook_path="$hook_dir/hook.sh" - cat << EOF > "$hook_path" -#!/bin/bash -xe -if [ "\$0" = "$renewal_dir_deploy_hook" ]; then - if [ -z "\$RENEWED_DOMAINS" -o -z "\$RENEWED_LINEAGE" ]; then - echo "Environment variables not properly set!" >&2 - exit 1 - fi -fi -echo \$(basename \$(dirname "\$0")) >> "\$HOOK_DIRS_TEST" -EOF - chmod +x "$hook_path" - done -} - -# Asserts that the hooks created by CreateDirHooks have been run once and -# resets the file. -# -# Arguments: -# The number of times the deploy hook should have been run. (It should run -# once for each certificate that was issued in that run of Certbot.) -CheckDirHooks() { - expected="pre\n" - for ((i=0; i<$1; i++)); do - expected=$expected"deploy\n" - done - expected=$expected"post" - - if ! diff "$HOOK_DIRS_TEST" <(echo -e "$expected"); then - echo "Unexpected directory hook output!" >&2 - echo "Expected:" >&2 - echo -e "$expected" >&2 - echo "Got:" >&2 - cat "$HOOK_DIRS_TEST" >&2 - exit 1 - fi - - rm -f "$HOOK_DIRS_TEST" - export HOOK_DIRS_TEST="$(mktemp)" -} - -common_no_force_renew() { - certbot_test_no_force_renew \ - --authenticator standalone \ - --installer null \ - "$@" -} - -common() { - common_no_force_renew \ - --renew-by-default \ - "$@" -} - -export HOOK_TEST="/tmp/hook$$" -CheckHooks() { - if [ $(head -n1 "$HOOK_TEST") = "wtf.pre" ]; then - expected="wtf.pre\ndeploy\n" - if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then - expected=$expected"deploy\nwtf2.pre\n" - else - expected=$expected"wtf2.pre\ndeploy\n" - fi - expected=$expected"deploy\ndeploy\nwtf.post\nwtf2.post" - else - expected="wtf2.pre\ndeploy\n" - if [ $(sed '3q;d' "$HOOK_TEST") = "deploy" ]; then - expected=$expected"deploy\nwtf.pre\n" - else - expected=$expected"wtf.pre\ndeploy\n" - fi - expected=$expected"deploy\ndeploy\nwtf2.post\nwtf.post" - fi - - if ! cmp --quiet <(echo -e "$expected") "$HOOK_TEST" ; then - echo Hooks did not run as expected\; got >&2 - cat "$HOOK_TEST" >&2 - echo -e "Expected\n$expected" >&2 - rm "$HOOK_TEST" - exit 1 - fi - rm "$HOOK_TEST" -} - -# Checks if deploy is in the hook output and deletes the file -DeployInHookOutput() { - CONTENTS=$(cat "$HOOK_TEST") - rm "$HOOK_TEST" - grep deploy <(echo "$CONTENTS") -} - -# Asserts that there is a saved renew_hook for a lineage. -# -# Arguments: -# Name of lineage to check -CheckSavedRenewHook() { - if ! grep renew_hook "$config_dir/renewal/$1.conf"; then - echo "Hook wasn't saved as renew_hook" >&2 - exit 1 - fi -} - -# Asserts the deploy hook was properly run and saved and deletes the hook file -# -# Arguments: -# Lineage name of the issued cert -CheckDeployHook() { - if ! DeployInHookOutput; then - echo "The deploy hook wasn't run" >&2 - exit 1 - fi - CheckSavedRenewHook $1 -} - -# Asserts the renew hook wasn't run but was saved and deletes the hook file -# -# Arguments: -# Lineage name of the issued cert -# Asserts the deploy hook wasn't run and deletes the hook file -CheckRenewHook() { - if DeployInHookOutput; then - echo "The renew hook was incorrectly run" >&2 - exit 1 - fi - CheckSavedRenewHook $1 -} - -# Return success only if input contains exactly $1 lines of text, of -# which $2 different values occur in the first field. -TotalAndDistinctLines() { - total=$1 - distinct=$2 - awk '{a[$1] = 1}; END {n = 0; for (i in a) { n++ }; exit(NR !='$total' || n !='$distinct')}' -} - -# Cleanup coverage data -coverage erase - -# test for regressions of #4719 -get_num_tmp_files() { - ls -1 /tmp | wc -l -} -num_tmp_files=$(get_num_tmp_files) -common --csr / > /dev/null && echo expected error && exit 1 || true -common --help > /dev/null -common --help all > /dev/null -common --version > /dev/null -if [ $(get_num_tmp_files) -ne $num_tmp_files ]; then - echo "New files or directories created in /tmp!" - exit 1 -fi -CreateDirHooks - -common register -for dir in $renewal_hooks_dirs; do - if [ ! -d "$dir" ]; then - echo "Hook directory not created by Certbot!" >&2 - exit 1 - fi -done - -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 -# unrequested challenge to prevent regressions in #3601. -python ./tests/run_http_server.py $https_port & -python_server_pid=$! -certname="le1.wtf" -common --domains le1.wtf --preferred-challenges http-01 auth \ - --cert-name $certname \ - --pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \ - --post-hook 'echo wtf.post >> "$HOOK_TEST"'\ - --deploy-hook 'echo deploy >> "$HOOK_TEST"' -CheckDeployHook $certname - -# Previous test used to be a tls-sni-01 challenge that is not supported anymore. -# Now it is a http-01 challenge and this makes it a duplicate of the following test. -# But removing it would break many tests here, as they are strongly coupled. -# See https://github.com/certbot/certbot/pull/6852 -certname="le2.wtf" -common --domains le2.wtf --preferred-challenges http-01 run \ - --cert-name $certname \ - --pre-hook 'echo wtf.pre >> "$HOOK_TEST"' \ - --post-hook 'echo wtf.post >> "$HOOK_TEST"'\ - --deploy-hook 'echo deploy >> "$HOOK_TEST"' -kill $python_server_pid -CheckDeployHook $certname - -certname="le.wtf" -common certonly -a manual -d le.wtf --rsa-key-size 4096 --cert-name $certname \ - --manual-auth-hook ./tests/manual-http-auth.sh \ - --manual-cleanup-hook ./tests/manual-http-cleanup.sh \ - --pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \ - --post-hook 'echo wtf2.post >> "$HOOK_TEST"' \ - --renew-hook 'echo deploy >> "$HOOK_TEST"' -CheckRenewHook $certname - -certname="dns.le.wtf" -common -a manual -d dns.le.wtf --preferred-challenges dns run \ - --cert-name $certname \ - --manual-auth-hook ./tests/manual-dns-auth.sh \ - --manual-cleanup-hook ./tests/manual-dns-cleanup.sh \ - --pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \ - --post-hook 'echo wtf2.post >> "$HOOK_TEST"' \ - --renew-hook 'echo deploy >> "$HOOK_TEST"' -CheckRenewHook $certname - -common certonly --cert-name newname -d newname.le.wtf - -export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \ - OPENSSL_CNF=examples/openssl.cnf -./examples/generate-csr.sh le3.wtf -common auth --csr "$CSR_PATH" \ - --cert-path "${root}/csr/cert.pem" \ - --chain-path "${root}/csr/chain.pem" -openssl x509 -in "${root}/csr/cert.pem" -text -openssl x509 -in "${root}/csr/chain.pem" -text - -common --domains le3.wtf install \ - --cert-path "${root}/csr/cert.pem" \ - --key-path "${root}/key.pem" - -CheckCertCount() { - CERTCOUNT=`ls "${root}/conf/archive/$1/cert"* | wc -l` - if [ "$CERTCOUNT" -ne "$2" ] ; then - echo Wrong cert count, not "$2" `ls "${root}/conf/archive/$1/"*` - exit 1 - fi -} - -CheckPermissions() { -# Args: -# Checks mode of two files match under - 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: -# 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: -# 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 -if [ -s "$HOOK_DIRS_TEST" ]; then - echo "Directory hooks were executed for non-renewal!" >&2; - exit 1 -fi - -rm -rf "$renewal_hooks_root" -# renew using HTTP manual auth hooks -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" - mkdir "$hook_dir/dir" -done -# renew using DNS manual auth hooks -common renew --cert-name dns.le.wtf --authenticator manual -CheckCertCount "dns.le.wtf" 2 - -# test with disabled directory hooks -rm -rf "$renewal_hooks_root" -CreateDirHooks -# This will renew because the expiry is less than 10 years from now -sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf" -common_no_force_renew renew --rsa-key-size 2048 --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 -fi - -# The 4096 bit setting should persist to the first renewal, but be overridden in the second - -size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1` -size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1` -size3=`wc -c ${root}/conf/archive/le.wtf/privkey3.pem | cut -d" " -f1` -# 4096 bit PEM keys are about ~3270 bytes, 2048 ones are about 1700 bytes -if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; then - echo key sizes violate assumptions: - ls -l "${root}/conf/archive/le.wtf/privkey"* - exit 1 -fi - -# --renew-by-default is used, so renewal should occur -[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST" -common renew -CheckCertCount "le.wtf" 4 -CheckHooks -CheckDirHooks 5 - -# test with overlapping directory hooks on the command line -common renew --cert-name le2.wtf \ - --pre-hook "$renewal_dir_pre_hook" \ - --deploy-hook "$renewal_dir_deploy_hook" \ - --post-hook "$renewal_dir_post_hook" -CheckDirHooks 1 - -# test with overlapping directory hooks in the renewal conf files -common renew --cert-name le2.wtf -CheckDirHooks 1 - -# manual-dns-auth.sh will skip completing the challenge for domains that begin -# with fail. -common -a manual -d dns1.le.wtf,fail.dns1.le.wtf \ - --allow-subset-of-names \ - --preferred-challenges dns \ - --manual-auth-hook ./tests/manual-dns-auth.sh \ - --manual-cleanup-hook ./tests/manual-dns-cleanup.sh - -if common certificates | grep "fail\.dns1\.le\.wtf"; then - echo "certificate should not have been issued for domain!" >&2 - exit 1 -fi - -# reuse-key -common --domains reusekey.le.wtf --reuse-key -common renew --cert-name reusekey.le.wtf -CheckCertCount "reusekey.le.wtf" 2 -ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"* -# The final awk command here exits successfully if its input consists of -# exactly two lines with identical first fields, and unsuccessfully otherwise. -sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 2 1 - -# don't reuse key (just by forcing reissuance without --reuse-key) -common --cert-name reusekey.le.wtf --domains reusekey.le.wtf --force-renewal -CheckCertCount "reusekey.le.wtf" 3 -ls -l "${root}/conf/archive/reusekey.le.wtf/privkey"* -# Exactly three lines, of which exactly two identical first fields. -sha256sum "${root}/conf/archive/reusekey.le.wtf/privkey"* | TotalAndDistinctLines 3 2 - -# Nonetheless, all three certificates are different even though two of them -# share the same subject key. -sha256sum "${root}/conf/archive/reusekey.le.wtf/cert"* | TotalAndDistinctLines 3 3 - -# ECDSA -openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem" -SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \ - -config "${OPENSSL_CNF:-openssl.cnf}" \ - -key "${root}/privkey-p384.pem" \ - -subj "/" \ - -reqexts san \ - -outform der \ - -out "${root}/csr-p384.der" -common auth --csr "${root}/csr-p384.der" \ - --cert-path "${root}/csr/cert-p384.pem" \ - --chain-path "${root}/csr/chain-p384.pem" -openssl x509 -in "${root}/csr/cert-p384.pem" -text | grep 'ASN1 OID: secp384r1' - -# OCSP Must Staple -common auth --must-staple --domains "must-staple.le.wtf" -openssl x509 -in "${root}/conf/live/must-staple.le.wtf/cert.pem" -text | grep -E 'status_request|1\.3\.6\.1\.5\.5\.7\.1\.24' - -# revoke by account key -common revoke --cert-path "$root/conf/live/le.wtf/cert.pem" --delete-after-revoke -# revoke renewed -common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" --no-delete-after-revoke -if [ ! -d "$root/conf/live/le1.wtf" ]; then - echo "cert deleted when --no-delete-after-revoke was used!" - exit 1 -fi -common delete --cert-name le1.wtf -# revoke by cert key -common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \ - --key-path "$root/conf/live/le2.wtf/privkey.pem" - -# Get new certs to test revoke with a reason, by account and by cert key -common --domains le1.wtf -common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem" \ - --reason cessationOfOperation -common --domains le2.wtf -common revoke --cert-path "$root/conf/live/le2.wtf/cert.pem" \ - --key-path "$root/conf/live/le2.wtf/privkey.pem" \ - --reason keyCompromise - -common unregister - -out=$(common certificates) -subdomains="le dns.le newname.le must-staple.le" -for subdomain in $subdomains; do - domain="$subdomain.wtf" - if ! echo $out | grep "$domain"; then - echo "$domain not in certificates output!" - exit 1; - fi -done - -# Testing that revocation also deletes by default -subdomains="le1 le2" -for subdomain in $subdomains; do - domain="$subdomain.wtf" - if echo $out | grep "$domain"; then - echo "Revoked $domain in certificates output! Should not be!" - exit 1; - fi -done - -# Test that revocation raises correct error when both --cert-name and --cert-path specified -common --domains le1.wtf -out=$(common revoke --cert-path "$root/conf/live/le1.wtf/fullchain.pem" --cert-name "le1.wtf" 2>&1) || true -if ! echo $out | grep "Exactly one of --cert-path or --cert-name must be specified"; then - echo "Non-interactive revoking with both --cert-name and --cert-path " - echo "did not raise the correct error!" - exit 1 -fi - -# Test that revocation doesn't delete if multiple lineages share an archive dir -common --domains le1.wtf -common --domains le2.wtf -sed -i "s|^archive_dir = .*$|archive_dir = $root/conf/archive/le1.wtf|" "$root/conf/renewal/le2.wtf.conf" -#common update_symlinks # not needed, but a bit more context for what this test is about -out=$(common revoke --cert-path "$root/conf/live/le1.wtf/cert.pem") -if ! echo $out | grep "Not deleting revoked certs due to overlapping archive dirs"; then - echo "Deleted a cert that had an overlapping archive dir with another lineage!" - exit 1 -fi - -cert_name="must-staple.le.wtf" -common delete --cert-name $cert_name -archive="$root/conf/archive/$cert_name" -conf="$root/conf/renewal/$cert_name.conf" -live="$root/conf/live/$cert_name" -for path in $archive $conf $live; do - if [ -e $path ]; then - echo "Lineage not properly deleted!" - exit 1 - fi -done - -# Test ACMEv2-only features -if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then - common -a manual -d '*.le4.wtf,le4.wtf' --preferred-challenges dns \ - --manual-auth-hook ./tests/manual-dns-auth.sh \ - --manual-cleanup-hook ./tests/manual-dns-cleanup.sh -fi - -# Test OCSP status - -## OCSP 1: Check stale OCSP status -pushd ./tests/integration - -OUT=`common certificates --config-dir sample-config` -TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l` -EXPIRED=`echo "$OUT" | grep EXPIRED | wc -l` - -if [ "$TEST_CERTS" != 2 ] ; then - echo "Did not find two test certs as expected ($TEST_CERTS)" - exit 1 -fi - -if [ "$EXPIRED" != 2 ] ; then - echo "Did not find two test certs as expected ($EXPIRED)" - exit 1 -fi - -popd - -## OSCP 2: Check live certificate OCSP status (VALID) -common --domains le-ocsp-check.wtf -OUT=`common certificates` -VALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep VALID | wc -l` -EXPIRED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep EXPIRED | wc -l` - -if [ "$VALID" != 1 ] ; then - echo "Expected le-ocsp-check.wtf to be VALID" - exit 1 -fi - -if [ "$EXPIRED" != 0 ] ; then - echo "Did not expect le-ocsp-check.wtf to be EXPIRED" - exit 1 -fi - -## OSCP 3: Check live certificate OCSP status (REVOKED) -common revoke --cert-name le-ocsp-check.wtf --no-delete-after-revoke -OUT=`common certificates` -INVALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep INVALID | wc -l` -REVOKED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep REVOKED | wc -l` - -if [ "$INVALID" != 1 ] ; then - echo "Expected le-ocsp-check.wtf to be INVALID" - exit 1 -fi - -if [ "$REVOKED" != 1 ] ; then - echo "Expected le-ocsp-check.wtf to be REVOKED" - exit 1 -fi - -coverage report --fail-under 64 --include 'certbot/*' --show-missing diff --git a/tests/certbot-pebble-integration.sh b/tests/certbot-pebble-integration.sh deleted file mode 100755 index 8711f72c1..000000000 --- a/tests/certbot-pebble-integration.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/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 diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json deleted file mode 100644 index 6fe0b47f3..000000000 --- a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/meta.json +++ /dev/null @@ -1 +0,0 @@ -{"creation_host": "ec2-52-91-193-99.compute-1.amazonaws.com", "creation_dt": "2016-12-23T02:08:32Z"} \ No newline at end of file diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json deleted file mode 100644 index 0affb573d..000000000 --- a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/private_key.json +++ /dev/null @@ -1 +0,0 @@ -{"e": "AQAB", "d": "W410Wny96RO4qJ207KGQ3RSn0KAwqb93JBMHWU1yS9H3fN_2eCpFYdMLNFI9t1__nW1okeUioEfvMN_YW-G9krw97kVdZ63MfbeJCf35Onc8VZhAnk_3V8MtS26Of8ml0tTYhlQ65nuzhvHbY7aP-Uk260oDN-AbCCVhu5G4CQiMY6sdtCc8YkB6gK7SK874oWU7ogvAIPtNtEI-AXDUBYNAfoh34s1r2fE6mJSX4UYtzWB2hTUisvZdVL5JUInvxpCQFttk1cwWLFwwb6d2ERCbseeudvGJ6fkYiJ-EYxfHKOQK2kxPeOlLFMwGYQ0khDxTNajxQ1Asl43r7wgAeQ", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw", "q": "0xdfHMMKYWHPE1UoQ10niDI7rnCM9vmPo4JpCOCYZf51KPNJgNaPCw62Q0Y-ZQfCBifypQyf291d0_2C_Rif0WMg07Y-Ypv8SpPK77vLV12GoAoAX2Xy3AJAz1gDBcyUzDtRlrzgCZja9YqIDVzMatkdPJXaBrBu5B-sXv4wGa0", "p": "7pl5xe_400Sn6PdN_F6KLWHFROVd7379WPWGHYmnvOvXx7DmrMjDsTOmhNRlrv7jPemVqMzp1FGsubGBizEMFGyCET30bUgH6ZU7Cmgv-2JKKN1FZnm1QTepZ7kjAT_qRCI6nvN6J0SIX197QOSz3hMmP7UYQXQ32QcVKdCksps", "kty": "RSA", "qi": "zG60VpLZjgR0o7dTeEP-HjbtxHUedyZLGe4FIPyWrPRl28anebkMUGzibpB8z5ohRsqHU2i4tmDq2NMvshISqkpk8t5PLiIcQgU46HQ24SCv7lunkVPKYU1n2uXVVfttrBP4c3UkjYzda1bcIVp6cJHanm_JuWI5nxy9ebVQJiw", "dp": "kRIBx0aj7Jh22x_aa9JzgypKDhzDY4W7tmX5-GWk9ioTVZgKeQ3MZiZ4XZTiimbxdchbNXn5xh0uvuzdTesxZA2he6hGwFcmcHBKqIY2fksBuhznQGpJuXCFcMpRLUZWQrzpFZIGOG_j1tEwGIG1lxXfkKakK8_k0PEMfhMcwHc", "dq": "AsoSRa0GHBdQxy6e45T9ir0vMLToB_NwRHbasHVXTjG4lpvwYrVzGnBNVEI_XNJna_FnMWsjSaJ5NO3qpzGGGxw2ONX1qRPql4mwas6Od08TElZPfvM37FRTSuoc0BzN8ozuHRHN3BKbAheciKCrStYnnr9ULDZ0oKsSegbd19k"} \ No newline at end of file diff --git a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json b/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json deleted file mode 100644 index fdd2df7da..000000000 --- a/tests/integration/sample-config/accounts/acme-staging.api.letsencrypt.org/directory/48d6b9e8d767eccf7e4d877d6ffa81e3/regr.json +++ /dev/null @@ -1 +0,0 @@ -{"body": {"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", "key": {"e": "AQAB", "kty": "RSA", "n": "xL5HzdhU_7P-_tphpRxpDSIL2L-aAlWt6r9EVyw53Sp-jx4fHDgnYv9HQOzNeL_IpLRCLLBItMzqnBvHUdHcS3aB6fv8HSNiHdVdC-c2rPFO8DLSGLNqi9G9WshjLDsKwc__BPNX5wHFcm8TZUJ4uZ_Ax1JCe05ePHWAf8GTr8vPaKtMpUVF55HPwpJtYvFZlH1LiVo8I_trJtHl8-pGeel3zdcaDJgNZrohZG2acTg95Ry46FE4HOslAg8Z6yECPyYLInJSDcb5yCgSqtOOp7rMVSPQFhoZRt4KDfew9lqIwNQSJoDE3bJWpwkzL1tp4clG8ExI1WnA86OjW83Vvw"}}, "uri": "https://acme-staging.api.letsencrypt.org/acme/reg/566631", "new_authzr_uri": "https://acme-staging.api.letsencrypt.org/acme/new-authz", "terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"} \ No newline at end of file diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem deleted file mode 100644 index 80739dd3f..000000000 --- a/tests/integration/sample-config/archive/a.encryption-example.com/cert1.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco -hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR -WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM -FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca -nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny -Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q -lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR -omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J -/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH -FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY -Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC -4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem deleted file mode 100644 index 29a54e2a1..000000000 --- a/tests/integration/sample-config/archive/a.encryption-example.com/chain1.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem deleted file mode 100644 index ba245d213..000000000 --- a/tests/integration/sample-config/archive/a.encryption-example.com/fullchain1.pem +++ /dev/null @@ -1,56 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPrA8hxQOlpVRMgAm/Ib0HYdqzANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTAyMDBaFw0xNzAzMjMwMTAyMDBaMCMxITAfBgNVBAMTGGEuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqz0cco -hsCqyWPwGr79a8j+JO3HqbphLTzhoNHYF+fW8glyMyBmOMyZjc8v8E3U3KYEXuuR -WzR+bvUXBcLOhSogIifZDNiMKEFyDNcDlG08ze9GTj2hTQyjet2ZuPWNuuJ4u5UM -FvobaceDqITuqEqUrjCBi5CmEXswrV3l2BVSiOcPf+l+ZR81xG7qcjGfLG6YQWca -nsYYorz/kSRtwYjAT4NaeUYNXVeH1luWTWhbed8pmKfBVfv+OEmwUyAhSE1ePfny -Cj37wo1+nqQz37IJNEpI0RNbxrE7ZCgA40QrFVqc9XevcypFi9DftVWzDNBtd97Q -lmHuIqA9Kb3C/e8CAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -C7/XcCnNRht91hnQVEB2E9AtNUowHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhhLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEAP04z87VVNYYHpBkCLkw3B+gTd/F0xDo7ab2HvJJAeOpZgSfoSYMR -omYWiug9wGQqKjs4kaOGjAkW1EV3qosumOtvK7uTvoa2caXDjPYAxRiVIp08Qm0J -/FU/FfGpUXBZW9Ne3m3nDYxOCAWAw9WmV+dUuvb7qZWQSKs7cQv3FY/NuQe0o9LH -FgL7T0W7vc6uVGeBgcoEkX7xX4T7A9V3BqL6mgkK+L++n0EFrDXXzWWENNdWYCvY -Ptu0Ez95IyYNRgI3U1waO9QZ944Pc9OuMCZD4ifbYoMKGqSQb3sGR+B2TQ+qqCUC -4sikdX4WRbEYKlBTcvSpCVJ7ndFTyD6lyg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem b/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem deleted file mode 100644 index b3059cb47..000000000 --- a/tests/integration/sample-config/archive/a.encryption-example.com/privkey1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj -8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 -FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH -g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 -/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN -fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg -PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY -JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo -fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc -xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj -bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D -WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 -mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu -wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 -enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO -iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x -nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL -W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 -q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R -zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz -h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 -Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS -xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy -YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY -o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl -wkqKc3PndJHrR40mswUOHbs= ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem deleted file mode 100644 index 0c1c6b5ef..000000000 --- a/tests/integration/sample-config/archive/b.encryption-example.com/cert1.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR -FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f -s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim -+144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi -Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl -eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 -XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u -SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw -U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT -0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ -omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV -ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem deleted file mode 100644 index 29a54e2a1..000000000 --- a/tests/integration/sample-config/archive/b.encryption-example.com/chain1.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem deleted file mode 100644 index 705cca6c3..000000000 --- a/tests/integration/sample-config/archive/b.encryption-example.com/fullchain1.pem +++ /dev/null @@ -1,56 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE9TCCA92gAwIBAgITAPqBl0IgXf6F9LO/8sV1SsoA9DANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjEyMjMw -MTA0MDBaFw0xNzAzMjMwMTA0MDBaMCMxITAfBgNVBAMTGGIuZW5jcnlwdGlvbi1l -eGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWA6tWR -FAfYyOEM9HtJXK4tCd1tGF2QZrlJHEL3PJzFHonv7ZaPo6Vkrar1uLinM4AVux/f -s9vcsbdebu54DXpj1IllzjKs3tjStHK46luMqj8gf+3yLZIIVnN4YxkItd1WBtim -+144ku1gULsGnnHmuCefXz6qqkLzFZsElqO7NY+TL4F4m/L0lDjYsU++XgbHT9gi -Tw0jAi8SyH8Ia4IYi4ynnMuHuS11e+yOtq16kLW1RdnxrYpleu9z0DU+6Xlr1tbl -eSkyzbWelDgdsicfOxZz5pbmALXErb472TidcHHK6bsMVhR/P1zQK9Ydc+tC33d0 -XCRRgPoduN8XRfcCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE -FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU -RJ6J6HcpXRdRjqfyGshMEzkJy4cwHwYDVR0jBBgwFoAUwMwDRrlYIMxccnDz4S7L -IKb1aDoweAYIKwYBBQUHAQEEbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z -dGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9j -ZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhiLmVu -Y3J5cHRpb24tZXhhbXBsZS5jb20wgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYG -CysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNy -eXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBv -bmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBp -biBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBh -dCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0B -AQsFAAOCAQEA2K8R+nSf9TmfSeUqB+ckObkf8bgyR0qKx/8fGoYGNAzKVE0KUs8u -SDIITjbcTivEuSChycZAGQMEMZal8uT8GsFqqJUcEJUzuxbv7nvZkCSdal1PrRsw -U4cBBuuZ/NvisEZCyjZe8mMdlhcSgThzqljF5Tcz3EWvaH9kxhqr8eL/6pYdAasT -0HqirveIQUrf9LqEEAYGB3P6VI2kjroxUZif7dt2jvOGwJEJfHOjiC8rp0Db0hVZ -omXSsZN6mVkbv1q0I7lgKWu1RHfNAefado3TJZHe8JJ5Oxrl3f2hxi3SzuPGgfXV -ZdKb0zjDXhgumrp0F2eT9zltTIUr8alYcg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw -GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 -MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 -8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym -oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 -ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN -xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 -dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 -AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw -HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 -BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu -b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq -hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF -UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 -AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp -DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 -IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf -zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI -PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w -SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em -2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 -WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt -n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= ------END CERTIFICATE----- diff --git a/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem b/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem deleted file mode 100644 index c43af4f50..000000000 --- a/tests/integration/sample-config/archive/b.encryption-example.com/privkey1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh -DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 -Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt -YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv -Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 -npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 -HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX -Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l -/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r -m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK -tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj -xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n -9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf -Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN -cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT -+kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P -JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F -NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ -IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv -JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc -Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY -EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT -xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe -vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj -t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar -Bwhs4S84KEJFT0LAC3YeRQ== ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/csr/0000_csr-certbot.pem b/tests/integration/sample-config/csr/0000_csr-certbot.pem deleted file mode 100644 index 16d73ffde..000000000 --- a/tests/integration/sample-config/csr/0000_csr-certbot.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChjCCAW4CAQIwFzEVMBMGA1UEAwwMaXMuaXNub3Qub3JnMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7nsHOCTvvQlRYXpI5xE7AggqTVmM8lGi18Y2 -gVlr3WYAS7higHRJjWroAmZ2Bx9IRfHOxwhVWm/hlc/u4w0IYlRnArg6suXrgtn+ -6Ea0WDUCiKEiKvQqD0kaI936hpydU/dY70UZnpKSyi0kiCrLzCkIaXS8HJdLOIXB -Q4FMVqjppYjUejMgrabthq1QTqU0S4MxwS1oj67VqaAkedGWxFgFQ2kIFV0/WL13 -Xs0SCTYyN96KK1Q2CF63HoN79zc+TVslg32DDU5UF7sVVvlkoHcl0OgR9l4jfou5 -HwmatMjXPI+0bWVxmw6iC6tbK7Dx+ytYIodhEOL52Youzy/lLwIDAQABoCowKAYJ -KoZIhvcNAQkOMRswGTAXBgNVHREEEDAOggxpcy5pc25vdC5vcmcwDQYJKoZIhvcN -AQELBQADggEBAAJsLiylvGq64wxVt8EBeXRB4ycBzC5J/pyOWMP9oexW1o3XPhCC -+0tIQVGk7wJMe3+WiPMVsn4pGOUGDaPvfC7ijlvipzaYyLEfnr+J7pukhYbzNHmu -XL5lbTJ0hTCfqUjmi1yE4M/v2eX5yNaEHsZExZ1NbtwutE/Tx5iSqt7kxbIoFqmF -7Tne2JHjt945+/l9yvqaIcEFOmblS0OxY9EjxgJdhKCKbhD/ZoYaVVisc52h/2/M -jtzvzZr1rZCvFnuQxGDco5vYe3u7uJ9tQHLCMpoIorT3kX3yTdgnWxst6XBVUY/P -Q6O18obG4ALoP/ESzvTauQIwFVGfal/jqyI= ------END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0001_csr-certbot.pem b/tests/integration/sample-config/csr/0001_csr-certbot.pem deleted file mode 100644 index 452bc45cd..000000000 --- a/tests/integration/sample-config/csr/0001_csr-certbot.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICgDCCAWgCAQIwFDESMBAGA1UEAwwJaXNub3Qub3JnMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAsEAy7rdPsYFFt9VsK9NZy+W9nbsYGmvIaMSyJkEg -Xe2P0MmnWG/hn6F1bLPm85uS5oQsOWDpwVz31tKhoWhUDbRzPWP5Ur2NnHY92Whz -5tP4ir4vEEDuB9etQ8+wZ7+3z9q1VhPcgDdYyouQVB0QejJ1yUBiVPr289bW//ln -kj9DFxn4oufoJ4ELSZSZgWFM92EGKMMy1zD2bJH87mI0Gs0pIOEo+QMJ8TvVEbau -+aFaTANslqRAF5LaWcrPgvHor7cK5w/4bVBZCmY2QYKqlYwZiRPpwg3Ii6B9Q8kz -rDkGSDjwsazca4api57cza13XkRl7KvyZbwTwlFBud+ydwIDAQABoCcwJQYJKoZI -hvcNAQkOMRgwFjAUBgNVHREEDTALgglpc25vdC5vcmcwDQYJKoZIhvcNAQELBQAD -ggEBAB3vniZw2ML6E9jrMY8DtQjPDDNr1BqOGzyOaJipqpGZSRvhTA44DAAjdFpS -5BLrnXniPIZGG4/6WorLTEDBnlFcLinUg7GDT2DpauQa+4PLxFi13hE1TuSVOp9A -08YXhzALvZxMIjQ/tVhAp0+PkGEWU2wI0SmDvUUTJqMwSJYgXkf/vBS34/koKywV -gPDod5AbLuhYgKiQYwDZ0dd69leT0REmizuaHtA6tW3mBgewSKotwqY3fHmhHV8o -YLSVhImz4jJjK3LjmcdXuBxqE0z+p6n/+lSGG8RR/E8pix4OAkVAP6nyt/loW1BX -ZzWOuSHozGN5UJSL248vLFWrsV8= ------END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0002_csr-certbot.pem b/tests/integration/sample-config/csr/0002_csr-certbot.pem deleted file mode 100644 index 2ee44b3fd..000000000 --- a/tests/integration/sample-config/csr/0002_csr-certbot.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYS5lbmNyeXB0aW9uLWV4YW1wbGUuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrPRxyiGwKrJY/Aavv1r -yP4k7cepumEtPOGg0dgX59byCXIzIGY4zJmNzy/wTdTcpgRe65FbNH5u9RcFws6F -KiAiJ9kM2IwoQXIM1wOUbTzN70ZOPaFNDKN63Zm49Y264ni7lQwW+htpx4OohO6o -SpSuMIGLkKYRezCtXeXYFVKI5w9/6X5lHzXEbupyMZ8sbphBZxqexhiivP+RJG3B -iMBPg1p5Rg1dV4fWW5ZNaFt53ymYp8FV+/44SbBTICFITV49+fIKPfvCjX6epDPf -sgk0SkjRE1vGsTtkKADjRCsVWpz1d69zKkWL0N+1VbMM0G133tCWYe4ioD0pvcL9 -7wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhhLmVuY3J5cHRp -b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJyKJHdUwR9BOKYJarUy -P8mqu6UBUt8faSu6o3EUeDHbnUgxGAVwB5TJV0+JwIjPFQFRofHE8CFhUvi0W0YJ -BsGVqblnJzz80NkUX9uwjBAGKaDxXqXDOctkQSAOJxM/rvD2uJLmlokibDDm7mnS -DX8SUVAPgORDGlVTGATjvmA3YeH05gHRFgRDWFP5DOZs99fx4957HrXhsIxew98s -Felupgswnouyq3crrgcjY0qo3Pc5gjUcuwaT2cjtvzi93f/ImDt6f1sdSSJB00wk -34lbs/Z+0G8bH1dqYIZzkwNgq7rolhDYh3WRgTlfkgkV7FlkQGm8qn5uoQvaXaaS -ShM= ------END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/csr/0003_csr-certbot.pem b/tests/integration/sample-config/csr/0003_csr-certbot.pem deleted file mode 100644 index 2a50dc33d..000000000 --- a/tests/integration/sample-config/csr/0003_csr-certbot.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICnjCCAYYCAQIwIzEhMB8GA1UEAwwYYi5lbmNyeXB0aW9uLWV4YW1wbGUuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYDq1ZEUB9jI4Qz0e0lc -ri0J3W0YXZBmuUkcQvc8nMUeie/tlo+jpWStqvW4uKczgBW7H9+z29yxt15u7ngN -emPUiWXOMqze2NK0crjqW4yqPyB/7fItkghWc3hjGQi13VYG2Kb7XjiS7WBQuwae -cea4J59fPqqqQvMVmwSWo7s1j5MvgXib8vSUONixT75eBsdP2CJPDSMCLxLIfwhr -ghiLjKecy4e5LXV77I62rXqQtbVF2fGtimV673PQNT7peWvW1uV5KTLNtZ6UOB2y -Jx87FnPmluYAtcStvjvZOJ1wccrpuwxWFH8/XNAr1h1z60Lfd3RcJFGA+h243xdF -9wIDAQABoDYwNAYJKoZIhvcNAQkOMScwJTAjBgNVHREEHDAaghhiLmVuY3J5cHRp -b24tZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACDw8/zjFaIdp4aqyrzT -fzaqAnoXZt3+0JDPLANy3DLCJmK2TQMyItg/Oid5NEQ45UluXv811IMCcONyVmrD -19W3XErhTJOJMgpjg4GLBRRFhLm+uTIcbv/xEeUgOYbslsqwi2gHECe1Vsj/Ahbo -QXXqcDg1cXe6VTQhX+Nw5q30t/oCmkJWcUVHBON2nbOujRz1+z6AjVl1dM+CYDRq -bsKn7m3biYS7lx7/ApIuhJQsghcmccCtWrH5GsOUsJUgiANv5u+QZgGaajkCRKYV -fD/u8qTPfKb/+lTxtDrfFOGH+mbZKbKf2/ibneYcql8fFQWiapbudI2cMk8yDxA9 -2Tw= ------END CERTIFICATE REQUEST----- diff --git a/tests/integration/sample-config/keys/0000_key-certbot.pem b/tests/integration/sample-config/keys/0000_key-certbot.pem deleted file mode 100644 index 9a018c41e..000000000 --- a/tests/integration/sample-config/keys/0000_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDuewc4JO+9CVFh -ekjnETsCCCpNWYzyUaLXxjaBWWvdZgBLuGKAdEmNaugCZnYHH0hF8c7HCFVab+GV -z+7jDQhiVGcCuDqy5euC2f7oRrRYNQKIoSIq9CoPSRoj3fqGnJ1T91jvRRmekpLK -LSSIKsvMKQhpdLwcl0s4hcFDgUxWqOmliNR6MyCtpu2GrVBOpTRLgzHBLWiPrtWp -oCR50ZbEWAVDaQgVXT9YvXdezRIJNjI33oorVDYIXrceg3v3Nz5NWyWDfYMNTlQX -uxVW+WSgdyXQ6BH2XiN+i7kfCZq0yNc8j7RtZXGbDqILq1srsPH7K1gih2EQ4vnZ -ii7PL+UvAgMBAAECggEBAIX9jeLXrfNSRu0z3b4mCjdsCwiGphCIGayOa5VlfptY -chYZNQ7jR2gzhsPCedIqm1rhL8LYRcyYS/D2cUwUyH8m2PHIPQLC9/3/KZ+sCiv9 -LL1De4USxobsFcnNMLNtT2Ab+1YERw63X85EauAu226MJ3PI6OBPiS3qyNl6zj9p -do9SyzsNFEGtDk+ndWf3keoHBKLge4DP1lA3Jt42wSUxVv9U5SLvFpMQm8PqbqrK -4ofXcgxMFIJHDDGXsoDI7LOOsV6ncBVlui0ELM/QWBb5x1605VxqEDRL+h/wMp5Y -JIc6HbgcERmtHmyFlHHNtjAXxeulJVDJQDekd/irJ5ECgYEA/WQJ4LwkkA/Yhf2W -WYJtD8LuwzRnvGs3R+rgx3+hOeO4TFZD5fzObZVRSwWQO2jbOtBJOaRLUsUngcJQ -DXr/FGf1rnGhLmNeLE+jN9FS73wBhEXViFZ/fzhVibGbc7u45Y5REykZj8HtUHP5 -hBKR2Nx94WDiv1MBgcKrRk6yI50CgYEA8O+vWcMzEdPtonHl8UgTa8/c5g/RBBvS -plB8mVsmM/E5CNwnetZM32cg7dC7yNaZzn3qF6w+LdE2vw3j5VbqvuVUvsRgvYcJ -3kMbHsbsxkRw+HVWZGgEtWNzuYQUL0xN+xzIZDWkbtuaihqYAy4voYNAM08BTNcE -POQEMIGxcDsCgYEAg+TLo3grS/WDjhM2bHcQT9D2uRMRIClqx/uBbzaG9HwNFWcd -xpv102KSwwstTU9CNfXu95sGPhozez5qrumj1rpaTqgE7wF4JnZ5jfdeRRv2KiSz -hlkH2m+3TontUauYDZ0rpF6TWJnn7iW/7jhARHJY77SfslkBgsqSnnEeFp0CgYEA -7FsFVvZRzCRt01UOsPL28mWYmyxa7D/rFvKQONUdFgmG3PUz2aIPCX2e5Q1GmlBD -1Djbg1uaJ9I8dZJHxbzNTnWk+/ujt2mYuax1F20n65xKgsKA/MC6FcM5TH2QW5Hs -UfI7d2rUI1hVMzPBeiU93qDmQy825E1uP9mjbn5cNe8CgYAsBpJgS1LkDruyWmjG -ZTzdHGciA1O3gUArLQmyUfJlPS3Hgwn7wnBBihtGZDHmjJ7734+PQ9ioCnO9Pb+K -8Cp29vJ85lka7o7I48OeScLmczgEUYOPCrbkkKJdKaG6gn5CKpRBVYDlhbWjVZ51 -4uda/BQ1hqHh8WmxK6x21qC9JQ== ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0001_key-certbot.pem b/tests/integration/sample-config/keys/0001_key-certbot.pem deleted file mode 100644 index a3a7faf55..000000000 --- a/tests/integration/sample-config/keys/0001_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwQDLut0+xgUW3 -1Wwr01nL5b2duxgaa8hoxLImQSBd7Y/QyadYb+GfoXVss+bzm5LmhCw5YOnBXPfW -0qGhaFQNtHM9Y/lSvY2cdj3ZaHPm0/iKvi8QQO4H161Dz7Bnv7fP2rVWE9yAN1jK -i5BUHRB6MnXJQGJU+vbz1tb/+WeSP0MXGfii5+gngQtJlJmBYUz3YQYowzLXMPZs -kfzuYjQazSkg4Sj5AwnxO9URtq75oVpMA2yWpEAXktpZys+C8eivtwrnD/htUFkK -ZjZBgqqVjBmJE+nCDciLoH1DyTOsOQZIOPCxrNxrhqmLntzNrXdeRGXsq/JlvBPC -UUG537J3AgMBAAECggEBAJoZR27X72GvORmmDFG1FInlcIf8EPLo0exoLaqsvnPh -RSCzbxEvoQFE1boZARB1MVdCsLfqN/bMJhU5TAAni3YAE9HVGyRwfuQRrbnsTYnA -Q0prRhLb8kIBHIhxijbrtPaSroF4FA42VfehVqt0TffJLpqrJE5QrqI7cPeVRCzk -laLyi2rjZBhN6l1OxFSIOrEDlcowlPUMORbmNDMbq/dLu5riVO/kP2x70K1IiANI -NZzVhMwkktYj3Ku2altRLcyRrC3Bs46w2QF6wiC88/LMapt79um65P/SgcCgyOYE -oxJywZwMnyw8ut1Y+KS8B7AdzqWmj7Q9wr0xbW6+4eECgYEA6sNrMGZVRUFRPAcr -m3y5fkM/WJ8tAkT3hI2/noljv3k8iameTy/B/y3p+aM8/6Oa/gdO/SWtfKPednkf -CIh/3J5tJ1yvK7wHEEU6r6qxVKr2FLCMfSXoGx+E+r9qPF8WdV+55beVgO86UqA5 -y9a6DhNA+Xt4jDJc+rbpga0pj60CgYEAwDHDV0lR7jVT6iiU6VhAu1gM/SBVqXE/ -VSfmGihgaO4pJ9OgfqusKbraNONc+oBub7B4T3sSnF/I0mSUclD6brmG99OWLIg8 -L6/ed+bLPRO0iTvKRLbyBLom1Totfh/X6iQ2Zci40vLIS7kbYDban16ca+iSm+0B -41RV4q6+vzMCgYBLoxiW6HGStZ+xonHHT+EHsCzppac/su64c18IeiV8HFiH1fFe -e/mZ+LYIqzJM/u5B6CLn5srFfJqBOzbnbescLqLmarM5eQQhltx4mps1tzs/oT4y -WBM3IembTC6zMsOun1/qhkKR3wHAe0UDyrP5MvTdLI3DRbq1QFdtY1gfpQKBgEgg -pNGWJ5RBGSvwbOohf7GPOtioEN3VLVJ09crtSjk23+Uda8b+AE9s20Ur6pHsLwXl -cVFKu9JJtCEZNAiu0T1KjRdmpZ4yxnuTAed3iuByC7fQ43jkO3GAtuAgxD/oDWzG -iE+sg4hPKtIYNujlzSgwJn3su1CfIq1A0jaPI/C3AoGAHGTBtsXdR1goFvcxwA+n -l2bAs/InoED5nj26a//JuONgtGlm//QKCxIgjjktpeZm8sfsaYeR+rwIUODWRX/e -LUF85a70SaH+FZRXBRS2d/zaNxO4F37nE5fwO+VAurSb7El7yOyCepK22iSHMYdl -xak78KZKv3HXW5yrfA+dc2Y= ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0002_key-certbot.pem b/tests/integration/sample-config/keys/0002_key-certbot.pem deleted file mode 100644 index b3059cb47..000000000 --- a/tests/integration/sample-config/keys/0002_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqs9HHKIbAqslj -8Bq+/WvI/iTtx6m6YS084aDR2Bfn1vIJcjMgZjjMmY3PL/BN1NymBF7rkVs0fm71 -FwXCzoUqICIn2QzYjChBcgzXA5RtPM3vRk49oU0Mo3rdmbj1jbrieLuVDBb6G2nH -g6iE7qhKlK4wgYuQphF7MK1d5dgVUojnD3/pfmUfNcRu6nIxnyxumEFnGp7GGKK8 -/5EkbcGIwE+DWnlGDV1Xh9Zblk1oW3nfKZinwVX7/jhJsFMgIUhNXj358go9+8KN -fp6kM9+yCTRKSNETW8axO2QoAONEKxVanPV3r3MqRYvQ37VVswzQbXfe0JZh7iKg -PSm9wv3vAgMBAAECggEAattP6Wz8FaWTlgTaqU44Z8R314VSQULNr7vKETJFnLKY -JsOfL5vt2F4TQGxQ8Ffcm+xGgw4l2tF+odv8ljrzbzBYUTt06CWsmXNMiFhMVKlo -fG01Uy0i71Ny+T9eYhCLuXM8cYv04jHA4M0Q8831+WHjPKgLdswOS2BoVkwoHQfc -xEo40D0sPynd+KRukhgR+5AjwMdaNOV7S8c5iuQYIaZ1Xe5AyfiQkMV4LdbobMDj -bHzGxdeC5GRVOHnMBYrRotgSt4+bsQGeoV9yWY0WAVvnoDfRBRdWK8yRVhuJY1+D -WB6sPJ5cOg7Ijclubo9b+EaUkddvP0aCA3FepqNwcQKBgQDR0hz9OSom2fBjLaR2 -mQe3LqnotwPCuMmXuKndGIwJz9KgelBaRNUcvDtnzSzQVZ3h9/YFJKUkoVPVCoAu -wAF9aBeDGs+LdHerBK8fI87PXwCV0OlZLQfUw1/82dpO/dyYXVeGorrO6FE/Oxb8 -enLerMW0Ocp/MhEgM5lFRUJM1wKBgQDQRauI9QuMoBnl516pOs+7EPRvTwe4oBpO -iH2U7ryJ/YQTgsx25sDWqQBouEnv3j83wnVh9kApkS8UXFd4ZwuizIFCMlgrxw4x -nKDsd1TZOLUO2FNi09YWPUnzxzQBOjBeekEIDKUQCLOKttTrjRHgGld3tmVtHWtL -W+OvNIdcqQKBgCMpqjAJr3W5Wl7UnFY/yRo62MCmQxwT6bzidp0V6woN6Qd52BN4 -q5pYNUBtExCK+J2Q94rfHEnqO2ldjCPJi7ZfhmkzSgrd5twjOdHnJ1Z7Xla9Hw4R -zNksMN7oB3zrcFecdPmcNeBM8Ki/F1gSkUOeArf0Y2ozkskpvIruU3EbAoGBAMVz -h7CMQKrNjj/8Hi5qZ05+QH7Wegd7IfWaSRTNUUmxY2nr81Q2aFQaXRzquo4CMgT3 -Arog76t4zR2MfhDUAKATKehMOnMmgDpgt9/3MiXOMTkltchX9PuYl2faT19qfzjS -xpyPAF43IaA8vZejYnMIBiyka3wLDBGhyDXuovYhAoGAB/AZnOM/4SQuIdtzmBSy -YsHpXcNgRPqvfauCus3e5I6H4wmi+nqF/jyt0oyDBDKZki67CpStwu5Eo7tcLLnY -o+VfJ9co8jUfVxRh0NlZwomF1t/8yAm/deWoV9sX9Yj71ft/eomCifNseeeg31Kl -wkqKc3PndJHrR40mswUOHbs= ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/keys/0003_key-certbot.pem b/tests/integration/sample-config/keys/0003_key-certbot.pem deleted file mode 100644 index c43af4f50..000000000 --- a/tests/integration/sample-config/keys/0003_key-certbot.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1gOrVkRQH2Mjh -DPR7SVyuLQndbRhdkGa5SRxC9zycxR6J7+2Wj6OlZK2q9bi4pzOAFbsf37Pb3LG3 -Xm7ueA16Y9SJZc4yrN7Y0rRyuOpbjKo/IH/t8i2SCFZzeGMZCLXdVgbYpvteOJLt -YFC7Bp5x5rgnn18+qqpC8xWbBJajuzWPky+BeJvy9JQ42LFPvl4Gx0/YIk8NIwIv -Esh/CGuCGIuMp5zLh7ktdXvsjratepC1tUXZ8a2KZXrvc9A1Pul5a9bW5XkpMs21 -npQ4HbInHzsWc+aW5gC1xK2+O9k4nXBxyum7DFYUfz9c0CvWHXPrQt93dFwkUYD6 -HbjfF0X3AgMBAAECggEAYjEnWnjNTF10d4Qps5UBxdzpzFfb6apYWH78AiJ9MRbX -Kaqab2ywDKdF6Qpcb9FM5EtdW6YLSLPBlUFKZEqgiAkAD4D7J6EsQkLjinkNmI+l -/tbXPuRY0PsfwgJsIjv7H44N0CGuNdAHdNI5eqTfDSHTmOP4hA+SYvvdQWsfD94r -m4ocr2YfL4BmEh3hujb8NjVD8csSnFlpeVibtJ1rWiv1otLaEuVmcN49n0rIj0IK -tiCIdqqIscVZ+P3fFfr/E3oL2nhBqxRnzqoK/HNTpI4JJAbRGP51nVr0QhZYpIuj -xDM+zeuIt0lMYOzoE+JD0612Q66mokBPHZAd5MuEwQKBgQDbdJUQfcw/9zHuWm4n -9+wYgMN1QhfJNEr21LUjbe551YapkU389mBJJIlmjH5p67PaMRuJ1o6uRJWv40hf -Y4xy6iViLc1FExIvRVznxMCIyCELtuvYMiCJtaekFKunziniw8yg5SwSZJY3GlXN -cDAwIcgb9PPU5rBEip8g0DIp1wKBgQDTunF3OtEoVqdsPSmw5y1767YTCsm3dnVT -+kwp7ZrX3TJ3Xd6EVPWUBP1HbGD3qfsIR+Ha3Vl8OiLNC4zDoZY886U4qY5Mtn4P -JhUN0H9zYZg2l9gFf9u8RkUoPZPXXuk+eQnlGT133PrkCloDlqP47u/fQ5dV1t6F -NghgwfOA4QKBgHI/IRMyylBKmj3h6hL4qHqhHiA/Ri7DAHu7hIlrQ4k9ths0wAr/ -IGUzlixC29S8libzBckeX60tm1ez1QuDwaxZZRjVi1V4djERxSoLbchHl5yHoAQv -JG1Mmnd7I1n6pCefkzn31JfGscUB+sU2sH9+NrUHMqEVb5JfMDRe7p6FAoGAcYGc -Xqz7gEKkUtSfSyVELxD4dVDtPxuUXsbqmfe1cVA2Q+Pg7NSXKxlZpzak7WEFITVY -EXtlA8Iu8fnlJuOzpU2BH9VWYi3beseRtew2x2Zksa/JsXkQFekeHiqU3XsWU9WT -xmw3ldCz+BjMlOvnUAbYNbsIoI4mkQecijKwFkECgYA2zafSyWCW5zAronUBQDEe -vJumAJ77TwpYzzvH2ic6siWimdePxQ6TgdM3s1FgpdkbaXgKzS5MbZbD0Uyg3MEj -t6ZT7GSWq39wLDJVDYJ5ClAi8mv9WNs8X8rJ0CkdiPZgHC77OwBELthGn2p9ncar -Bwhs4S84KEJFT0LAC3YeRQ== ------END PRIVATE KEY----- diff --git a/tests/integration/sample-config/live/a.encryption-example.com/README b/tests/integration/sample-config/live/a.encryption-example.com/README deleted file mode 100644 index 15194ae3a..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/README +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains your keys and certificates. - -`privkey.pem` : the private key for your certificate. -`fullchain.pem`: the certificate file used in most server software. -`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. -`cert.pem` : will break many server configurations, and should not be used - without reading further documentation (see link below). - -We recommend not moving these files. For more information, see the Certbot -User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem b/tests/integration/sample-config/live/a.encryption-example.com/cert.pem deleted file mode 120000 index 79b6abdf9..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem b/tests/integration/sample-config/live/a.encryption-example.com/chain.pem deleted file mode 120000 index 2d6b30420..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem deleted file mode 120000 index b801ef735..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem deleted file mode 120000 index 74e20c5ff..000000000 --- a/tests/integration/sample-config/live/a.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/a.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/README b/tests/integration/sample-config/live/b.encryption-example.com/README deleted file mode 100644 index 15194ae3a..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/README +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains your keys and certificates. - -`privkey.pem` : the private key for your certificate. -`fullchain.pem`: the certificate file used in most server software. -`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. -`cert.pem` : will break many server configurations, and should not be used - without reading further documentation (see link below). - -We recommend not moving these files. For more information, see the Certbot -User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem b/tests/integration/sample-config/live/b.encryption-example.com/cert.pem deleted file mode 120000 index 41b06370e..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/cert.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/cert1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem b/tests/integration/sample-config/live/b.encryption-example.com/chain.pem deleted file mode 120000 index 2d3e18bec..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/chain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/chain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem b/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem deleted file mode 120000 index 3a08c1432..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/fullchain.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/fullchain1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem b/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem deleted file mode 120000 index 182aa6d78..000000000 --- a/tests/integration/sample-config/live/b.encryption-example.com/privkey.pem +++ /dev/null @@ -1 +0,0 @@ -../../archive/b.encryption-example.com/privkey1.pem \ No newline at end of file diff --git a/tests/integration/sample-config/options-ssl-apache.conf b/tests/integration/sample-config/options-ssl-apache.conf deleted file mode 100644 index ec07a4ba3..000000000 --- a/tests/integration/sample-config/options-ssl-apache.conf +++ /dev/null @@ -1,22 +0,0 @@ -# Baseline setting to Include for SSL sites - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/tests/integration/sample-config/renewal/a.encryption-example.com.conf b/tests/integration/sample-config/renewal/a.encryption-example.com.conf deleted file mode 100644 index 4455137b4..000000000 --- a/tests/integration/sample-config/renewal/a.encryption-example.com.conf +++ /dev/null @@ -1,15 +0,0 @@ -# renew_before_expiry = 30 days -version = 0.10.0.dev0 -archive_dir = sample-config/archive/a.encryption-example.com -cert = sample-config/live/a.encryption-example.com/cert.pem -privkey = sample-config/live/a.encryption-example.com/privkey.pem -chain = sample-config/live/a.encryption-example.com/chain.pem -fullchain = sample-config/live/a.encryption-example.com/fullchain.pem - -# Options used in the renewal process -[renewalparams] -authenticator = apache -installer = apache -account = 48d6b9e8d767eccf7e4d877d6ffa81e3 -config_dir = sample-config -server = https://acme-staging.api.letsencrypt.org/directory diff --git a/tests/integration/sample-config/renewal/b.encryption-example.com.conf b/tests/integration/sample-config/renewal/b.encryption-example.com.conf deleted file mode 100644 index 58d8a13d9..000000000 --- a/tests/integration/sample-config/renewal/b.encryption-example.com.conf +++ /dev/null @@ -1,15 +0,0 @@ -# renew_before_expiry = 30 days -version = 0.10.0.dev0 -archive_dir = sample-config/archive/b.encryption-example.com -cert = sample-config/live/b.encryption-example.com/cert.pem -privkey = sample-config/live/b.encryption-example.com/privkey.pem -chain = sample-config/live/b.encryption-example.com/chain.pem -fullchain = sample-config/live/b.encryption-example.com/fullchain.pem - -# Options used in the renewal process -[renewalparams] -authenticator = apache -installer = apache -account = 48d6b9e8d767eccf7e4d877d6ffa81e3 -config_dir = sample-config -server = https://acme-staging.api.letsencrypt.org/directory diff --git a/tests/manual-dns-auth.sh b/tests/manual-dns-auth.sh deleted file mode 100755 index febecf455..000000000 --- a/tests/manual-dns-auth.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# If domain begins with fail, fail the challenge by not completing it. -if [[ "$CERTBOT_DOMAIN" != fail* ]]; then - curl -X POST 'http://localhost:8055/set-txt' -d \ - "{\"host\": \"_acme-challenge.$CERTBOT_DOMAIN.\", \ - \"value\": \"$CERTBOT_VALIDATION\"}" -fi diff --git a/tests/manual-dns-cleanup.sh b/tests/manual-dns-cleanup.sh deleted file mode 100755 index 1c09e892c..000000000 --- a/tests/manual-dns-cleanup.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# If domain begins with fail, we didn't complete the challenge so there is -# nothing to clean up. -if [[ "$CERTBOT_DOMAIN" != fail* ]]; then - curl -X POST 'http://localhost:8055/clear-txt' -d \ - "{\"host\": \"_acme-challenge.$CERTBOT_DOMAIN.\"}" -fi diff --git a/tests/manual-http-auth.sh b/tests/manual-http-auth.sh deleted file mode 100755 index 48c33f04b..000000000 --- a/tests/manual-http-auth.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -uri_path=".well-known/acme-challenge/$CERTBOT_TOKEN" - -# This script should be run from the top level. e.g. ./tests/manual-http-auth.sh -source_dir="$(pwd)" -cd $(mktemp -d) -mkdir -p $(dirname $uri_path) -echo $CERTBOT_VALIDATION > $uri_path -python "$source_dir/tests/run_http_server.py" $http_01_port >/dev/null 2>&1 & -server_pid=$! -while ! curl "http://localhost:$http_01_port/$uri_path" >/dev/null 2>&1; do - sleep 1s -done -echo $server_pid diff --git a/tests/manual-http-cleanup.sh b/tests/manual-http-cleanup.sh deleted file mode 100755 index 5e437bf08..000000000 --- a/tests/manual-http-cleanup.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -kill $CERTBOT_AUTH_OUTPUT diff --git a/tests/pebble-fetch.sh b/tests/pebble-fetch.sh index 6b562eec2..52a7d8c98 100755 --- a/tests/pebble-fetch.sh +++ b/tests/pebble-fetch.sh @@ -2,48 +2,17 @@ # Download and run Pebble instance for integration testing set -xe -PEBBLE_VERSION=v1.0.1 - -# 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} +PEBBLEPATH=${PEBBLEPATH:-$GOPATH/pebble} +if [[ ! -d ${PEBBLEPATH} ]]; then + git clone --depth=1 https://github.com/letsencrypt/pebble ${PEBBLEPATH} +fi -cat << UNLIKELY_EOF > "$PEBBLEPATH/docker-compose.yml" -version: '3' -services: - pebble: - image: letsencrypt/pebble:${PEBBLE_VERSION} - command: pebble -dnsserver 10.30.50.3:8053 - environment: - - PEBBLE_VA_NOSLEEP=1 - ports: - - 14000:14000 - networks: - acmenet: - ipv4_address: 10.30.50.2 - challtestsrv: - image: letsencrypt/pebble-challtestsrv:${PEBBLE_VERSION} - command: pebble-challtestsrv -defaultIPv6 "" -defaultIPv4 10.30.50.1 - ports: - - 8055:8055 - networks: - acmenet: - ipv4_address: 10.30.50.3 -networks: - acmenet: - driver: bridge - ipam: - driver: default - config: - - subnet: 10.30.50.0/24 -UNLIKELY_EOF +cd ${PEBBLEPATH} -docker-compose -f "$PEBBLEPATH/docker-compose.yml" up -d pebble +docker-compose up -d -set +x # reduce verbosity while waiting for boulder +set +x # reduce verbosity while waiting for pebble for n in `seq 1 150` ; do if curl -k https://localhost:14000/dir 2>/dev/null; then break @@ -56,3 +25,6 @@ if ! curl -k https://localhost:14000/dir 2>/dev/null; then echo "timed out waiting for pebble to start" exit 1 fi + +# Setup the DNS resolution used by pebble instance to docker host +curl -X POST -d '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 diff --git a/tests/run_http_server.py b/tests/run_http_server.py deleted file mode 100644 index 0e4f8ac79..000000000 --- a/tests/run_http_server.py +++ /dev/null @@ -1,11 +0,0 @@ -import runpy -import sys - -# Run Python's built-in HTTP server -# Usage: python ./tests/run_http_server.py port_num -# NOTE: This script should be compatible with 2.7, 3.4+ - -# sys.argv (port number) is passed as-is to the HTTP server module -runpy.run_module( - 'http.server' if sys.version_info[0] == 3 else 'SimpleHTTPServer', - run_name='__main__') diff --git a/tests/tox-boulder-integration.sh b/tests/tox-boulder-integration.sh deleted file mode 100755 index 8c8a967fd..000000000 --- a/tests/tox-boulder-integration.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -e -# A simple wrapper around tests/boulder-integration.sh that activates the tox -# virtual environment defined by the environment variable TOXENV before running -# integration tests. - -if [ -z "${TOXENV+x}" ]; then - echo "The environment variable TOXENV must be set to use this script!" >&2 - exit 1 -fi - -source .tox/$TOXENV/bin/activate -tests/boulder-integration.sh diff --git a/tools/_venv_common.py b/tools/_venv_common.py index cb19d583c..1594fd05f 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -42,6 +42,7 @@ REQUIREMENTS = [ '-e certbot-postfix', '-e letshelp-certbot', '-e certbot-compatibility-test', + '-e certbot-ci', ] VERSION_PATTERN = re.compile(r'^(\d+)\.(\d+).*$') diff --git a/tox.ini b/tox.ini index 1087a1969..7f2c18d38 100644 --- a/tox.ini +++ b/tox.ini @@ -117,11 +117,6 @@ commands = setenv = {[testenv:py27-oldest]setenv} -[testenv:py27_install] -basepython = python2.7 -commands = - {[base]install_packages} - [testenv:py27-cover] basepython = python2.7 commands = @@ -254,12 +249,28 @@ passenv = DOCKER_* [testenv:integration] commands = {[base]pip_install} acme . certbot-nginx certbot-ci - pytest {toxinidir}/certbot-ci/certbot_integration_tests \ + pytest certbot-ci/certbot_integration_tests \ --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ - --cov-config={toxinidir}/certbot-ci/certbot_integration_tests/.coveragerc \ - -W 'ignore:Unverified HTTPS request' - coverage report --include 'certbot/*' --show-missing --fail-under=64 + --cov-config=certbot-ci/certbot_integration_tests/.coveragerc + coverage report --include 'certbot/*' --show-missing --fail-under=67 coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 -passenv = - DOCKER_* +passenv = DOCKER_* + +[testenv:integration-certbot-oldest] +commands = + {[base]pip_install} . + {[base]pip_install} certbot-ci + pytest certbot-ci/certbot_integration_tests/certbot_tests \ + --acme-server={env:ACME_SERVER:pebble} +passenv = DOCKER_* +setenv = {[testenv:py27-oldest]setenv} + +[testenv:integration-nginx-oldest] +commands = + {[base]pip_install} certbot-nginx + {[base]pip_install} certbot-ci + pytest certbot-ci/certbot_integration_tests/nginx_tests \ + --acme-server={env:ACME_SERVER:pebble} +passenv = DOCKER_* +setenv = {[testenv:py27-oldest]setenv} -- cgit v1.2.3 From 5ab6a597b0e23b604e4d6e339a9d3784abc57217 Mon Sep 17 00:00:00 2001 From: Andreas Vogler Date: Thu, 16 May 2019 10:40:17 +0200 Subject: Add an option to dns_rfc2136 plugin to specify an authorative base domain. (#7029) * Add an option to dns_rfc2136 plugin to explicitly specify an authorative base domain. * Updated CHANGELOG mentioning added base domain option * Made the comment on the new option more clear on auto-detection * Updated comment on how the authorative base domain is determined --- CHANGELOG.md | 4 +- .../certbot_dns_rfc2136/__init__.py | 6 ++- .../certbot_dns_rfc2136/dns_rfc2136.py | 50 +++++++++++++++------- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 29 ++++++++++++- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7f34a09..2f699feec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* dns_rfc2136 plugin now supports explicitly specifing an authorative + base domain for cases when the automatic method does not work (e.g. + Split horizon DNS) ### Changed diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py index 12b360959..cebff2841 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py @@ -21,8 +21,8 @@ Credentials ----------- Use of this plugin requires a configuration file containing the target DNS -server and optional port that supports RFC 2136 Dynamic Updates, the name -of the TSIG key, the TSIG key secret itself and the algorithm used if it's +server, optional authorative domain and optional port that supports RFC 2136 Dynamic Updates, +the name of the TSIG key, the TSIG key secret itself and the algorithm used if it's different to HMAC-MD5. .. code-block:: ini @@ -33,6 +33,8 @@ different to HMAC-MD5. dns_rfc2136_server = 192.0.2.1 # Target DNS port dns_rfc2136_port = 53 + # Authorative domain (optional, will try to auto-detect if missing) + dns_rfc2136_base_domain = example.com # TSIG key name dns_rfc2136_name = keyname. # TSIG key secret diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index 2061374e0..5db8c3020 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -79,25 +79,33 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_rfc2136_client().del_txt_record(validation_name, validation) def _get_rfc2136_client(self): + key = _RFC2136Key(self.credentials.conf('name'), + self.credentials.conf('secret'), + self.ALGORITHMS.get(self.credentials.conf('algorithm'), + dns.tsig.HMAC_MD5)) return _RFC2136Client(self.credentials.conf('server'), int(self.credentials.conf('port') or self.PORT), - self.credentials.conf('name'), - self.credentials.conf('secret'), - self.ALGORITHMS.get(self.credentials.conf('algorithm'), - dns.tsig.HMAC_MD5)) + key, + self.credentials.conf('base-domain')) +class _RFC2136Key(object): + def __init__(self, name, secret, algorithm): + self.name = name + self.secret = secret + self.algorithm = algorithm class _RFC2136Client(object): """ Encapsulates all communication with the target DNS server. """ - def __init__(self, server, port, key_name, key_secret, key_algorithm): + def __init__(self, server, port, base_domain, key): self.server = server self.port = port self.keyring = dns.tsigkeyring.from_text({ - key_name: key_secret + key.name: key.secret }) - self.algorithm = key_algorithm + self.algorithm = key.algorithm + self.base_domain = base_domain def add_txt_record(self, record_name, record_content, record_ttl): """ @@ -171,23 +179,33 @@ class _RFC2136Client(object): def _find_domain(self, record_name): """ - Find the closest domain with an SOA record for a given domain name. + If 'base_domain' option is specified check if the requested domain matches this base domain + and return it. If not explicitly specified find the closest domain with an SOA record for + the given domain name. - :param str record_name: The record name for which to find the closest SOA record. + :param str record_name: The record name for which to find the base domain. :returns: The domain, if found. :rtype: str :raises certbot.errors.PluginError: if no SOA record can be found. """ - domain_name_guesses = dns_common.base_domain_name_guesses(record_name) + if self.base_domain: + if not record_name.endswith(self.base_domain): + raise errors.PluginError('Requested domain {0} does not match specified base ' + 'domain {1}.' + .format(record_name, self.base_domain)) + else: + return self.base_domain + else: + domain_name_guesses = dns_common.base_domain_name_guesses(record_name) - # Loop through until we find an authoritative SOA record - for guess in domain_name_guesses: - if self._query_soa(guess): - return guess + # Loop through until we find an authoritative SOA record + for guess in domain_name_guesses: + if self._query_soa(guess): + return guess - raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' - .format(record_name, domain_name_guesses)) + raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' + .format(record_name, domain_name_guesses)) def _query_soa(self, domain_name): """ diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index d800f1ec7..bed3445b6 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -73,9 +73,12 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic class RFC2136ClientTest(unittest.TestCase): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client + from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client, _RFC2136Key - self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) + self.rfc2136_client = _RFC2136Client(SERVER, + PORT, + None, + _RFC2136Key(NAME, SECRET, dns.tsig.HMAC_MD5)) @mock.patch("dns.query.tcp") def test_add_txt_record(self, query_mock): @@ -162,6 +165,28 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._find_domain, 'foo.bar.'+DOMAIN) + def test_find_domain_with_base(self): + # _query_soa | pylint: disable=protected-access + self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) + self.rfc2136_client.base_domain = 'bar.' + DOMAIN + + # _find_domain | pylint: disable=protected-access + domain = self.rfc2136_client._find_domain('foo.bar.' + DOMAIN) + + self.assertTrue(domain == 'bar.' + DOMAIN) + + def test_find_domain_with_wrong_base(self): + + # _query_soa | pylint: disable=protected-access + self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) + self.rfc2136_client.base_domain = 'wrong.' + DOMAIN + + self.assertRaises( + errors.PluginError, + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain, + 'foo.bar.' + DOMAIN) + @mock.patch("dns.query.udp") def test_query_soa_found(self, query_mock): query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) -- cgit v1.2.3 From 7cfbeaeac8d81397b1d7a23ad17e4d5c683a987d Mon Sep 17 00:00:00 2001 From: Andreas Vogler Date: Thu, 16 May 2019 13:06:30 +0200 Subject: Added certbot-dns-rfc2136 to list of changed modules in CHANGELOG (#7074) * Add an option to dns_rfc2136 plugin to explicitly specify an authorative base domain. * Updated CHANGELOG mentioning added base domain option * Made the comment on the new option more clear on auto-detection * Updated comment on how the authorative base domain is determined * Added certbot-dns-rfc2136 to list of changed modules in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f699feec..3cc3ff41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ 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-dns-rfc2136 More details about these changes can be found on our GitHub repo. -- cgit v1.2.3 From 26d01537cb1ffb68a373e478403f6d3d110adda5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 01:41:11 -0700 Subject: Add test farm tests requirements file (#7061) * Add requirements.txt. * update readme --- tests/letstest/README.md | 19 +++++++++++-------- tests/letstest/requirements.txt | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/letstest/requirements.txt diff --git a/tests/letstest/README.md b/tests/letstest/README.md index 0155065b0..f8a15208e 100644 --- a/tests/letstest/README.md +++ b/tests/letstest/README.md @@ -14,15 +14,17 @@ Simple AWS testfarm scripts for certbot client testing - AWS EC2 has a default limit of 20 t2/t1 instances, if more are needed, they need to be requested via online webform. -## Usage - - To install the necessary dependencies on Ubuntu 16.04, run: +## Installation and configuration +These tests require Python 2.7, awscli, boto3, PyYAML, and fabric<2.0. If you +have Python 2.7 and virtualenv installed, you can use requirements.txt to +create a virtual environment with a known set of dependencies by running: ``` -sudo apt install awscli python-yaml python-boto3 fabric +virtualenv --python $(command -v python2.7 || command -v python2 || command -v python) venv +. ./venv/bin/activate +pip install --requirement requirements.txt ``` - - Requires AWS IAM secrets to be set up with aws cli - - Requires an AWS associated keyfile .pem - +You can then configure AWS credentials and create a key by running: ``` >aws configure --profile [interactive: enter secrets for IAM role] @@ -30,9 +32,10 @@ sudo apt install awscli python-yaml python-boto3 fabric ``` Note: whatever you pick for `` will be shown to other users with AWS access. -When prompted for a default region name, enter: `us-east-1` +When prompted for a default region name, enter: `us-east-1`. -then: +## Usage +To run tests, activate the virtual environment you created above and run: ``` >python multitester.py targets.yaml /path/to/your/key.pem scripts/ ``` diff --git a/tests/letstest/requirements.txt b/tests/letstest/requirements.txt new file mode 100644 index 000000000..64e1f6a0c --- /dev/null +++ b/tests/letstest/requirements.txt @@ -0,0 +1,25 @@ +asn1crypto==0.24.0 +awscli==1.16.157 +bcrypt==3.1.6 +boto3==1.9.146 +botocore==1.12.147 +cffi==1.12.3 +colorama==0.3.9 +cryptography==2.4.2 +docutils==0.14 +enum34==1.1.6 +Fabric==1.14.1 +futures==3.2.0 +idna==2.8 +ipaddress==1.0.22 +jmespath==0.9.4 +paramiko==2.4.2 +pyasn1==0.4.5 +pycparser==2.19 +PyNaCl==1.3.0 +python-dateutil==2.8.0 +PyYAML==3.10 +rsa==3.4.2 +s3transfer==0.2.0 +six==1.12.0 +urllib3==1.24.3 -- cgit v1.2.3 From def9af9f5e83fe27294ffda9fd5cae91fd8f1bb3 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 01:49:06 -0700 Subject: Update known good apache2 targets. (#7067) --- tests/letstest/apache2_targets.yaml | 67 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index e707b8636..dda4d4b1c 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -1,13 +1,18 @@ targets: #----------------------------------------------------------------------------- - # Apache 2.4 - - ami: ami-26d5af4c - name: ubuntu15.10 + #Ubuntu + - ami: ami-064bd2d44a1d6c097 + name: ubuntu18.10 type: ubuntu virt: hvm user: ubuntu - - ami: ami-d92e6bb3 - name: ubuntu15.04LTS + - ami: ami-012fd5eb46f56731f + name: ubuntu18.04LTS + type: ubuntu + virt: hvm + user: ubuntu + - ami: ami-09677e0a6b14905b0 + name: ubuntu16.04LTS type: ubuntu virt: hvm user: ubuntu @@ -21,37 +26,29 @@ targets: type: ubuntu virt: pv user: ubuntu - - ami: ami-116d857a + #----------------------------------------------------------------------------- + # Debian + - ami: ami-003f19e0e687de1cd + name: debian9 + type: ubuntu + virt: hvm + user: admin + - ami: ami-077bf3962f29d3fa4 name: debian8.1 - type: debian + type: ubuntu virt: hvm user: admin - userdata: | - #cloud-init - runcmd: - - [ apt-get, install, -y, curl ] #----------------------------------------------------------------------------- - # Apache 2.2 - # - ami: ami-0611546c - # name: ubuntu12.04LTS - # type: ubuntu - # virt: hvm - # user: ubuntu - # - ami: ami-e0efab88 - # name: debian7.8.aws.1 - # type: debian - # virt: hvm - # user: admin - # userdata: | - # #cloud-init - # runcmd: - # - [ apt-get, install, -y, curl ] - # - ami: ami-e6eeaa8e - # name: debian7.8.aws.1_32bit - # type: debian - # virt: pv - # user: admin - # userdata: | - # #cloud-init - # runcmd: - # - [ apt-get, install, -y, curl ] \ No newline at end of file + # Fedora + - ami: ami-5c69df23 + name: fedora28 + type: centos + virt: hvm + user: fedora + #----------------------------------------------------------------------------- + # CentOS + - ami: ami-9887c6e7 + name: centos7 + type: centos + virt: hvm + user: centos -- cgit v1.2.3 From f8614e7c04a935de883b3004b9aa97eb4614517a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 02:03:00 -0700 Subject: Fix centos6 test_sdists (#7068) * Use Python 3 when appropriate. * fix venv path --- tests/letstest/scripts/test_sdists.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index a9177f690..f407d5d2c 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,15 +1,24 @@ #!/bin/sh -xe cd letsencrypt -./certbot-auto --os-packages-only -n --debug +./certbot-auto --install-only -n --debug PLUGINS="certbot-apache certbot-nginx" +PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) VERSION=$(letsencrypt-auto-source/version.py) +if [ "$PYTHON_MAJOR_VERSION" = "3" ]; then + VENV_PATH="venv3" + VENV_SCRIPT="tools/venv3.py" +else + VENV_SCRIPT="tools/venv.py" + VENV_PATH="venv" +fi + # setup venv -tools/venv.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt -. ./venv/bin/activate +"$VENV_SCRIPT" --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt +. "$VENV_PATH/bin/activate" # pytest is needed to run tests on some of our packages so we install a pinned version here. tools/pip_install.py pytest -- cgit v1.2.3 From f4bbaadd18f3433521879cfa0039d59fda4ab12b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 02:17:27 -0700 Subject: Fix cleanup on failure. (#7070) * Fix cleanup on failure. * Incrementally build instances list. --- tests/letstest/multitester.py | 57 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 430acb634..c1299da01 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -314,31 +314,27 @@ def grab_certbot_log(): sudo('if [ -f ./certbot.log ]; then \ cat ./certbot.log; else echo "[nolocallog]"; fi') -def create_client_instances(targetlist, security_group_id, subnet_id): - "Create a fleet of client instances" - instances = [] - print("Creating instances: ", end="") - for target in targetlist: - if target['virt'] == 'hvm': - machine_type = 't2.medium' if cl_args.fast else 't2.micro' - else: - # 32 bit systems - machine_type = 'c1.medium' if cl_args.fast else 't1.micro' - if 'userdata' in target.keys(): - userdata = target['userdata'] - else: - userdata = '' - name = 'le-%s'%target['name'] - print(name, end=" ") - instances.append(make_instance(name, - target['ami'], - KEYNAME, - machine_type=machine_type, - security_group_id=security_group_id, - subnet_id=subnet_id, - userdata=userdata)) - print() - return instances + +def create_client_instance(target, security_group_id, subnet_id): + """Create a single client instance for running tests.""" + if target['virt'] == 'hvm': + machine_type = 't2.medium' if cl_args.fast else 't2.micro' + else: + # 32 bit systems + machine_type = 'c1.medium' if cl_args.fast else 't1.micro' + if 'userdata' in target.keys(): + userdata = target['userdata'] + else: + userdata = '' + name = 'le-%s'%target['name'] + print(name, end=" ") + return make_instance(name, + target['ami'], + KEYNAME, + machine_type=machine_type, + security_group_id=security_group_id, + subnet_id=subnet_id, + userdata=userdata) def test_client_process(inqueue, outqueue): @@ -377,7 +373,10 @@ def test_client_process(inqueue, outqueue): def cleanup(cl_args, instances, targetlist): print('Logs in ', LOGDIR) - if not cl_args.saveinstances: + # If lengths of instances and targetlist aren't equal, instances failed to + # start before running tests so leaving instances running for debugging + # isn't very useful. Let's cleanup after ourselves instead. + if len(instances) == len(targetlist) or not cl_args.saveinstances: print('Terminating EC2 Instances') if cl_args.killboulder: boulder_server.terminate() @@ -488,9 +487,13 @@ else: security_group_id=security_group_id, subnet_id=subnet_id) +instances = [] try: if not cl_args.boulderonly: - instances = create_client_instances(targetlist, security_group_id, subnet_id) + print("Creating instances: ", end="") + for target in targetlist: + instances.append(create_client_instance(target, security_group_id, subnet_id)) + print() # Configure and launch boulder server #------------------------------------------------------------------------------- -- cgit v1.2.3 From 7a6545b747fa10be11c5b0f5b21ba46964230099 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 02:21:57 -0700 Subject: Regularly print output in the test farm tests (#7079) * Occasionally print output in test farm tests. * Flush output. --- tests/letstest/multitester.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index c1299da01..43ed6f486 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -549,9 +549,14 @@ try: # add SENTINELs to end client processes for i in range(num_processes): inqueue.put(SENTINEL) - # wait on termination of client processes + print('Waiting on client processes', end='') for p in jobs: - p.join() + while p.is_alive(): + p.join(5 * 60) + # Regularly print output to keep Travis happy + print('.', end='') + sys.stdout.flush() + print() # add SENTINEL to output queue outqueue.put(SENTINEL) -- cgit v1.2.3 From 6bcd0415e0beed080df53758d7661f260ae48e05 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 02:27:40 -0700 Subject: Fix race condition adding tags to instance. (#7080) --- tests/letstest/multitester.py | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 43ed6f486..2db1e2a9e 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -146,32 +146,19 @@ def make_instance(instance_name, subnet_id, machine_type='t2.micro', userdata=""): #userdata contains bash or cloud-init script - - new_instance = EC2.create_instances( - BlockDeviceMappings=_get_block_device_mappings(ami_id), - ImageId=ami_id, - SecurityGroupIds=[security_group_id], - SubnetId=subnet_id, - KeyName=keyname, - MinCount=1, - MaxCount=1, - UserData=userdata, - InstanceType=machine_type)[0] - - # brief pause to prevent rare error on EC2 delay, should block until ready instead - time.sleep(1.0) - - # give instance a name - try: - new_instance.create_tags(Tags=[{'Key': 'Name', 'Value': instance_name}]) - except ClientError as e: - if "InvalidInstanceID.NotFound" in str(e): - # This seems to be ephemeral... retry - time.sleep(1) - new_instance.create_tags(Tags=[{'Key': 'Name', 'Value': instance_name}]) - else: - raise - return new_instance + block_device_mappings = _get_block_device_mappings(ami_id) + tags = [{'Key': 'Name', 'Value': instance_name}] + tag_spec = [{'ResourceType': 'instance', 'Tags': tags}] + return EC2.create_instances(BlockDeviceMappings=block_device_mappings, + ImageId=ami_id, + SecurityGroupIds=[security_group_id], + SubnetId=subnet_id, + KeyName=keyname, + MinCount=1, + MaxCount=1, + UserData=userdata, + InstanceType=machine_type, + TagSpecifications=tag_spec)[0] def _get_block_device_mappings(ami_id): """Returns the list of block device mappings to ensure cleanup. -- cgit v1.2.3 From 11c3e7107c1e0735433ee3063301c4d99096440c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 10:44:51 -0700 Subject: Exit with a nonzero status when tests fail. (#7065) --- tests/letstest/multitester.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 2db1e2a9e..37a4a40c1 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -106,6 +106,11 @@ EC2 = None SECURITY_GROUP_NAME = 'certbot-security-group' SUBNET_NAME = 'certbot-subnet' +class Status(object): + """Possible statuses of client tests.""" + PASS = 'pass' + FAIL = 'fail' + # Boto3/AWS automation functions #------------------------------------------------------------------------------- def should_use_subnet(subnet): @@ -340,10 +345,10 @@ def test_client_process(inqueue, outqueue): try: install_and_launch_certbot(instances[ii], boulder_url, target) - outqueue.put((ii, target, 'pass')) + outqueue.put((ii, target, Status.PASS)) print("%s - %s SUCCESS"%(target['ami'], target['name'])) except: - outqueue.put((ii, target, 'fail')) + outqueue.put((ii, target, Status.FAIL)) print("%s - %s FAIL"%(target['ami'], target['name'])) traceback.print_exc(file=sys.stdout) pass @@ -554,17 +559,24 @@ try: results_file = open(LOGDIR+'/results', 'w') outputs = [outq for outq in iter(outqueue.get, SENTINEL)] outputs.sort(key=lambda x: x[0]) + failed = False for outq in outputs: ii, target, status = outq + if status == Status.FAIL: + failed = True print('%d %s %s'%(ii, target['name'], status)) results_file.write('%d %s %s\n'%(ii, target['name'], status)) if len(outputs) != num_processes: + failed = True failure_message = 'FAILURE: Some target machines failed to run and were not tested. ' +\ 'Tests should be rerun.' print(failure_message) results_file.write(failure_message + '\n') results_file.close() + if failed: + sys.exit(1) + finally: cleanup(cl_args, instances, targetlist) -- cgit v1.2.3 From 16834a0d7825a08ec7f3aa952320195ac5faca78 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 11:36:58 -0700 Subject: Stop sharing state between processes in test farm tests (#7057) * Set LOGDIR at top of script. * Set sentinel at top of script. * Don't use EC2 global to block on instance start. * Remove global boto3 state. * Pass in boulder_url. * Create main function. * Add link to reload docs. --- tests/letstest/multitester.py | 467 +++++++++++++++++++++--------------------- 1 file changed, 237 insertions(+), 230 deletions(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 37a4a40c1..39f8739df 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -99,11 +99,9 @@ PROFILE = cl_args.aws_profile # Globals #------------------------------------------------------------------------------- BOULDER_AMI = 'ami-072a9534772bec854' # premade shared boulder AMI 18.04LTS us-east-1 -LOGDIR = "" #points to logging / working directory -# boto3/AWS api globals -AWS_SESSION = None -EC2 = None +LOGDIR = "letest-%d"%int(time.time()) #points to logging / working directory SECURITY_GROUP_NAME = 'certbot-security-group' +SENTINEL = None #queue kill signal SUBNET_NAME = 'certbot-subnet' class Status(object): @@ -144,28 +142,30 @@ def make_security_group(vpc): mysg.authorize_ingress(IpProtocol="udp", CidrIp="0.0.0.0/0", FromPort=60000, ToPort=61000) return mysg -def make_instance(instance_name, +def make_instance(ec2_client, + instance_name, ami_id, keyname, security_group_id, subnet_id, machine_type='t2.micro', userdata=""): #userdata contains bash or cloud-init script - block_device_mappings = _get_block_device_mappings(ami_id) + block_device_mappings = _get_block_device_mappings(ec2_client, ami_id) tags = [{'Key': 'Name', 'Value': instance_name}] tag_spec = [{'ResourceType': 'instance', 'Tags': tags}] - return EC2.create_instances(BlockDeviceMappings=block_device_mappings, - ImageId=ami_id, - SecurityGroupIds=[security_group_id], - SubnetId=subnet_id, - KeyName=keyname, - MinCount=1, - MaxCount=1, - UserData=userdata, - InstanceType=machine_type, - TagSpecifications=tag_spec)[0] - -def _get_block_device_mappings(ami_id): + return ec2_client.create_instances( + BlockDeviceMappings=block_device_mappings, + ImageId=ami_id, + SecurityGroupIds=[security_group_id], + SubnetId=subnet_id, + KeyName=keyname, + MinCount=1, + MaxCount=1, + UserData=userdata, + InstanceType=machine_type, + TagSpecifications=tag_spec)[0] + +def _get_block_device_mappings(ec2_client, ami_id): """Returns the list of block device mappings to ensure cleanup. This list sets connected EBS volumes to be deleted when the EC2 @@ -178,7 +178,7 @@ def _get_block_device_mappings(ami_id): # * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html return [{'DeviceName': mapping['DeviceName'], 'Ebs': {'DeleteOnTermination': True}} - for mapping in EC2.Image(ami_id).block_device_mappings + for mapping in ec2_client.Image(ami_id).block_device_mappings if not mapping.get('Ebs', {}).get('DeleteOnTermination', True)] @@ -217,20 +217,18 @@ def block_until_ssh_open(ipstring, wait_time=10, timeout=120): def block_until_instance_ready(booting_instance, wait_time=5, extra_wait_time=20): "Blocks booting_instance until AWS EC2 instance is ready to accept SSH connections" - # the reinstantiation from id is necessary to force boto3 - # to correctly update the 'state' variable during init - _id = booting_instance.id - _instance = EC2.Instance(id=_id) - _state = _instance.state['Name'] - _ip = _instance.public_ip_address - while _state != 'running' or _ip is None: + state = booting_instance.state['Name'] + ip = booting_instance.public_ip_address + while state != 'running' or ip is None: time.sleep(wait_time) - _instance = EC2.Instance(id=_id) - _state = _instance.state['Name'] - _ip = _instance.public_ip_address - block_until_ssh_open(_ip) + # The instance needs to be reloaded to update its local attributes. See + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Instance.reload. + booting_instance.reload() + state = booting_instance.state['Name'] + ip = booting_instance.public_ip_address + block_until_ssh_open(ip) time.sleep(extra_wait_time) - return _instance + return booting_instance # Fabric Routines @@ -307,7 +305,7 @@ def grab_certbot_log(): cat ./certbot.log; else echo "[nolocallog]"; fi') -def create_client_instance(target, security_group_id, subnet_id): +def create_client_instance(ec2_client, target, security_group_id, subnet_id): """Create a single client instance for running tests.""" if target['virt'] == 'hvm': machine_type = 't2.medium' if cl_args.fast else 't2.micro' @@ -320,7 +318,8 @@ def create_client_instance(target, security_group_id, subnet_id): userdata = '' name = 'le-%s'%target['name'] print(name, end=" ") - return make_instance(name, + return make_instance(ec2_client, + name, target['ami'], KEYNAME, machine_type=machine_type, @@ -329,22 +328,28 @@ def create_client_instance(target, security_group_id, subnet_id): userdata=userdata) -def test_client_process(inqueue, outqueue): +def test_client_process(inqueue, outqueue, boulder_url): cur_proc = mp.current_process() for inreq in iter(inqueue.get, SENTINEL): - ii, target = inreq + ii, instance_id, target = inreq + + # Each client process is given its own session due to the suggestion at + # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/resources.html?highlight=multithreading#multithreading-multiprocessing. + aws_session = boto3.session.Session(profile_name=PROFILE) + ec2_client = aws_session.resource('ec2') + instance = ec2_client.Instance(id=instance_id) #save all stdout to log file sys.stdout = open(LOGDIR+'/'+'%d_%s.log'%(ii,target['name']), 'w') print("[%s : client %d %s %s]" % (cur_proc.name, ii, target['ami'], target['name'])) - instances[ii] = block_until_instance_ready(instances[ii]) - print("server %s at %s"%(instances[ii], instances[ii].public_ip_address)) - env.host_string = "%s@%s"%(target['user'], instances[ii].public_ip_address) + instance = block_until_instance_ready(instance) + print("server %s at %s"%(instance, instance.public_ip_address)) + env.host_string = "%s@%s"%(target['user'], instance.public_ip_address) print(env.host_string) try: - install_and_launch_certbot(instances[ii], boulder_url, target) + install_and_launch_certbot(instance, boulder_url, target) outqueue.put((ii, target, Status.PASS)) print("%s - %s SUCCESS"%(target['ami'], target['name'])) except: @@ -382,203 +387,205 @@ def cleanup(cl_args, instances, targetlist): "%s@%s"%(target['user'], instances[ii].public_ip_address)) +def main(): + # Fabric library controlled through global env parameters + env.key_filename = KEYFILE + env.shell = '/bin/bash -l -i -c' + env.connection_attempts = 5 + env.timeout = 10 + # replace default SystemExit thrown by fabric during trouble + class FabricException(Exception): + pass + env['abort_exception'] = FabricException -#------------------------------------------------------------------------------- -# SCRIPT BEGINS -#------------------------------------------------------------------------------- - -# Fabric library controlled through global env parameters -env.key_filename = KEYFILE -env.shell = '/bin/bash -l -i -c' -env.connection_attempts = 5 -env.timeout = 10 -# replace default SystemExit thrown by fabric during trouble -class FabricException(Exception): - pass -env['abort_exception'] = FabricException - -# Set up local copy of git repo -#------------------------------------------------------------------------------- -LOGDIR = "letest-%d"%int(time.time()) -print("Making local dir for test repo and logs: %s"%LOGDIR) -local('mkdir %s'%LOGDIR) - -# figure out what git object to test and locally create it in LOGDIR -print("Making local git repo") -try: - if cl_args.pull_request != '~': - print('Testing PR %s '%cl_args.pull_request, - "MERGING into master" if cl_args.merge_master else "") - execute(local_git_PR, cl_args.repo, cl_args.pull_request, cl_args.merge_master) - elif cl_args.branch != '~': - print('Testing branch %s of %s'%(cl_args.branch, cl_args.repo)) - execute(local_git_branch, cl_args.repo, cl_args.branch) + # Set up local copy of git repo + #------------------------------------------------------------------------------- + print("Making local dir for test repo and logs: %s"%LOGDIR) + local('mkdir %s'%LOGDIR) + + # figure out what git object to test and locally create it in LOGDIR + print("Making local git repo") + try: + if cl_args.pull_request != '~': + print('Testing PR %s '%cl_args.pull_request, + "MERGING into master" if cl_args.merge_master else "") + execute(local_git_PR, cl_args.repo, cl_args.pull_request, cl_args.merge_master) + elif cl_args.branch != '~': + print('Testing branch %s of %s'%(cl_args.branch, cl_args.repo)) + execute(local_git_branch, cl_args.repo, cl_args.branch) + else: + print('Testing master of %s'%cl_args.repo) + execute(local_git_clone, cl_args.repo) + except FabricException: + print("FAIL: trouble with git repo") + traceback.print_exc() + exit() + + + # Set up EC2 instances + #------------------------------------------------------------------------------- + configdata = yaml.load(open(cl_args.config_file, 'r')) + targetlist = configdata['targets'] + print('Testing against these images: [%d total]'%len(targetlist)) + for target in targetlist: + print(target['ami'], target['name']) + + print("Connecting to EC2 using\n profile %s\n keyname %s\n keyfile %s"%(PROFILE, KEYNAME, KEYFILE)) + aws_session = boto3.session.Session(profile_name=PROFILE) + ec2_client = aws_session.resource('ec2') + + print("Determining Subnet") + for subnet in ec2_client.subnets.all(): + if should_use_subnet(subnet): + subnet_id = subnet.id + vpc_id = subnet.vpc.id + break else: - print('Testing master of %s'%cl_args.repo) - execute(local_git_clone, cl_args.repo) -except FabricException: - print("FAIL: trouble with git repo") - traceback.print_exc() - exit() - + print("No usable subnet exists!") + print("Please create a VPC with a subnet named {0}".format(SUBNET_NAME)) + print("that maps public IPv4 addresses to instances launched in the subnet.") + sys.exit(1) -# Set up EC2 instances -#------------------------------------------------------------------------------- -configdata = yaml.load(open(cl_args.config_file, 'r')) -targetlist = configdata['targets'] -print('Testing against these images: [%d total]'%len(targetlist)) -for target in targetlist: - print(target['ami'], target['name']) - -print("Connecting to EC2 using\n profile %s\n keyname %s\n keyfile %s"%(PROFILE, KEYNAME, KEYFILE)) -AWS_SESSION = boto3.session.Session(profile_name=PROFILE) -EC2 = AWS_SESSION.resource('ec2') - -print("Determining Subnet") -for subnet in EC2.subnets.all(): - if should_use_subnet(subnet): - subnet_id = subnet.id - vpc_id = subnet.vpc.id - break -else: - print("No usable subnet exists!") - print("Please create a VPC with a subnet named {0}".format(SUBNET_NAME)) - print("that maps public IPv4 addresses to instances launched in the subnet.") - sys.exit(1) - -print("Making Security Group") -vpc = EC2.Vpc(vpc_id) -sg_exists = False -for sg in vpc.security_groups.all(): - if sg.group_name == SECURITY_GROUP_NAME: - security_group_id = sg.id - sg_exists = True - print(" %s already exists"%SECURITY_GROUP_NAME) -if not sg_exists: - security_group_id = make_security_group(vpc).id - time.sleep(30) - -boulder_preexists = False -boulder_servers = EC2.instances.filter(Filters=[ - {'Name': 'tag:Name', 'Values': ['le-boulderserver']}, - {'Name': 'instance-state-name', 'Values': ['running']}]) - -boulder_server = next(iter(boulder_servers), None) - -print("Requesting Instances...") -if boulder_server: - print("Found existing boulder server:", boulder_server) - boulder_preexists = True -else: - print("Can't find a boulder server, starting one...") - boulder_server = make_instance('le-boulderserver', - BOULDER_AMI, - KEYNAME, - machine_type='t2.micro', - #machine_type='t2.medium', - security_group_id=security_group_id, - subnet_id=subnet_id) - -instances = [] -try: - if not cl_args.boulderonly: - print("Creating instances: ", end="") - for target in targetlist: - instances.append(create_client_instance(target, security_group_id, subnet_id)) + print("Making Security Group") + vpc = ec2_client.Vpc(vpc_id) + sg_exists = False + for sg in vpc.security_groups.all(): + if sg.group_name == SECURITY_GROUP_NAME: + security_group_id = sg.id + sg_exists = True + print(" %s already exists"%SECURITY_GROUP_NAME) + if not sg_exists: + security_group_id = make_security_group(vpc).id + time.sleep(30) + + boulder_preexists = False + boulder_servers = ec2_client.instances.filter(Filters=[ + {'Name': 'tag:Name', 'Values': ['le-boulderserver']}, + {'Name': 'instance-state-name', 'Values': ['running']}]) + + boulder_server = next(iter(boulder_servers), None) + + print("Requesting Instances...") + if boulder_server: + print("Found existing boulder server:", boulder_server) + boulder_preexists = True + else: + print("Can't find a boulder server, starting one...") + boulder_server = make_instance(ec2_client, + 'le-boulderserver', + BOULDER_AMI, + KEYNAME, + machine_type='t2.micro', + #machine_type='t2.medium', + security_group_id=security_group_id, + subnet_id=subnet_id) + + instances = [] + try: + if not cl_args.boulderonly: + print("Creating instances: ", end="") + for target in targetlist: + instances.append( + create_client_instance(ec2_client, target, + security_group_id, subnet_id) + ) + print() + + # Configure and launch boulder server + #------------------------------------------------------------------------------- + print("Waiting on Boulder Server") + boulder_server = block_until_instance_ready(boulder_server) + print(" server %s"%boulder_server) + + + # env.host_string defines the ssh user and host for connection + env.host_string = "ubuntu@%s"%boulder_server.public_ip_address + print("Boulder Server at (SSH):", env.host_string) + if not boulder_preexists: + print("Configuring and Launching Boulder") + config_and_launch_boulder(boulder_server) + # blocking often unnecessary, but cheap EC2 VMs can get very slow + block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address, + wait_time=10, timeout=500) + + boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address + print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address) + print("Boulder Server at (EC2 private ip): %s"%boulder_url) + + if cl_args.boulderonly: + sys.exit(0) + + # Install and launch client scripts in parallel + #------------------------------------------------------------------------------- + print("Uploading and running test script in parallel: %s"%cl_args.test_script) + print("Output routed to log files in %s"%LOGDIR) + # (Advice: always use Manager.Queue, never regular multiprocessing.Queue + # the latter has implementation flaws that deadlock it in some circumstances) + manager = Manager() + outqueue = manager.Queue() + inqueue = manager.Queue() + + # launch as many processes as clients to test + num_processes = len(targetlist) + jobs = [] #keep a reference to current procs + + + # initiate process execution + for i in range(num_processes): + p = mp.Process(target=test_client_process, args=(inqueue, outqueue, boulder_url)) + jobs.append(p) + p.daemon = True # kills subprocesses if parent is killed + p.start() + + # fill up work queue + for ii, target in enumerate(targetlist): + inqueue.put((ii, instances[ii].id, target)) + + # add SENTINELs to end client processes + for i in range(num_processes): + inqueue.put(SENTINEL) + print('Waiting on client processes', end='') + for p in jobs: + while p.is_alive(): + p.join(5 * 60) + # Regularly print output to keep Travis happy + print('.', end='') + sys.stdout.flush() print() + # add SENTINEL to output queue + outqueue.put(SENTINEL) + + # clean up + execute(local_repo_clean) + + # print and save summary results + results_file = open(LOGDIR+'/results', 'w') + outputs = [outq for outq in iter(outqueue.get, SENTINEL)] + outputs.sort(key=lambda x: x[0]) + failed = False + for outq in outputs: + ii, target, status = outq + if status == Status.FAIL: + failed = True + print('%d %s %s'%(ii, target['name'], status)) + results_file.write('%d %s %s\n'%(ii, target['name'], status)) + if len(outputs) != num_processes: + failed = True + failure_message = 'FAILURE: Some target machines failed to run and were not tested. ' +\ + 'Tests should be rerun.' + print(failure_message) + results_file.write(failure_message + '\n') + results_file.close() - # Configure and launch boulder server - #------------------------------------------------------------------------------- - print("Waiting on Boulder Server") - boulder_server = block_until_instance_ready(boulder_server) - print(" server %s"%boulder_server) - - - # env.host_string defines the ssh user and host for connection - env.host_string = "ubuntu@%s"%boulder_server.public_ip_address - print("Boulder Server at (SSH):", env.host_string) - if not boulder_preexists: - print("Configuring and Launching Boulder") - config_and_launch_boulder(boulder_server) - # blocking often unnecessary, but cheap EC2 VMs can get very slow - block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address, - wait_time=10, timeout=500) - - boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address - print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address) - print("Boulder Server at (EC2 private ip): %s"%boulder_url) + if failed: + sys.exit(1) - if cl_args.boulderonly: - sys.exit(0) + finally: + cleanup(cl_args, instances, targetlist) - # Install and launch client scripts in parallel - #------------------------------------------------------------------------------- - print("Uploading and running test script in parallel: %s"%cl_args.test_script) - print("Output routed to log files in %s"%LOGDIR) - # (Advice: always use Manager.Queue, never regular multiprocessing.Queue - # the latter has implementation flaws that deadlock it in some circumstances) - manager = Manager() - outqueue = manager.Queue() - inqueue = manager.Queue() - SENTINEL = None #queue kill signal - - # launch as many processes as clients to test - num_processes = len(targetlist) - jobs = [] #keep a reference to current procs - - - # initiate process execution - for i in range(num_processes): - p = mp.Process(target=test_client_process, args=(inqueue, outqueue)) - jobs.append(p) - p.daemon = True # kills subprocesses if parent is killed - p.start() - - # fill up work queue - for ii, target in enumerate(targetlist): - inqueue.put((ii, target)) - - # add SENTINELs to end client processes - for i in range(num_processes): - inqueue.put(SENTINEL) - print('Waiting on client processes', end='') - for p in jobs: - while p.is_alive(): - p.join(5 * 60) - # Regularly print output to keep Travis happy - print('.', end='') - sys.stdout.flush() - print() - # add SENTINEL to output queue - outqueue.put(SENTINEL) - - # clean up - execute(local_repo_clean) - - # print and save summary results - results_file = open(LOGDIR+'/results', 'w') - outputs = [outq for outq in iter(outqueue.get, SENTINEL)] - outputs.sort(key=lambda x: x[0]) - failed = False - for outq in outputs: - ii, target, status = outq - if status == Status.FAIL: - failed = True - print('%d %s %s'%(ii, target['name'], status)) - results_file.write('%d %s %s\n'%(ii, target['name'], status)) - if len(outputs) != num_processes: - failed = True - failure_message = 'FAILURE: Some target machines failed to run and were not tested. ' +\ - 'Tests should be rerun.' - print(failure_message) - results_file.write(failure_message + '\n') - results_file.close() - - if failed: - sys.exit(1) + # kill any connections + fabric.network.disconnect_all() -finally: - cleanup(cl_args, instances, targetlist) - # kill any connections - fabric.network.disconnect_all() +if __name__ == '__main__': + main() -- cgit v1.2.3 From d1753e46f9cd43e966a6296d1bf7bb48cbd11ed5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 11:40:20 -0700 Subject: Allow magic profile name none for configuring test farm tests. (#7069) * Allow magic profile name none. * Naming is hard. --- tests/letstest/multitester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 39f8739df..51edd2671 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -94,7 +94,7 @@ cl_args = parser.parse_args() # assumes naming: = .pem KEYFILE = cl_args.key_file KEYNAME = os.path.split(cl_args.key_file)[1].split('.pem')[0] -PROFILE = cl_args.aws_profile +PROFILE = None if cl_args.aws_profile == 'SET_BY_ENV' else cl_args.aws_profile # Globals #------------------------------------------------------------------------------- -- cgit v1.2.3 From 51a7e7cd1928352a96b6849004e176bbd6cd1863 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 17 May 2019 23:50:29 +0200 Subject: Fix unpinned dependencies tests towards botocore and urllib3 (#7081) * Limit transitive dependency on urllib3 to <1.25 to fulfill requirements in certbot-dns-route53 throught botocore dependency. --- certbot-dns-route53/setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 09cd4acd2..ab9bc4d06 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -6,6 +6,11 @@ version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ + # boto3 requires urllib<1.25 while requests 2.22+ requires urllib<1.26 + # Since pip lacks a real dependency graph resolver, it will peak the constraint only from + # requests, and install urllib==1.25.2. Setting an explicit dependency here solves the issue. + # Check https://github.com/boto/botocore/issues/1733 for resolution in botocore. + 'urllib3<1.25', 'acme>=0.29.0', 'certbot>=0.34.0', 'boto3', -- cgit v1.2.3 From 8b684e9b9543c015669844222b8960e1b9a71e97 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 17 May 2019 16:30:20 -0700 Subject: Enable test farm tests in Travis (#7072) You can see the full test suite running at https://travis-ci.com/certbot/certbot/builds/112291892. A few noteworthy things: --fast is included because without, the tests would sometimes reach Travis' 50 minute timeout even with 1 test script per Travis build. The only script that is run at release time which is not being run here is https://github.com/certbot/certbot/blob/master/tests/letstest/scripts/test_tests.sh because that script runs tests on the packages installed by certbot-auto which won't be updated until midway through a release. We check TRAVIS_PULL_REQUEST and error out if it is not false for simplicity which should be fine because these tests are never run on PRs. The reason it's more complex to run test farm tests on PRs is the test farm tests need a named branch to pull from and Travis effectively merges the PR into the target branch before running tests complicating this. I don't think this should block this PRs, but the one final change we may want to make to the current setup is #7071. * Add encrypted private key. * Add test farm tests to tox and travis. * Change magic profile name. * Further split test farm tests. * Build local branch. * more depth --- .travis.yml | 22 +++++++++++++++ tests/letstest/travis-setup.sh | 10 +++++++ tests/letstest/travis-test-farm.pem.enc | Bin 0 -> 1680 bytes tox.ini | 47 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100755 tests/letstest/travis-setup.sh create mode 100644 tests/letstest/travis-test-farm.pem.enc diff --git a/.travis.yml b/.travis.yml index 3a054b419..52595c111 100644 --- a/.travis.yml +++ b/.travis.yml @@ -94,6 +94,28 @@ matrix: <<: *not-on-master # Extended test suite on cron jobs and pushes to tested branches other than master + - python: "2.7" + env: + - TOXENV=travis-test-farm-apache2 + - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" + <<: *extended-test-suite + - python: "2.7" + env: + - TOXENV=travis-test-farm-leauto-upgrades + - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" + git: + depth: false # This is needed to have the history to checkout old versions of certbot-auto. + <<: *extended-test-suite + - python: "2.7" + env: + - TOXENV=travis-test-farm-certonly-standalone + - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" + <<: *extended-test-suite + - python: "2.7" + env: + - TOXENV=travis-test-farm-sdists + - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" + <<: *extended-test-suite - python: "3.7" dist: xenial env: TOXENV=py37 CERTBOT_NO_PIN=1 diff --git a/tests/letstest/travis-setup.sh b/tests/letstest/travis-setup.sh new file mode 100755 index 000000000..261a1504f --- /dev/null +++ b/tests/letstest/travis-setup.sh @@ -0,0 +1,10 @@ +#!/bin/bash -ex +# +# Preps the test farm tests to be run in Travis. + +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo This script must be run in Travis on a non-pull request build + exit 1 +fi + +openssl aes-256-cbc -K "${encrypted_9a387195a62e_key}" -iv "${encrypted_9a387195a62e_iv}" -in travis-test-farm.pem.enc -out travis-test-farm.pem -d diff --git a/tests/letstest/travis-test-farm.pem.enc b/tests/letstest/travis-test-farm.pem.enc new file mode 100644 index 000000000..f8b1d576c Binary files /dev/null and b/tests/letstest/travis-test-farm.pem.enc differ diff --git a/tox.ini b/tox.ini index 7f2c18d38..52ae1e97d 100644 --- a/tox.ini +++ b/tox.ini @@ -274,3 +274,50 @@ commands = --acme-server={env:ACME_SERVER:pebble} passenv = DOCKER_* setenv = {[testenv:py27-oldest]setenv} + +[testenv:travis-test-farm-tests-base] +changedir = tests/letstest +commands = + ./travis-setup.sh +deps = -rtests/letstest/requirements.txt +passenv = + AWS_* + TRAVIS_* + encrypted_* +setenv = AWS_DEFAULT_REGION=us-east-1 + +[testenv:travis-test-farm-apache2] +changedir = {[testenv:travis-test-farm-tests-base]changedir} +commands = + {[testenv:travis-test-farm-tests-base]commands} + python multitester.py apache2_targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_apache2.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast +deps = {[testenv:travis-test-farm-tests-base]deps} +passenv = {[testenv:travis-test-farm-tests-base]passenv} +setenv = {[testenv:travis-test-farm-tests-base]setenv} + +[testenv:travis-test-farm-leauto-upgrades] +changedir = {[testenv:travis-test-farm-tests-base]changedir} +commands = + {[testenv:travis-test-farm-tests-base]commands} + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_leauto_upgrades.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast +deps = {[testenv:travis-test-farm-tests-base]deps} +passenv = {[testenv:travis-test-farm-tests-base]passenv} +setenv = {[testenv:travis-test-farm-tests-base]setenv} + +[testenv:travis-test-farm-certonly-standalone] +changedir = {[testenv:travis-test-farm-tests-base]changedir} +commands = + {[testenv:travis-test-farm-tests-base]commands} + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_letsencrypt_auto_certonly_standalone.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast +deps = {[testenv:travis-test-farm-tests-base]deps} +passenv = {[testenv:travis-test-farm-tests-base]passenv} +setenv = {[testenv:travis-test-farm-tests-base]setenv} + +[testenv:travis-test-farm-sdists] +changedir = {[testenv:travis-test-farm-tests-base]changedir} +commands = + {[testenv:travis-test-farm-tests-base]commands} + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_sdists.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast +deps = {[testenv:travis-test-farm-tests-base]deps} +passenv = {[testenv:travis-test-farm-tests-base]passenv} +setenv = {[testenv:travis-test-farm-tests-base]setenv} -- cgit v1.2.3 From bf818036eb4f23164f92800b9ea83591149c0814 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 May 2019 15:20:54 -0700 Subject: Revert "Fix unpinned dependencies tests towards botocore and urllib3 (#7081)" (#7101) This reverts commit 51a7e7cd1928352a96b6849004e176bbd6cd1863. --- certbot-dns-route53/setup.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index ab9bc4d06..09cd4acd2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -6,11 +6,6 @@ version = '0.35.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - # boto3 requires urllib<1.25 while requests 2.22+ requires urllib<1.26 - # Since pip lacks a real dependency graph resolver, it will peak the constraint only from - # requests, and install urllib==1.25.2. Setting an explicit dependency here solves the issue. - # Check https://github.com/boto/botocore/issues/1733 for resolution in botocore. - 'urllib3<1.25', 'acme>=0.29.0', 'certbot>=0.34.0', 'boto3', -- cgit v1.2.3 From d2a2b880903967f49fde6c54126e61b40455c91d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 28 May 2019 14:36:10 -0700 Subject: Update Ubuntu AMI to 19.04. (#7099) --- tests/letstest/apache2_targets.yaml | 4 ++-- tests/letstest/targets.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index dda4d4b1c..4da6abb15 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -1,8 +1,8 @@ targets: #----------------------------------------------------------------------------- #Ubuntu - - ami: ami-064bd2d44a1d6c097 - name: ubuntu18.10 + - ami: ami-08ab45c4343f5f5c6 + name: ubuntu19.04 type: ubuntu virt: hvm user: ubuntu diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index d1eba43de..340fe6bf8 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -1,8 +1,8 @@ targets: #----------------------------------------------------------------------------- #Ubuntu - - ami: ami-064bd2d44a1d6c097 - name: ubuntu18.10 + - ami: ami-08ab45c4343f5f5c6 + name: ubuntu19.04 type: ubuntu virt: hvm user: ubuntu -- cgit v1.2.3 From 7d35f95293b40159ef15f38eed448168e158ce56 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 29 May 2019 00:16:12 +0200 Subject: Avoid to delete both webroot_map and webroot_path (#7095) * Always restore webroot_path in renewal config. * Add unit tests to ensure correct behavior * Add changelog * Add certbot as modified package --- CHANGELOG.md | 4 +++- certbot/plugins/webroot_test.py | 13 +++++++++++++ certbot/renewal.py | 10 +++++----- certbot/tests/renewal_test.py | 24 ++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc3ff41f..8669d34a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,14 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Renewal parameter `webroot_path` is always saved, avoiding some regressions + when `webroot` authenticator plugin is invoked with no challenge to perform. 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 * certbot-dns-rfc2136 More details about these changes can be found on our GitHub repo. diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index 3a902b91f..bca1045d8 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -295,6 +295,19 @@ class WebrootActionTest(unittest.TestCase): self.assertEqual( config.webroot_map[self.achall.domain], self.path) + def test_webroot_map_partial_without_perform(self): + # This test acknowledges the fact that webroot_map content will be partial if webroot + # plugin perform method is not invoked (corner case when all auths are already valid). + # To not be a problem, the webroot_path must always been conserved during renew. + # This condition is challenged by: + # certbot.tests.renewal_tests::RenewalTest::test_webroot_params_conservation + # See https://github.com/certbot/certbot/pull/7095 for details. + other_webroot_path = tempfile.mkdtemp() + args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( + self.path, self.achall.domain, other_webroot_path).split()) + self.assertEqual(args.webroot_map, {self.achall.domain: self.path}) + self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) + def _get_config_after_perform(self, config): from certbot.plugins.webroot import Authenticator auth = Authenticator(config, "webroot") diff --git a/certbot/renewal.py b/certbot/renewal.py index f932269b6..4b4a5a082 100644 --- a/certbot/renewal.py +++ b/certbot/renewal.py @@ -106,11 +106,11 @@ def _restore_webroot_config(config, renewalparams): restoring logic is not able to correctly parse it from the serialized form. """ - if "webroot_map" in renewalparams: - if not cli.set_by_cli("webroot_map"): - config.webroot_map = renewalparams["webroot_map"] - elif "webroot_path" in renewalparams: - logger.debug("Ancient renewal conf file without webroot-map, restoring webroot-path") + if "webroot_map" in renewalparams and not cli.set_by_cli("webroot_map"): + config.webroot_map = renewalparams["webroot_map"] + # To understand why webroot_path and webroot_map processing are not mutually exclusive, + # see https://github.com/certbot/certbot/pull/7095 + if "webroot_path" in renewalparams and not cli.set_by_cli("webroot_path"): wp = renewalparams["webroot_path"] if isinstance(wp, six.string_types): # prior to 0.1.0, webroot_path was a string wp = [wp] diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index dac585239..e33869e13 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -28,6 +28,29 @@ class RenewalTest(test_util.ConfigTestCase): renewal._restore_webroot_config(config, renewalparams) self.assertEqual(config.webroot_path, ['/var/www/']) + @mock.patch('certbot.renewal.cli.set_by_cli') + def test_webroot_params_conservation(self, mock_set_by_cli): + # For more details about why this test is important, see: + # certbot.plugins.webroot_test::WebrootActionTest::test_webroot_map_partial_without_perform + from certbot import renewal + mock_set_by_cli.return_value = False + + renewalparams = { + 'webroot_map': {'test.example.com': '/var/www/test'}, + 'webroot_path': ['/var/www/test', '/var/www/other'], + } + renewal._restore_webroot_config(self.config, renewalparams) # pylint: disable=protected-access + self.assertEqual(self.config.webroot_map, {'test.example.com': '/var/www/test'}) + self.assertEqual(self.config.webroot_path, ['/var/www/test', '/var/www/other']) + + renewalparams = { + 'webroot_map': {}, + 'webroot_path': '/var/www/test', + } + renewal._restore_webroot_config(self.config, renewalparams) # pylint: disable=protected-access + self.assertEqual(self.config.webroot_map, {}) + self.assertEqual(self.config.webroot_path, ['/var/www/test']) + class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): """Tests for certbot.renewal.restore_required_config_elements.""" @@ -89,5 +112,6 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): self.assertRaises( errors.Error, self._call, self.config, renewalparams) + if __name__ == "__main__": unittest.main() # pragma: no cover -- cgit v1.2.3 From 561534b754344f3ce24b52c9ac938d352f552c3d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 28 May 2019 23:54:26 -0700 Subject: Move IRC notifications to #certbot-devel. (#7098) * Move IRC notifications to #certbot-devel. * Don't use notice. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52595c111..1eee09898 100644 --- a/.travis.yml +++ b/.travis.yml @@ -276,8 +276,11 @@ notifications: email: false irc: channels: - - secure: "SGWZl3ownKx9xKVV2VnGt7DqkTmutJ89oJV9tjKhSs84kLijU6EYdPnllqISpfHMTxXflNZuxtGo0wTDYHXBuZL47w1O32W6nzuXdra5zC+i4sYQwYULUsyfOv9gJX8zWAULiK0Z3r0oho45U+FR5ZN6TPCidi8/eGU+EEPwaAw=" + # This is set to a secure variable to prevent forks from sending + # notifications. This value was created by installing + # https://github.com/travis-ci/travis.rb and running + # `travis encrypt "chat.freenode.net#certbot-devel"`. + - secure: "EWW66E2+KVPZyIPR8ViENZwfcup4Gx3/dlimmAZE0WuLwxDCshBBOd3O8Rf6pBokEoZlXM5eDT6XdyJj8n0DLslgjO62pExdunXpbcMwdY7l1ELxX2/UbnDTE6UnPYa09qVBHNG7156Z6yE0x2lH4M9Ykvp0G0cubjPQHylAwo0=" on_cancel: never on_success: never on_failure: always - use_notice: true -- cgit v1.2.3 From 4c299be9657ab8ab4a0c9be79c7b4c7d2f76286d Mon Sep 17 00:00:00 2001 From: Pete Cooper Date: Wed, 29 May 2019 22:16:16 +0100 Subject: Update docs/cli-help.txt -- typo and formatting (#7105) * Update docs/cli-help.txt -- yypo and formatting 'areusing' -> 'are using' * Update cli.py -- formatting See https://github.com/certbot/certbot/pull/7105 Addresses https://github.com/certbot/certbot/pull/7105#issuecomment-497079342 --- certbot/cli.py | 2 +- docs/cli-help.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot/cli.py b/certbot/cli.py index 866b64aa6..b7c021ed4 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1453,7 +1453,7 @@ def _plugins_parsing(helpful, plugins): "using DNSimple for DNS).")) helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", default=flag_default("dns_dnsmadeeasy"), - help=("Obtain certificates using a DNS TXT record (if you are" + help=("Obtain certificates using a DNS TXT record (if you are " "using DNS Made Easy for DNS).")) helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", default=flag_default("dns_gehirn"), diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 3e5fdc53b..f65384bbc 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -454,8 +454,8 @@ plugins: using DigitalOcean for DNS). (default: False) --dns-dnsimple Obtain certificates using a DNS TXT record (if you are using DNSimple for DNS). (default: False) - --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you - areusing DNS Made Easy for DNS). (default: False) + --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you are + using DNS Made Easy for DNS). (default: False) --dns-gehirn Obtain certificates using a DNS TXT record (if you are using Gehirn Infrastracture Service for DNS). (default: False) -- cgit v1.2.3 From 926c8c198cfb6158b7ad59988702c30e33f13d53 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 30 May 2019 16:09:09 +0200 Subject: Remove dependency on acme in certbot-ci (#7055) Following discussion in #6947 (comment), I have second thoughts about relying on acme in certbot-ci. Indeed, I think it is a good design to not rely in tests on the code you are testing. Obviously in unit tests it is very difficult, since most of the time the unit that is tested needs input generated by other part of the code. However it is not really a problem in a unit test, as its purpose is to make assertions about a specific portion of the code, not the others parts. In the scope of integration tests, the software tested is treated as a black box. In this case, having some parts of the test logic that use in fact part of the code in the black box, increase the risk that some assertions compared two results coming from the same flawed logic from the tested software. Since using acme in certbot-ci is only saving few lines of code, I think it does not worth the risk and the added complexity to declare acme as a dependency. I prefer to duplicate these lines and keep certbot-ci free of any dependency coming from the certbot project. --- certbot-ci/certbot_integration_tests/utils/misc.py | 21 +++++++++++++-------- certbot-ci/setup.py | 1 - 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 4647a229e..2144b9cee 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -23,8 +23,6 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption from six.moves import socketserver, SimpleHTTPServer -from acme import crypto_util - RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' @@ -250,13 +248,20 @@ def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE): else: raise ValueError('Invalid key type: {0}'.format(key_type)) - key_bytes = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) - with open(key_path, 'wb') as file: - file.write(key_bytes) + with open(key_path, 'wb') as file_h: + file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) + + req = crypto.X509Req() + san = ', '.join(['DNS:{0}'.format(item) for item in domains]) + san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8')) + req.add_extensions([san_constraint]) + + req.set_pubkey(key) + req.set_version(2) + req.sign(key, 'sha256') - csr_bytes = crypto_util.make_csr(key_bytes, domains) - with open(csr_path, 'wb') as file: - file.write(csr_bytes) + with open(csr_path, 'wb') as file_h: + file_h.write(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req)) def read_certificate(cert_path): diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 88372bffc..eecbe2887 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -5,7 +5,6 @@ from setuptools import find_packages version = '0.32.0.dev0' install_requires = [ - 'acme', 'coverage', 'cryptography', 'pyopenssl', -- cgit v1.2.3 From 641aba68b1c2e57a912e5edf95178cd29fd66b4b Mon Sep 17 00:00:00 2001 From: Felix Lechner Date: Thu, 30 May 2019 15:02:15 -0700 Subject: Ignore editor backups when running hooks. (#7109) * Ignore editor backups when running hooks. When processing hooks, certbot also runs editor backups even though such files are outdated, clearly warranted correction and may quite possibly be defective. That behavior could lead to unexpected breakage, and perhaps even pose security risks---for example, if a previous script was careless with file permissions. As an aggravating factor, the backup runs after the corrected version and could unintentionally override a fix the user thought was properly implemented. This commit causes editor backup files ending in tilde (~) to be excluded when running hooks. Additional information can be found here: https://github.com/certbot/certbot/issues/7107 https://community.letsencrypt.org/t/editor-backup-files-executed-as-renewal-hooks/94750 * Add unit test for hook scripts with filenames ending in tilde. * Provide changelog entry for not running hook scripts ending in tilde. * Add Felix Lechner to the list of contributors. --- AUTHORS.md | 1 + CHANGELOG.md | 2 ++ certbot/hooks.py | 5 +++-- certbot/tests/hook_test.py | 6 ++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 6ee739bc0..0e8d88a4d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -75,6 +75,7 @@ Authors * [Fabian](https://github.com/faerbit) * [Faidon Liambotis](https://github.com/paravoid) * [Fan Jiang](https://github.com/tcz001) +* [Felix Lechner](https://github.com/lechner) * [Felix Schwarz](https://github.com/FelixSchwarz) * [Felix Yan](https://github.com/felixonmars) * [Filip Ochnik](https://github.com/filipochnik) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8669d34a5..83e62c792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Renewal parameter `webroot_path` is always saved, avoiding some regressions when `webroot` authenticator plugin is invoked with no challenge to perform. +* Scripts in Certbot hook directories are no longer executed when their + filenames end in a tilde. 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 diff --git a/certbot/hooks.py b/certbot/hooks.py index 7de846ae4..34e06e0a3 100644 --- a/certbot/hooks.py +++ b/certbot/hooks.py @@ -266,5 +266,6 @@ def list_hooks(dir_path): :rtype: sorted list of absolute paths to executables in dir_path """ - paths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) - return sorted(path for path in paths if util.is_exe(path)) + allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) + hooks = [path for path in allpaths if util.is_exe(path) and not path.endswith('~')] + return sorted(hooks) diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 2a3742aa2..2ed7d4229 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -480,6 +480,12 @@ class ListHooksTest(util.TempDirTestCase): self.assertEqual(self._call(self.tempdir), [name]) + def test_ignore_tilde(self): + name = os.path.join(self.tempdir, "foo~") + create_hook(name) + + self.assertEqual(self._call(self.tempdir), []) + def create_hook(file_path): """Creates an executable file at the specified path. -- cgit v1.2.3 From 4b06eeae6437b91b8b2e91c0f0490460d87a8ba2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 31 May 2019 18:08:52 -0700 Subject: Update Fedora AMI (#7102) Fixes #6955. This updates the Fedora version used in our test farm tests to Fedora 30. The AMI ID comes from https://alt.fedoraproject.org/cloud/ where it is listed as their standard HVM AMI for the region we use us-east-1 (US East (N. Virginia)). Unfortunately, there were a lot of small changes required for this. The big reason for this is on Fedora, there isn't a Python 2 executable installed. In fact, there's not even an executable named python. It's just python3. Rather than installing another Python in each test, I wrote a script that the test scripts can share to figure out the different paths and names that should be used in their script. (This isn't used in test_sdists.sh because the logic is a little different.) Other changes here worth flagging are: I changed the name of the variable RUN_PYTHON3_TESTS in test_leauto_upgrades.sh to RUN_RHEL6_TESTS. The tests that are run when this variable is set test the upgrade from Python 2 to Python 3 on RHEL 6. I think this new name is much better now that we also have Fedora running Python 3. I made tools/simple_http_server.py work on Python 3. You can see tests passing with these changes at https://travis-ci.com/certbot/certbot/builds/113821476. I also ran test_tests.sh and they passed. * Update to Fedora 30 in test farm tests. Fedora 28 is likely to reach its EOL soon. * Add set_python_envvars.sh. * Fix test_apache2.sh on python3 only distros. * Fix test_leauto_upgrades.sh on python3 systems. * Fix certonly_standalone tests with python3 only * Fix test_sdists.sh on python3 only distros. * Make simple_http_server.py work on Python 3. * add comments --- tests/letstest/apache2_targets.yaml | 4 ++-- tests/letstest/scripts/set_python_envvars.sh | 17 +++++++++++++++++ tests/letstest/scripts/test_apache2.sh | 11 ++++++++--- tests/letstest/scripts/test_leauto_upgrades.sh | 17 +++++++++++------ .../test_letsencrypt_auto_certonly_standalone.sh | 8 +++++++- tests/letstest/scripts/test_sdists.sh | 6 +++++- tests/letstest/targets.yaml | 4 ++-- tools/simple_http_server.py | 9 ++++++--- 8 files changed, 58 insertions(+), 18 deletions(-) create mode 100755 tests/letstest/scripts/set_python_envvars.sh diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 4da6abb15..44faf8027 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -40,8 +40,8 @@ targets: user: admin #----------------------------------------------------------------------------- # Fedora - - ami: ami-5c69df23 - name: fedora28 + - ami: ami-00bbc6858140f19ed + name: fedora30 type: centos virt: hvm user: fedora diff --git a/tests/letstest/scripts/set_python_envvars.sh b/tests/letstest/scripts/set_python_envvars.sh new file mode 100755 index 000000000..668444209 --- /dev/null +++ b/tests/letstest/scripts/set_python_envvars.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# This is a simple script that can be sourced to set Python environment +# variables for use in Certbot's letstest test farm tests. + +# Some distros like Fedora may only have an executable named python3 installed. +if command -v python; then + PYTHON_NAME="python" + VENV_SCRIPT="tools/venv.py" + VENV_PATH="venv" +else + # We could check for "python2" here, however, the addition of "python3" + # only systems is what necessitated this change so checking for "python2" + # isn't necessary. + PYTHON_NAME="python3" + VENV_PATH="venv3" + VENV_SCRIPT="tools/venv3.py" +fi diff --git a/tests/letstest/scripts/test_apache2.sh b/tests/letstest/scripts/test_apache2.sh index c52578003..007ab720e 100755 --- a/tests/letstest/scripts/test_apache2.sh +++ b/tests/letstest/scripts/test_apache2.sh @@ -45,8 +45,13 @@ if [ $? -ne 0 ] ; then exit 1 fi -python tools/venv.py -e acme[dev] -e .[dev,docs] -e certbot-apache -sudo venv/bin/certbot -v --debug --text --agree-dev-preview --agree-tos \ +# This script sets the environment variables PYTHON_NAME, VENV_PATH, and +# VENV_SCRIPT based on the version of Python available on the system. For +# instance, Fedora uses Python 3 and Python 2 is not installed. +. tests/letstest/scripts/set_python_envvars.sh + +"$VENV_SCRIPT" -e acme[dev] -e .[dev,docs] -e certbot-apache +sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-dev-preview --agree-tos \ --renew-by-default --redirect --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL if [ $? -ne 0 ] ; then @@ -55,7 +60,7 @@ fi if [ "$OS_TYPE" = "ubuntu" ] ; then export SERVER="$BOULDER_URL" - venv/bin/tox -e apacheconftest + "$VENV_PATH/bin/tox" -e apacheconftest else echo Not running hackish apache tests on $OS_TYPE fi diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 8cc1748ed..49606b49c 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -21,7 +21,7 @@ if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | se # 0.20.0 is the latest version of letsencrypt-auto that doesn't install # Python 3 on RHEL 6. INITIAL_VERSION="0.20.0" - RUN_PYTHON3_TESTS=1 + RUN_RHEL6_TESTS=1 else # 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. INITIAL_VERSION="0.33.1" @@ -32,14 +32,19 @@ if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | exit 1 fi +# This script sets the environment variables PYTHON_NAME, VENV_PATH, and +# VENV_SCRIPT based on the version of Python available on the system. For +# instance, Fedora uses Python 3 and Python 2 is not installed. +. tests/letstest/scripts/set_python_envvars.sh + # Now that python and openssl have been installed, we can set up a fake server # to provide a new version of letsencrypt-auto. First, we start the server and # directory to be served. MY_TEMP_DIR=$(mktemp -d) PORT_FILE="$MY_TEMP_DIR/port" -SERVER_PATH=$(tools/readlink.py tools/simple_http_server.py) +SERVER_PATH=$("$PYTHON_NAME" tools/readlink.py tools/simple_http_server.py) cd "$MY_TEMP_DIR" -"$SERVER_PATH" 0 > $PORT_FILE & +"$PYTHON_NAME" "$SERVER_PATH" 0 > $PORT_FILE & SERVER_PID=$! trap 'kill "$SERVER_PID" && rm -rf "$MY_TEMP_DIR"' EXIT cd ~- @@ -72,7 +77,7 @@ iQIDAQAB -----END PUBLIC KEY----- " -if [ "$RUN_PYTHON3_TESTS" = 1 ]; then +if [ "$RUN_RHEL6_TESTS" = 1 ]; then if command -v python3; then echo "Didn't expect Python 3 to be installed!" exit 1 @@ -110,7 +115,7 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then exit 1 fi -if [ "$RUN_PYTHON3_TESTS" = 1 ]; then +if [ "$RUN_RHEL6_TESTS" = 1 ]; then if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 @@ -120,7 +125,7 @@ if [ "$RUN_PYTHON3_TESTS" = 1 ]; then exit 1 fi - if [ "$(tools/readlink.py $OLD_VENV_PATH)" != "/opt/eff.org/certbot/venv" ]; then + if [ "$("$PYTHON_NAME" tools/readlink.py $OLD_VENV_PATH)" != "/opt/eff.org/certbot/venv" ]; then echo symlink from old venv path not properly created! exit 1 fi diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index 0973bbc03..eb63b9ca7 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -18,6 +18,11 @@ export PATH="$LE_AUTO_DIR:$PATH" letsencrypt-auto --os-packages-only --debug --version +# This script sets the environment variables PYTHON_NAME, VENV_PATH, and +# VENV_SCRIPT based on the version of Python available on the system. For +# instance, Fedora uses Python 3 and Python 2 is not installed. +. tests/letstest/scripts/set_python_envvars.sh + # Create a venv-like layout at the old virtual environment path to test that a # symlink is properly created when letsencrypt-auto runs. HOME=${HOME:-~root} @@ -32,7 +37,8 @@ letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \ --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL -if [ "$(tools/readlink.py ${XDG_DATA_HOME:-~/.local/share}/letsencrypt)" != "/opt/eff.org/certbot/venv" ]; then +LINK_PATH=$("$PYTHON_NAME" tools/readlink.py ${XDG_DATA_HOME:-~/.local/share}/letsencrypt) +if [ "$LINK_PATH" != "/opt/eff.org/certbot/venv" ]; then echo symlink from old venv path not properly created! exit 1 fi diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index f407d5d2c..e48e95848 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -6,16 +6,20 @@ cd letsencrypt PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) -VERSION=$(letsencrypt-auto-source/version.py) if [ "$PYTHON_MAJOR_VERSION" = "3" ]; then + # Some distros like Fedora may only have an executable named python3 installed. + PYTHON_NAME="python3" VENV_PATH="venv3" VENV_SCRIPT="tools/venv3.py" else + PYTHON_NAME="python" VENV_SCRIPT="tools/venv.py" VENV_PATH="venv" fi +VERSION=$("$PYTHON_NAME" letsencrypt-auto-source/version.py) + # setup venv "$VENV_SCRIPT" --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt . "$VENV_PATH/bin/activate" diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 340fe6bf8..1ca605b5d 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -49,8 +49,8 @@ targets: type: centos virt: hvm user: ec2-user - - ami: ami-5c69df23 - name: fedora28 + - ami: ami-00bbc6858140f19ed + name: fedora30 type: centos virt: hvm user: fedora diff --git a/tools/simple_http_server.py b/tools/simple_http_server.py index 14ac9a3d3..233aa6bd3 100755 --- a/tools/simple_http_server.py +++ b/tools/simple_http_server.py @@ -1,8 +1,11 @@ #!/usr/bin/env python -"""A version of Python 2.x's SimpleHTTPServer that flushes its output.""" -from BaseHTTPServer import HTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler +"""A version of Python's SimpleHTTPServer that flushes its output.""" import sys +try: + from http.server import HTTPServer, SimpleHTTPRequestHandler +except ImportError: + from BaseHTTPServer import HTTPServer + from SimpleHTTPServer import SimpleHTTPRequestHandler def serve_forever(port=0): """Spins up an HTTP server on all interfaces and the given port. -- cgit v1.2.3 From 31e81e7ae0c6561b31960278f661912f9910b65c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 31 May 2019 18:09:17 -0700 Subject: Add explanation of the purpose of test_tests.sh. (#7112) This is one of the two action items from the conversation at https://opensource.eff.org/eff-open-source/pl/rno49hd6q7ba7dr18ph11njc6o. Just to make sure I didn't make a typo, I ran this script with these changes and the tests still pass. --- tests/letstest/scripts/test_tests.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/letstest/scripts/test_tests.sh b/tests/letstest/scripts/test_tests.sh index d5fd6e14a..77ef44270 100755 --- a/tests/letstest/scripts/test_tests.sh +++ b/tests/letstest/scripts/test_tests.sh @@ -1,4 +1,8 @@ #!/bin/sh -xe +# +# This script is useful for testing that the packages we've built for a release +# work on a variety of systems. For an example of the kinds of problems that +# can occur, see https://github.com/certbot/certbot/issues/3455. REPO_ROOT="letsencrypt" LE_AUTO="$REPO_ROOT/letsencrypt-auto-source/letsencrypt-auto" -- cgit v1.2.3 From 18797dca79c163449ab6892649da82ae4a89f22b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 3 Jun 2019 00:20:20 -0700 Subject: Remove scripts that are never run. (#7111) * Remove scripts that are never run. * Update example in multitester.py docstring. --- tests/letstest/multitester.py | 2 +- .../scripts/test_letsencrypt_auto_venv_only.sh | 7 --- tests/letstest/scripts/test_renew_standalone.sh | 55 ---------------------- tests/letstest/scripts/test_tox.sh | 18 ------- 4 files changed, 1 insertion(+), 81 deletions(-) delete mode 100755 tests/letstest/scripts/test_letsencrypt_auto_venv_only.sh delete mode 100755 tests/letstest/scripts/test_renew_standalone.sh delete mode 100755 tests/letstest/scripts/test_tox.sh diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 51edd2671..7bc4e034d 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -23,7 +23,7 @@ Usage: >aws ec2 create-key-pair --profile HappyHacker --key-name MyKeyPair \ --query 'KeyMaterial' --output text > MyKeyPair.pem then: ->python multitester.py targets.yaml MyKeyPair.pem HappyHacker scripts/test_letsencrypt_auto_venv_only.sh +>python multitester.py targets.yaml MyKeyPair.pem HappyHacker scripts/test_leauto_upgrades.sh see: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html https://docs.aws.amazon.com/cli/latest/userguide/cli-ec2-keypairs.html diff --git a/tests/letstest/scripts/test_letsencrypt_auto_venv_only.sh b/tests/letstest/scripts/test_letsencrypt_auto_venv_only.sh deleted file mode 100755 index c55e12e8b..000000000 --- a/tests/letstest/scripts/test_letsencrypt_auto_venv_only.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -x - -# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL are dynamically set at execution - -cd letsencrypt -# help installs virtualenv and does nothing else -./letsencrypt-auto-source/letsencrypt-auto -v --debug --help all diff --git a/tests/letstest/scripts/test_renew_standalone.sh b/tests/letstest/scripts/test_renew_standalone.sh deleted file mode 100755 index 31c38ea46..000000000 --- a/tests/letstest/scripts/test_renew_standalone.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -x - -# $OS_TYPE $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL -# are dynamically set at execution - -# run certbot-apache2 via letsencrypt-auto -cd letsencrypt - -export SUDO=sudo -if [ -f /etc/debian_version ] ; then - echo "Bootstrapping dependencies for Debian-based OSes..." - $SUDO bootstrap/_deb_common.sh -elif [ -f /etc/redhat-release ] ; then - echo "Bootstrapping dependencies for RedHat-based OSes..." - $SUDO bootstrap/_rpm_common.sh -else - echo "Don't have bootstrapping for this OS!" - exit 1 -fi - -bootstrap/dev/venv.sh -sudo venv/bin/certbot certonly --debug --standalone -t --agree-dev-preview --agree-tos \ - --renew-by-default --redirect --register-unsafely-without-email \ - --domain $PUBLIC_HOSTNAME --server $BOULDER_URL -v -if [ $? -ne 0 ] ; then - FAIL=1 -fi - -if [ "$OS_TYPE" = "ubuntu" ] ; then - venv/bin/tox -e apacheconftest -else - echo Not running hackish apache tests on $OS_TYPE -fi - -if [ $? -ne 0 ] ; then - FAIL=1 -fi - -sudo venv/bin/certbot renew --renew-by-default - -if [ $? -ne 0 ] ; then - FAIL=1 -fi - - -ls /etc/letsencrypt/archive/$PUBLIC_HOSTNAME | grep -q 2.pem - -if [ $? -ne 0 ] ; then - FAIL=1 -fi - -# return error if any of the subtests failed -if [ "$FAIL" = 1 ] ; then - exit 1 -fi diff --git a/tests/letstest/scripts/test_tox.sh b/tests/letstest/scripts/test_tox.sh deleted file mode 100755 index bb9126673..000000000 --- a/tests/letstest/scripts/test_tox.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -x -XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share} -VENV_NAME="venv" -# The path to the letsencrypt-auto script. Everything that uses these might -# at some point be inlined... -LEA_PATH=./letsencrypt/ -VENV_PATH=${LEA_PATH/$VENV_NAME} -VENV_BIN=${VENV_PATH}/bin - - -# virtualenv call is not idempotent: it overwrites pip upgraded in -# later steps, causing "ImportError: cannot import name unpack_url" - -"$LEA_PATH/letsencrypt-auto" --os-packages-only - -cd letsencrypt -python tools/venv.py -venv/bin/tox -e py27 -- cgit v1.2.3 From 09b7d2f461d523651fc979826b8d2282aaacd34b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 3 Jun 2019 10:25:23 -0700 Subject: Configure the stale bot (#7108) * Configure the stale bot. * Add top level comment. * except assignees * Give warning about closing issues. --- .github/stale.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..c248fe9d4 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,34 @@ +# Configuration for https://github.com/marketplace/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 180 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Ignore issues with an assignee (defaults to false) +exemptAssignees: true + +# Label to use when marking as stale +staleLabel: stale + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + To help us better see what issues are still affecting our users, this issue + has been automatically marked as stale. If you still have this issue with an + up-to-date version of Certbot and are interested in seeing it resolved, + please add a comment letting us know. If there is no further activity, this + issue will be automatically closed. + +# Comment to post when closing a stale Issue or Pull Request. +closeComment: > + This issue has been closed due to lack of activity, but if you think it + should be reopened, please open a new issue with a link to this one and we'll + take a look. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 1 + +# Don't mark pull requests as stale. +only: issues -- cgit v1.2.3 From 889aeb31df68e106addf62ae6dc46819dbc1ce4c Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 3 Jun 2019 21:55:26 +0200 Subject: Validate OCSP responses in case an explicit responder is designated (#7054) * Validate OCSP response for responders that are not the certificate's issuer. * Improve OCSP tests using a issuer/responder pair for OCSP responses * Clean code * Update ocsp_test.py * Add various comments * Add several cases of ocsp responder. More factories for the resilience tests. * Update ocsp_test.py --- certbot/ocsp.py | 53 ++++++- certbot/tests/ocsp_test.py | 170 ++++++++++++++------- certbot/tests/testdata/google_certificate.pem | 41 ----- .../tests/testdata/google_issuer_certificate.pem | 26 ---- certbot/tests/testdata/ocsp_certificate.pem | 37 +++++ certbot/tests/testdata/ocsp_issuer_certificate.pem | 38 +++++ .../tests/testdata/ocsp_responder_certificate.pem | 27 ++++ 7 files changed, 260 insertions(+), 132 deletions(-) delete mode 100644 certbot/tests/testdata/google_certificate.pem delete mode 100644 certbot/tests/testdata/google_issuer_certificate.pem create mode 100644 certbot/tests/testdata/ocsp_certificate.pem create mode 100644 certbot/tests/testdata/ocsp_issuer_certificate.pem create mode 100644 certbot/tests/testdata/ocsp_responder_certificate.pem diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 68f73a821..0e35f023f 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -155,7 +155,7 @@ def _check_ocsp_cryptography(cert_path, chain_path, url): # Check OCSP signature try: - _check_ocsp_response(response_ocsp, request, issuer) + _check_ocsp_response(response_ocsp, request, issuer, cert_path) except UnsupportedAlgorithm as e: logger.error(str(e)) except errors.Error as e: @@ -173,7 +173,7 @@ def _check_ocsp_cryptography(cert_path, chain_path, url): return False -def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): +def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): """Verify that the OCSP is valid for serveral criterias""" # Assert OCSP response corresponds to the certificate we are talking about if response_ocsp.serial_number != request_ocsp.serial_number: @@ -181,7 +181,7 @@ def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): 'to the certificate in request') # Assert signature is valid - _check_ocsp_response_signature(response_ocsp, issuer_cert) + _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path) # Assert issuer in response is the expected one if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) @@ -207,11 +207,52 @@ def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert): raise AssertionError('param nextUpdate is in the past.') -def _check_ocsp_response_signature(response_ocsp, issuer_cert): - """Verify an OCSP response signature against certificate issuer""" +def _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path): + """Verify an OCSP response signature against certificate issuer or responder""" + if response_ocsp.responder_name == issuer_cert.subject: + # Case where the OCSP responder is also the certificate issuer + logger.debug('OCSP response for certificate %s is signed by the certificate\'s issuer.', + cert_path) + responder_cert = issuer_cert + else: + # Case where the OCSP responder is not the certificate issuer + logger.debug('OCSP response for certificate %s is delegated to an external responder.', + cert_path) + + responder_certs = [cert for cert in response_ocsp.certificates + if cert.subject == response_ocsp.responder_name] + if not responder_certs: + raise AssertionError('no matching responder certificate could be found') + + # We suppose here that the ACME server support only one certificate in the OCSP status + # request. This is currently the case for LetsEncrypt servers. + # See https://github.com/letsencrypt/boulder/issues/2331 + responder_cert = responder_certs[0] + + if responder_cert.issuer != issuer_cert.subject: + raise AssertionError('responder certificate is not signed ' + 'by the certificate\'s issuer') + + try: + extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value + except (x509.ExtensionNotFound, IndexError): + delegate_authorized = False + if not delegate_authorized: + raise AssertionError('responder is not authorized by issuer to sign OCSP responses') + + # Following line may raise UnsupportedAlgorithm + chosen_hash = responder_cert.signature_hash_algorithm + # For a delegate OCSP responder, we need first check that its certificate is effectively + # signed by the certificate issuer. + crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, + responder_cert.tbs_certificate_bytes, chosen_hash) + # Following line may raise UnsupportedAlgorithm chosen_hash = response_ocsp.signature_hash_algorithm - crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature, + # We check that the OSCP response is effectively signed by the responder + # (an authorized delegate one or the certificate issuer itself). + crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature, response_ocsp.tbs_response_bytes, chosen_hash) diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 768c49eac..e8c1b9d03 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,5 +1,6 @@ """Tests for ocsp.py""" # pylint: disable=protected-access +import contextlib import unittest from datetime import datetime, timedelta @@ -87,11 +88,11 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(mock_run.call_count, 2) def test_determine_ocsp_server(self): - cert_path = test_util.vector_path('google_certificate.pem') + cert_path = test_util.vector_path('ocsp_certificate.pem') from certbot import ocsp result = ocsp._determine_ocsp_server(cert_path) - self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result) + self.assertEqual(('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com'), result) @mock.patch('certbot.ocsp.logger') @mock.patch('certbot.util.run_script') @@ -128,8 +129,8 @@ class OSCPTestCryptography(unittest.TestCase): def setUp(self): from certbot import ocsp self.checker = ocsp.RevocationChecker() - self.cert_path = test_util.vector_path('google_certificate.pem') - self.chain_path = test_util.vector_path('google_issuer_certificate.pem') + self.cert_path = test_util.vector_path('ocsp_certificate.pem') + self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem') @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.ocsp._check_ocsp_cryptography') @@ -139,91 +140,140 @@ class OSCPTestCryptography(unittest.TestCase): mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') - @mock.patch('certbot.ocsp.requests.post') - @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') - def test_revoke(self, mock_ocsp_response, mock_post): - with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload'): - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) + def test_revoke(self): + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - self.assertTrue(revoked) - @mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') - @mock.patch('certbot.ocsp.requests.post') - @mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') - def test_revoke_resiliency(self, mock_ocsp_response, mock_post, mock_check): + def test_responder_is_issuer(self): + issuer = x509.load_pem_x509_certificate( + test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend()) + + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, + ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: + mocks['mock_response'].return_value.responder_name = issuer.subject + self.checker.ocsp_revoked(self.cert_path, self.chain_path) + # Here responder and issuer are the same. So only the signature of the OCSP + # response is checked (using the issuer/responder public key). + self.assertEqual(mocks['mock_check'].call_count, 1) + self.assertEqual(mocks['mock_check'].call_args[0][0].public_numbers(), + issuer.public_key().public_numbers()) + + def test_responder_is_authorized_delegate(self): + issuer = x509.load_pem_x509_certificate( + test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend()) + responder = x509.load_pem_x509_certificate( + test_util.load_vector('ocsp_responder_certificate.pem'), default_backend()) + + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, + ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: + self.checker.ocsp_revoked(self.cert_path, self.chain_path) + # Here responder and issuer are not the same. Two signatures will be checked then, + # first to verify the responder cert (using the issuer public key), second to + # to verify the OCSP response itself (using the responder public key). + self.assertEqual(mocks['mock_check'].call_count, 2) + self.assertEqual(mocks['mock_check'].call_args_list[0][0][0].public_numbers(), + issuer.public_key().public_numbers()) + self.assertEqual(mocks['mock_check'].call_args_list[1][0][0].public_numbers(), + responder.public_key().public_numbers()) + + def test_revoke_resiliency(self): # Server return an invalid HTTP response - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=400) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - + with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, + http_status_code=400): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) # OCSP response in invalid - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED) - mock_post.return_value = mock.Mock(status_code=200) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - + with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) # OCSP response is valid, but certificate status is unknown - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - + with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) # The OCSP response says that the certificate is revoked, but certificate # does not contain the OCSP extension. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - with mock.patch('cryptography.x509.Extensions.get_extension_for_class', - side_effect=x509.ExtensionNotFound( - 'Not found', x509.AuthorityInformationAccessOID.OCSP)): + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): + with mock.patch('cryptography.x509.Extensions.get_extension_for_class', + side_effect=x509.ExtensionNotFound( + 'Not found', x509.AuthorityInformationAccessOID.OCSP)): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.assertFalse(revoked) + + # OCSP response uses an unsupported signature. + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, + check_signature_side_effect=UnsupportedAlgorithm('foo')): revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.assertFalse(revoked) + # OSCP signature response is invalid. + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, + check_signature_side_effect=InvalidSignature('foo')): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) - # Valid response, OCSP extension is present, - # but OCSP response uses an unsupported signature. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = UnsupportedAlgorithm('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + # Assertion error on OCSP response validity + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, + check_signature_side_effect=AssertionError('foo')): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.assertFalse(revoked) + # No responder cert in OCSP response + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, + ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: + mocks['mock_response'].return_value.certificates = [] + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) - # And now, the signature itself is invalid. - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = InvalidSignature('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + # Responder cert is not signed by certificate issuer + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, + ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: + cert = mocks['mock_response'].return_value.certificates[0] + mocks['mock_response'].return_value.certificates[0] = mock.Mock( + issuer='fake', subject=cert.subject) + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.assertFalse(revoked) + with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): + # This mock is necessary to avoid the first call contained in _determine_ocsp_server + # of the method cryptography.x509.Extensions.get_extension_for_class. + with mock.patch('certbot.ocsp._determine_ocsp_server') as mock_server: + mock_server.return_value = ('https://example.com', 'example.com') + with mock.patch('cryptography.x509.Extensions.get_extension_for_class', + side_effect=x509.ExtensionNotFound( + 'Not found', x509.AuthorityInformationAccessOID.OCSP)): + revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) self.assertFalse(revoked) - # Finally, assertion error on OCSP response validity - mock_ocsp_response.return_value = _construct_mock_ocsp_response( - ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) - mock_post.return_value = mock.Mock(status_code=200) - mock_check.side_effect = AssertionError('foo') - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) - self.assertFalse(revoked) +@contextlib.contextmanager +def _ocsp_mock(certificate_status, response_status, + http_status_code=200, check_signature_side_effect=None): + with mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') as mock_response: + mock_response.return_value = _construct_mock_ocsp_response( + certificate_status, response_status) + with mock.patch('certbot.ocsp.requests.post') as mock_post: + mock_post.return_value = mock.Mock(status_code=http_status_code) + with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') as mock_check: + if check_signature_side_effect: + mock_check.side_effect = check_signature_side_effect + yield { + 'mock_response': mock_response, + 'mock_post': mock_post, + 'mock_check': mock_check, + } def _construct_mock_ocsp_response(certificate_status, response_status): cert = x509.load_pem_x509_certificate( - test_util.load_vector('google_certificate.pem'), default_backend()) + test_util.load_vector('ocsp_certificate.pem'), default_backend()) issuer = x509.load_pem_x509_certificate( - test_util.load_vector('google_issuer_certificate.pem'), default_backend()) + test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend()) + responder = x509.load_pem_x509_certificate( + test_util.load_vector('ocsp_responder_certificate.pem'), default_backend()) builder = ocsp_lib.OCSPRequestBuilder() builder = builder.add_certificate(cert, issuer, hashes.SHA1()) request = builder.build() @@ -234,6 +284,8 @@ def _construct_mock_ocsp_response(certificate_status, response_status): serial_number=request.serial_number, issuer_key_hash=request.issuer_key_hash, issuer_name_hash=request.issuer_name_hash, + responder_name=responder.subject, + certificates=[responder], hash_algorithm=hashes.SHA1(), next_update=datetime.now() + timedelta(days=1), this_update=datetime.now() - timedelta(days=1), diff --git a/certbot/tests/testdata/google_certificate.pem b/certbot/tests/testdata/google_certificate.pem deleted file mode 100644 index c26fea0b1..000000000 --- a/certbot/tests/testdata/google_certificate.pem +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1 -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk -IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy -MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB -BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF -Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG -A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD -VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ -w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj -/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE -sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ -xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM -fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag -re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG -A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE -AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0 -oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy -LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy -dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB -FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF -BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS -BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 -U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA -MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY -BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT -Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg -U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE -AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51 -vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV -CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN -8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG -eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v -ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN -HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB -Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk -myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq -WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik= ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/google_issuer_certificate.pem b/certbot/tests/testdata/google_issuer_certificate.pem deleted file mode 100644 index 50db47bc4..000000000 --- a/certbot/tests/testdata/google_issuer_certificate.pem +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw -HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs -U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy -MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg -U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW -XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK -71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9 -RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z -ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT -kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz -AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH -AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa -Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu -MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv -b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz -cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc -aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA -HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e -ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq -wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu -FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy -7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV -c7o835DLAFshEWfC7TIe3g== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/ocsp_certificate.pem b/certbot/tests/testdata/ocsp_certificate.pem new file mode 100644 index 000000000..471844859 --- /dev/null +++ b/certbot/tests/testdata/ocsp_certificate.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGYDCCBEigAwIBAgIKcjrC4hZcebbtODANBgkqhkiG9w0BAQsFADBRMQswCQYD +VQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNVBAMM +GkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDUxMjE1NTgyMVoXDTE5 +MTEwODIyNTkwMFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9P +b+YhJPypm4ui+AZUHPrJ6IsB9R/6Wvgec2G/GuW/UNQFktIhU10HOHAbiJeYLqNZ +1Cia8JD6NXXGbprOjIbZWvjulYTaLSlClcK0H7HZrcgrK60OeIGEtur27ga68RML +hs1FG7TNyWVysifOtwW9Oo1mZQQtxViiE2Yb+Q4QqIxitnbrnFmKrVJSUHVXi8/I +BK1yLrJiRBZMIw0wvAWcWEG2Gpp9PAbemlb11Zx8sm/RSGh7u60rmETbB2Pu941s +XJCSQRtq5yKdtjIJTIgbe12SPkknqTqa3aUh7hgho0IymlDSeeocL60SUiUAsPEr +QRWleodOR1ChXz5mFokCAwEAAaOCAokwggKFMAkGA1UdEwQCMAAwHwYDVR0jBBgw +FoAUd9nQBpFm2N0ZJo1JrNowL2p7YrEwHQYDVR0OBBYEFExS23I6sLCeO6KIxzoc +tr9s+HmiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwIAYDVR0gBBkwFzALBglghEIBGgEAAgcwCAYGZ4EMAQIBMEIGA1UdHwQ7 +MDkwN6A1oDOGMWh0dHA6Ly9jcmwudGVzdDQuYnV5cGFzcy5uby9jcmwvQlBDbGFz +czJUNENBNS5jcmwwIQYDVR0RAQH/BBcwFYITYnV5cGFzcy5wYWNhbGlzLm5ldDB4 +BggrBgEFBQcBAQRsMGowKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1 +eXBhc3MuY29tMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnRlc3Q0LmJ1eXBhc3Mu +bm8vY3J0L0JQQ2xhc3MyVDRDQTUuY2VyMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDw +AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsTLGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMA +RzBFAiEA1oWB4c6q7+tqGA4HhLNACOemr9c2aIUuWxeQE7/PlSYCIEolZ7pWVs1J +VyQW/AqeuXGB7qScwUgLh9C1uOJoeRe6AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsT +LGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMARzBFAiAoLaNvIwMDifsDAXJBsAKHlYx7 +QPLXL8onYKm8f+Sf1wIhAMepo2GX84UR7WtooqzkBZLG+PaBy1zMuUAG6mwnroF9 +MA0GCSqGSIb3DQEBCwUAA4ICAQAPWLdjNS5lLL5SEtghYebtDmNj2968NYSDvb1L +1/uFwg3LCVRR1Xb3z1Hc/sc1W0IFXU0zOqEQiuP8jkVP7UqkaWuK5Eu0eP0zPI83 +WBZM0+eBwxwzIMK/Q7fYKTu1+vg/FlH0WhtV43DQSik66366zvPi2Tfag9IPvRei +DOjbSOBF0o4er2oCrtI0lK5YrHOdWtD7xwQIuA606P9ucuufMf+JcmduRJsVZ2Zu +3K32SMDdAnyjvQWZNbt1ex3G8vuFQEi690UBhPcha/SO8QvLS89wcaLJnyMIWdv7 +54cbw+fa1nLKM7qph6Mk1yb0qpomPqLmKw4T6WX36c0vDlFSpexJLGgWDFqLUxPN +qV7cJz4mi1qaYfdWXRrnyU4bl55pHTTgEzbohV7apsmytkCe1uFNrpcTh8jzAhGN +PQqarX9UoESR56B/ufbBGlBWi0pkV49BFks6Ue0GVKo7djoxuV6+SsmYSE+6MNPv +IUsm54TSnwxjA8WyG7pl14g1hkGFQ4NRYJMiVqK3DMABaPxVmT7NRxUQQiM0mmM7 +EKNzLBeWHJF5ecdDR1MiIF3ayn+RiZb0r8aSQBMLwN1YwUZw+hSYz1eCd7bHN1gC +1ksxP61f8LBz0SwDoyOTr8wY++wqF26KfoYuKQ3LjLeHvuUtL3EMnAhiyuej8ZOZ +22spng== +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/ocsp_issuer_certificate.pem b/certbot/tests/testdata/ocsp_issuer_certificate.pem new file mode 100644 index 000000000..4f894ae4b --- /dev/null +++ b/certbot/tests/testdata/ocsp_issuer_certificate.pem @@ -0,0 +1,38 @@ +22spng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGMzCCBBugAwIBAgIJMvsa+ZFQCj8nMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEmMCQGA1UEAwwd +QnV5cGFzcyBDbGFzcyAyIFRlc3Q0IFJvb3QgQ0EwHhcNMTcwMjEzMTY1MjQ2WhcN +MjcwMjEzMTY1MjQ2WjBRMQswCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBB +Uy05ODMxNjMzMjcxIzAhBgNVBAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi/vpgO2sbUQZsoxWd6us +QvT/59kvw5ehoJABBXFs1J1AV1/K2hjhDXit/sNGKjzDvkfE9PJqXMnhKpPFkUzC +z/NmDK++d6aRflnDvJrxlPVpp0QGbe3qOErByFjWiHoobuVItlpRO/BaBdlgGvmQ +LeZFBXs/ZrLNFUKBcE+DZIyJH7vy2EB5dNNVn2mx0n+371InpKsYUaHNlxPpp+uj +TOL+e4OjWTBwDaI7rVzpavozb8SPzFxjpxLLVH/j+8VPwoe3lmxr8ATyI178iRdA +uxYfaKURSfu7PWjnDNTnq26E3pwW3E5zUbsADgUMh/PzoJAcszL1eHGUQaAGBP85 +PlLmHr+nsPMHXOUyl7Ts6KGkZlvjnVshKwUxYAqjAC7/BY0iI0xc406NK9heeVDk +NiFA8/To6mQ09vO/TBxQtkfNk2yuxiixa101peSg4/+E4VhwYv6MJxS/oVqBd2d3 +wemYW/JUVeJg9wXGq1e/c09/UjGwUGwU9s5LNFEgj4v1tcvWnONzWNXkyMrs5g4e +U8L/DQ3XgNrcA9zrfFq0cQhSJonj/VI/jbBYyB2yEuQAIjAN6eDIOoLmHGIIvZtE +0LL5jaZC3W518jB1OF7QSvaFtaFl0VqDy6LMXL50elMVC+hr9KpDnN0t8gaSiPyZ +wEC9SMdQ7SLVOUK1Xdh3dh0CAwEAAaOCAQkwggEFMA8GA1UdEwEB/wQFMAMBAf8w +HwYDVR0jBBgwFoAU0aT+MaGsc75ZynH0up0oH+tVHh4wHQYDVR0OBBYEFHfZ0AaR +ZtjdGSaNSazaMC9qe2KxMA4GA1UdDwEB/wQEAwIBBjAgBgNVHSAEGTAXMAsGCWCE +QgEaAQACBzAIBgZngQwBAgEwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC50 +ZXN0NC5idXlwYXNzLm5vL2NybC9CUENsYXNzMlQ0Um9vdENBLmNybDA5BggrBgEF +BQcBAQQtMCswKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1eXBhc3Mu +Y29tMA0GCSqGSIb3DQEBCwUAA4ICAQBOgxedV31NCpZQRc8yFxoqQNgBnY1UeH/h +/s/9fGQzyGnTWZldEi5MGJKF6ulcYnklitlg/jic9au3xSoqP/i2smUHByX2wMrC +mDpLCwio2x2p/0Wscj5asqzJE2cCWqob2iHxo36nsr3Jdd2GIlzhZ0wm8rMZxsQG +FgbgHYIer79S+PIdHoZuUnCJhsJ+1PRUmm2t7vcmZpu8l4CeL0XJX98l2L8kbBds +MGo1EazGAEirZnSfQKCARhUcEdavsKl067+irsGGcK4+L78Vl9S1/QPfKG30L5fv +nM1X1qAdhsbjwVdrhLkjpzabT0icsW6W17HLh8UBYdA7k4GclA6h+mNrXAt7JAeZ +PzMFq0I7vVJNEdolZHTVCqT0sdJiTj+phS1ztK86Wb1R/5d5B1VSb789zSdJfrwV +ppXgPtZq5x3GQi6ooteWyuWj3cBcNu9TU1D8u1F0XI5gw4Y0VpxlDxysUgFQJlo4 +VYmMpgr442o/35UgwzkIC7x/6dkvMZvM4jYB5JZJXjynR35XawXB/hzybermJ8BB +DsY0MCOwxhpsTbyEC4wfxZ08B4JtORkToOt4OWuejovsr68Ht6ytOPj7dquoPPNM +9eGNSp94nEIiZ2n75ZMg0gIQArXU9OCV6B2TXxB7w2YB0y0teDgVhoM3IY/ltqJ/ +PJrUUjM8OQ== +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/ocsp_responder_certificate.pem b/certbot/tests/testdata/ocsp_responder_certificate.pem new file mode 100644 index 000000000..53bc4a92a --- /dev/null +++ b/certbot/tests/testdata/ocsp_responder_certificate.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpjCCAo6gAwIBAgINARMIGYlEsD1LTt6D7zANBgkqhkiG9w0BAQsFADBRMQsw +CQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNV +BAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDQwNTEwMDAwMFoX +DTE5MDcwNDEwMDAwMFowSTELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3Mg +QVMtOTgzMTYzMzI3MRswGQYDVQQDDBJCdXlwYXNzIFRlc3Q0IE9DU1AwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKGF+kYNd1fbhYT7Vf9xouZlx+4w45 +Y5EowPoaSKFo4uUDDxkj4PwmMiH4w9Q2bGrCbZRrDrvlNVY/kwzLu4CIk6Ip0dgm +VZGNFB3Xo9nai7rI5pn/YVvVnDIQXh1LRbekzLVyHvhRgMpRb19xN/iYsxaOJDph +8eAgbTKf6eitvfbvn/zXHj4KGKycuULI4+mwlfV3uioT4ulbT7PTVJetgi/XXFDO +xMjbqx6I1ZMmzKJ6LNaFlfx6GdZsaLRDCidHzGp8Fm4ZdV+UPvMZcVDQO6rvQ3wU +iGyCqgfE5e0aFvfeLoBPBtaoT0Ht1CvGdTfVet6PXrF6gh40fdEH5Ob5AgMBAAGj +gYQwgYEwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBR32dAGkWbY3RkmjUms2jAvanti +sTAdBgNVHQ4EFgQU3VlR+sSIVpmXklieP7IlpVUcXIowDgYDVR0PAQH/BAQDAgeA +MBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GCSsGAQUFBzABBQQCBQAwDQYJKoZIhvcN +AQELBQADggIBAFBRLVsBadNFAoFi0HOrfxYsiqggZGJLlgxGyi/0NBIgduG4kcpM +THvplwBwMQEqyp5511pSEbLPAFj8EqC5c46hXZXmT49xlfRvr2Bo+qtTPV9szuWr +8muEIejwRrkATpqWPZWR2zVTXfB90mU2oGuRvxUVmnW4v+FrCChJo7+9yTocZJKx +p4vxYfPMeggomdGAAUz94+0ppSjOLDzs3MA8uOcR0zJ2Y7UHb7PBf/HiM3GO2uKB +sRgdDaGIf/PNpav0xJ/abGNNNwvXzHiMgqqImsuv/JoncPQWbClNurhXpdN7xt9C +HcLX2AdggabcogjWm4guBFuFTsL1i0l8Bsu/6iPJ7ddCeANfYzf7h6AcQq12uFl3 +070F29DtPh8D3FPWgRZZsxoANFjXErxfj4a4+DR+jhhkb9YM/wI0vCOM7W6PKxVn +ZK5kHGOQTcQMj7RCX52gEf27M33zC7HVam+kKhGvwq7D9Bs5hZclzcbjpR4eIxT7 +tzuiy5VpPh1DRLPrphPUB4xsA1dy6zbkg8OqddG6NxD++ja/iZyzSB3SeWyO02qA +QoK2FzDasxpZ9rT3ioAcms3wVNe4lcd4OP8gHZONuat/gvxk6OZvAld6cnIrQZYB +Tbu89ZWvhsyI3p4YC/15pUvA95j9Y0te+G+CF22Eoyb+rtz6mMletnUB +-----END CERTIFICATE----- -- cgit v1.2.3 From 419ad7df1edb229bd63b624d868e5122d0d82770 Mon Sep 17 00:00:00 2001 From: Thue Date: Tue, 4 Jun 2019 14:46:40 +0200 Subject: Fix typo cerbot->certbot. (#7118) --- certbot/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/main.py b/certbot/main.py index 5365cd591..9ed44cceb 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -671,7 +671,7 @@ def register(config, unused_plugins): # 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") + "Please use 'certbot update_account [options]' instead.\n") logger.warning(msg) return update_account(config, unused_plugins) -- cgit v1.2.3 From bfd1ce97ef9d55fcfffcceaf487c54b6debf649f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 4 Jun 2019 20:37:54 +0200 Subject: Add Adrien Ferrand to the authors list (#7119) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 0e8d88a4d..340a0a94b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -5,6 +5,7 @@ Authors * Aaron Zuehlke * Ada Lovelace * [Adam Woodbeck](https://github.com/awoodbeck) +* [Adrien Ferrand](https://github.com/adferrand) * [Aidin Gharibnavaz](https://github.com/aidin36) * [AJ ONeal](https://github.com/coolaj86) * [Alcaro](https://github.com/Alcaro) -- cgit v1.2.3 From 459ba89aef834afa41923091d56cce3080bd872f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 4 Jun 2019 14:17:49 -0700 Subject: Add changelog entry about #7054. (#7122) * Add changelog entry about #7054. * Fix typo noticed by cpu Co-Authored-By: Daniel McCarney --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e62c792..b4e238535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Renewal parameter `webroot_path` is always saved, avoiding some regressions when `webroot` authenticator plugin is invoked with no challenge to perform. +* Certbot now accepts OCSP responses when an explicit authorized + responder, different from the issuer, is used to sign OCSP + responses. * Scripts in Certbot hook directories are no longer executed when their filenames end in a tilde. -- cgit v1.2.3 From 8e92577cb043b175a0b4c7eee8a2cd42e2450634 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 5 Jun 2019 13:39:05 -0700 Subject: Update changelog for 0.35.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e238535..95833ad7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.35.0 - master +## 0.35.0 - 2019-06-05 ### Added -- cgit v1.2.3 From 3568070c7356c7ebcc3983c9fd9d5cb711bb6efa Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 5 Jun 2019 14:00:46 -0700 Subject: Release 0.35.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 26 ++++++++++----------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------- letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++++---------- 26 files changed, 79 insertions(+), 79 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 56a9a63f3..b5291862b 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.35.0.dev0' +version = '0.35.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e14bcb3b6..e991a8825 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index c5a9989c5..abe655083 100755 --- a/certbot-auto +++ b/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.34.2" +LE_AUTO_VERSION="0.35.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.2 \ - --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ - --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb -acme==0.34.2 \ - --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ - --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a -certbot-apache==0.34.2 \ - --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ - --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 -certbot-nginx==0.34.2 \ - --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ - --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 +certbot==0.35.0 \ + --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ + --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 +acme==0.35.0 \ + --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ + --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 +certbot-apache==0.35.0 \ + --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ + --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 +certbot-nginx==0.35.0 \ + --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ + --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index c95864e09..dacf43870 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index afdfb09e1..b552b017f 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4883150fb..9156e7a6c 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 07d406fd9..0c97eadb5 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index f781bd0a5..91745a27d 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 41c1b75a5..eaf048d33 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f009df8e8..3c5022832 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index e1d6aeed0..73ba0e795 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index ae8739d61..d2e84a8c1 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index b2e14869e..7160c857a 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index e839cd71d..3e0f6787c 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a6a52d648..f5cb7782d 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index e05104e5d..0d5cb5498 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 09cd4acd2..efc5c4d7a 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 29f458542..c29a20169 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0.dev0' +version = '0.35.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 51055ce64..e7d742b7f 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0.dev0' +version = '0.35.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index abec68040..0d0c96fb7 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.35.0.dev0' +__version__ = '0.35.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index f65384bbc..03338dbea 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.34.2 + "". (default: CertbotACMEClient/0.35.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index c5a9989c5..abe655083 100755 --- a/letsencrypt-auto +++ b/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.34.2" +LE_AUTO_VERSION="0.35.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.2 \ - --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ - --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb -acme==0.34.2 \ - --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ - --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a -certbot-apache==0.34.2 \ - --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ - --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 -certbot-nginx==0.34.2 \ - --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ - --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 +certbot==0.35.0 \ + --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ + --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 +acme==0.35.0 \ + --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ + --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 +certbot-apache==0.35.0 \ + --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ + --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 +certbot-nginx==0.35.0 \ + --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ + --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 54ea543e9..fe8053e4f 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlzR4cYACgkQTRfJlc2X -dfKDMQf/RTQ53OV2UMp/9qc7Ig8GdHG0MT8h3d2dhFtfT3aAVYGxWXPnZp68Ut2l -hL9qpoDX1VbMcG110oQp4SXGIfMfs/aUZXs6bsW1yfTHv63CT0j4oxycShZWy5vp -mMj2T/huW/yXcaHPdIGUmYyxAKr/CyZ9o3jTg5YARoaO2q5VcSII6MpBtrvlPr2r -3fNhvuQf0tjjpYec/iyR1sg/0cK/ZxdsqdSC7HpDUsxBNqwxLrXhW27KdB4GU5mI -y6ngzrg32FEj2MDkna52/HFsVroqpoIbmdB6LdVxWH2xMRW5YbE3+p2ntT+T0NBt -Us2cca3NgnM938Fo/oto4GNZU+bqaQ== -=VxSR +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlz4LVUACgkQTRfJlc2X +dfL3rQf/UUZa51owdNuiGl7EbbyGyQFMAzq4pYDofsSlFWYPYQSMtcxuBa/w3Cxp +TzFMpWs2tr4y+axFZwNleVDdD5bw/CWLQ+aScvRYblYFPOucwz9/GMEjpy1056Gh +4hZ3FyLYPtD9oP9YR5VMPy109Djv4DX5F6de3JgC2Wv3FbvtZkkZfforCHxEnbjU +6dHDT1qxEzqqEyFHjtlRi1hGymeMbu56+ZqhzkkZJVO0KS1+y7M8DxuKa7kbTP43 +337ASSFZ/eL/A9HGJNoxWLX0+VazNJomHyG9zGmscj1lK1/S/HoIREzdZV84tUME +0iroJw2aax7jpgrlTm6z2zt/vPpemw== +=rvAB -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index f063b789b..abe655083 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.35.0.dev0" +LE_AUTO_VERSION="0.35.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.34.2 \ - --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ - --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb -acme==0.34.2 \ - --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ - --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a -certbot-apache==0.34.2 \ - --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ - --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 -certbot-nginx==0.34.2 \ - --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ - --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 +certbot==0.35.0 \ + --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ + --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 +acme==0.35.0 \ + --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ + --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 +certbot-apache==0.35.0 \ + --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ + --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 +certbot-nginx==0.35.0 \ + --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ + --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index fa9dabdc2..987dd3a91 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 3b33abb33..8aab11cef 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.34.2 \ - --hash=sha256:238bb1c100d0d17f0bda147387435c307e128b2f1a8339eb85cef7fb99909cb9 \ - --hash=sha256:30732ddcb10ccd8b8410c515a76ae0429ad907130b8bf8caa58b73826d0ec9bb -acme==0.34.2 \ - --hash=sha256:f2b3cec09270499211fa54e588571bac67a015d375a4806c6c23431c91fdf7e3 \ - --hash=sha256:bd5b0dfcbca82a2be6fe12e7c7939721d6b3dacb7d8529ba519b56274060dc2a -certbot-apache==0.34.2 \ - --hash=sha256:c9cbbc2499084361a741f865a6f9af717296d5b0fec5fdd45819df2a56014a63 \ - --hash=sha256:74c302b2099c9906dd4783cd57f546393235902dcc179302a2da280d83e72b96 -certbot-nginx==0.34.2 \ - --hash=sha256:4883f638e703b8fbab0ec15df6d9f0ebbb3cd81e221521b65ca27cdc9e9d070d \ - --hash=sha256:13d58e40097f6b36e323752c146dc90d06120dc69a313e141476e0bc1a74ee17 +certbot==0.35.0 \ + --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ + --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 +acme==0.35.0 \ + --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ + --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 +certbot-apache==0.35.0 \ + --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ + --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 +certbot-nginx==0.35.0 \ + --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ + --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f -- cgit v1.2.3 From f25a9b2004a8085eaca6019042b4b12e1570dd70 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 5 Jun 2019 14:00:54 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95833ad7b..3ad479282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.36.0 - 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. + ## 0.35.0 - 2019-06-05 ### Added -- cgit v1.2.3 From f3b73c4d2a36249054fd39635eb326f514a9e6ab Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 5 Jun 2019 14:00:54 -0700 Subject: Bump version to 0.36.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index b5291862b..9fca57e01 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e991a8825..396f1ccf2 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index dacf43870..362043531 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index b552b017f..bd201aca2 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 9156e7a6c..d8d7aa9b8 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 0c97eadb5..92a9b2b14 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 91745a27d..709ca8330 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index eaf048d33..1d55b0fe4 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 3c5022832..6dac126d0 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 73ba0e795..5c31a81f8 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index d2e84a8c1..b70a6a39c 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 7160c857a..5f5322319 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 3e0f6787c..00ed64032 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index f5cb7782d..bff394bf9 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0d5cb5498..25c6ae1d1 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index efc5c4d7a..b8af58b30 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index c29a20169..b674b7199 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index e7d742b7f..09a1ab8d5 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 0d0c96fb7..32ab75aaa 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.35.0' +__version__ = '0.36.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index abe655083..29938c165 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.35.0" +LE_AUTO_VERSION="0.36.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 89d907b1822993623ba9cc18b5fcfe231f6676d5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Jun 2019 10:57:21 -0700 Subject: Improve Apache error message when run with insufficient privileges (#7129) * fixes #6369 * Add changelog entry. * Improve error message again. --- CHANGELOG.md | 5 +++-- certbot-apache/certbot_apache/configurator.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad479282..be50f44f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* The error message when Certbot's Apache plugin is unable to modify your + Apache configuration has been improved. ### Fixed @@ -20,7 +21,7 @@ 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-apache More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 9174143a4..37f8ba289 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -276,7 +276,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): util.lock_dir_until_exit(self.option("server_root")) except (OSError, errors.LockError): logger.debug("Encountered error:", exc_info=True) - raise errors.PluginError("Unable to lock {0}".format(self.option("server_root"))) + raise errors.PluginError( + "Unable to create a lock file in {0}. Are you running" + " Certbot with sufficient privileges to modify your" + " Apache configuration?".format(self.option("server_root"))) self._prepared = True def _verify_exe_availability(self, exe): -- cgit v1.2.3 From 5c663d4d97270dd0f8cd387df7ca5828fbdb4bb8 Mon Sep 17 00:00:00 2001 From: Rob Stradling Date: Fri, 7 Jun 2019 21:03:35 +0100 Subject: Update the 'manage your account' help to be more generic. (#7127) Resolves #7121. * Update the 'manage your account' help to be more generic. * Add changelog entry about #7127. --- CHANGELOG.md | 1 + certbot/cli.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be50f44f2..50677a94d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* Update the 'manage your account' help to be more generic. * The error message when Certbot's Apache plugin is unable to modify your Apache configuration has been improved. diff --git a/certbot/cli.py b/certbot/cli.py index b7c021ed4..3334352db 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -96,10 +96,10 @@ manage certificates: revoke Revoke a certificate (supply --cert-path or --cert-name) delete Delete a certificate -manage your account with Let's Encrypt: - register Create a Let's Encrypt ACME account - unregister Deactivate a Let's Encrypt ACME account - update_account Update a Let's Encrypt ACME account +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account --agree-tos Agree to the ACME server's Subscriber Agreement -m EMAIL Email address for important account notifications """ -- cgit v1.2.3 From 391f742df7310476f521eb910b0ec290f84a7794 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Jun 2019 13:56:38 -0700 Subject: List Certbot package given #7127. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50677a94d..1e54230cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ 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 * certbot-apache More details about these changes can be found on our GitHub repo. -- cgit v1.2.3 From 4d034122c6026a94d837fdaef9bd4df3472056f7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 7 Jun 2019 14:34:40 -0700 Subject: Ask for updates, the issue isn't stale. (#7133) This PR attempts to improve the behavior our "stale" bot by asking for updates instead of telling people that their issue is stale. --- .github/stale.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index c248fe9d4..6b317a4b5 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -11,15 +11,15 @@ daysUntilClose: 7 exemptAssignees: true # Label to use when marking as stale -staleLabel: stale +staleLabel: needs-update # Comment to post when marking as stale. Set to `false` to disable markComment: > - To help us better see what issues are still affecting our users, this issue - has been automatically marked as stale. If you still have this issue with an - up-to-date version of Certbot and are interested in seeing it resolved, - please add a comment letting us know. If there is no further activity, this - issue will be automatically closed. + We've made a lot of changes to Certbot since this issue was opened. If you + still have this issue with an up-to-date version of Certbot, can you please + add a comment letting us know? This helps us to better see what issues are + still affecting our users. If there is no further activity, this issue will + be automatically closed. # Comment to post when closing a stale Issue or Pull Request. closeComment: > -- cgit v1.2.3 From 23b52ca1c8798285698b2a051a36b3c98d70191b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 10 Jun 2019 13:56:57 -0700 Subject: Fix dns rfc2136 (#7142) * Revert "Add an option to dns_rfc2136 plugin to specify an authorative base domain. (#7029)" This reverts commit 5ab6a597b0e23b604e4d6e339a9d3784abc57217. * Update changelog. --- CHANGELOG.md | 6 ++- .../certbot_dns_rfc2136/__init__.py | 6 +-- .../certbot_dns_rfc2136/dns_rfc2136.py | 50 +++++++--------------- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 29 +------------ 4 files changed, 25 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e54230cc..6c2370397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Support for specifying an authoritative base domain in our dns-rfc2136 plugin + has been removed. This feature was added in our last release but had a bug + which caused the plugin to fail so the feature has been removed until it can + be added properly. 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 @@ -24,6 +27,7 @@ package with changes other than its version number was: * certbot * certbot-apache +* certbot-dns-rfc2136 More details about these changes can be found on our GitHub repo. diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py index cebff2841..12b360959 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py @@ -21,8 +21,8 @@ Credentials ----------- Use of this plugin requires a configuration file containing the target DNS -server, optional authorative domain and optional port that supports RFC 2136 Dynamic Updates, -the name of the TSIG key, the TSIG key secret itself and the algorithm used if it's +server and optional port that supports RFC 2136 Dynamic Updates, the name +of the TSIG key, the TSIG key secret itself and the algorithm used if it's different to HMAC-MD5. .. code-block:: ini @@ -33,8 +33,6 @@ different to HMAC-MD5. dns_rfc2136_server = 192.0.2.1 # Target DNS port dns_rfc2136_port = 53 - # Authorative domain (optional, will try to auto-detect if missing) - dns_rfc2136_base_domain = example.com # TSIG key name dns_rfc2136_name = keyname. # TSIG key secret diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index 5db8c3020..2061374e0 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -79,33 +79,25 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_rfc2136_client().del_txt_record(validation_name, validation) def _get_rfc2136_client(self): - key = _RFC2136Key(self.credentials.conf('name'), - self.credentials.conf('secret'), - self.ALGORITHMS.get(self.credentials.conf('algorithm'), - dns.tsig.HMAC_MD5)) return _RFC2136Client(self.credentials.conf('server'), int(self.credentials.conf('port') or self.PORT), - key, - self.credentials.conf('base-domain')) + self.credentials.conf('name'), + self.credentials.conf('secret'), + self.ALGORITHMS.get(self.credentials.conf('algorithm'), + dns.tsig.HMAC_MD5)) -class _RFC2136Key(object): - def __init__(self, name, secret, algorithm): - self.name = name - self.secret = secret - self.algorithm = algorithm class _RFC2136Client(object): """ Encapsulates all communication with the target DNS server. """ - def __init__(self, server, port, base_domain, key): + def __init__(self, server, port, key_name, key_secret, key_algorithm): self.server = server self.port = port self.keyring = dns.tsigkeyring.from_text({ - key.name: key.secret + key_name: key_secret }) - self.algorithm = key.algorithm - self.base_domain = base_domain + self.algorithm = key_algorithm def add_txt_record(self, record_name, record_content, record_ttl): """ @@ -179,33 +171,23 @@ class _RFC2136Client(object): def _find_domain(self, record_name): """ - If 'base_domain' option is specified check if the requested domain matches this base domain - and return it. If not explicitly specified find the closest domain with an SOA record for - the given domain name. + Find the closest domain with an SOA record for a given domain name. - :param str record_name: The record name for which to find the base domain. + :param str record_name: The record name for which to find the closest SOA record. :returns: The domain, if found. :rtype: str :raises certbot.errors.PluginError: if no SOA record can be found. """ - if self.base_domain: - if not record_name.endswith(self.base_domain): - raise errors.PluginError('Requested domain {0} does not match specified base ' - 'domain {1}.' - .format(record_name, self.base_domain)) - else: - return self.base_domain - else: - domain_name_guesses = dns_common.base_domain_name_guesses(record_name) + domain_name_guesses = dns_common.base_domain_name_guesses(record_name) - # Loop through until we find an authoritative SOA record - for guess in domain_name_guesses: - if self._query_soa(guess): - return guess + # Loop through until we find an authoritative SOA record + for guess in domain_name_guesses: + if self._query_soa(guess): + return guess - raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' - .format(record_name, domain_name_guesses)) + raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' + .format(record_name, domain_name_guesses)) def _query_soa(self, domain_name): """ diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index bed3445b6..d800f1ec7 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -73,12 +73,9 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic class RFC2136ClientTest(unittest.TestCase): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client, _RFC2136Key + from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client - self.rfc2136_client = _RFC2136Client(SERVER, - PORT, - None, - _RFC2136Key(NAME, SECRET, dns.tsig.HMAC_MD5)) + self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) @mock.patch("dns.query.tcp") def test_add_txt_record(self, query_mock): @@ -165,28 +162,6 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._find_domain, 'foo.bar.'+DOMAIN) - def test_find_domain_with_base(self): - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) - self.rfc2136_client.base_domain = 'bar.' + DOMAIN - - # _find_domain | pylint: disable=protected-access - domain = self.rfc2136_client._find_domain('foo.bar.' + DOMAIN) - - self.assertTrue(domain == 'bar.' + DOMAIN) - - def test_find_domain_with_wrong_base(self): - - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) - self.rfc2136_client.base_domain = 'wrong.' + DOMAIN - - self.assertRaises( - errors.PluginError, - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain, - 'foo.bar.' + DOMAIN) - @mock.patch("dns.query.udp") def test_query_soa_found(self, query_mock): query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) -- cgit v1.2.3 From c3edc25fb71cf5255500a850a515bf44d6ca66ef Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 10 Jun 2019 14:12:59 -0700 Subject: Fix dns rfc2136 (#7142) (#7143) * Revert "Add an option to dns_rfc2136 plugin to specify an authorative base domain. (#7029)" This reverts commit 5ab6a597b0e23b604e4d6e339a9d3784abc57217. * Update changelog. (cherry picked from commit 23b52ca1c8798285698b2a051a36b3c98d70191b) --- CHANGELOG.md | 17 ++++++++ .../certbot_dns_rfc2136/__init__.py | 6 +-- .../certbot_dns_rfc2136/dns_rfc2136.py | 50 +++++++--------------- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 29 +------------ 4 files changed, 37 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95833ad7b..4daa6f928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.35.1 - master + +### Fixed + +* Support for specifying an authoritative base domain in our dns-rfc2136 plugin + has been removed. This feature was added in our last release but had a bug + which caused the plugin to fail so the feature has been removed until it can + be added properly. + +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-dns-rfc2136 + +More details about these changes can be found on our GitHub repo. + ## 0.35.0 - 2019-06-05 ### Added diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py index cebff2841..12b360959 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py @@ -21,8 +21,8 @@ Credentials ----------- Use of this plugin requires a configuration file containing the target DNS -server, optional authorative domain and optional port that supports RFC 2136 Dynamic Updates, -the name of the TSIG key, the TSIG key secret itself and the algorithm used if it's +server and optional port that supports RFC 2136 Dynamic Updates, the name +of the TSIG key, the TSIG key secret itself and the algorithm used if it's different to HMAC-MD5. .. code-block:: ini @@ -33,8 +33,6 @@ different to HMAC-MD5. dns_rfc2136_server = 192.0.2.1 # Target DNS port dns_rfc2136_port = 53 - # Authorative domain (optional, will try to auto-detect if missing) - dns_rfc2136_base_domain = example.com # TSIG key name dns_rfc2136_name = keyname. # TSIG key secret diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index 5db8c3020..2061374e0 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -79,33 +79,25 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_rfc2136_client().del_txt_record(validation_name, validation) def _get_rfc2136_client(self): - key = _RFC2136Key(self.credentials.conf('name'), - self.credentials.conf('secret'), - self.ALGORITHMS.get(self.credentials.conf('algorithm'), - dns.tsig.HMAC_MD5)) return _RFC2136Client(self.credentials.conf('server'), int(self.credentials.conf('port') or self.PORT), - key, - self.credentials.conf('base-domain')) + self.credentials.conf('name'), + self.credentials.conf('secret'), + self.ALGORITHMS.get(self.credentials.conf('algorithm'), + dns.tsig.HMAC_MD5)) -class _RFC2136Key(object): - def __init__(self, name, secret, algorithm): - self.name = name - self.secret = secret - self.algorithm = algorithm class _RFC2136Client(object): """ Encapsulates all communication with the target DNS server. """ - def __init__(self, server, port, base_domain, key): + def __init__(self, server, port, key_name, key_secret, key_algorithm): self.server = server self.port = port self.keyring = dns.tsigkeyring.from_text({ - key.name: key.secret + key_name: key_secret }) - self.algorithm = key.algorithm - self.base_domain = base_domain + self.algorithm = key_algorithm def add_txt_record(self, record_name, record_content, record_ttl): """ @@ -179,33 +171,23 @@ class _RFC2136Client(object): def _find_domain(self, record_name): """ - If 'base_domain' option is specified check if the requested domain matches this base domain - and return it. If not explicitly specified find the closest domain with an SOA record for - the given domain name. + Find the closest domain with an SOA record for a given domain name. - :param str record_name: The record name for which to find the base domain. + :param str record_name: The record name for which to find the closest SOA record. :returns: The domain, if found. :rtype: str :raises certbot.errors.PluginError: if no SOA record can be found. """ - if self.base_domain: - if not record_name.endswith(self.base_domain): - raise errors.PluginError('Requested domain {0} does not match specified base ' - 'domain {1}.' - .format(record_name, self.base_domain)) - else: - return self.base_domain - else: - domain_name_guesses = dns_common.base_domain_name_guesses(record_name) + domain_name_guesses = dns_common.base_domain_name_guesses(record_name) - # Loop through until we find an authoritative SOA record - for guess in domain_name_guesses: - if self._query_soa(guess): - return guess + # Loop through until we find an authoritative SOA record + for guess in domain_name_guesses: + if self._query_soa(guess): + return guess - raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' - .format(record_name, domain_name_guesses)) + raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' + .format(record_name, domain_name_guesses)) def _query_soa(self, domain_name): """ diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index bed3445b6..d800f1ec7 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -73,12 +73,9 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic class RFC2136ClientTest(unittest.TestCase): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client, _RFC2136Key + from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client - self.rfc2136_client = _RFC2136Client(SERVER, - PORT, - None, - _RFC2136Key(NAME, SECRET, dns.tsig.HMAC_MD5)) + self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) @mock.patch("dns.query.tcp") def test_add_txt_record(self, query_mock): @@ -165,28 +162,6 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._find_domain, 'foo.bar.'+DOMAIN) - def test_find_domain_with_base(self): - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) - self.rfc2136_client.base_domain = 'bar.' + DOMAIN - - # _find_domain | pylint: disable=protected-access - domain = self.rfc2136_client._find_domain('foo.bar.' + DOMAIN) - - self.assertTrue(domain == 'bar.' + DOMAIN) - - def test_find_domain_with_wrong_base(self): - - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) - self.rfc2136_client.base_domain = 'wrong.' + DOMAIN - - self.assertRaises( - errors.PluginError, - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain, - 'foo.bar.' + DOMAIN) - @mock.patch("dns.query.udp") def test_query_soa_found(self, query_mock): query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) -- cgit v1.2.3 From 6334d065cf2b5e34af65edffa1f9daf0a66e5cf8 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 10 Jun 2019 15:02:09 -0700 Subject: Update changelog for 0.35.1 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4daa6f928..6b664f8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.35.1 - master +## 0.35.1 - 2019-06-10 ### Fixed -- cgit v1.2.3 From 0cc56677e28fd29f82644037e35dd747e4278fff Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 10 Jun 2019 15:25:09 -0700 Subject: Release 0.35.1 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 26 ++++++++++----------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------- letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++++---------- 26 files changed, 79 insertions(+), 79 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index b5291862b..160010504 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.35.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e991a8825..e6317eb73 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index abe655083..756b8e247 100755 --- a/certbot-auto +++ b/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.35.0" +LE_AUTO_VERSION="0.35.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.0 \ - --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ - --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 -acme==0.35.0 \ - --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ - --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 -certbot-apache==0.35.0 \ - --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ - --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 -certbot-nginx==0.35.0 \ - --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ - --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f +certbot==0.35.1 \ + --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ + --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 +acme==0.35.1 \ + --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ + --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 +certbot-apache==0.35.1 \ + --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ + --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 +certbot-nginx==0.35.1 \ + --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ + --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index dacf43870..2300b6208 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index b552b017f..9de84f2df 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 9156e7a6c..3c0df32a4 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 0c97eadb5..c597ff2cd 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 91745a27d..fec720b07 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index eaf048d33..0e3c160c7 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 3c5022832..a3f8be72a 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 73ba0e795..6f9c50825 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index d2e84a8c1..2a55aa6e9 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 7160c857a..8e1231d1d 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 3e0f6787c..c63dfa70f 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index f5cb7782d..288772e3e 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0d5cb5498..cc406aa1a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index efc5c4d7a..b7260362c 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index c29a20169..689240772 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.0' +version = '0.35.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index e7d742b7f..2f108ee5f 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.0' +version = '0.35.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 0d0c96fb7..18803420f 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.35.0' +__version__ = '0.35.1' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 03338dbea..3499acf19 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.35.0 + "". (default: CertbotACMEClient/0.35.1 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index abe655083..756b8e247 100755 --- a/letsencrypt-auto +++ b/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.35.0" +LE_AUTO_VERSION="0.35.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.0 \ - --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ - --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 -acme==0.35.0 \ - --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ - --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 -certbot-apache==0.35.0 \ - --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ - --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 -certbot-nginx==0.35.0 \ - --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ - --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f +certbot==0.35.1 \ + --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ + --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 +acme==0.35.1 \ + --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ + --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 +certbot-apache==0.35.1 \ + --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ + --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 +certbot-nginx==0.35.1 \ + --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ + --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index fe8053e4f..0abdfdfdb 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlz4LVUACgkQTRfJlc2X -dfL3rQf/UUZa51owdNuiGl7EbbyGyQFMAzq4pYDofsSlFWYPYQSMtcxuBa/w3Cxp -TzFMpWs2tr4y+axFZwNleVDdD5bw/CWLQ+aScvRYblYFPOucwz9/GMEjpy1056Gh -4hZ3FyLYPtD9oP9YR5VMPy109Djv4DX5F6de3JgC2Wv3FbvtZkkZfforCHxEnbjU -6dHDT1qxEzqqEyFHjtlRi1hGymeMbu56+ZqhzkkZJVO0KS1+y7M8DxuKa7kbTP43 -337ASSFZ/eL/A9HGJNoxWLX0+VazNJomHyG9zGmscj1lK1/S/HoIREzdZV84tUME -0iroJw2aax7jpgrlTm6z2zt/vPpemw== -=rvAB +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlz+2KgACgkQTRfJlc2X +dfKLvwf/XUnWRyKldk/d8Egx514mpjzV38grCcZTZrY0O/Rd3YMv5KtrxnTnmvxJ +zAkTfNIo7Y1894mZ6XUIF3D7BiPoRqLj6F8tYLV6jbdsPJKC75dQY/6rcttTSJab +Zbqcw+WTXYNZ72AlHw0uTaxNT+S31KvrJ0pNmuj2ezKzZcDfgcxeeqmI1pJYozbQ ++AfqJMFgP9qHtfZVnmRO5UFBW2qPM522E02wWCtkaQSybI9ikRTvJOtilQgsLFJK +vBdD/MgGHm/xQC6lxG6l/SD4pebvNBaIPCiPAo2XC8ML+4tpjuJ5lPoK/aFCvfYQ +wSMHbDbse/Ndw+ssjmriiBreKB37Mg== +=flRo -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index abe655083..756b8e247 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.35.0" +LE_AUTO_VERSION="0.35.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.0 \ - --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ - --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 -acme==0.35.0 \ - --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ - --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 -certbot-apache==0.35.0 \ - --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ - --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 -certbot-nginx==0.35.0 \ - --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ - --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f +certbot==0.35.1 \ + --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ + --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 +acme==0.35.1 \ + --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ + --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 +certbot-apache==0.35.1 \ + --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ + --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 +certbot-nginx==0.35.1 \ + --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ + --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 987dd3a91..d3824cf47 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 8aab11cef..71c041934 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.35.0 \ - --hash=sha256:6e02460eefdb37094b8ef283e76257a3557268dde519a6dbf6bf8954669cd754 \ - --hash=sha256:33bcdee9fd97a244545f0c1c6480a9889f0382d915927cdfd707c050e78544d9 -acme==0.35.0 \ - --hash=sha256:7b5483f635a994b2220d5cf49d313027dfd2fcd8ce90f94e92c6ec768c08852c \ - --hash=sha256:ddcc530bd8d8434687188115c5b7c6219717d50a4658e58fce24561134463880 -certbot-apache==0.35.0 \ - --hash=sha256:55a5c8c3785cc17d71d6a2cfc406e8bcea631a46f9bce52cce07ea041024d586 \ - --hash=sha256:a92cc1e5854cc7e7a386b3e7616c2787e3867ef604d76cad7ba922366f4a26d7 -certbot-nginx==0.35.0 \ - --hash=sha256:74af57b071c2c62e1066cc367f106f457d34d956be4ad14528f3fd133ce25d79 \ - --hash=sha256:435b37794204dbb02b4b346ea65758bdd6dfb58581bebef43b82911371e7a41f +certbot==0.35.1 \ + --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ + --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 +acme==0.35.1 \ + --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ + --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 +certbot-apache==0.35.1 \ + --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ + --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 +certbot-nginx==0.35.1 \ + --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ + --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 -- cgit v1.2.3 From f18143b11776d8821db305e6391ff982ba4ce870 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 10 Jun 2019 15:25:15 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b664f8cb..a07efae75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.36.0 - 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. + ## 0.35.1 - 2019-06-10 ### Fixed -- cgit v1.2.3 From 3bceae4a892a9c42a1a142502e45906dc708a387 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Mon, 10 Jun 2019 15:25:16 -0700 Subject: Bump version to 0.36.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 160010504..9fca57e01 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.35.1' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e6317eb73..396f1ccf2 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 2300b6208..362043531 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 9de84f2df..bd201aca2 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 3c0df32a4..d8d7aa9b8 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index c597ff2cd..92a9b2b14 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index fec720b07..709ca8330 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 0e3c160c7..1d55b0fe4 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index a3f8be72a..6dac126d0 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 6f9c50825..5c31a81f8 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 2a55aa6e9..b70a6a39c 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 8e1231d1d..5f5322319 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index c63dfa70f..00ed64032 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 288772e3e..bff394bf9 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index cc406aa1a..25c6ae1d1 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index b7260362c..b8af58b30 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 689240772..b674b7199 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.35.1' +version = '0.36.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 2f108ee5f..09a1ab8d5 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.35.1' +version = '0.36.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 18803420f..32ab75aaa 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.35.1' +__version__ = '0.36.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 756b8e247..24e9b295e 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.35.1" +LE_AUTO_VERSION="0.36.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 5385375571d06c0c6a1c2c07b46df03496d90e47 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 11 Jun 2019 14:02:54 -0700 Subject: Remove list of modified packages from changelog. (#7146) --- CHANGELOG.md | 7 ------- pull_request_template.md | 5 ++--- tools/_changelog_top.txt | 6 ------ 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f01dbfcda..9919910f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,13 +18,6 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * -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 -* certbot-apache - More details about these changes can be found on our GitHub repo. ## 0.35.1 - 2019-06-10 diff --git a/pull_request_template.md b/pull_request_template.md index 60fd6da7e..48d408ac3 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,3 +1,2 @@ -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. +Be sure to edit the `master` section of `CHANGELOG.md` to include a description +of the change being made in this PR. diff --git a/tools/_changelog_top.txt b/tools/_changelog_top.txt index 6983b3a43..a0f9b6553 100644 --- a/tools/_changelog_top.txt +++ b/tools/_changelog_top.txt @@ -12,10 +12,4 @@ * -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. -- cgit v1.2.3 From 0c5f526f8bd69de37ee7b6de05f45040394cd6f2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 11 Jun 2019 14:41:25 -0700 Subject: Remove the Postfix plugin (#7097) * Remove the postfix plugin. * Remove references to postfix plugin in code. * Remove reference to postfix plugin in docs. --- .travis.yml | 2 +- certbot-postfix/LICENSE.txt | 190 ------------- certbot-postfix/MANIFEST.in | 4 - certbot-postfix/README.rst | 23 -- certbot-postfix/certbot_postfix/__init__.py | 3 - certbot-postfix/certbot_postfix/constants.py | 63 ---- certbot-postfix/certbot_postfix/installer.py | 285 ------------------- certbot-postfix/certbot_postfix/postconf.py | 152 ---------- certbot-postfix/certbot_postfix/tests/__init__.py | 1 - .../certbot_postfix/tests/installer_test.py | 316 --------------------- .../certbot_postfix/tests/postconf_test.py | 109 ------- certbot-postfix/certbot_postfix/tests/util_test.py | 205 ------------- certbot-postfix/certbot_postfix/util.py | 292 ------------------- certbot-postfix/docs/.gitignore | 1 - certbot-postfix/docs/Makefile | 20 -- certbot-postfix/docs/api.rst | 8 - certbot-postfix/docs/api/installer.rst | 5 - certbot-postfix/docs/api/postconf.rst | 5 - certbot-postfix/docs/conf.py | 190 ------------- certbot-postfix/docs/index.rst | 28 -- certbot-postfix/docs/make.bat | 36 --- certbot-postfix/local-oldest-requirements.txt | 3 - certbot-postfix/setup.cfg | 2 - certbot-postfix/setup.py | 64 ----- certbot/plugins/disco.py | 1 - docs/using.rst | 2 - tools/_venv_common.py | 1 - tools/install_and_test.py | 3 +- tox.cover.py | 6 +- tox.ini | 8 - 30 files changed, 4 insertions(+), 2024 deletions(-) delete mode 100644 certbot-postfix/LICENSE.txt delete mode 100644 certbot-postfix/MANIFEST.in delete mode 100644 certbot-postfix/README.rst delete mode 100644 certbot-postfix/certbot_postfix/__init__.py delete mode 100644 certbot-postfix/certbot_postfix/constants.py delete mode 100644 certbot-postfix/certbot_postfix/installer.py delete mode 100644 certbot-postfix/certbot_postfix/postconf.py delete mode 100644 certbot-postfix/certbot_postfix/tests/__init__.py delete mode 100644 certbot-postfix/certbot_postfix/tests/installer_test.py delete mode 100644 certbot-postfix/certbot_postfix/tests/postconf_test.py delete mode 100644 certbot-postfix/certbot_postfix/tests/util_test.py delete mode 100644 certbot-postfix/certbot_postfix/util.py delete mode 100644 certbot-postfix/docs/.gitignore delete mode 100644 certbot-postfix/docs/Makefile delete mode 100644 certbot-postfix/docs/api.rst delete mode 100644 certbot-postfix/docs/api/installer.rst delete mode 100644 certbot-postfix/docs/api/postconf.rst delete mode 100644 certbot-postfix/docs/conf.py delete mode 100644 certbot-postfix/docs/index.rst delete mode 100644 certbot-postfix/docs/make.bat delete mode 100644 certbot-postfix/local-oldest-requirements.txt delete mode 100644 certbot-postfix/setup.cfg delete mode 100644 certbot-postfix/setup.py diff --git a/.travis.yml b/.travis.yml index 1eee09898..13ee9cc88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ matrix: env: TOXENV=mypy <<: *not-on-master - python: "2.7" - env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest' + env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' sudo: required services: docker <<: *not-on-master diff --git a/certbot-postfix/LICENSE.txt b/certbot-postfix/LICENSE.txt deleted file mode 100644 index c8314fd1c..000000000 --- a/certbot-postfix/LICENSE.txt +++ /dev/null @@ -1,190 +0,0 @@ - Copyright 2017 Electronic Frontier Foundation and others - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/certbot-postfix/MANIFEST.in b/certbot-postfix/MANIFEST.in deleted file mode 100644 index 273381403..000000000 --- a/certbot-postfix/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE.txt -include README.rst -recursive-include certbot_postfix/testdata * -recursive-include certbot_postfix/docs * diff --git a/certbot-postfix/README.rst b/certbot-postfix/README.rst deleted file mode 100644 index 1ae9cb980..000000000 --- a/certbot-postfix/README.rst +++ /dev/null @@ -1,23 +0,0 @@ -========================== -Postfix plugin for Certbot -========================== - -Note: this MTA installer is in **developer beta**-- we appreciate any testing, feedback, or -feature requests for this plugin. - -To install this plugin, in the root of this repo, run:: - - python tools/venv.py - source venv/bin/activate - -You can use this installer with any `authenticator plugin -`_. -For instance, with the `standalone authenticator -`_, which requires no extra server -software, you might run:: - - sudo ./venv/bin/certbot run --standalone -i postfix -d - -To just install existing certs with this plugin, run:: - - sudo ./venv/bin/certbot install -i postfix --cert-path --key-path -d diff --git a/certbot-postfix/certbot_postfix/__init__.py b/certbot-postfix/certbot_postfix/__init__.py deleted file mode 100644 index 122c54bc6..000000000 --- a/certbot-postfix/certbot_postfix/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Certbot Postfix plugin.""" - -from certbot_postfix.installer import Installer diff --git a/certbot-postfix/certbot_postfix/constants.py b/certbot-postfix/certbot_postfix/constants.py deleted file mode 100644 index 40a263a53..000000000 --- a/certbot-postfix/certbot_postfix/constants.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Postfix plugin constants.""" - -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Tuple, Union -# pylint: enable=unused-import, no-name-in-module - -MINIMUM_VERSION = (2, 11,) - -# If the value of a default VAR is a tuple, then the values which -# come LATER in the tuple are more strict/more secure. -# Certbot will default to the first value in the tuple, but will -# not override "more secure" settings. - -ACCEPTABLE_SERVER_SECURITY_LEVELS = ("may", "encrypt") -ACCEPTABLE_CLIENT_SECURITY_LEVELS = ("may", "encrypt", - "dane", "dane-only", - "fingerprint", - "verify", "secure") -ACCEPTABLE_CIPHER_LEVELS = ("medium", "high") - -# Exporting certain ciphers to prevent logjam: https://weakdh.org/sysadmin.html -EXCLUDE_CIPHERS = ("aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, " - "EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA",) - - -TLS_VERSIONS = ("SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2") -# Should NOT use SSLv2/3. -ACCEPTABLE_TLS_VERSIONS = ("TLSv1", "TLSv1.1", "TLSv1.2") - -# Variables associated with enabling opportunistic TLS. -TLS_SERVER_VARS = { - "smtpd_tls_security_level": ACCEPTABLE_SERVER_SECURITY_LEVELS, -} # type:Dict[str, Tuple[str, ...]] -TLS_CLIENT_VARS = { - "smtp_tls_security_level": ACCEPTABLE_CLIENT_SECURITY_LEVELS, -} # type:Dict[str, Tuple[str, ...]] -# Default variables for a secure MTA server [receiver]. -DEFAULT_SERVER_VARS = { - "smtpd_tls_auth_only": ("yes",), - "smtpd_tls_mandatory_protocols": ("!SSLv2, !SSLv3",), - "smtpd_tls_protocols": ("!SSLv2, !SSLv3",), - "smtpd_tls_ciphers": ACCEPTABLE_CIPHER_LEVELS, - "smtpd_tls_mandatory_ciphers": ACCEPTABLE_CIPHER_LEVELS, - "smtpd_tls_exclude_ciphers": EXCLUDE_CIPHERS, - "smtpd_tls_eecdh_grade": ("strong",), -} # type:Dict[str, Tuple[str, ...]] - -# Default variables for a secure MTA client [sender]. -DEFAULT_CLIENT_VARS = { - "smtp_tls_ciphers": ACCEPTABLE_CIPHER_LEVELS, - "smtp_tls_exclude_ciphers": EXCLUDE_CIPHERS, - "smtp_tls_mandatory_ciphers": ACCEPTABLE_CIPHER_LEVELS, -} # type:Dict[str, Tuple[str, ...]] - -CLI_DEFAULTS = dict( - config_dir="/etc/postfix", - ctl="postfix", - config_utility="postconf", - tls_only=False, - ignore_master_overrides=False, - server_only=False, -) -"""CLI defaults.""" diff --git a/certbot-postfix/certbot_postfix/installer.py b/certbot-postfix/certbot_postfix/installer.py deleted file mode 100644 index b5e4df5ec..000000000 --- a/certbot-postfix/certbot_postfix/installer.py +++ /dev/null @@ -1,285 +0,0 @@ -"""certbot installer plugin for postfix.""" -import logging - -import six -import zope.component -import zope.interface - -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Dict, List -# pylint: enable=unused-import, no-name-in-module - -from certbot import errors -from certbot import interfaces -from certbot import util as certbot_util -from certbot.compat import os -from certbot.plugins import common as plugins_common - -from certbot_postfix import constants -from certbot_postfix import postconf -from certbot_postfix import util - -logger = logging.getLogger(__name__) - -@zope.interface.implementer(interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class Installer(plugins_common.Installer): - """Certbot installer plugin for Postfix. - - :ivar str config_dir: Postfix configuration directory to modify - :ivar list save_notes: documentation for proposed changes. This is - cleared and stored in Certbot checkpoints when save() is called - - :ivar postconf: Wrapper for Postfix configuration command-line tool. - :type postconf: :class: `certbot_postfix.postconf.ConfigMain` - :ivar postfix: Wrapper for Postfix command-line tool. - :type postfix: :class: `certbot_postfix.util.PostfixUtil` - """ - - description = "Configure TLS with the Postfix MTA" - - @classmethod - def add_parser_arguments(cls, add): - add("ctl", default=constants.CLI_DEFAULTS["ctl"], - help="Path to the 'postfix' control program.") - # This directory points to Postfix's configuration directory. - add("config-dir", default=constants.CLI_DEFAULTS["config_dir"], - help="Path to the directory containing the " - "Postfix main.cf file to modify instead of using the " - "default configuration paths.") - add("config-utility", default=constants.CLI_DEFAULTS["config_utility"], - help="Path to the 'postconf' executable.") - add("tls-only", action="store_true", default=constants.CLI_DEFAULTS["tls_only"], - help="Only set params to enable opportunistic TLS and install certificates.") - add("server-only", action="store_true", default=constants.CLI_DEFAULTS["server_only"], - help="Only set server params (prefixed with smtpd*)") - add("ignore-master-overrides", action="store_true", - default=constants.CLI_DEFAULTS["ignore_master_overrides"], - help="Ignore errors reporting overridden TLS parameters in master.cf.") - - def __init__(self, *args, **kwargs): - super(Installer, self).__init__(*args, **kwargs) - # Wrapper around postconf commands - self.postfix = None - self.postconf = None - - # Files to save - self.save_notes = [] # type: List[str] - - self._enhance_func = {} # type: Dict[str, Callable[[str, str], None]] - # Since we only need to enable TLS once for all domains, - # keep track of whether this enhancement was already called. - self._tls_enabled = False - - def prepare(self): - """Prepare the installer. - - :raises errors.PluginError: when an unexpected error occurs - :raises errors.MisconfigurationError: when the config is invalid - :raises errors.NoInstallationError: when can't find installation - :raises errors.NotSupportedError: when version is not supported - """ - # Verify postfix and postconf are installed - for param in ("ctl", "config_utility",): - util.verify_exe_exists(self.conf(param), - "Cannot find executable '{0}'. You can provide the " - "path to this command with --{1}".format( - self.conf(param), - self.option_name(param))) - - # Set up CLI tools - self.postfix = util.PostfixUtil(self.conf('config-dir')) - self.postconf = postconf.ConfigMain(self.conf('config-utility'), - self.conf('ignore-master-overrides'), - self.conf('config-dir')) - - # Ensure current configuration is valid. - self.config_test() - - # Check Postfix version - self._check_version() - self._lock_config_dir() - self.install_ssl_dhparams() - - def config_test(self): - """Test to see that the current Postfix configuration is valid. - - :raises errors.MisconfigurationError: If the configuration is invalid. - """ - self.postfix.test() - - def _check_version(self): - """Verifies that the installed Postfix version is supported. - - :raises errors.NotSupportedError: if the version is unsupported - """ - if self._get_version() < constants.MINIMUM_VERSION: - version_string = '.'.join([str(n) for n in constants.MINIMUM_VERSION]) - raise errors.NotSupportedError('Postfix version must be at least %s' % version_string) - - def _lock_config_dir(self): - """Stop two Postfix plugins from modifying the config at once. - - :raises .PluginError: if unable to acquire the lock - """ - try: - certbot_util.lock_dir_until_exit(self.conf('config-dir')) - except (OSError, errors.LockError): - logger.debug("Encountered error:", exc_info=True) - raise errors.PluginError( - "Unable to lock %s" % self.conf('config-dir')) - - def more_info(self): - """Human-readable string to help the user. Describes steps taken and any relevant - info to help the user decide which plugin to use. - - :rtype: str - """ - return ( - "Configures Postfix to try to authenticate mail servers, use " - "installed certificates and disable weak ciphers and protocols.{0}" - "Server root: {root}{0}" - "Version: {version}".format( - os.linesep, - root=self.conf('config-dir'), - version='.'.join([str(i) for i in self._get_version()])) - ) - - def _get_version(self): - """Return the version of Postfix, as a tuple. (e.g. '2.11.3' is (2, 11, 3)) - - :returns: version - :rtype: tuple - - :raises errors.PluginError: Unable to find Postfix version. - """ - mail_version = self.postconf.get_default("mail_version") - return tuple(int(i) for i in mail_version.split('.')) - - def get_all_names(self): - """Returns all names that may be authenticated. - - :rtype: `set` of `str` - - """ - return certbot_util.get_filtered_names(self.postconf.get(var) - for var in ('mydomain', 'myhostname', 'myorigin',)) - - def _set_vars(self, var_dict): - """Sets all parameters in var_dict to config file. If current value is already set - as more secure (acceptable), then don't set/overwrite it. - """ - for param, acceptable in six.iteritems(var_dict): - if not util.is_acceptable_value(param, self.postconf.get(param), acceptable): - self.postconf.set(param, acceptable[0], acceptable) - - def _confirm_changes(self): - """Confirming outstanding updates for configuration parameters. - - :raises errors.PluginError: when user rejects the configuration changes. - """ - updates = self.postconf.get_changes() - output_string = "Postfix TLS configuration parameters to update in main.cf:\n" - for name, value in six.iteritems(updates): - output_string += "{0} = {1}\n".format(name, value) - output_string += "Is this okay?\n" - if not zope.component.getUtility(interfaces.IDisplay).yesno(output_string, - force_interactive=True, default=True): - raise errors.PluginError( - "Manually rejected configuration changes.\n" - "Try using --tls-only or --server-only to change a particular" - "subset of configuration parameters.") - - def deploy_cert(self, domain, cert_path, - key_path, chain_path, fullchain_path): # pylint: disable=unused-argument - """Configure the Postfix SMTP server to use the given TLS cert. - - :param str domain: domain to deploy certificate file - :param str cert_path: absolute path to the certificate file - :param str key_path: absolute path to the private key file - :param str chain_path: absolute path to the certificate chain file - :param str fullchain_path: absolute path to the certificate fullchain - file (cert plus chain) - - :raises .PluginError: when cert cannot be deployed - - """ - if self._tls_enabled: - return - self._tls_enabled = True - self.save_notes.append("Configuring TLS for {0}".format(domain)) - self.postconf.set("smtpd_tls_cert_file", cert_path) - self.postconf.set("smtpd_tls_key_file", key_path) - self._set_vars(constants.TLS_SERVER_VARS) - if not self.conf('server_only'): - self._set_vars(constants.TLS_CLIENT_VARS) - if not self.conf('tls_only'): - self._set_vars(constants.DEFAULT_SERVER_VARS) - if not self.conf('server_only'): - self._set_vars(constants.DEFAULT_CLIENT_VARS) - # Despite the name, this option also supports 2048-bit DH params. - # http://www.postfix.org/FORWARD_SECRECY_README.html#server_fs - self.postconf.set("smtpd_tls_dh1024_param_file", self.ssl_dhparams) - self._confirm_changes() - - def enhance(self, domain, enhancement, options=None): # pylint: disable=unused-argument - """Raises an exception since this installer doesn't support any enhancements. - """ - raise errors.PluginError( - "Unsupported enhancement: {0}".format(enhancement)) - - def supported_enhancements(self): - """Returns a list of supported enhancements. - - :rtype: list - - """ - return [] - - def save(self, title=None, temporary=False): - """Creates backups and writes changes to configuration files. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. `title` has no effect if temporary is true. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (challenges) - - :raises errors.PluginError: when save is unsuccessful - """ - save_files = set((os.path.join(self.conf('config-dir'), "main.cf"),)) - self.add_to_checkpoint(save_files, - "\n".join(self.save_notes), temporary) - self.postconf.flush() - - del self.save_notes[:] - - if title and not temporary: - self.finalize_checkpoint(title) - - def recovery_routine(self): - super(Installer, self).recovery_routine() - self.postconf = postconf.ConfigMain(self.conf('config-utility'), - self.conf('ignore-master-overrides'), - self.conf('config-dir')) - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - """ - super(Installer, self).rollback_checkpoints(rollback) - self.postconf = postconf.ConfigMain(self.conf('config-utility'), - self.conf('ignore-master-overrides'), - self.conf('config-dir')) - - def restart(self): - """Restart or refresh the server content. - - :raises .PluginError: when server cannot be restarted - """ - self.postfix.restart() diff --git a/certbot-postfix/certbot_postfix/postconf.py b/certbot-postfix/certbot_postfix/postconf.py deleted file mode 100644 index efa208597..000000000 --- a/certbot-postfix/certbot_postfix/postconf.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Classes that wrap the postconf command line utility. -""" -import six -from certbot import errors -from certbot_postfix import util - -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Tuple -# pylint: enable=unused-import, no-name-in-module - - -class ConfigMain(util.PostfixUtilBase): - """A parser for Postfix's main.cf file.""" - - def __init__(self, executable, ignore_master_overrides=False, config_dir=None): - super(ConfigMain, self).__init__(executable, config_dir) - # Whether to ignore overrides from master. - self._ignore_master_overrides = ignore_master_overrides - # List of all current Postfix parameters, from `postconf` command. - self._db = {} # type: Dict[str, str] - # List of current master.cf overrides from Postfix config. Dictionary - # of parameter name => list of tuples (service name, paramter value) - # Note: We should never modify master without explicit permission. - self._master_db = {} # type: Dict[str, List[Tuple[str, str]]] - # List of all changes requested to the Postfix parameters as they are now - # in _db. These changes are flushed to `postconf` on `flush`. - self._updated = {} # type: Dict[str, str] - self._read_from_conf() - - def _read_from_conf(self): - """Reads initial parameter state from `main.cf` into this object. - """ - out = self._get_output() - for name, value in _parse_main_output(out): - self._db[name] = value - out = self._get_output_master() - for name, value in _parse_main_output(out): - service, param_name = name.rsplit("/", 1) - if param_name not in self._master_db: - self._master_db[param_name] = [] - self._master_db[param_name].append((service, value)) - - def _get_output_master(self): - """Retrieves output for `master.cf` parameters.""" - return self._get_output('-P') - - def get_default(self, name): - """Retrieves default value of parameter `name` from postfix parameters. - - :param str name: The name of the parameter to fetch. - :returns: The default value of parameter `name`. - :rtype: str - """ - out = self._get_output(['-d', name]) - _, value = next(_parse_main_output(out), (None, None)) - return value - - def get(self, name): - """Retrieves working value of parameter `name` from postfix parameters. - - :param str name: The name of the parameter to fetch. - :returns: The value of parameter `name`. - :rtype: str - """ - if name in self._updated: - return self._updated[name] - return self._db[name] - - def get_master_overrides(self, name): - """Retrieves list of overrides for parameter `name` in postfix's Master config - file. - - :returns: List of tuples (service, value), meaning that parameter `name` - is overridden as `value` for `service`. - :rtype: `list` of `tuple` of `str` - """ - if name in self._master_db: - return self._master_db[name] - return None - - def set(self, name, value, acceptable_overrides=None): - """Sets parameter `name` to `value`. If `name` is overridden by a particular service in - `master.cf`, reports any of these parameter conflicts as long as - `ignore_master_overrides` was not set. - - .. note:: that this function does not flush these parameter values to main.cf; - To do that, use `flush`. - - :param str name: The name of the parameter to set. - :param str value: The value of the parameter. - :param tuple acceptable_overrides: If the master configuration file overrides `value` - with a value in acceptable_overrides. - """ - if name not in self._db: - raise KeyError("Parameter name {0} is not a valid Postfix parameter name.".format(name)) - # Check to see if this parameter is overridden by master. - overrides = self.get_master_overrides(name) - if not self._ignore_master_overrides and overrides is not None: - util.report_master_overrides(name, overrides, acceptable_overrides) - if value != self._db[name]: - # _db contains the "original" state of parameters. We only care about - # writes if they cause a delta from the original state. - self._updated[name] = value - elif name in self._updated: - # If this write reverts a previously updated parameter back to the - # original DB's state, we don't have to keep track of it in _updated. - del self._updated[name] - - def flush(self): - """Flushes all parameter changes made using `self.set`, to `main.cf` - - :raises error.PluginError: When flush to main.cf fails for some reason. - """ - if not self._updated: - return - args = ['-e'] - for name, value in six.iteritems(self._updated): - args.append('{0}={1}'.format(name, value)) - try: - self._get_output(args) - except IOError as e: - raise errors.PluginError("Unable to save to Postfix config: {0}".format(e)) - for name, value in six.iteritems(self._updated): - self._db[name] = value - self._updated = {} - - def get_changes(self): - """ Return queued changes to main.cf. - - :rtype: dict[str, str] - """ - return self._updated - - -def _parse_main_output(output): - """Parses the raw output from Postconf about main.cf. - - Expects the output to look like: - - .. code-block:: none - - name1 = value1 - name2 = value2 - - :param str output: data postconf wrote to stdout about main.cf - - :returns: generator providing key-value pairs from main.cf - :rtype: Iterator[tuple(str, str)] - """ - for line in output.splitlines(): - name, _, value = line.partition(" =") - yield name, value.strip() diff --git a/certbot-postfix/certbot_postfix/tests/__init__.py b/certbot-postfix/certbot_postfix/tests/__init__.py deleted file mode 100644 index 7316b5888..000000000 --- a/certbot-postfix/certbot_postfix/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" Certbot Postfix Tests """ diff --git a/certbot-postfix/certbot_postfix/tests/installer_test.py b/certbot-postfix/certbot_postfix/tests/installer_test.py deleted file mode 100644 index a24643379..000000000 --- a/certbot-postfix/certbot_postfix/tests/installer_test.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Tests for certbot_postfix.installer.""" -import copy -import functools -import unittest -from contextlib import contextmanager - -import mock -import pkg_resources -import six -from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import,no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.tests import util as certbot_test_util - - -DEFAULT_MAIN_CF = { - "smtpd_tls_cert_file": "", - "smtpd_tls_key_file": "", - "smtpd_tls_dh1024_param_file": "", - "smtpd_tls_security_level": "none", - "smtpd_tls_auth_only": "", - "smtpd_tls_mandatory_protocols": "", - "smtpd_tls_protocols": "", - "smtpd_tls_ciphers": "", - "smtpd_tls_exclude_ciphers": "", - "smtpd_tls_mandatory_ciphers": "", - "smtpd_tls_eecdh_grade": "medium", - "smtp_tls_security_level": "", - "smtp_tls_ciphers": "", - "smtp_tls_exclude_ciphers": "", - "smtp_tls_mandatory_ciphers": "", - "mail_version": "3.2.3" -} - -def _main_cf_with(obj): - main_cf = copy.copy(DEFAULT_MAIN_CF) - main_cf.update(obj) - return main_cf - -class InstallerTest(certbot_test_util.ConfigTestCase): - # pylint: disable=too-many-public-methods - - def setUp(self): - super(InstallerTest, self).setUp() - _config_file = pkg_resources.resource_filename("certbot_postfix.tests", - os.path.join("testdata", "config.json")) - self.config.postfix_ctl = "postfix" - self.config.postfix_config_dir = self.tempdir - self.config.postfix_config_utility = "postconf" - self.config.postfix_tls_only = False - self.config.postfix_server_only = False - self.config.config_dir = self.tempdir - - @mock.patch("certbot_postfix.installer.util.is_acceptable_value") - def test_set_vars(self, mock_is_acceptable_value): - mock_is_acceptable_value.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - mock_is_acceptable_value.return_value = False - - @mock.patch("certbot_postfix.installer.util.is_acceptable_value") - def test_acceptable_value(self, mock_is_acceptable_value): - mock_is_acceptable_value.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - mock_is_acceptable_value.return_value = False - - @certbot_test_util.patch_get_utility() - def test_confirm_changes_no_raises_error(self, mock_util): - mock_util().yesno.return_value = False - with create_installer(self.config) as installer: - installer.prepare() - self.assertRaises(errors.PluginError, installer.deploy_cert, - "example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - - @certbot_test_util.patch_get_utility() - def test_save(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.postconf.flush = mock.Mock() - installer.reverter = mock.Mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - installer.save() - self.assertEqual(installer.save_notes, []) - self.assertEqual(installer.postconf.flush.call_count, 1) - self.assertEqual(installer.reverter.add_to_checkpoint.call_count, 1) - - @certbot_test_util.patch_get_utility() - def test_save_with_title(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.postconf.flush = mock.Mock() - installer.reverter = mock.Mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - installer.save(title="new_file!") - self.assertEqual(installer.reverter.finalize_checkpoint.call_count, 1) - - @certbot_test_util.patch_get_utility() - def test_rollback_checkpoints_resets_postconf(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - installer.rollback_checkpoints() - self.assertEqual(installer.postconf.get_changes(), {}) - - @certbot_test_util.patch_get_utility() - def test_recovery_routine_resets_postconf(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - installer.recovery_routine() - self.assertEqual(installer.postconf.get_changes(), {}) - - def test_restart(self): - with create_installer(self.config) as installer: - installer.prepare() - installer.restart() - self.assertEqual(installer.postfix.restart.call_count, 1) # pylint: disable=no-member - - def test_add_parser_arguments(self): - options = set(("ctl", "config-dir", "config-utility", - "tls-only", "server-only", "ignore-master-overrides")) - mock_add = mock.MagicMock() - - from certbot_postfix import installer - installer.Installer.add_parser_arguments(mock_add) - - for call in mock_add.call_args_list: - self.assertTrue(call[0][0] in options) - - def test_no_postconf_prepare(self): - with create_installer(self.config) as installer: - installer_path = "certbot_postfix.installer" - exe_exists_path = installer_path + ".certbot_util.exe_exists" - path_surgery_path = "certbot_postfix.util.plugins_util.path_surgery" - with mock.patch(path_surgery_path, return_value=False): - with mock.patch(exe_exists_path, return_value=False): - self.assertRaises(errors.NoInstallationError, - installer.prepare) - - def test_old_version(self): - with create_installer(self.config, main_cf=_main_cf_with({"mail_version": "0.0.1"}))\ - as installer: - self.assertRaises(errors.NotSupportedError, installer.prepare) - - def test_lock_error(self): - with create_installer(self.config) as installer: - assert_raises = functools.partial(self.assertRaises, - errors.PluginError, - installer.prepare) - certbot_test_util.lock_and_call(assert_raises, self.tempdir) - - - @mock.patch('certbot.util.lock_dir_until_exit') - def test_dir_locked(self, lock_dir): - with create_installer(self.config) as installer: - lock_dir.side_effect = errors.LockError - self.assertRaises(errors.PluginError, installer.prepare) - - def test_more_info(self): - with create_installer(self.config) as installer: - installer.prepare() - output = installer.more_info() - self.assertTrue("Postfix" in output) - self.assertTrue(self.tempdir in output) - self.assertTrue(DEFAULT_MAIN_CF["mail_version"] in output) - - def test_get_all_names(self): - config = {"mydomain": "example.org", - "myhostname": "mail.example.org", - "myorigin": "example.org"} - with create_installer(self.config, main_cf=_main_cf_with(config)) as installer: - installer.prepare() - result = installer.get_all_names() - self.assertEqual(result, set(config.values())) - - @certbot_test_util.patch_get_utility() - def test_deploy(self, mock_util): - mock_util().yesno.return_value = True - from certbot_postfix import constants - with create_installer(self.config) as installer: - installer.prepare() - - # pylint: disable=protected-access - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - changes = installer.postconf.get_changes() - expected = {} # type: Dict[str, Tuple[str, ...]] - expected.update(constants.TLS_SERVER_VARS) - expected.update(constants.DEFAULT_SERVER_VARS) - expected.update(constants.DEFAULT_CLIENT_VARS) - self.assertEqual(changes["smtpd_tls_key_file"], "key_path") - self.assertEqual(changes["smtpd_tls_cert_file"], "cert_path") - for name, value in six.iteritems(expected): - self.assertEqual(changes[name], value[0]) - - @certbot_test_util.patch_get_utility() - def test_tls_only(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.conf = lambda x: x == "tls_only" - installer.postconf.set = mock.Mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - self.assertEqual(installer.postconf.set.call_count, 4) - - @certbot_test_util.patch_get_utility() - def test_server_only(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.conf = lambda x: x == "server_only" - installer.postconf.set = mock.Mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - self.assertEqual(installer.postconf.set.call_count, 11) - - @certbot_test_util.patch_get_utility() - def test_tls_and_server_only(self, mock_util): - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - installer.conf = lambda x: True - installer.postconf.set = mock.Mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - self.assertEqual(installer.postconf.set.call_count, 3) - - @certbot_test_util.patch_get_utility() - def test_deploy_twice(self, mock_util): - # Deploying twice on the same installer shouldn't do anything! - mock_util().yesno.return_value = True - with create_installer(self.config) as installer: - installer.prepare() - from certbot_postfix.postconf import ConfigMain - with mock.patch.object(ConfigMain, "set", wraps=installer.postconf.set) as fake_set: - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - self.assertEqual(fake_set.call_count, 15) - fake_set.reset_mock() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - self.assertFalse(fake_set.called) - - @certbot_test_util.patch_get_utility() - def test_deploy_already_secure(self, mock_util): - # Should not overwrite "more-secure" parameters - mock_util().yesno.return_value = True - more_secure = { - "smtpd_tls_security_level": "encrypt", - "smtpd_tls_protocols": "!SSLv3, !SSLv2, !TLSv1", - "smtpd_tls_eecdh_grade": "strong" - } - with create_installer(self.config,\ - main_cf=_main_cf_with(more_secure)) as installer: - installer.prepare() - installer.deploy_cert("example.com", "cert_path", "key_path", - "chain_path", "fullchain_path") - for param in more_secure: - self.assertFalse(param in installer.postconf.get_changes()) - - def test_enhance(self): - with create_installer(self.config) as installer: - installer.prepare() - self.assertRaises(errors.PluginError, - installer.enhance, - "example.org", "redirect") - - def test_supported_enhancements(self): - with create_installer(self.config) as installer: - installer.prepare() - self.assertEqual(installer.supported_enhancements(), []) - - -@contextmanager -def create_installer(config, main_cf=None): - """Creates a Postfix installer with calls to `postconf` and `postfix` mocked out. - - In particular, creates a ConfigMain object that does regular things, but seeds it - with values from `main_cf` and `master_cf` dicts. - """ - if main_cf is None: - main_cf = DEFAULT_MAIN_CF - - from certbot_postfix.postconf import ConfigMain - from certbot_postfix import installer - - def _mock_init_postconf(postconf, executable, ignore_master_overrides=False, config_dir=None): - # pylint: disable=protected-access,unused-argument - postconf._ignore_master_overrides = ignore_master_overrides - postconf._db = main_cf - postconf._master_db = {} - postconf._updated = {} - # override get_default to get from main - postconf.get_default = lambda name: main_cf[name] - with mock.patch.object(ConfigMain, "__init__", _mock_init_postconf): - exe_exists_path = "certbot_postfix.installer.certbot_util.exe_exists" - with mock.patch(exe_exists_path, return_value=True): - with mock.patch("certbot_postfix.installer.util.PostfixUtil", - return_value=mock.Mock()): - yield installer.Installer(config, "postfix") - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-postfix/certbot_postfix/tests/postconf_test.py b/certbot-postfix/certbot_postfix/tests/postconf_test.py deleted file mode 100644 index c042093d0..000000000 --- a/certbot-postfix/certbot_postfix/tests/postconf_test.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Tests for certbot_postfix.postconf.""" - -import unittest - -import mock - -from certbot import errors - - -class PostConfTest(unittest.TestCase): - """Tests for certbot_postfix.util.PostConf.""" - def setUp(self): - from certbot_postfix.postconf import ConfigMain - super(PostConfTest, self).setUp() - with mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') as mock_call: - with mock.patch('certbot_postfix.postconf.ConfigMain._get_output_master') as \ - mock_master_call: - with mock.patch('certbot_postfix.postconf.util.verify_exe_exists') as verify_exe: - verify_exe.return_value = True - mock_call.return_value = ('default_parameter = value\n' - 'extra_param =\n' - 'overridden_by_master = default\n') - mock_master_call.return_value = ( - 'service/type/overridden_by_master = master_value\n' - 'service2/type/overridden_by_master = master_value2\n' - ) - self.config = ConfigMain('postconf', False) - - @mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') - @mock.patch('certbot_postfix.postconf.util.verify_exe_exists') - def test_get_output_master(self, mock_verify_exe, mock_get_output): - from certbot_postfix.postconf import ConfigMain - mock_verify_exe.return_value = True - ConfigMain('postconf', lambda x, y, z: None) - mock_get_output.assert_called_with('-P') - - @mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') - def test_read_default(self, mock_get_output): - mock_get_output.return_value = 'param = default_value' - self.assertEqual(self.config.get_default('param'), 'default_value') - - @mock.patch('certbot_postfix.util.PostfixUtilBase._call') - def test_set(self, mock_call): - self.config.set('extra_param', 'other_value') - self.assertEqual(self.config.get('extra_param'), 'other_value') - self.config.flush() - mock_call.assert_called_with(['-e', 'extra_param=other_value']) - - def test_set_bad_param_name(self): - self.assertRaises(KeyError, self.config.set, 'nonexistent_param', 'some_value') - - @mock.patch('certbot_postfix.util.PostfixUtilBase._call') - def test_write_revert(self, mock_call): - self.config.set('default_parameter', 'fake_news') - # revert config set - self.config.set('default_parameter', 'value') - self.config.flush() - mock_call.assert_not_called() - - @mock.patch('certbot_postfix.util.PostfixUtilBase._call') - def test_write_default(self, mock_call): - self.config.set('default_parameter', 'value') - self.config.flush() - mock_call.assert_not_called() - - def test_master_overrides(self): - self.assertEqual(self.config.get_master_overrides('overridden_by_master'), - [('service/type', 'master_value'), - ('service2/type', 'master_value2')]) - - def test_set_check_override(self): - self.assertRaises(errors.PluginError, self.config.set, - 'overridden_by_master', 'new_value') - - def test_ignore_check_override(self): - # pylint: disable=protected-access - self.config._ignore_master_overrides = True - self.config.set('overridden_by_master', 'new_value') - - def test_check_acceptable_overrides(self): - self.config.set('overridden_by_master', 'new_value', - ('master_value', 'master_value2')) - - @mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') - def test_flush(self, mock_out): - self.config.set('default_parameter', 'new_value') - self.config.set('extra_param', 'another_value') - self.config.flush() - arguments = mock_out.call_args_list[-1][0][0] - self.assertEqual('-e', arguments[0]) - self.assertTrue('default_parameter=new_value' in arguments) - self.assertTrue('extra_param=another_value' in arguments) - - @mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') - def test_flush_updates_object(self, mock_out): - self.config.set('default_parameter', 'new_value') - self.config.flush() - mock_out.reset_mock() - self.config.set('default_parameter', 'new_value') - mock_out.assert_not_called() - - @mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') - def test_flush_throws_error_on_fail(self, mock_out): - mock_out.side_effect = [IOError("oh no!")] - self.config.set('default_parameter', 'new_value') - self.assertRaises(errors.PluginError, self.config.flush) - -if __name__ == '__main__': # pragma: no cover - unittest.main() diff --git a/certbot-postfix/certbot_postfix/tests/util_test.py b/certbot-postfix/certbot_postfix/tests/util_test.py deleted file mode 100644 index fa38f83ab..000000000 --- a/certbot-postfix/certbot_postfix/tests/util_test.py +++ /dev/null @@ -1,205 +0,0 @@ -"""Tests for certbot_postfix.util.""" - -import subprocess -import unittest - -import mock - -from certbot import errors - - -class PostfixUtilBaseTest(unittest.TestCase): - """Tests for certbot_postfix.util.PostfixUtilBase.""" - - @classmethod - def _create_object(cls, *args, **kwargs): - from certbot_postfix.util import PostfixUtilBase - return PostfixUtilBase(*args, **kwargs) - - @mock.patch('certbot_postfix.util.verify_exe_exists') - def test_no_exe(self, mock_verify): - expected_error = errors.NoInstallationError - mock_verify.side_effect = expected_error - self.assertRaises(expected_error, self._create_object, 'nonexistent') - - def test_object_creation(self): - with mock.patch('certbot_postfix.util.verify_exe_exists'): - self._create_object('existent') - - @mock.patch('certbot_postfix.util.check_all_output') - def test_call_extends_args(self, mock_output): - # pylint: disable=protected-access - with mock.patch('certbot_postfix.util.verify_exe_exists'): - mock_output.return_value = 'expected' - postfix = self._create_object('executable') - postfix._call(['many', 'extra', 'args']) - mock_output.assert_called_with(['executable', 'many', 'extra', 'args']) - postfix._call() - mock_output.assert_called_with(['executable']) - - def test_create_with_config(self): - # pylint: disable=protected-access - with mock.patch('certbot_postfix.util.verify_exe_exists'): - postfix = self._create_object('exec', 'config_dir') - self.assertEqual(postfix._base_command, ['exec', '-c', 'config_dir']) - -class PostfixUtilTest(unittest.TestCase): - def setUp(self): - # pylint: disable=protected-access - from certbot_postfix.util import PostfixUtil - with mock.patch('certbot_postfix.util.verify_exe_exists'): - self.postfix = PostfixUtil() - self.postfix._call = mock.Mock() - self.mock_call = self.postfix._call - - def test_test(self): - self.postfix.test() - self.mock_call.assert_called_with(['check']) - - def test_test_raises_error_when_check_fails(self): - self.mock_call.side_effect = [subprocess.CalledProcessError(1, "")] - self.assertRaises(errors.MisconfigurationError, self.postfix.test) - self.mock_call.assert_called_with(['check']) - - def test_restart_while_running(self): - self.mock_call.side_effect = [subprocess.CalledProcessError(1, ""), None] - self.postfix.restart() - self.mock_call.assert_called_with(['start']) - - def test_restart_while_not_running(self): - self.postfix.restart() - self.mock_call.assert_called_with(['reload']) - - def test_restart_raises_error_when_reload_fails(self): - self.mock_call.side_effect = [None, subprocess.CalledProcessError(1, "")] - self.assertRaises(errors.PluginError, self.postfix.restart) - self.mock_call.assert_called_with(['reload']) - - def test_restart_raises_error_when_start_fails(self): - self.mock_call.side_effect = [ - subprocess.CalledProcessError(1, ""), - subprocess.CalledProcessError(1, "")] - self.assertRaises(errors.PluginError, self.postfix.restart) - self.mock_call.assert_called_with(['start']) - -class CheckAllOutputTest(unittest.TestCase): - """Tests for certbot_postfix.util.check_all_output.""" - - @classmethod - def _call(cls, *args, **kwargs): - from certbot_postfix.util import check_all_output - return check_all_output(*args, **kwargs) - - @mock.patch('certbot_postfix.util.logger') - @mock.patch('certbot_postfix.util.subprocess.Popen') - def test_command_error(self, mock_popen, mock_logger): - command = 'foo' - retcode = 42 - output = 'bar' - err = 'baz' - - mock_popen().communicate.return_value = (output, err) - mock_popen().poll.return_value = 42 - - self.assertRaises(subprocess.CalledProcessError, self._call, command) - log_args = mock_logger.debug.call_args[0] - for value in (command, retcode, output, err,): - self.assertTrue(value in log_args) - - @mock.patch('certbot_postfix.util.subprocess.Popen') - def test_success(self, mock_popen): - command = 'foo' - expected = ('bar', '') - mock_popen().communicate.return_value = expected - mock_popen().poll.return_value = 0 - - self.assertEqual(self._call(command), expected) - - def test_stdout_error(self): - self.assertRaises(ValueError, self._call, stdout=None) - - def test_stderr_error(self): - self.assertRaises(ValueError, self._call, stderr=None) - - def test_universal_newlines_error(self): - self.assertRaises(ValueError, self._call, universal_newlines=False) - - -class VerifyExeExistsTest(unittest.TestCase): - """Tests for certbot_postfix.util.verify_exe_exists.""" - - @classmethod - def _call(cls, *args, **kwargs): - from certbot_postfix.util import verify_exe_exists - return verify_exe_exists(*args, **kwargs) - - @mock.patch('certbot_postfix.util.certbot_util.exe_exists') - @mock.patch('certbot_postfix.util.plugins_util.path_surgery') - def test_failure(self, mock_exe_exists, mock_path_surgery): - mock_exe_exists.return_value = mock_path_surgery.return_value = False - self.assertRaises(errors.NoInstallationError, self._call, 'foo') - - @mock.patch('certbot_postfix.util.certbot_util.exe_exists') - def test_simple_success(self, mock_exe_exists): - mock_exe_exists.return_value = True - self._call('foo') - - @mock.patch('certbot_postfix.util.certbot_util.exe_exists') - @mock.patch('certbot_postfix.util.plugins_util.path_surgery') - def test_successful_surgery(self, mock_exe_exists, mock_path_surgery): - mock_exe_exists.return_value = False - mock_path_surgery.return_value = True - self._call('foo') - -class TestUtils(unittest.TestCase): - """ Testing random utility functions in util.py - """ - def test_report_master_overrides(self): - from certbot_postfix.util import report_master_overrides - self.assertRaises(errors.PluginError, report_master_overrides, 'name', - [('service/type', 'value')]) - # Shouldn't raise error - report_master_overrides('name', [('service/type', 'value')], - acceptable_overrides=('value',)) - - def test_no_acceptable_value(self): - from certbot_postfix.util import is_acceptable_value - self.assertFalse(is_acceptable_value('name', 'value', None)) - - def test_is_acceptable_value(self): - from certbot_postfix.util import is_acceptable_value - self.assertTrue(is_acceptable_value('name', 'value', ('value',))) - self.assertFalse(is_acceptable_value('name', 'bad', ('value',))) - - def test_is_acceptable_tuples(self): - from certbot_postfix.util import is_acceptable_value - self.assertTrue(is_acceptable_value('name', 'value', ('value', 'value1'))) - self.assertFalse(is_acceptable_value('name', 'bad', ('value', 'value1'))) - - def test_is_acceptable_protocols(self): - from certbot_postfix.util import is_acceptable_value - # SSLv2 and SSLv3 are both not supported, unambiguously - self.assertFalse(is_acceptable_value('tls_mandatory_protocols_lol', - 'SSLv2, SSLv3', None)) - self.assertFalse(is_acceptable_value('tls_protocols_lol', - 'SSLv2, SSLv3', None)) - self.assertFalse(is_acceptable_value('tls_protocols_lol', - '!SSLv2, !TLSv1', None)) - self.assertFalse(is_acceptable_value('tls_protocols_lol', - '!SSLv2, SSLv3, !SSLv3, ', None)) - self.assertTrue(is_acceptable_value('tls_protocols_lol', - '!SSLv2, !SSLv3', None)) - self.assertTrue(is_acceptable_value('tls_protocols_lol', - '!SSLv3, !TLSv1, !SSLv2', None)) - # TLSv1.2 is supported unambiguously - self.assertFalse(is_acceptable_value('tls_protocols_lol', - 'TLSv1, TLSv1.1,', None)) - self.assertFalse(is_acceptable_value('tls_protocols_lol', - 'TLSv1.2, !TLSv1.2,', None)) - self.assertTrue(is_acceptable_value('tls_protocols_lol', - 'TLSv1.2, ', None)) - self.assertTrue(is_acceptable_value('tls_protocols_lol', - 'TLSv1, TLSv1.1, TLSv1.2', None)) - -if __name__ == '__main__': # pragma: no cover - unittest.main() diff --git a/certbot-postfix/certbot_postfix/util.py b/certbot-postfix/certbot_postfix/util.py deleted file mode 100644 index 86a892140..000000000 --- a/certbot-postfix/certbot_postfix/util.py +++ /dev/null @@ -1,292 +0,0 @@ -"""Utility functions for use in the Postfix installer.""" -import logging -import re -import subprocess - -from certbot import errors -from certbot import util as certbot_util -from certbot.plugins import util as plugins_util -from certbot_postfix import constants - -logger = logging.getLogger(__name__) - -COMMAND = "postfix" - - -class PostfixUtilBase(object): - """A base class for wrapping Postfix command line utilities.""" - - def __init__(self, executable, config_dir=None): - """Sets up the Postfix utility class. - - :param str executable: name or path of the Postfix utility - :param str config_dir: path to an alternative Postfix config - - :raises .NoInstallationError: when the executable isn't found - - """ - self.executable = executable - verify_exe_exists(executable) - self._set_base_command(config_dir) - self.config_dir = None - - def _set_base_command(self, config_dir): - self._base_command = [self.executable] - if config_dir is not None: - self._base_command.extend(('-c', config_dir,)) - - def _call(self, extra_args=None): - """Runs the Postfix utility and returns the result. - - :param list extra_args: additional arguments for the command - - :returns: data written to stdout and stderr - :rtype: `tuple` of `str` - - :raises subprocess.CalledProcessError: if the command fails - - """ - args = list(self._base_command) - if extra_args is not None: - args.extend(extra_args) - return check_all_output(args) - - def _get_output(self, extra_args=None): - """Runs the Postfix utility and returns only stdout output. - - This function relies on self._call for running the utility. - - :param list extra_args: additional arguments for the command - - :returns: data written to stdout - :rtype: str - - :raises subprocess.CalledProcessError: if the command fails - - """ - return self._call(extra_args)[0] - -class PostfixUtil(PostfixUtilBase): - """Wrapper around Postfix CLI tool. - """ - - def __init__(self, config_dir=None): - super(PostfixUtil, self).__init__(COMMAND, config_dir) - - def test(self): - """Make sure the configuration is valid. - - :raises .MisconfigurationError: if the config is invalid - """ - try: - self._call(["check"]) - except subprocess.CalledProcessError as e: - logger.debug("Could not check postfix configuration:\n%s", e) - raise errors.MisconfigurationError( - "Postfix failed internal configuration check.") - - def restart(self): - """Restart or refresh the server content. - - :raises .PluginError: when server cannot be restarted - - """ - logger.info("Reloading Postfix configuration...") - if self._is_running(): - self._reload() - else: - self._start() - - - def _is_running(self): - """Is Postfix currently running? - - Uses the 'postfix status' command to determine if Postfix is - currently running using the specified configuration files. - - :returns: True if Postfix is running, otherwise, False - :rtype: bool - - """ - try: - self._call(["status"]) - except subprocess.CalledProcessError: - return False - return True - - def _start(self): - """Instructions Postfix to start running. - - :raises .PluginError: when Postfix cannot start - - """ - try: - self._call(["start"]) - except subprocess.CalledProcessError: - raise errors.PluginError("Postfix failed to start") - - def _reload(self): - """Instructs Postfix to reload its configuration. - - If Postfix isn't currently running, this method will fail. - - :raises .PluginError: when Postfix cannot reload - """ - try: - self._call(["reload"]) - except subprocess.CalledProcessError: - raise errors.PluginError( - "Postfix failed to reload its configuration") - -def check_all_output(*args, **kwargs): - """A version of subprocess.check_output that also captures stderr. - - This is the same as :func:`subprocess.check_output` except output - written to stderr is also captured and returned to the caller. The - return value is a tuple of two strings (rather than byte strings). - To accomplish this, the caller cannot set the stdout, stderr, or - universal_newlines parameters to :class:`subprocess.Popen`. - - Additionally, if the command exits with a nonzero status, output is - not included in the raised :class:`subprocess.CalledProcessError` - because Python 2.6 does not support this. Instead, the failure - including the output is logged. - - :param tuple args: positional arguments for Popen - :param dict kwargs: keyword arguments for Popen - - :returns: data written to stdout and stderr - :rtype: `tuple` of `str` - - :raises ValueError: if arguments are invalid - :raises subprocess.CalledProcessError: if the command fails - - """ - for keyword in ('stdout', 'stderr', 'universal_newlines',): - if keyword in kwargs: - raise ValueError( - keyword + ' argument not allowed, it will be overridden.') - - kwargs['stdout'] = subprocess.PIPE - kwargs['stderr'] = subprocess.PIPE - kwargs['universal_newlines'] = True - - process = subprocess.Popen(*args, **kwargs) - output, err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kwargs.get('args') - if cmd is None: - cmd = args[0] - logger.debug( - "'%s' exited with %d. stdout output was:\n%s\nstderr output was:\n%s", - cmd, retcode, output, err) - raise subprocess.CalledProcessError(retcode, cmd) - return (output, err) - - -def verify_exe_exists(exe, message=None): - """Ensures an executable with the given name is available. - - If an executable isn't found for the given path or name, extra - directories are added to the user's PATH to help find system - utilities that may not be available in the default cron PATH. - - :param str exe: executable path or name - :param str message: Error message to print. - - :raises .NoInstallationError: when the executable isn't found - - """ - if message is None: - message = "Cannot find executable '{0}'.".format(exe) - if not (certbot_util.exe_exists(exe) or plugins_util.path_surgery(exe)): - raise errors.NoInstallationError(message) - -def report_master_overrides(name, overrides, acceptable_overrides=None): - """If the value for a parameter `name` is overridden by other services, - report a warning to notify the user. If `parameter` is a TLS version parameter - (i.e., `parameter` contains 'tls_protocols' or 'tls_mandatory_protocols'), then - `acceptable_overrides` isn't used each value in overrides is inspected for secure TLS - versions. - - :param str name: The name of the parameter that is being overridden. - :param list overrides: The values that other services are setting for `name`. - Each override is a tuple: (service name, value) - :param tuple acceptable_overrides: Override values that are acceptable. For instance, if - another service is overriding our parameter with a more secure option, we don't have - to warn. If this is set to None, errors are raised for *any* overrides of `name`! - """ - error_string = "" - for override in overrides: - service, value = override - # If this override is acceptable: - if acceptable_overrides is not None and \ - is_acceptable_value(name, value, acceptable_overrides): - continue - error_string += " {0}: {1}\n".format(service, value) - if error_string: - raise errors.PluginError("{0} is overridden with less secure options by the " - "following services in master.cf:\n".format(name) + error_string) - - -def is_acceptable_value(parameter, value, acceptable=None): - """ Returns whether the `value` for this `parameter` is acceptable, - given a tuple of `acceptable` values. If `parameter` is a TLS version parameter - (i.e., `parameter` contains 'tls_protocols' or 'tls_mandatory_protocols'), then - `acceptable` isn't used and `value` is inspected for secure TLS versions. - - :param str parameter: The name of the parameter being set. - :param str value: Proposed new value for parameter. - :param tuple acceptable: List of acceptable values for parameter. - """ - # Check if param value is a comma-separated list of protocols. - # Otherwise, just check whether the value is in the acceptable list. - if 'tls_protocols' in parameter or 'tls_mandatory_protocols' in parameter: - return _has_acceptable_tls_versions(value) - if acceptable is not None: - return value in acceptable - return False - - -def _has_acceptable_tls_versions(parameter_string): - """ - Checks to see if the list of TLS protocols is acceptable. - This requires that TLSv1.2 is supported, and neither SSLv2 nor SSLv3 are supported. - - Should be a string of protocol names delimited by commas, spaces, or colons. - - Postfix's documents suggest listing protocols to exclude, like "!SSLv2, !SSLv3". - Listing the protocols to include, like "TLSv1, TLSv1.1, TLSv1.2" is okay as well, - though not recommended - - When these two modes are interspersed, the presence of a single non-negated protocol name - (i.e. "TLSv1" rather than "!TLSv1") automatically excludes all other unnamed protocols. - - In addition, the presence of both a protocol name inclusion and exclusion isn't explicitly - documented, so this method should return False if it encounters contradicting statements - about TLSv1.2, SSLv2, or SSLv3. (for instance, "SSLv3, !SSLv3"). - """ - if not parameter_string: - return False - bad_versions = list(constants.TLS_VERSIONS) - for version in constants.ACCEPTABLE_TLS_VERSIONS: - del bad_versions[bad_versions.index(version)] - supported_version_list = re.split("[, :]+", parameter_string) - # The presence of any non-"!" protocol listing excludes the others by default. - inclusion_list = False - for version in supported_version_list: - if not version: - continue - if version in bad_versions: # short-circuit if we recognize any bad version - return False - if version[0] != "!": - inclusion_list = True - if inclusion_list: # For any inclusion list, we still require TLS 1.2. - if "TLSv1.2" not in supported_version_list or "!TLSv1.2" in supported_version_list: - return False - else: - for bad_version in bad_versions: - if "!" + bad_version not in supported_version_list: - return False - return True diff --git a/certbot-postfix/docs/.gitignore b/certbot-postfix/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-postfix/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-postfix/docs/Makefile b/certbot-postfix/docs/Makefile deleted file mode 100644 index 717ff654f..000000000 --- a/certbot-postfix/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = certbot-postfix -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/certbot-postfix/docs/api.rst b/certbot-postfix/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-postfix/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-postfix/docs/api/installer.rst b/certbot-postfix/docs/api/installer.rst deleted file mode 100644 index 121d58d5b..000000000 --- a/certbot-postfix/docs/api/installer.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_postfix.installer` --------------------------------------- - -.. automodule:: certbot_postfix.installer - :members: diff --git a/certbot-postfix/docs/api/postconf.rst b/certbot-postfix/docs/api/postconf.rst deleted file mode 100644 index 917150e45..000000000 --- a/certbot-postfix/docs/api/postconf.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_postfix.postconf` -------------------------------- - -.. automodule:: certbot_postfix.postconf - :members: diff --git a/certbot-postfix/docs/conf.py b/certbot-postfix/docs/conf.py deleted file mode 100644 index a2f1a8092..000000000 --- a/certbot-postfix/docs/conf.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -from certbot.compat import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = u'certbot-postfix' -copyright = u'2018, Certbot Project' -author = u'Certbot Project' - -# The short X.Y version -version = u'0' -# The full version, including alpha/beta/rc tags -release = u'0' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -needs_sphinx = '1.0' - -# 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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = u'en' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] - -default_role = 'py:obj' - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'certbot-postfixdoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'certbot-postfix.tex', u'certbot-postfix Documentation', - u'Certbot Project', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'certbot-postfix', u'certbot-postfix Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'certbot-postfix', u'certbot-postfix Documentation', - author, 'certbot-postfix', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), -} - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/certbot-postfix/docs/index.rst b/certbot-postfix/docs/index.rst deleted file mode 100644 index 3d6697bcb..000000000 --- a/certbot-postfix/docs/index.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. certbot-postfix documentation master file, created by - sphinx-quickstart on Wed May 2 16:01:06 2018. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-postfix's documentation! -=========================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -.. automodule:: certbot_postfix - :members: - -.. toctree:: - :maxdepth: 1 - - api - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/certbot-postfix/docs/make.bat b/certbot-postfix/docs/make.bat deleted file mode 100644 index 23fbdc93c..000000000 --- a/certbot-postfix/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=certbot-postfix - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/certbot-postfix/local-oldest-requirements.txt b/certbot-postfix/local-oldest-requirements.txt deleted file mode 100644 index da509406e..000000000 --- a/certbot-postfix/local-oldest-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Remember to update setup.py to match the package versions below. -acme[dev]==0.29.0 --e .[dev] diff --git a/certbot-postfix/setup.cfg b/certbot-postfix/setup.cfg deleted file mode 100644 index 2a9acf13d..000000000 --- a/certbot-postfix/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/certbot-postfix/setup.py b/certbot-postfix/setup.py deleted file mode 100644 index e708863b2..000000000 --- a/certbot-postfix/setup.py +++ /dev/null @@ -1,64 +0,0 @@ -from setuptools import setup -from setuptools import find_packages - - -version = '0.26.0.dev0' - -install_requires = [ - 'acme>=0.29.0', - 'certbot>=0.34.0.dev0', - 'setuptools', - 'six', - 'zope.component', - 'zope.interface', -] - -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - -setup( - name='certbot-postfix', - version=version, - description="Postfix plugin for Certbot", - url='https://github.com/certbot/certbot', - author="Certbot Project", - author_email='client-dev@letsencrypt.org', - license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Plugins', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Communications :: Email :: Mail Transport Agents', - 'Topic :: Security', - 'Topic :: System :: Installation/Setup', - 'Topic :: System :: Networking', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ], - - packages=find_packages(), - include_package_data=True, - install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, - entry_points={ - 'certbot.plugins': [ - 'postfix = certbot_postfix:Installer', - ], - }, - test_suite='certbot_postfix', -) diff --git a/certbot/plugins/disco.py b/certbot/plugins/disco.py index 4b8e9f7ab..ec2bff8b7 100644 --- a/certbot/plugins/disco.py +++ b/certbot/plugins/disco.py @@ -39,7 +39,6 @@ class PluginEntryPoint(object): "certbot-dns-route53", "certbot-dns-sakuracloud", "certbot-nginx", - "certbot-postfix", ] """Distributions for which prefix will be omitted.""" diff --git a/docs/using.rst b/docs/using.rst index d398c1c4d..8e93bcb41 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -280,7 +280,6 @@ external_ Y N A plugin for convenient scripting (See also ticket 2782_) icecast_ N Y Deploy certificates to Icecast 2 streaming media servers pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers proxmox_ N Y Install certificates in Proxmox Virtualization servers -postfix_ N Y STARTTLS Everywhere is becoming a Certbot Postfix/Exim plugin heroku_ Y Y Integration with Heroku SSL =========== ==== ==== =============================================================== @@ -294,7 +293,6 @@ heroku_ Y Y Integration with Heroku SSL .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox .. _external: https://github.com/marcan/letsencrypt-external -.. _postfix: https://github.com/EFForg/starttls-everywhere .. _heroku: https://github.com/gboudreau/certbot-heroku If you're interested, you can also :ref:`write your own plugin `. diff --git a/tools/_venv_common.py b/tools/_venv_common.py index 1594fd05f..ac3b5f98d 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -39,7 +39,6 @@ REQUIREMENTS = [ '-e certbot-dns-route53', '-e certbot-dns-sakuracloud', '-e certbot-nginx', - '-e certbot-postfix', '-e letshelp-certbot', '-e certbot-compatibility-test', '-e certbot-ci', diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 288226527..6987cf2b1 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -14,8 +14,7 @@ import shutil import subprocess import re -SKIP_PROJECTS_ON_WINDOWS = [ - 'certbot-apache', 'certbot-postfix', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] def call_with_print(command, cwd=None): diff --git a/tox.cover.py b/tox.cover.py index d0f97626a..65dc4a8a9 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -9,7 +9,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_digitalocean', 'certbot_dns_dnsimple', 'certbot_dns_dnsmadeeasy', 'certbot_dns_gehirn', 'certbot_dns_google', 'certbot_dns_linode', 'certbot_dns_luadns', 'certbot_dns_nsone', 'certbot_dns_ovh', 'certbot_dns_rfc2136', 'certbot_dns_route53', - 'certbot_dns_sakuracloud', 'certbot_nginx', 'certbot_postfix', 'letshelp_certbot'] + 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] COVER_THRESHOLDS = { 'certbot': {'linux': 98, 'windows': 93}, @@ -30,12 +30,10 @@ COVER_THRESHOLDS = { 'certbot_dns_route53': {'linux': 92, 'windows': 92}, 'certbot_dns_sakuracloud': {'linux': 97, 'windows': 97}, 'certbot_nginx': {'linux': 97, 'windows': 97}, - 'certbot_postfix': {'linux': 100, 'windows': 100}, 'letshelp_certbot': {'linux': 100, 'windows': 100} } -SKIP_PROJECTS_ON_WINDOWS = [ - 'certbot-apache', 'certbot-postfix', 'letshelp-certbot'] +SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] def cover(package): diff --git a/tox.ini b/tox.ini index 52ae1e97d..fed30976f 100644 --- a/tox.ini +++ b/tox.ini @@ -35,7 +35,6 @@ all_packages = certbot-apache \ {[base]dns_packages} \ certbot-nginx \ - certbot-postfix \ letshelp-certbot install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages} @@ -59,7 +58,6 @@ source_paths = certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx - certbot-postfix/certbot_postfix letshelp-certbot/letshelp_certbot tests/lock_test.py @@ -111,12 +109,6 @@ commands = setenv = {[testenv:py27-oldest]setenv} -[testenv:py27-postfix-oldest] -commands = - {[base]install_and_test} certbot-postfix -setenv = - {[testenv:py27-oldest]setenv} - [testenv:py27-cover] basepython = python2.7 commands = -- cgit v1.2.3 From 72e5d89e957e0741ba1408dc4eccd1f08f28da78 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 11 Jun 2019 23:54:36 +0200 Subject: Move nginx_compat to nightly (#7001) With the various optimizations already done and upcoming (certbot-ci), the time execution of integration tests have significantly decreased, allowing potentially a complete execution of a Travis PR job to be done within 5min30s. However, one job is significantly longer that the other ones after this migration: this is nginx_compat, that takes more that 11min to finish. I tried to split the nginx_compat in terms of tested configuration and of tests to execute (auth, install, enhance). Both are not satisfactory: splitting by configuration may work, but add a significant complexity in the tests splitting by tests type is supported almost out-of-the-box, but fails to make two fast tests (see https://travis-ci.org/adferrand/certbot/builds/525892885?utm_source=github_status&utm_medium=notification for instance) Since these tests are designed to check corner cases on the nginx parser, this is mostly useless to execute them on each PR, as the nginx parser is rarely updated. After some discussion with @bmw, I think that we can just move the nginx_compat from the PR tests to the nightly tests. This PR does that. --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13ee9cc88..a3f7490f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,12 +41,6 @@ matrix: - python: "2.7" env: TOXENV=py27-cover FYI="py27 tests + code coverage" - - sudo: required - env: TOXENV=nginx_compat - services: docker - before_install: - addons: - <<: *not-on-master - python: "2.7" env: TOXENV=lint <<: *not-on-master @@ -94,6 +88,12 @@ matrix: <<: *not-on-master # Extended test suite on cron jobs and pushes to tested branches other than master + - sudo: required + env: TOXENV=nginx_compat + services: docker + before_install: + addons: + <<: *extended-test-suite - python: "2.7" env: - TOXENV=travis-test-farm-apache2 -- cgit v1.2.3 From d75908c645e0d13827b60c4c94491e0c41859ad4 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 12 Jun 2019 02:08:48 +0200 Subject: [Windows] Security model for files permissions - STEP 3b (#6965) * Modifications for misc * Add some types * Use os_rename * Move rename into filesystem * Use our os package * Rename filesystem.rename to filesystem.replace * Disable globally function redefined lint in os module --- certbot/compat/filesystem.py | 20 +++++++++++ certbot/compat/misc.py | 63 ++++++++------------------------- certbot/compat/os.py | 17 +++++++++ certbot/main.py | 2 +- certbot/reverter.py | 3 +- certbot/storage.py | 6 ++-- certbot/tests/compat/compat_test.py | 21 ----------- certbot/tests/compat/filesystem_test.py | 21 +++++++++++ certbot/tests/reverter_test.py | 6 ++-- 9 files changed, 82 insertions(+), 77 deletions(-) create mode 100644 certbot/compat/filesystem.py delete mode 100644 certbot/tests/compat/compat_test.py create mode 100644 certbot/tests/compat/filesystem_test.py diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py new file mode 100644 index 000000000..8025c9ad0 --- /dev/null +++ b/certbot/compat/filesystem.py @@ -0,0 +1,20 @@ +"""Compat module to handle files security on Windows and Linux""" +from __future__ import absolute_import + +import os # pylint: disable=os-module-forbidden + + +def replace(src, dst): + # type: (str, str) -> None + """ + Rename a file to a destination path and handles situations where the destination exists. + :param str src: The current file path. + :param str dst: The new file path. + """ + if hasattr(os, 'replace'): + # Use replace if possible. On Windows, only Python >= 3.4 is supported + # so we can assume that os.replace() is always available for this platform. + getattr(os, 'replace')(src, dst) + else: + # Otherwise, use os.rename() that behaves like os.replace() on Linux. + os.rename(src, dst) diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 4f0e22078..c61672364 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -2,42 +2,31 @@ This compat module handles various platform specific calls that do not fall into one particular category. """ -import ctypes -import errno +from __future__ import absolute_import + import select import stat import sys +try: + from win32com.shell import shell as shellwin32 # pylint: disable=import-error + POSIX_MODE = False +except ImportError: # pragma: no cover + POSIX_MODE = True + from certbot import errors from certbot.compat import os -UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ - 'certificates', 'enhance', 'revoke', 'delete', - 'register', 'unregister', 'config_changes', 'plugins'] - - -def raise_for_non_administrative_windows_rights(subcommand): +def raise_for_non_administrative_windows_rights(): + # type: () -> None """ On Windows, raise if current shell does not have the administrative rights. Do nothing on Linux. - :param str subcommand: The subcommand (like 'certonly') passed to the certbot client. - - :raises .errors.Error: If the provided subcommand must be run on a shell with - administrative rights, and current shell does not have these rights. - + :raises .errors.Error: If the current shell does not have administrative rights on Windows. """ - # Why not simply try ctypes.windll.shell32.IsUserAnAdmin() and catch AttributeError ? - # Because windll exists only on a Windows runtime, and static code analysis engines - # do not like at all non existent objects when run from Linux (even if we handle properly - # all the cases in the code). - # So we access windll only by reflection to trick these engines. - if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED: - windll = getattr(ctypes, 'windll') - if windll.shell32.IsUserAnAdmin() == 0: - raise errors.Error( - 'Error, "{0}" subcommand must be run on a shell with administrative rights.' - .format(subcommand)) + if not POSIX_MODE and shellwin32.IsUserAnAdmin() == 0: # pragma: no cover + raise errors.Error('Error, certbot must be run on a shell with administrative rights.') def os_geteuid(): @@ -56,31 +45,8 @@ def os_geteuid(): return 0 -def os_rename(src, dst): - """ - Rename a file to a destination path and handles situations where the destination exists. - - :param str src: The current file path. - :param str dst: The new file path. - """ - try: - os.rename(src, dst) - except OSError as err: - # Windows specific, renaming a file on an existing path is not possible. - # On Python 3, the best fallback with atomic capabilities we have is os.replace. - if err.errno != errno.EEXIST: - # Every other error is a legitimate exception. - raise - if not hasattr(os, 'replace'): # pragma: no cover - # We should never go on this line. Either we are on Linux and os.rename has succeeded, - # or we are on Windows, and only Python >= 3.4 is supported where os.replace is - # available. - raise RuntimeError('Error: tried to run os_rename on Python < 3.3. ' - 'Certbot supports only Python 3.4 >= on Windows.') - getattr(os, 'replace')(src, dst) - - def readline_with_timeout(timeout, prompt): + # type: (float, str) -> str """ Read user input to return the first line entered, or raise after specified timeout. @@ -132,6 +98,7 @@ LINUX_DEFAULT_FOLDERS = { def get_default_folder(folder_type): + # type: (str) -> str """ Return the relevant default folder for the current OS diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 8a139a141..68f4e7cf6 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -3,6 +3,7 @@ This compat modules is a wrapper of the core os module that forbids usage of spe (e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. This module is intended to replace standard os module throughout certbot projects (except acme). """ +# pylint: disable=function-redefined from __future__ import absolute_import # First round of wrapping: we import statically all public attributes exposed by the os module @@ -29,3 +30,19 @@ std_sys.modules[__name__ + '.path'] = path # Clean all remaining importables that are not from the core os module. del ourselves, std_os, std_sys + + +# Because of the blocking strategy on file handlers on Windows, rename does not behave as expected +# with POSIX systems: an exception will be raised if dst already exists. +def rename(*unused_args, **unused_kwargs): + """Method os.rename() is forbidden""" + raise RuntimeError('Usage of os.rename() is forbidden. ' + 'Use certbot.compat.filesystem.replace() instead.') + + +# Behavior of os.replace is consistent between Windows and Linux. However, it is not supported on +# Python 2.x. So, as for os.rename, we forbid it in favor of filesystem.replace. +def replace(*unused_args, **unused_kwargs): + """Method os.replace() is forbidden""" + raise RuntimeError('Usage of os.replace() is forbidden. ' + 'Use certbot.compat.filesystem.replace() instead.') diff --git a/certbot/main.py b/certbot/main.py index 9ed44cceb..d5239fa11 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1359,7 +1359,7 @@ def main(cli_args=None): # On windows, shell without administrative right cannot create symlinks required by certbot. # So we check the rights before continuing. - misc.raise_for_non_administrative_windows_rights(config.verb) + misc.raise_for_non_administrative_windows_rights() try: log.post_arg_parse_setup(config) diff --git a/certbot/reverter.py b/certbot/reverter.py index c7992e73c..f3088ed7e 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -16,6 +16,7 @@ from certbot import interfaces from certbot import util from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem logger = logging.getLogger(__name__) @@ -583,7 +584,7 @@ class Reverter(object): timestamp = self._checkpoint_timestamp() final_dir = os.path.join(self.config.backup_dir, timestamp) try: - misc.os_rename(self.config.in_progress_dir, final_dir) + filesystem.replace(self.config.in_progress_dir, final_dir) return except OSError: logger.warning("Extreme, unexpected race condition, retrying (%s)", timestamp) diff --git a/certbot/storage.py b/certbot/storage.py index 01e012920..048b224d7 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -18,8 +18,8 @@ from certbot import crypto_util from certbot import error_handler from certbot import errors from certbot import util -from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins import common as plugins_common from certbot.plugins import disco as plugins_disco @@ -162,7 +162,7 @@ def rename_renewal_config(prev_name, new_name, cli_config): raise errors.ConfigurationError("The new certificate name " "is already in use.") try: - os.rename(prev_filename, new_filename) + filesystem.replace(prev_filename, new_filename) except OSError: raise errors.ConfigurationError("Please specify a valid filename " "for the new certificate name.") @@ -191,7 +191,7 @@ def update_configuration(lineagename, archive_dir, target, cli_config): # Save only the config items that are relevant to renewal values = relevant_values(vars(cli_config.namespace)) write_renewal_config(config_filename, temp_filename, archive_dir, target, values) - misc.os_rename(temp_filename, config_filename) + filesystem.replace(temp_filename, config_filename) return configobj.ConfigObj(config_filename) diff --git a/certbot/tests/compat/compat_test.py b/certbot/tests/compat/compat_test.py deleted file mode 100644 index 832c557f6..000000000 --- a/certbot/tests/compat/compat_test.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Tests for certbot.compat.""" -import certbot.tests.util as test_util -from certbot.compat import misc -from certbot.compat import os - - -class OsReplaceTest(test_util.TempDirTestCase): - """Test to ensure consistent behavior of os_rename method""" - - def test_os_rename_to_existing_file(self): - """Ensure that os_rename will effectively rename src into dst for all platforms.""" - src = os.path.join(self.tempdir, 'src') - dst = os.path.join(self.tempdir, 'dst') - open(src, 'w').close() - open(dst, 'w').close() - - # On Windows, a direct call to os.rename will fail because dst already exists. - misc.os_rename(src, dst) - - self.assertFalse(os.path.exists(src)) - self.assertTrue(os.path.exists(dst)) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py new file mode 100644 index 000000000..591ee87c3 --- /dev/null +++ b/certbot/tests/compat/filesystem_test.py @@ -0,0 +1,21 @@ +"""Tests for certbot.compat.filesystem""" +import certbot.tests.util as test_util +from certbot.compat import os +from certbot.compat import filesystem + + +class OsReplaceTest(test_util.TempDirTestCase): + """Test to ensure consistent behavior of rename method""" + + def test_os_replace_to_existing_file(self): + """Ensure that replace will effectively rename src into dst for all platforms.""" + src = os.path.join(self.tempdir, 'src') + dst = os.path.join(self.tempdir, 'dst') + open(src, 'w').close() + open(dst, 'w').close() + + # On Windows, a direct call to os.rename would fail because dst already exists. + filesystem.replace(src, dst) + + self.assertFalse(os.path.exists(src)) + self.assertTrue(os.path.exists(dst)) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index f90708a69..6a805072b 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -347,11 +347,11 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.finalize_checkpoint, "Title") - @mock.patch("certbot.reverter.misc.os_rename") - def test_finalize_checkpoint_no_rename_directory(self, mock_rename): + @mock.patch("certbot.reverter.filesystem.replace") + def test_finalize_checkpoint_no_rename_directory(self, mock_replace): self.reverter.add_to_checkpoint(self.sets[0], "perm save") - mock_rename.side_effect = OSError + mock_replace.side_effect = OSError self.assertRaises( errors.ReverterError, self.reverter.finalize_checkpoint, "Title") -- cgit v1.2.3 From e394889864c33b69ea23264df67f77a3b21d90f2 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 13 Jun 2019 02:19:23 +0200 Subject: Add executable scripts to start certbot and acme server in certbot-ci (#7073) During review of #6989, we saw that some of our test bash scripts were still used in the Boulder project in particular. It is about `tests/integration/_common.sh` in particular, to expose the `certbot_test` bash function, that is an appropriate way to execute a local version of certbot in test mode: define a custom server, remove several checks, full log and so on. This PR is an attempt to assert this goal: exposing a new `certbot_test` executable for test purpose. More generally, this PR is about giving well suited scripts to quickly make manual tests against certbot without launching the full automated pytest suite. The idea here is to leverage the existing logic in certbot-ci, and expose it as executable scripts. This is done thanks to the `console_scripts` entry of setuptools entrypoint feature, that install scripts in the `PATH`, when `pip install` is invoked, that delegate to specific functions in the installed packages. Two scripts are defined this way: * `certbot_test`: it executes certbot in test mode in a very similar way than the original `certbot_test` in `_common.sh`, by delegating to `certbot_integration_tests.utils.certbot_call:main`. By default this execution will target a pebble directory url started locally. The url, and also http-01/tls-alpn-01 challenge ports can be configured using ad-hoc environment variables. All arguments passed to `certbot_test` are transferred to the underlying certbot command. * `acme_server`: it set up a fully running instance of an ACME server, ready for tests (in particular, all FQDN resolves to localhost in order to target a locally running `certbot_test` command) by delegating to `certbot_integration_tests.utils.acme_server:main`. The choice of the ACME server is given by the first parameter passed to `acme_server`, it can be `pebble`, `boulder-v1` or `boulder-v2`. The command keeps running on foreground, displaying the logs of the ACME server on stdout/stderr. The server is shut down and resources cleaned upon entering CTRL+C. This two commands can be run also through the underlying python modules, that are executable. Finally, a typical workflow on certbot side to run manual tests would be: ``` cd certbot tools/venv.py source venv/bin/activate acme_server pebble & certbot_test certonly --standalone -d test.example.com ``` On boulder side it could be: ``` # Follow certbot dev environment setup instructions, then ... cd boulder docker-compose run --use-aliases -e FAKE_DNS=172.17.0.1 --service-ports boulder ./start.py SERVER=http://localhost:4001/directory certbot_test certonly --standalone -d test.example.com ``` * Configure certbot-ci to expose a certbot_test console script calling certbot in test mode against a local pebble instance * Add a command to start pebble/boulder * Use explicit start * Add execution permission to acme_server * Add a docstring to certbot_test function * Change executable name * Increase sleep to 3600s * Implement a context manager to handle the acme server * Add certbot_test workspace in .gitignore * Add documentation * Remove one function in context, split logic of certbot_test towards capturing non capturing * Use an explicit an properly configured ACMEServer as handler. * Add doc. Put constants. --- .gitignore | 3 + .../certbot_tests/context.py | 72 ++-------------- .../certbot_tests/test_main.py | 8 +- certbot-ci/certbot_integration_tests/conftest.py | 8 +- .../nginx_tests/context.py | 7 +- .../nginx_tests/test_main.py | 6 +- .../certbot_integration_tests/utils/acme_server.py | 99 ++++++++++++++++------ .../utils/certbot_call.py | 98 +++++++++++++++++++++ .../certbot_integration_tests/utils/constants.py | 8 ++ certbot-ci/setup.py | 7 ++ docs/contributing.rst | 67 ++++++++++++++- 11 files changed, 274 insertions(+), 109 deletions(-) mode change 100644 => 100755 certbot-ci/certbot_integration_tests/utils/acme_server.py create mode 100755 certbot-ci/certbot_integration_tests/utils/certbot_call.py create mode 100644 certbot-ci/certbot_integration_tests/utils/constants.py diff --git a/.gitignore b/.gitignore index 54545e883..56ec23fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ tests/letstest/venv/ # docker files .docker + +# certbot tests +.certbot_test_workspace diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index c82793d3d..c4c02a25e 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -1,12 +1,10 @@ """Module to handle the context of integration tests.""" import os import shutil -import subprocess import sys import tempfile -from distutils.version import LooseVersion -from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils import misc, certbot_call class IntegrationTestsContext(object): @@ -30,11 +28,6 @@ class IntegrationTestsContext(object): # is listening on challtestsrv_port. self.challtestsrv_port = acme_xdist['challtestsrv_port'] - # Certbot version does not depend on the test context. But getting its value requires - # calling certbot from a subprocess. Since it will be called a lot of times through - # _common_test_no_force_renew, we cache its value as a member of the fixture context. - self.certbot_version = misc.get_certbot_version() - self.workspace = tempfile.mkdtemp() self.config_dir = os.path.join(self.workspace, 'conf') self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1] @@ -60,71 +53,18 @@ class IntegrationTestsContext(object): """Cleanup the integration test context.""" shutil.rmtree(self.workspace) - def _common_test_no_force_renew(self, args): - """ - Base command to execute certbot in a distributed integration test context, - not renewing certificates by default. - """ - new_environ = os.environ.copy() - new_environ['TMPDIR'] = self.workspace - - additional_args = [] - if self.certbot_version >= LooseVersion('0.30.0'): - additional_args.append('--no-random-sleep-on-renew') - - command = [ - 'certbot', - '--server', self.directory_url, - '--no-verify-ssl', - '--http-01-port', str(self.http_01_port), - '--https-port', str(self.tls_alpn_01_port), - '--manual-public-ip-logging-ok', - '--config-dir', self.config_dir, - '--work-dir', os.path.join(self.workspace, 'work'), - '--logs-dir', os.path.join(self.workspace, 'logs'), - '--non-interactive', - '--no-redirect', - '--agree-tos', - '--register-unsafely-without-email', - '--debug', - '-vv' - ] - - command.extend(args) - command.extend(additional_args) - - print('Invoke command:\n{0}'.format(subprocess.list2cmdline(command))) - return subprocess.check_output(command, universal_newlines=True, - cwd=self.workspace, env=new_environ) - - def _common_test(self, args): - """ - Base command to execute certbot in a distributed integration test context, - renewing certificates by default. - """ - command = ['--renew-by-default'] - command.extend(args) - return self._common_test_no_force_renew(command) - - def certbot_no_force_renew(self, args): + def certbot(self, args, force_renew=True): """ Execute certbot with given args, not renewing certificates by default. :param args: args to pass to certbot + :param force_renew: set to False to not renew by default :return: output of certbot execution """ command = ['--authenticator', 'standalone', '--installer', 'null'] command.extend(args) - return self._common_test_no_force_renew(command) - - def certbot(self, args): - """ - Execute certbot with given args, renewing certificates by default. - :param args: args to pass to certbot - :return: output of certbot execution - """ - command = ['--renew-by-default'] - command.extend(args) - return self.certbot_no_force_renew(command) + return certbot_call.certbot_test( + command, self.directory_url, self.http_01_port, self.tls_alpn_01_port, + self.config_dir, self.workspace, force_renew=force_renew) def get_domain(self, subdomain='le'): """ diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 5ce19dcb8..5428f1a09 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -229,8 +229,8 @@ def test_graceful_renew_it_is_not_time(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - context.certbot_no_force_renew([ - 'renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)]) + context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 1) with pytest.raises(AssertionError): @@ -250,8 +250,8 @@ def test_graceful_renew_it_is_time(context): with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'w') as file: file.writelines(lines) - context.certbot_no_force_renew([ - 'renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)]) + context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 2) assert_hook_execution(context.hook_probe, 'deploy') diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index 892c16266..e84a866b9 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -86,7 +86,9 @@ def _setup_primary_node(config): # By calling setup_acme_server we ensure that all necessary acme server instances will be # fully started. This runtime is reflected by the acme_xdist returned. - acme_xdist = acme_lib.setup_acme_server(config.option.acme_server, workers) - print('ACME xdist config:\n{0}'.format(acme_xdist)) + acme_server = acme_lib.setup_acme_server(config.option.acme_server, workers) + config.add_cleanup(acme_server.stop) + print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist)) + acme_server.start() - return acme_xdist + return acme_server.acme_xdist diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py index 3da8a7dd9..61facc6af 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/context.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -2,7 +2,7 @@ import os import subprocess from certbot_integration_tests.certbot_tests import context as certbot_context -from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils import misc, certbot_call from certbot_integration_tests.nginx_tests import nginx_config as config @@ -33,11 +33,14 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext): """ Main command to execute certbot using the nginx plugin. :param list args: list of arguments to pass to nginx + :param bool force_renew: set to False to not renew by default """ command = ['--authenticator', 'nginx', '--installer', 'nginx', '--nginx-server-root', self.nginx_root] command.extend(args) - return self._common_test(command) + return certbot_call.certbot_test( + command, self.directory_url, self.http_01_port, self.tls_alpn_01_port, + self.config_dir, self.workspace, force_renew=True) def _start_nginx(self, default_server): self.nginx_config = config.construct_nginx_config( diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py index 176bb220a..1a62ea8d7 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/test_main.py @@ -30,6 +30,7 @@ def context(request): ('nginx6.{0}.wtf,nginx7.{0}.wtf', ['--preferred-challenges', 'http'], {'default_server': False}), ], indirect=['context']) def test_certificate_deployment(certname_pattern, params, context): + # type: (str, list, nginx_context.IntegrationTestsContext) -> None """ Test various scenarios to deploy a certificate to nginx using certbot. """ @@ -45,10 +46,7 @@ def test_certificate_deployment(certname_pattern, params, context): assert server_cert == certbot_cert - command = ['--authenticator', 'nginx', '--installer', 'nginx', - '--nginx-server-root', context.nginx_root, - 'rollback', '--checkpoints', '1'] - context._common_test_no_force_renew(command) + context.certbot_test_nginx(['rollback', '--checkpoints', '1']) with open(context.nginx_config_path, 'r') as file_h: current_nginx_config = file_h.read() diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py old mode 100644 new mode 100755 index 44010d899..ed17d1fd9 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -1,11 +1,11 @@ +#!/usr/bin/env python """Module to setup an ACME CA server environment able to run multiple tests in parallel""" from __future__ import print_function import tempfile -import atexit +import time import os import subprocess import shutil -import stat import sys from os.path import join @@ -14,33 +14,52 @@ import json import yaml from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils.constants import * -# These ports are set implicitly in the docker-compose.yml files of Boulder/Pebble. -CHALLTESTSRV_PORT = 8055 -HTTP_01_PORT = 5002 +class ACMEServer(object): + """ + Handler exposing methods to start and stop the ACME server, and get its configuration + (eg. challenges ports). ACMEServer is also a context manager, and so can be used to + ensure ACME server is started/stopped upon context enter/exit. + """ + def __init__(self, acme_xdist, start, stop): + self.acme_xdist = acme_xdist + self.start = start + self.stop = stop + + def __enter__(self): + self.start() + return self.acme_xdist + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() -def setup_acme_server(acme_server, nodes): + +def setup_acme_server(acme_server, nodes, proxy=True): """ This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel execution of integration tests against the unique http-01 port expected by the ACME CA server. - Instances are properly closed and cleaned when the Python process exits using atexit. Typically all pytest integration tests will be executed in this context. - This method returns an object describing ports and directory url to use for each pytest node - with the relevant pytest xdist node. + An ACMEServer instance will be returned, giving access to the ports and directory url to use + for each pytest node, and its start and stop methods are appropriately configured to + respectively start the server, and stop it with proper resources cleanup. :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) :param str[] nodes: list of node names that will be setup by pytest xdist - :return: a dict describing the challenge ports that have been setup for the nodes - :rtype: dict + :param bool proxy: set to False to not start the Traefik proxy + :return: a properly configured ACMEServer instance + :rtype: ACMEServer """ acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' acme_xdist = _construct_acme_xdist(acme_server, nodes) - workspace = _construct_workspace(acme_type) + workspace, stop = _construct_workspace(acme_type) - _prepare_traefik_proxy(workspace, acme_xdist) - _prepare_acme_server(workspace, acme_type, acme_xdist) + def start(): + if proxy: + _prepare_traefik_proxy(workspace, acme_xdist) + _prepare_acme_server(workspace, acme_type, acme_xdist) - return acme_xdist + return ACMEServer(acme_xdist, start, stop) def _construct_acme_xdist(acme_server, nodes): @@ -49,10 +68,10 @@ def _construct_acme_xdist(acme_server, nodes): # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. if acme_server == 'pebble': - acme_xdist['directory_url'] = 'https://localhost:14000/dir' + acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL else: # boulder - port = 4001 if acme_server == 'boulder-v2' else 4000 - acme_xdist['directory_url'] = 'http://localhost:{0}/directory'.format(port) + acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \ + if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL acme_xdist['http_port'] = {node: port for (node, port) in zip(nodes, range(5200, 5200 + len(nodes)))} @@ -82,10 +101,7 @@ def _construct_workspace(acme_type): shutil.rmtree(workspace) - # Here with atexit we ensure that clean function is called no matter what. - atexit.register(cleanup) - - return workspace + return workspace, cleanup def _prepare_acme_server(workspace, acme_type, acme_xdist): @@ -136,7 +152,6 @@ def _prepare_traefik_proxy(workspace, acme_xdist): print('=> Starting traefik instance deployment...') instance_path = join(workspace, 'traefik') traefik_subnet = '10.33.33' - traefik_api_port = 8056 try: os.mkdir(instance_path) @@ -159,12 +174,12 @@ networks: config: - subnet: {traefik_subnet}.0/24 '''.format(traefik_subnet=traefik_subnet, - traefik_api_port=traefik_api_port, + traefik_api_port=TRAEFIK_API_PORT, http_01_port=HTTP_01_PORT)) _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) - misc.check_until_timeout('http://localhost:{0}/api'.format(traefik_api_port)) + misc.check_until_timeout('http://localhost:{0}/api'.format(TRAEFIK_API_PORT)) config = { 'backends': { node: { @@ -178,7 +193,7 @@ networks: } for node in acme_xdist['http_port'].keys() } } - response = requests.put('http://localhost:{0}/api/providers/rest'.format(traefik_api_port), + response = requests.put('http://localhost:{0}/api/providers/rest'.format(TRAEFIK_API_PORT), data=json.dumps(config)) response.raise_for_status() @@ -195,3 +210,35 @@ def _launch_command(command, cwd=os.getcwd()): except subprocess.CalledProcessError as e: sys.stderr.write(e.output) raise + + +def main(): + args = sys.argv[1:] + server_type = args[0] if args else 'pebble' + possible_values = ('pebble', 'boulder-v1', 'boulder-v2') + if server_type not in possible_values: + raise ValueError('Invalid server value {0}, should be one of {1}' + .format(server_type, possible_values)) + + acme_server = setup_acme_server(server_type, [], False) + process = None + + try: + with acme_server as acme_xdist: + print('--> Instance of {0} is running, directory URL is {0}' + .format(acme_xdist['directory_url'])) + print('--> Press CTRL+C to stop the ACME server.') + + docker_name = 'pebble_pebble_1' if 'pebble' in server_type else 'boulder_boulder_1' + process = subprocess.Popen(['docker', 'logs', '-f', docker_name]) + + while True: + time.sleep(3600) + except KeyboardInterrupt: + if process: + process.terminate() + process.wait() + + +if __name__ == '__main__': + main() diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py new file mode 100755 index 000000000..1bff94e75 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +"""Module to call certbot in test mode""" +from __future__ import absolute_import +from distutils.version import LooseVersion +import subprocess +import sys +import os + +from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils.constants import * + + +def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, force_renew=True): + """ + Invoke the certbot executable available in PATH in a test context for the given args. + The test context consists in running certbot in debug mode, with various flags suitable + for tests (eg. no ssl check, customizable ACME challenge ports and config directory ...). + This command captures stdout and returns it to the caller. + :param list certbot_args: the arguments to pass to the certbot executable + :param str directory_url: URL of the ACME directory server to use + :param int http_01_port: port for the HTTP-01 challenges + :param int tls_alpn_01_port: port for the TLS-ALPN-01 challenges + :param str config_dir: certbot configuration directory to use + :param str workspace: certbot current directory to use + :param bool force_renew: set False to not force renew existing certificates (default: True) + :return: stdout as string + :rtype: str + """ + command, env = _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, force_renew) + + return subprocess.check_output(command, universal_newlines=True, cwd=workspace, env=env) + + +def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, force_renew): + new_environ = os.environ.copy() + new_environ['TMPDIR'] = workspace + + additional_args = [] + if misc.get_certbot_version() >= LooseVersion('0.30.0'): + additional_args.append('--no-random-sleep-on-renew') + + if force_renew: + additional_args.append('--renew-by-default') + + command = [ + 'certbot', + '--server', directory_url, + '--no-verify-ssl', + '--http-01-port', str(http_01_port), + '--https-port', str(tls_alpn_01_port), + '--manual-public-ip-logging-ok', + '--config-dir', config_dir, + '--work-dir', os.path.join(workspace, 'work'), + '--logs-dir', os.path.join(workspace, 'logs'), + '--non-interactive', + '--no-redirect', + '--agree-tos', + '--register-unsafely-without-email', + '--debug', + '-vv' + ] + + command.extend(certbot_args) + command.extend(additional_args) + + print('--> Invoke command:\n=====\n{0}\n====='.format(subprocess.list2cmdline(command))) + + return command, new_environ + + +def main(): + args = sys.argv[1:] + + # Default config is pebble + directory_url = os.environ.get('SERVER', PEBBLE_DIRECTORY_URL) + http_01_port = int(os.environ.get('HTTP_01_PORT', HTTP_01_PORT)) + tls_alpn_01_port = int(os.environ.get('TLS_ALPN_01_PORT', TLS_ALPN_01_PORT)) + + # Execution of certbot in a self-contained workspace + workspace = os.environ.get('WORKSPACE', os.path.join(os.getcwd(), '.certbot_test_workspace')) + if not os.path.exists(workspace): + print('--> Creating a workspace for certbot_test: {0}'.format(workspace)) + os.mkdir(workspace) + else: + print('--> Using an existing workspace for certbot_test: {0}'.format(workspace)) + config_dir = os.path.join(workspace, 'conf') + + # Invoke certbot in test mode, without capturing output so users see directly the outcome. + command, env = _prepare_args_env(args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, True) + subprocess.check_call(command, universal_newlines=True, cwd=workspace, env=env) + + +if __name__ == '__main__': + main() diff --git a/certbot-ci/certbot_integration_tests/utils/constants.py b/certbot-ci/certbot_integration_tests/utils/constants.py new file mode 100644 index 000000000..5cf6fd776 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/constants.py @@ -0,0 +1,8 @@ +"""Some useful constants to use throughout certbot-ci integration tests""" +HTTP_01_PORT = 5002 +TLS_ALPN_01_PORT = 5001 +CHALLTESTSRV_PORT = 8055 +TRAEFIK_API_PORT = 8056 +BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory' +BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory' +PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir' diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index eecbe2887..2adf137b8 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -44,4 +44,11 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + + entry_points={ + 'console_scripts': [ + 'certbot_test=certbot_integration_tests.utils.certbot_call:main', + 'run_acme_server=certbot_integration_tests.utils.acme_server:main', + ], + } ) diff --git a/docs/contributing.rst b/docs/contributing.rst index eed7d1bce..dad886da9 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -17,6 +17,8 @@ its dependencies, Certbot needs to be run on a UNIX-like OS so if you're using Windows, you'll need to set up a (virtual) machine running an OS such as Linux and continue with these instructions on that UNIX-like OS. +.. _local copy: + Running a local copy of the client ---------------------------------- @@ -89,6 +91,17 @@ tests, and be compliant with the :ref:`coding style `. Testing ------- +You can test your code in several ways: + +- running the `automated unit`_ tests, +- running the `automated integration`_ tests +- running an *ad hoc* `manual integration`_ test + +.. _automated unit: + +Running automated unit tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + When you are working in a file ``foo.py``, there should also be a file ``foo_test.py`` either in the same directory as ``foo.py`` or in the ``tests`` subdirectory (if there isn't, make one). While you are working on your code and tests, run @@ -114,16 +127,16 @@ of output can make it hard to find specific failures when they happen. config if your user has sudo permissions, so it should not be run on a production Apache server. -.. _integration: +.. _automated integration: -Integration testing with the Pebble CA -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Running automated integration tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generally it is sufficient to open a pull request and let Github and Travis run integration tests for you. However, you may want to run them locally before submitting your pull request. You need Docker and docker-compose installed and working. -The tox environment `integration` will setup Pebble, the Let's Encrypt ACME CA server +The tox environment `integration` will setup `Pebble`_, the Let's Encrypt ACME CA server for integration testing, then launch the Certbot integration tests. With a user allowed to access your local Docker daemon, run: @@ -135,6 +148,52 @@ With a user allowed to access your local Docker daemon, run: Tests will be run using pytest. A test report and a code coverage report will be displayed at the end of the integration tests execution. +.. _Pebble: https://github.com/letsencrypt/pebble + +.. _manual integration: + +Running manual integration tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also manually execute Certbot against a local instance of the `Pebble`_ ACME server. +This is useful to verify that the modifications done to the code makes Certbot behave as expected. + +To do so you need: + +- Docker installed, and a user with access to the Docker client, +- an available `local copy`_ of Certbot. + +The virtual environment set up with `python tools/venv.py` contains two commands +that can be used once the virtual environment is activated: + +.. code-block:: shell + + run_acme_server + +- Starts a local instance of Pebble and runs in the foreground printing its logs. +- Press CTRL+C to stop this instance. +- This instance is configured to validate challenges against certbot executed locally. + +.. code-block:: shell + + certbot_tests [ARGS...] + +- Execute certbot with the provided arguments and other arguments useful for testing purposes, + such as: verbose output, full tracebacks in case Certbot crashes, *etc.* +- Execution is preconfigured to interact with the Pebble CA started with ``run_acme_server``. +- Any arguments can be passed as they would be to Certbot (eg. ``certbot_test certonly -d test.example.com``). + +Here is a typical workflow to verify that Certbot successfully issued a certificate +using an HTTP-01 challenge on a machine with Python 3: + +.. code-block:: shell + + python tools/venv3.py + source venv3/bin/activate + run_acme_server & + certbot_test certonly --standalone -d test.example.com + # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C + Code components and layout ========================== -- cgit v1.2.3 From e60651057ef2c80a041c2ba13d48896bc74b9870 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 13 Jun 2019 22:27:06 +0200 Subject: Add a branch in acme_server to properly clean the boulder workspace (#7154) Currently integration tests against Boulder fail during nightly tests. See https://travis-ci.com/certbot/certbot/builds/115373954. This is due to a failure to cleanup the workspace associated to the Boulder docker started during the integration tests. Indeed this docker compile several artifacts whose owner is root, and permissions are 0744. These files are persisted in the workspace folder attached to the Docker. Since tox is run as a non-root user (but this user still have access to the Docker daemon), everything works fine until the end of the test suite, when all resources are cleaned up. At this point, pytest fires a PermissionError when failing to delete these artifacts, return with a non-zero exit code, and so fail the build. Since this situation could happen outside of the CI, I made appropriate corrections to allow the integration tests to be run as a non-root user, instead of changing Travis to execute tests as root user. The correction is to add a step to the cleanup process: the deletion of these artifacts through an ad-hoc docker instance. --- certbot-ci/certbot_integration_tests/utils/acme_server.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index ed17d1fd9..930755fc7 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -99,6 +99,14 @@ def _construct_workspace(acme_type): pass print('=> Finished tear down of {0} instance.'.format(acme_type)) + if acme_type == 'boulder' and os.path.exists(os.path.join(workspace, 'boulder')): + # Boulder docker generates build artifacts owned by root user with 0o744 permissions. + # If we started the acme server from a normal user that has access to the Docker + # daemon, this user will not be able to delete these artifacts from the host. + # We need to do it through a docker. + _launch_command(['docker', 'run', '--rm', '-v', '{0}:/workspace'.format(workspace), + 'alpine', 'rm', '-rf', '/workspace/boulder']) + shutil.rmtree(workspace) return workspace, cleanup -- cgit v1.2.3 From 1b54c74621607de55fdadfc7823f2ce5390dea50 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 13 Jun 2019 23:09:09 +0200 Subject: Remove the remaining integration tests bash scripts (#7153) Since #7073 for Certbot and letsencrypt/boulder@3918714 for Boulder have landed, the bash scripts that remained after certbot-ci are not useful anymore outside of Certbot. Only remaining place is the apacheconftest-with-pebble tox target, which leverages pebble-fetch.py script to expose a running ACME server to the apache-conf-test script. This PR refactor apacheconftest-with-pebble to use certbot-ci instead. Finally, this PR remove the remaining integration tests bash scripts, that are _common.sh, boulder-fetch.py and pebble-fetch.py. * Disconnect common and boulder-fetch * Prepare reconnection of apacheconftest to new pebble deployment logic * Finish the configuration for apacheconftest * Add executable flag to python script * Fix shebang * Delete pebble-fetch.sh --- .../apache-conf-files/apache-conf-test-pebble.py | 27 ++++++++ docs/contributing.rst | 1 - tests/boulder-fetch.sh | 32 ---------- tests/integration/_common.sh | 74 ---------------------- tests/pebble-fetch.sh | 30 --------- tox.ini | 11 +--- 6 files changed, 29 insertions(+), 146 deletions(-) create mode 100755 certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py delete mode 100755 tests/boulder-fetch.sh delete mode 100755 tests/integration/_common.sh delete mode 100755 tests/pebble-fetch.sh diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py new file mode 100755 index 000000000..34f32f2d7 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +""" +This executable script wraps the apache-conf-test bash script, in order to setup a pebble instance +before its execution. Directory URL is passed through the SERVER environment variable. +""" +import os +import subprocess +import sys + +from certbot_integration_tests.utils import acme_server + +SCRIPT_DIRNAME = os.path.dirname(__file__) + + +def main(args=None): + if not args: + args = sys.argv[1:] + with acme_server.setup_acme_server('pebble', [], False) as acme_xdist: + environ = os.environ.copy() + environ['SERVER'] = acme_xdist['directory_url'] + command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')] + command.extend(args) + return subprocess.call(command, env=environ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/docs/contributing.rst b/docs/contributing.rst index dad886da9..8685a0dd5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -302,7 +302,6 @@ virtualenv like this: .. code-block:: shell . venv/bin/activate - . tests/integration/_common.sh pip install -e examples/plugins/ certbot_test plugins diff --git a/tests/boulder-fetch.sh b/tests/boulder-fetch.sh deleted file mode 100755 index f34deb74e..000000000 --- a/tests/boulder-fetch.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Download and run Boulder instance for integration testing -set -xe - -# Clone Boulder into a GOPATH-style directory structure even if Go isn't -# installed, because Boulder's docker-compose.yml file wll look for it there. -export GOPATH=${GOPATH:-$HOME/gopath} -BOULDERPATH=${BOULDERPATH:-$GOPATH/src/github.com/letsencrypt/boulder} -if [ ! -d ${BOULDERPATH} ]; then - git clone --depth=1 https://github.com/letsencrypt/boulder ${BOULDERPATH} -fi - -cd ${BOULDERPATH} - -docker-compose up -d boulder - -set +x # reduce verbosity while waiting for boulder -for n in `seq 1 150` ; do - if curl http://localhost:4000/directory 2>/dev/null; then - break - else - sleep 1 - fi -done - -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 diff --git a/tests/integration/_common.sh b/tests/integration/_common.sh deleted file mode 100755 index a0cf3d1b4..000000000 --- a/tests/integration/_common.sh +++ /dev/null @@ -1,74 +0,0 @@ -# The -t is required on macOS. It provides a template file path for -# the kernel to use. -root=${root:-$(mktemp -d -t leitXXXX)} -echo "Root integration tests directory: $root" -config_dir="$root/conf" -https_port=5001 -http_01_port=5002 -sources="acme/,$(ls -dm certbot*/ | tr -d ' \n')" -export root config_dir https_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 \ - --renew-by-default \ - "$@" -} - -# 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/*" - omit_patterns="$omit_patterns,certbot-compatibility-test/*,certbot-dns*/" - omit_patterns="$omit_patterns,certbot-nginx/certbot_nginx/parser_obj.py" - coverage run \ - --append \ - --source $sources \ - --omit $omit_patterns \ - "$certbot_path" \ - --server "${SERVER:-http://localhost:4000/directory}" \ - --no-verify-ssl \ - --http-01-port $http_01_port \ - --https-port $https_port \ - --manual-public-ip-logging-ok \ - $other_flags \ - --non-interactive \ - --no-redirect \ - --agree-tos \ - --register-unsafely-without-email \ - --debug \ - -vv \ - "$@" -} diff --git a/tests/pebble-fetch.sh b/tests/pebble-fetch.sh deleted file mode 100755 index 52a7d8c98..000000000 --- a/tests/pebble-fetch.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Download and run Pebble instance for integration testing -set -xe - -export GOPATH=${GOPATH:-$HOME/gopath} -PEBBLEPATH=${PEBBLEPATH:-$GOPATH/pebble} -if [[ ! -d ${PEBBLEPATH} ]]; then - git clone --depth=1 https://github.com/letsencrypt/pebble ${PEBBLEPATH} -fi - -cd ${PEBBLEPATH} - -docker-compose up -d - -set +x # reduce verbosity while waiting for pebble -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 - -# Setup the DNS resolution used by pebble instance to docker host -curl -X POST -d '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 diff --git a/tox.ini b/tox.ini index fed30976f..404afc165 100644 --- a/tox.ini +++ b/tox.ini @@ -138,7 +138,6 @@ commands = mypy {[base]source_paths} [testenv:apacheconftest] -#basepython = python2.7 commands = {[base]pip_install} acme . certbot-apache certbot-compatibility-test {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules @@ -147,14 +146,8 @@ passenv = [testenv:apacheconftest-with-pebble] commands = - {toxinidir}/tests/pebble-fetch.sh - {[testenv:apacheconftest]commands} -passenv = - HOME - GOPATH - PEBBLEPATH -setenv = - SERVER=https://localhost:14000/dir + {[base]pip_install} acme . certbot-apache certbot-ci certbot-compatibility-test + {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules [testenv:nginxroundtrip] commands = -- cgit v1.2.3 From add90cef32d3989717099e5786d50cd6aae1d188 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 13 Jun 2019 15:38:39 -0700 Subject: Tell people they can add their name to AUTHORS.md. (#7155) --- pull_request_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pull_request_template.md b/pull_request_template.md index 48d408ac3..9ab07e3aa 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,2 +1,4 @@ Be sure to edit the `master` section of `CHANGELOG.md` to include a description of the change being made in this PR. + +You are also welcome to add your name to `AUTHORS.md` if you like. -- cgit v1.2.3 From 6c53f5d8ed2875a8ef17906d44db476bdbc580db Mon Sep 17 00:00:00 2001 From: sydneyli Date: Fri, 14 Jun 2019 13:44:50 -0700 Subject: Turn off session tickets for versions of Nginx that support it (#7092) * Turn off session tickets for versions of Nginx that support it In line with Mozilla's security recommendations. * Changelog. * Set version before installing config files * lint: remove unused import * windows testfix * another windows testfix? * Testing path of updating src file with old nginx * Fix windows, and make config update tests fail if update doesn't happen --- CHANGELOG.md | 2 +- certbot-nginx/MANIFEST.in | 1 + certbot-nginx/certbot_nginx/configurator.py | 29 +++++++++----- certbot-nginx/certbot_nginx/constants.py | 13 +++--- .../certbot_nginx/options-ssl-nginx-old.conf | 13 ++++++ certbot-nginx/certbot_nginx/options-ssl-nginx.conf | 1 + .../certbot_nginx/tests/configurator_test.py | 46 +++++++++++++++++----- 7 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 9919910f7..6e76970b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Turn off session tickets for nginx plugin by default ### Changed diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index 2daca6738..8707f9443 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -3,3 +3,4 @@ include README.rst recursive-include docs * recursive-include certbot_nginx/tests/testdata * include certbot_nginx/options-ssl-nginx.conf +include certbot_nginx/options-ssl-nginx-old.conf diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 6da00e513..e078ad4cb 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -6,6 +6,8 @@ import subprocess import tempfile import time +import pkg_resources + import OpenSSL import zope.interface @@ -120,6 +122,14 @@ class NginxConfigurator(common.Installer): self.reverter.recovery_routine() + @property + def mod_ssl_conf_src(self): + """Full absolute path to SSL configuration file source.""" + config_filename = "options-ssl-nginx.conf" + if self.version < (1, 5, 9): + config_filename = "options-ssl-nginx-old.conf" + return pkg_resources.resource_filename("certbot_nginx", config_filename) + @property def mod_ssl_conf(self): """Full absolute path to SSL configuration file.""" @@ -130,6 +140,11 @@ class NginxConfigurator(common.Installer): """Full absolute path to digest of updated SSL configuration file.""" return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) + def install_ssl_options_conf(self, options_ssl, options_ssl_digest): + """Copy Certbot's SSL options file into the system's config dir if required.""" + return common.install_version_controlled_file(options_ssl, options_ssl_digest, + self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES) + # This is called in determine_authenticator and determine_installer def prepare(self): """Prepare the authenticator/installer. @@ -148,14 +163,14 @@ class NginxConfigurator(common.Installer): self.parser = parser.NginxParser(self.conf('server-root')) - install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) - - self.install_ssl_dhparams() - # Set Version if self.version is None: self.version = self.get_version() + self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) + + self.install_ssl_dhparams() + # Prevent two Nginx plugins from modifying a config at once try: util.lock_dir_until_exit(self.conf('server-root')) @@ -1131,12 +1146,6 @@ def nginx_restart(nginx_ctl, nginx_conf): time.sleep(1) -def install_ssl_options_conf(options_ssl, options_ssl_digest): - """Copy Certbot's SSL options file into the system's config dir if required.""" - return common.install_version_controlled_file(options_ssl, options_ssl_digest, - constants.MOD_SSL_CONF_SRC, constants.ALL_SSL_OPTIONS_HASHES) - - def _determine_default_server_root(): if os.environ.get("CERTBOT_DOCS") == "1": default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT, diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index 41716db0f..cec7acaf5 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -1,8 +1,6 @@ """nginx plugin constants.""" import platform -import pkg_resources - FREEBSD_DARWIN_SERVER_ROOT = "/usr/local/etc/nginx" LINUX_SERVER_ROOT = "/etc/nginx" @@ -21,14 +19,13 @@ CLI_DEFAULTS = dict( MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" """Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" -MOD_SSL_CONF_SRC = pkg_resources.resource_filename( - "certbot_nginx", "options-ssl-nginx.conf") -"""Path to the nginx mod_ssl config file found in the Certbot -distribution.""" - UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" +SSL_OPTIONS_HASHES_NEW = [ + '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', +] +"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9""" ALL_SSL_OPTIONS_HASHES = [ '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', @@ -37,7 +34,7 @@ ALL_SSL_OPTIONS_HASHES = [ '7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff', '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', -] +] + SSL_OPTIONS_HASHES_NEW """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" def os_constant(key): diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf new file mode 100644 index 000000000..292d42984 --- /dev/null +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:1m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; + +ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf index 292d42984..57a332d2f 100644 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf @@ -6,6 +6,7 @@ ssl_session_cache shared:le_nginx_SSL:1m; ssl_session_timeout 1440m; +ssl_session_tickets off; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 6c3f4f0cf..5e9d61a44 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -13,7 +13,6 @@ from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_test_util -from certbot_nginx import constants from certbot_nginx import obj from certbot_nginx import parser from certbot_nginx.configurator import _redirect_block_for_domain @@ -883,12 +882,11 @@ class InstallSslOptionsConfTest(util.NginxTest): self.config_path, self.config_dir, self.work_dir, self.logs_dir) def _call(self): - from certbot_nginx.configurator import install_ssl_options_conf - install_ssl_options_conf(self.config.mod_ssl_conf, self.config.updated_mod_ssl_conf_digest) + self.config.install_ssl_options_conf(self.config.mod_ssl_conf, + self.config.updated_mod_ssl_conf_digest) def _current_ssl_options_hash(self): - from certbot_nginx.constants import MOD_SSL_CONF_SRC - return crypto_util.sha256sum(MOD_SSL_CONF_SRC) + return crypto_util.sha256sum(self.config.mod_ssl_conf_src) def _assert_current_file(self): self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) @@ -908,12 +906,32 @@ class InstallSslOptionsConfTest(util.NginxTest): self._call() self._assert_current_file() + def _mock_hash_except_ssl_conf_src(self, fake_hash): + # Write a bad file in place so that update tests fail if no update occurs. + # We're going to pretend this file (the currently installed conf file) + # actually hashes to `fake_hash` for the update tests. + with open(self.config.mod_ssl_conf, "w") as f: + f.write("bogus") + sha256 = crypto_util.sha256sum + def _hash(filename): + return sha256(filename) if filename == self.config.mod_ssl_conf_src else fake_hash + return _hash + def test_prev_file_updates_to_current(self): from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES - with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: - mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] + with mock.patch('certbot.crypto_util.sha256sum', + new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): + self._call() + self._assert_current_file() + + def test_prev_file_updates_to_current_old_nginx(self): + from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES, SSL_OPTIONS_HASHES_NEW + self.config.version = (1, 5, 8) + with mock.patch('certbot.crypto_util.sha256sum', + new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() + self.assertTrue(self._current_ssl_options_hash() not in SSL_OPTIONS_HASHES_NEW) def test_manually_modified_current_file_does_not_update(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: @@ -922,7 +940,7 @@ class InstallSslOptionsConfTest(util.NginxTest): self._call() self.assertFalse(mock_logger.warning.called) self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(constants.MOD_SSL_CONF_SRC), + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), self._current_ssl_options_hash()) self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) @@ -937,7 +955,7 @@ class InstallSslOptionsConfTest(util.NginxTest): self.assertEqual(mock_logger.warning.call_args[0][0], "%s has been manually modified; updated file " "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum(constants.MOD_SSL_CONF_SRC), + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), self._current_ssl_options_hash()) # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: @@ -950,6 +968,16 @@ class InstallSslOptionsConfTest(util.NginxTest): "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") + def test_old_nginx_version_uses_old_config(self): + self.config.version = (1, 5, 8) + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-old.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 5, 9) + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx.conf") + class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): """Tests for certbot_nginx.configurator._determine_default_server_root.""" -- cgit v1.2.3 From 20ca47dec6111b55fa1f59d1b17fc3b242469f06 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 14 Jun 2019 15:51:15 -0700 Subject: Bump stale threshold to 1 year. (#7149) While I expect stale bot will close out 150 - 250 issues, that'll still leave us with 400+ open issues. My concern is that with a threshold of 6 months, most of these 400 issues will be in the same state 6 months from now and stale bot will annoy people by asking them if their issue is still valid too frequently. Doubling the stale threshold to 1 year should mitigate this problem a bit I think. --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index 6b317a4b5..9c095ebd6 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for https://github.com/marketplace/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 180 +daysUntilStale: 365 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -- cgit v1.2.3 From 1df778859b7ace699c02039b269abd426058a237 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Sat, 15 Jun 2019 01:28:14 +0200 Subject: [Windows|Linux] Use builtin Python proxy capabilities for Certbot-CI (#7156) This PR is a part of the actions necessary to make Certbot-CI work on Windows, in order to execute the integration tests on this platform. I initially used the fully-fledged HTTP proxy [Traefik](https://docs.traefik.io/) to distribute HTTP challenges among several pytest nodes, and so parallelize the integration tests. Traefik for this purpose is overkill. We just want to redirect the ACME server to a pytest node depending on the `Host` header, and we use here a production-grade HTTP proxy for that. However it was not a problem on Linux, as soon as you can have Docker, because this instance is deployed through it. But this becomes a problem for Windows, where Docker is not available everywhere, very compelling on its setup, and limited by the implemented network drivers. See my comments here https://github.com/letsencrypt/pebble/pull/240 for more details. Hopefully Python ships with everything needed to implement a simple HTTP proxy, with strictly what we need for the parallelization of integration tests. This PR implements this kind of HTTP proxy, and remove the coupling to Traefik. This PR has been tested successfully with integration tests on Pebble under Linux for Python 2.x and Python 3.x, and the proxy alone has been also tested successfully on Windows (no integration tests can be run for now on this platform). * Create a python proxy * Refactor proxy config * Working logic * Resolve from the path * Give proxy process to the ACMEServer context manager --- .../certbot_integration_tests/utils/acme_server.py | 107 +++++++-------------- .../certbot_integration_tests/utils/constants.py | 1 - .../certbot_integration_tests/utils/proxy.py | 36 +++++++ 3 files changed, 71 insertions(+), 73 deletions(-) create mode 100644 certbot-ci/certbot_integration_tests/utils/proxy.py diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index 930755fc7..f8f4b2c69 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """Module to setup an ACME CA server environment able to run multiple tests in parallel""" from __future__ import print_function +import json import tempfile import time import os @@ -10,10 +11,9 @@ import sys from os.path import join import requests -import json import yaml -from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils import misc, proxy from certbot_integration_tests.utils.constants import * @@ -23,13 +23,20 @@ class ACMEServer(object): (eg. challenges ports). ACMEServer is also a context manager, and so can be used to ensure ACME server is started/stopped upon context enter/exit. """ - def __init__(self, acme_xdist, start, stop): + def __init__(self, acme_xdist, start, server_cleanup): + self._proxy_process = None + self._server_cleanup = server_cleanup self.acme_xdist = acme_xdist self.start = start - self.stop = stop + + def stop(self): + if self._proxy_process: + self._proxy_process.terminate() + self._proxy_process.wait() + self._server_cleanup() def __enter__(self): - self.start() + self._proxy_process = self.start() return self.acme_xdist def __exit__(self, exc_type, exc_val, exc_tb): @@ -46,20 +53,21 @@ def setup_acme_server(acme_server, nodes, proxy=True): respectively start the server, and stop it with proper resources cleanup. :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) :param str[] nodes: list of node names that will be setup by pytest xdist - :param bool proxy: set to False to not start the Traefik proxy + :param bool proxy: set to False to not start the HTTP proxy :return: a properly configured ACMEServer instance :rtype: ACMEServer """ acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' acme_xdist = _construct_acme_xdist(acme_server, nodes) - workspace, stop = _construct_workspace(acme_type) + workspace, server_cleanup = _construct_workspace(acme_type) def start(): - if proxy: - _prepare_traefik_proxy(workspace, acme_xdist) + proxy_process = _prepare_http_proxy(acme_xdist) if proxy else None _prepare_acme_server(workspace, acme_type, acme_xdist) - return ACMEServer(acme_xdist, start, stop) + return proxy_process + + return ACMEServer(acme_xdist, start, server_cleanup) def _construct_acme_xdist(acme_server, nodes): @@ -89,15 +97,14 @@ def _construct_workspace(acme_type): def cleanup(): """Cleanup function to call that will teardown relevant dockers and their configuration.""" - for instance in [acme_type, 'traefik']: - print('=> Tear down the {0} instance...'.format(instance)) - instance_path = join(workspace, instance) - try: - if os.path.isfile(join(instance_path, 'docker-compose.yml')): - _launch_command(['docker-compose', 'down'], cwd=instance_path) - except subprocess.CalledProcessError: - pass - print('=> Finished tear down of {0} instance.'.format(acme_type)) + print('=> Tear down the {0} instance...'.format(acme_type)) + instance_path = join(workspace, acme_type) + try: + if os.path.isfile(join(instance_path, 'docker-compose.yml')): + _launch_command(['docker-compose', 'down'], cwd=instance_path) + except subprocess.CalledProcessError: + pass + print('=> Finished tear down of {0} instance.'.format(acme_type)) if acme_type == 'boulder' and os.path.exists(os.path.join(workspace, 'boulder')): # Boulder docker generates build artifacts owned by root user with 0o744 permissions. @@ -155,60 +162,16 @@ def _prepare_acme_server(workspace, acme_type, acme_xdist): raise -def _prepare_traefik_proxy(workspace, acme_xdist): - """Configure and launch Traefik, the HTTP reverse proxy""" - print('=> Starting traefik instance deployment...') - instance_path = join(workspace, 'traefik') - traefik_subnet = '10.33.33' - try: - os.mkdir(instance_path) - - with open(join(instance_path, 'docker-compose.yml'), 'w') as file_h: - file_h.write('''\ -version: '3' -services: - traefik: - image: traefik - command: --api --rest - ports: - - {http_01_port}:80 - - {traefik_api_port}:8080 - networks: - traefiknet: - ipv4_address: {traefik_subnet}.2 -networks: - traefiknet: - ipam: - config: - - subnet: {traefik_subnet}.0/24 -'''.format(traefik_subnet=traefik_subnet, - traefik_api_port=TRAEFIK_API_PORT, - http_01_port=HTTP_01_PORT)) +def _prepare_http_proxy(acme_xdist): + """Configure and launch an HTTP proxy""" + print('=> Configuring the HTTP proxy...') + mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) + for node, port in acme_xdist['http_port'].items()} + command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)] + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + print('=> Finished configuring the HTTP proxy.') - _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) - - misc.check_until_timeout('http://localhost:{0}/api'.format(TRAEFIK_API_PORT)) - config = { - 'backends': { - node: { - 'servers': {node: {'url': 'http://{0}.1:{1}'.format(traefik_subnet, port)}} - } for node, port in acme_xdist['http_port'].items() - }, - 'frontends': { - node: { - 'backend': node, 'passHostHeader': True, - 'routes': {node: {'rule': 'HostRegexp: {{subdomain:.+}}.{0}.wtf'.format(node)}} - } for node in acme_xdist['http_port'].keys() - } - } - response = requests.put('http://localhost:{0}/api/providers/rest'.format(TRAEFIK_API_PORT), - data=json.dumps(config)) - response.raise_for_status() - - print('=> Finished traefik instance deployment.') - except BaseException: - print('Error while setting up traefik instance.') - raise + return process def _launch_command(command, cwd=os.getcwd()): diff --git a/certbot-ci/certbot_integration_tests/utils/constants.py b/certbot-ci/certbot_integration_tests/utils/constants.py index 5cf6fd776..9266e25ea 100644 --- a/certbot-ci/certbot_integration_tests/utils/constants.py +++ b/certbot-ci/certbot_integration_tests/utils/constants.py @@ -2,7 +2,6 @@ HTTP_01_PORT = 5002 TLS_ALPN_01_PORT = 5001 CHALLTESTSRV_PORT = 8055 -TRAEFIK_API_PORT = 8056 BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory' BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory' PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir' diff --git a/certbot-ci/certbot_integration_tests/utils/proxy.py b/certbot-ci/certbot_integration_tests/utils/proxy.py new file mode 100644 index 000000000..69248c771 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/proxy.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +import json +import sys +import re + +import requests +from six.moves import BaseHTTPServer + +from certbot_integration_tests.utils.misc import GracefulTCPServer + + +def _create_proxy(mapping): + class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + headers = {key.lower(): value for key, value in self.headers.items()} + backend = [backend for pattern, backend in mapping.items() + if re.match(pattern, headers['host'])][0] + response = requests.get(backend + self.path, headers=headers) + + self.send_response(response.status_code) + for key, value in response.headers.items(): + self.send_header(key, value) + self.end_headers() + self.wfile.write(response.content) + + return ProxyHandler + + +if __name__ == '__main__': + http_port = int(sys.argv[1]) + port_mapping = json.loads(sys.argv[2]) + httpd = GracefulTCPServer(('', http_port), _create_proxy(port_mapping)) + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass -- cgit v1.2.3 From dde16df77830a4d25f305b03acfe914a984c9bfc Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 17 Jun 2019 15:56:06 -0700 Subject: Fixes #3400. (#7162) The person who wrote this code no longer works on Certbot and regardless of what the intended behavior was, let's document the actual behavior. --- certbot/cli.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/certbot/cli.py b/certbot/cli.py index 3334352db..794242c24 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -751,9 +751,10 @@ class HelpfulArgumentParser(object): """Add a new command line argument. :param topics: str or [str] help topic(s) this should be listed under, - or None for "always documented". The first entry - determines where the flag lives in the "--help all" - output (None -> "optional arguments"). + or None for options that don't fit under a specific + topic which will only be shown in "--help all" output. + The first entry determines where the flag lives in the + "--help all" output (None -> "optional arguments"). :param list *args: the names of this argument flag :param dict **kwargs: various argparse settings for this argument -- cgit v1.2.3 From 9863c2d18e3131e8129bfc223205d67fb74931ac Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 18 Jun 2019 12:07:45 -0700 Subject: Update Ubuntu 18.04 AMI to fix blocking on input (#7166) --- tests/letstest/apache2_targets.yaml | 2 +- tests/letstest/targets.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 44faf8027..5ee2632a0 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -6,7 +6,7 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-012fd5eb46f56731f + - ami: ami-095192256fe1477ad name: ubuntu18.04LTS type: ubuntu virt: hvm diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 1ca605b5d..547c33ffa 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -6,7 +6,7 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-012fd5eb46f56731f + - ami: ami-095192256fe1477ad name: ubuntu18.04LTS type: ubuntu virt: hvm -- cgit v1.2.3 From 8efe3fb19a4aa54ba7dff3362e55dc6141ab6ef4 Mon Sep 17 00:00:00 2001 From: David Drobner Date: Tue, 18 Jun 2019 20:29:53 -0400 Subject: RFC8555 Messages (#7131) Add new error types and descriptions from RFC 8555 to acme (#7116) --- CHANGELOG.md | 1 + acme/acme/messages.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e76970b7..a2c0e4738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added * Turn off session tickets for nginx plugin by default +* Added missing error types from RFC8555 to acme ### Changed diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 14ad011f6..df96b5f2b 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -18,20 +18,35 @@ OLD_ERROR_PREFIX = "urn:acme:error:" ERROR_PREFIX = "urn:ietf:params:acme:error:" ERROR_CODES = { + 'accountDoesNotExist': 'The request specified an account that does not exist', + 'alreadyRevoked': 'The request specified a certificate to be revoked that has' \ + ' already been revoked', 'badCSR': 'The CSR is unacceptable (e.g., due to a short key)', 'badNonce': 'The client sent an unacceptable anti-replay nonce', + 'badPublicKey': 'The JWS was signed by a public key the server does not support', + 'badRevocationReason': 'The revocation reason provided is not allowed by the server', + 'badSignatureAlgorithm': 'The JWS was signed with an algorithm the server does not support', + 'caa': 'Certification Authority Authorization (CAA) records forbid the CA from issuing' \ + ' a certificate', + 'compound': 'Specific error conditions are indicated in the "subproblems" array', 'connection': ('The server could not connect to the client to verify the' ' domain'), + 'dns': 'There was a problem with a DNS query during identifier validation', 'dnssec': 'The server could not validate a DNSSEC signed domain', + 'incorrectResponse': 'Response recieved didn\'t match the challenge\'s requirements', # deprecate invalidEmail 'invalidEmail': 'The provided email for a registration was invalid', 'invalidContact': 'The provided contact URI was invalid', 'malformed': 'The request message was malformed', + 'rejectedIdentifier': 'The server will not issue certificates for the identifier', + 'orderNotReady': 'The request attempted to finalize an order that is not ready to be finalized', 'rateLimited': 'There were too many requests of a given type', 'serverInternal': 'The server experienced an internal error', 'tls': 'The server experienced a TLS error during domain verification', 'unauthorized': 'The client lacks sufficient authorization', + 'unsupportedContact': 'A contact URL for an account used an unsupported protocol scheme', 'unknownHost': 'The server could not resolve a domain name', + 'unsupportedIdentifier': 'An identifier is of an unsupported type', 'externalAccountRequired': 'The server requires external account binding', } -- cgit v1.2.3 From 5078b58de9465ad9cf4da5422387822c29f6a0b5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 19 Jun 2019 14:09:30 -0700 Subject: Upgrade to the latest macOS image (#7167) This fixes the test failures we saw last night at https://travis-ci.com/certbot/certbot/builds/116073070. The problem is that the Homebrew installation included in the Travis image is outdated and when it tries to install packages, it fails. You can see this at https://travis-ci.com/certbot/certbot/jobs/209185570#L83. There is a thread in Travis' community froum about this at https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. To fix this, we could either upgrade Hombrew which can be a slow process according to both Travis and the original poster of the issue or we could upgrade to a newer version of macOS. I chose the latter to avoid the speed problems and picked the latest version available. You can see tests passing with these changes at https://travis-ci.com/certbot/certbot/builds/116186095. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index a3f7490f8..d7d328c8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -234,6 +234,9 @@ matrix: - language: generic env: TOXENV=py27 os: osx + # Using this osx_image is a workaround for + # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. + osx_image: xcode10.2 addons: homebrew: packages: @@ -243,6 +246,9 @@ matrix: - language: generic env: TOXENV=py3 os: osx + # Using this osx_image is a workaround for + # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. + osx_image: xcode10.2 addons: homebrew: packages: -- cgit v1.2.3 From e9bcaaa576eb3bb29a71252f64c6cbde03d230d5 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 20 Jun 2019 19:52:43 +0200 Subject: [Windows] Security model for files permissions - STEP 3a (#6964) This PR implements the filesystem.chmod method from #6497. * Implement filesystem.chmod * Conditionally add pywin32 on setuptools versions that support environment markers. * Update apache plugin requirements * Use a try/except import approach similar to lock * Add comments about well-known SIDs * Add main command * Call filesystem.chmod in tests, remove one test * Add test for os module * Update environment marker * Ensure we are not building wheels using an old version of setuptools * Added a link to list of NTFS rights * Simplify sid comparison * Enable coverage * Sometimes, double-quote is the solution * Add entrypoint * Add unit tests to filesystem * Resolve recursively the link, add doc * Move imports to the top of the file * Remove string conversion of the ACL, fix setup * Ensure admins have all permissions * Simplify dacl comparison * Conditionally raise for windows temporary workaround * Add a test to check filesystem.chown is protected against symlink loops --- certbot-apache/certbot_apache/http_01.py | 5 +- .../certbot_apache/tests/configurator_test.py | 3 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 2 +- certbot/compat/filesystem.py | 155 ++++++++++++++++++ certbot/compat/os.py | 19 +++ certbot/plugins/common.py | 7 +- certbot/plugins/dns_test_common.py | 4 +- certbot/plugins/webroot_test.py | 5 +- certbot/storage.py | 4 +- certbot/tests/client_test.py | 3 +- certbot/tests/compat/filesystem_test.py | 175 +++++++++++++++++++++ certbot/tests/compat/os_test.py | 15 ++ certbot/tests/hook_test.py | 5 +- certbot/tests/storage_test.py | 9 +- certbot/tests/util.py | 10 +- certbot/tests/util_test.py | 9 +- setup.py | 14 +- tox.cover.py | 2 +- tox.ini | 2 +- 20 files changed, 421 insertions(+), 29 deletions(-) create mode 100644 certbot/tests/compat/os_test.py diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py index 1d1f99919..a6c271aa5 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/http_01.py @@ -5,6 +5,7 @@ from acme.magic_typing import List, Set # pylint: disable=unused-import, no-nam from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins import common from certbot_apache.obj import VirtualHost # pylint: disable=unused-import @@ -169,7 +170,7 @@ class ApacheHttp01(common.TLSSNI01): def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): os.makedirs(self.challenge_dir) - os.chmod(self.challenge_dir, 0o755) + filesystem.chmod(self.challenge_dir, 0o755) responses = [] for achall in self.achalls: @@ -185,7 +186,7 @@ class ApacheHttp01(common.TLSSNI01): self.configurator.reverter.register_file_creation(True, name) with open(name, 'wb') as f: f.write(validation.encode()) - os.chmod(name, 0o644) + filesystem.chmod(name, 0o644) return response diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 5b2884eb2..906232596 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -16,6 +16,7 @@ from certbot import achallenges from certbot import crypto_util from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import acme_util from certbot.tests import util as certbot_util @@ -1366,7 +1367,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules.add("mod_ssl.c") self.config.parser.modules.add("socache_shmcb_module") tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot")) - os.chmod(tmp_path, 0o755) + filesystem.chmod(tmp_path, 0o755) mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path" mock_a = "certbot_apache.parser.ApacheParser.add_include" diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 396f1ccf2..6e6f0277f 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '0.36.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.36.0.dev0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 8025c9ad0..5dc01a622 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -2,6 +2,38 @@ from __future__ import absolute_import import os # pylint: disable=os-module-forbidden +import stat + +try: + import ntsecuritycon # pylint: disable=import-error + import win32security # pylint: disable=import-error +except ImportError: + POSIX_MODE = True +else: + POSIX_MODE = False + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + + +def chmod(file_path, mode): + # type: (str, int) -> None + """ + Apply a POSIX mode on given file_path: + * for Linux, the POSIX mode will be directly applied using chmod, + * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for + Certbot context, and applied to the file using kernel calls. + + The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, + is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the + method _generate_windows_flags(). + + :param str file_path: Path of the file + :param int mode: POSIX mode to apply + """ + if POSIX_MODE: + os.chmod(file_path, mode) + else: + _apply_win_mode(file_path, mode) def replace(src, dst): @@ -18,3 +50,126 @@ def replace(src, dst): else: # Otherwise, use os.rename() that behaves like os.replace() on Linux. os.rename(src, dst) + + +def _apply_win_mode(file_path, mode): + """ + This function converts the given POSIX mode into a Windows ACL list, and applies it to the + file given its path. If the given path is a symbolic link, it will resolved to apply the + mode on the targeted file. + """ + original_path = file_path + inspected_paths = [] # type: List[str] + while os.path.islink(file_path): + link_path = file_path + file_path = os.readlink(file_path) + if not os.path.isabs(file_path): + file_path = os.path.join(os.path.dirname(link_path), file_path) + if file_path in inspected_paths: + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + inspected_paths.append(file_path) + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # New DACL, that will overwrite existing one (including inherited permissions) + dacl = _generate_dacl(user, mode) + + # Apply the new DACL + security.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) + + +def _generate_dacl(user_sid, mode): + analysis = _analyze_mode(mode) + + # Get standard accounts from "well-known" sid + # See the list here: + # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + system = win32security.ConvertStringSidToSid('S-1-5-18') + admins = win32security.ConvertStringSidToSid('S-1-5-32-544') + everyone = win32security.ConvertStringSidToSid('S-1-1-0') + + # New dacl, without inherited permissions + dacl = win32security.ACL() + + # If user is already system or admins, any ACE defined here would be superseded by + # the full control ACE that will be added after. + if user_sid not in [system, admins]: + # Handle user rights + user_flags = _generate_windows_flags(analysis['user']) + if user_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) + + # Handle everybody rights + everybody_flags = _generate_windows_flags(analysis['all']) + if everybody_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) + + # Handle administrator rights + full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) + + return dacl + + +def _analyze_mode(mode): + return { + 'user': { + 'read': mode & stat.S_IRUSR, + 'write': mode & stat.S_IWUSR, + 'execute': mode & stat.S_IXUSR, + }, + 'all': { + 'read': mode & stat.S_IROTH, + 'write': mode & stat.S_IWOTH, + 'execute': mode & stat.S_IXOTH, + }, + } + + +def _generate_windows_flags(rights_desc): + # Some notes about how each POSIX right is interpreted. + # + # For the rights read and execute, we have a pretty bijective relation between + # POSIX flags and their generic counterparts on Windows, so we use them directly + # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). + # + # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a + # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or + # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. + # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS + # substracted of the rights corresponding to POSIX read and POSIX execute. + # + # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, + # so a "Full Control" on the file. + # + # A complete list of the rights defined on NTFS can be found here: + # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders + flag = 0 + if rights_desc['read']: + flag = flag | ntsecuritycon.FILE_GENERIC_READ + if rights_desc['write']: + flag = flag | (ntsecuritycon.FILE_ALL_ACCESS + ^ ntsecuritycon.FILE_GENERIC_READ + ^ ntsecuritycon.FILE_GENERIC_EXECUTE + # Despite bit `512` being present in ntsecuritycon.FILE_ALL_ACCESS, it is + # not effectively applied to the file or the directory. + # As _generate_windows_flags is also used to compare two dacls, we remove + # it right now to have flags that contain only the bits effectively applied + # by Windows. + ^ 512) + if rights_desc['execute']: + flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE + + return flag + + +def _compare_dacls(dacl1, dacl2): + """ + This method compare the two given DACLs to check if they are identical. + Identical means here that they contains the same set of ACEs in the same order. + """ + return ([dacl1.GetAce(index) for index in range(0, dacl1.GetAceCount())] == + [dacl2.GetAce(index) for index in range(0, dacl2.GetAceCount())]) diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 68f4e7cf6..f704055f4 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -32,6 +32,25 @@ std_sys.modules[__name__ + '.path'] = path del ourselves, std_os, std_sys +# Chmod is the root of all evil for our security model on Windows. With the default implementation +# of os.chmod on Windows, almost all bits on mode will be ignored, and only a general RO or RW will +# be applied. The DACL, the inner mechanism to control file access on Windows, will stay on its +# default definition, giving effectively at least read permissions to any one, as the default +# permissions on root path will be inherit by the file (as NTFS state), and root path can be read +# by anyone. So the given mode needs to be translated into a secured and not inherited DACL that +# will be applied to this file using filesystem.chmod, calling internally the win32security +# module to construct and apply the DACL. Complete security model to translate a POSIX mode into +# a suitable DACL on Windows for Certbot can be found here: +# https://github.com/certbot/certbot/issues/6356 +# Basically, it states that appropriate permissions will be set for the owner, nothing for the +# group, appropriate permissions for the "Everyone" group, and all permissions to the +# "Administrators" group + "System" user, as they can do everything anyway. +def chmod(*unused_args, **unused_kwargs): # pylint: disable=function-redefined + """Method os.chmod() is forbidden""" + raise RuntimeError('Usage of os.chmod() is forbidden. ' + 'Use certbot.compat.filesystem.chmod() instead.') + + # Because of the blocking strategy on file handlers on Windows, rename does not behave as expected # with POSIX systems: an exception will be raised if dst already exists. def rename(*unused_args, **unused_kwargs): diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 3dd9534db..820e86a13 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -20,6 +20,7 @@ from certbot import interfaces from certbot import reverter from certbot import util from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins.storage import PluginStorage logger = logging.getLogger(__name__) @@ -482,9 +483,9 @@ def dir_setup(test_dir, pkg): # pragma: no cover config_dir = expanded_tempdir("config") work_dir = expanded_tempdir("work") - os.chmod(temp_dir, constants.CONFIG_DIRS_MODE) - os.chmod(config_dir, constants.CONFIG_DIRS_MODE) - os.chmod(work_dir, constants.CONFIG_DIRS_MODE) + filesystem.chmod(temp_dir, constants.CONFIG_DIRS_MODE) + filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE) + filesystem.chmod(work_dir, constants.CONFIG_DIRS_MODE) test_configs = pkg_resources.resource_filename( pkg, os.path.join("testdata", test_dir)) diff --git a/certbot/plugins/dns_test_common.py b/certbot/plugins/dns_test_common.py index 7f57b9431..0fc0c9a71 100644 --- a/certbot/plugins/dns_test_common.py +++ b/certbot/plugins/dns_test_common.py @@ -8,7 +8,7 @@ import six from acme import challenges from certbot import achallenges -from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import acme_util from certbot.tests import util as test_util @@ -60,4 +60,4 @@ def write(values, path): with open(path, "wb") as f: config.write(outfile=f) - os.chmod(path, 0o600) + filesystem.chmod(path, 0o600) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index bca1045d8..2d15c46fb 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -19,6 +19,7 @@ from certbot import achallenges from certbot import errors from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem from certbot.display import util as display_util from certbot.tests import acme_util from certbot.tests import util as test_util @@ -132,14 +133,14 @@ class AuthenticatorTest(unittest.TestCase): permission_canary = os.path.join(self.path, "rnd") with open(permission_canary, "w") as f: f.write("thingimy") - os.chmod(self.path, 0o000) + filesystem.chmod(self.path, 0o000) try: open(permission_canary, "r") print("Warning, running tests as root skips permissions tests...") except IOError: # ok, permissions work, test away... self.assertRaises(errors.PluginError, self.auth.perform, []) - os.chmod(self.path, 0o700) + filesystem.chmod(self.path, 0o700) @test_util.skip_on_windows('On Windows, there is no chown.') @mock.patch("certbot.plugins.webroot.os.chown") diff --git a/certbot/storage.py b/certbot/storage.py index 048b224d7..bfec72c40 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -143,7 +143,7 @@ def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_d # Copy permissions from the old version of the file, if it exists. if os.path.exists(o_filename): current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) - os.chmod(n_filename, current_permissions) + filesystem.chmod(n_filename, current_permissions) with open(n_filename, "wb") as f: config.write(outfile=f) @@ -1110,7 +1110,7 @@ class RenewableCert(object): 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) + filesystem.chmod(target["privkey"], mode) # Save everything else with open(target["cert"], "wb") as f: diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 844758ad5..8d2cd76b9 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -12,6 +12,7 @@ import certbot.tests.util as test_util from certbot import account from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot import util KEY = test_util.load_vector("rsa512_key.pem") @@ -423,7 +424,7 @@ class ClientTest(ClientTestCommon): # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() - os.chmod(tmp_path, 0o755) # TODO: really?? + filesystem.chmod(tmp_path, 0o755) # TODO: really?? cert_pem = test_util.load_vector(certs[0]) chain_pem = (test_util.load_vector(certs[0]) + test_util.load_vector(certs[1])) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 591ee87c3..3d1363e6a 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -1,7 +1,157 @@ """Tests for certbot.compat.filesystem""" +import unittest + +try: + import win32api # pylint: disable=import-error + import win32security # pylint: disable=import-error + import ntsecuritycon # pylint: disable=import-error + POSIX_MODE = False +except ImportError: + POSIX_MODE = True + import certbot.tests.util as test_util from certbot.compat import os from certbot.compat import filesystem +from certbot.tests.util import TempDirTestCase + + +EVERYBODY_SID = 'S-1-1-0' +SYSTEM_SID = 'S-1-5-18' +ADMINS_SID = 'S-1-5-32-544' + + +@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') +class WindowsChmodTests(TempDirTestCase): + """Unit tests for Windows chmod function in filesystem module""" + def setUp(self): + super(WindowsChmodTests, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_symlink_resolution(self): + link_path = os.path.join(self.tempdir, 'link') + os.symlink(self.probe_path, link_path) + + ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + ref_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl() + + filesystem.chmod(link_path, 0o700) + + # Assert the real file is impacted, not the link. + cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + cur_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl() + self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access + self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link)) # pylint: disable=protected-access + + def test_symlink_loop_mitigation(self): + link1_path = os.path.join(self.tempdir, 'link1') + link2_path = os.path.join(self.tempdir, 'link2') + link3_path = os.path.join(self.tempdir, 'link3') + os.symlink(link1_path, link2_path) + os.symlink(link2_path, link3_path) + os.symlink(link3_path, link1_path) + + with self.assertRaises(RuntimeError) as error: + filesystem.chmod(link1_path, 0o755) + self.assertTrue('link1 is a loop!' in str(error.exception)) + + def test_world_permission(self): + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + filesystem.chmod(self.probe_path, 0o700) + dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + filesystem.chmod(self.probe_path, 0o704) + dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + + self.assertTrue([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_group_permissions_noop(self): + filesystem.chmod(self.probe_path, 0o700) + ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + + filesystem.chmod(self.probe_path, 0o740) + cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + + self.assertTrue(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access + + def test_admin_permissions(self): + system = win32security.ConvertStringSidToSid(SYSTEM_SID) + admins = win32security.ConvertStringSidToSid(ADMINS_SID) + + filesystem.chmod(self.probe_path, 0o400) + dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + + system_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == system] + admin_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == admins] + + self.assertEqual(len(system_aces), 1) + self.assertEqual(len(admin_aces), 1) + + self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) + self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) + + def test_read_flag(self): + self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ) + + def test_execute_flag(self): + self._test_flag(1, ntsecuritycon.FILE_GENERIC_EXECUTE) + + def test_write_flag(self): + self._test_flag(2, (ntsecuritycon.FILE_ALL_ACCESS + ^ ntsecuritycon.FILE_GENERIC_READ + ^ ntsecuritycon.FILE_GENERIC_EXECUTE + ^ 512)) + + def test_full_flag(self): + self._test_flag(7, (ntsecuritycon.FILE_ALL_ACCESS + ^ 512)) + + def _test_flag(self, everyone_mode, windows_flag): + # Note that flag is tested against `everyone`, not `user`, because practically these unit + # tests are executed with admin privilege, so current user is effectively the admins group, + # and so will always have all rights. + filesystem.chmod(self.probe_path, 0o700 | everyone_mode) + dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + acls_everybody = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody] + + self.assertEqual(len(acls_everybody), 1) + + acls_everybody = acls_everybody[0] + + self.assertEqual(acls_everybody[1], windows_flag) + + def test_user_admin_dacl_consistency(self): + # Set ownership of target to authenticated user + authenticated_user, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_owner = _get_security_owner(self.probe_path) + _set_owner(self.probe_path, security_owner, authenticated_user) + + filesystem.chmod(self.probe_path, 0o700) + + security_dacl = _get_security_dacl(self.probe_path) + # We expect three ACE: one for admins, one for system, and one for the user + self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 3) + + # Set ownership of target to Administrators user group + admin_user = win32security.ConvertStringSidToSid(ADMINS_SID) + security_owner = _get_security_owner(self.probe_path) + _set_owner(self.probe_path, security_owner, admin_user) + + filesystem.chmod(self.probe_path, 0o700) + + security_dacl = _get_security_dacl(self.probe_path) + # We expect only two ACE: one for admins, one for system, + # since the user is also the admins group + self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2) class OsReplaceTest(test_util.TempDirTestCase): @@ -19,3 +169,28 @@ class OsReplaceTest(test_util.TempDirTestCase): self.assertFalse(os.path.exists(src)) self.assertTrue(os.path.exists(dst)) + + +def _get_security_dacl(target): + return win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION) + + +def _get_security_owner(target): + return win32security.GetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION) + + +def _set_owner(target, security_owner, user): + security_owner.SetSecurityDescriptorOwner(user, False) + win32security.SetFileSecurity( + target, win32security.OWNER_SECURITY_INFORMATION, security_owner) + + +def _create_probe(tempdir): + filesystem.chmod(tempdir, 0o744) + probe_path = os.path.join(tempdir, 'probe') + open(probe_path, 'w').close() + return probe_path + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py new file mode 100644 index 000000000..85a3fa9f4 --- /dev/null +++ b/certbot/tests/compat/os_test.py @@ -0,0 +1,15 @@ +"""Unit test for os module.""" +import unittest + +from certbot.compat import os + + +class OsTest(unittest.TestCase): + """Unit tests for os module.""" + def test_forbidden_methods(self): + for method in ['chmod', 'rename', 'replace']: + self.assertRaises(RuntimeError, getattr(os, method)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 2ed7d4229..ef40d674a 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -7,6 +7,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import util @@ -65,7 +66,7 @@ class HookTest(util.ConfigTestCase): """Common base class for hook tests.""" @classmethod - def _call(cls, *args, **kwargs): + def _call(cls, *args, **kwargs): # pragma: no cover """Calls the method being tested with the given arguments.""" raise NotImplementedError @@ -494,7 +495,7 @@ def create_hook(file_path): """ open(file_path, "w").close() - os.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR) + filesystem.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR) if __name__ == '__main__': diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 13c09395d..307c053d8 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -15,6 +15,7 @@ import certbot.tests.util as test_util from certbot import errors from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem from certbot.storage import ALL_FOUR CERT = test_util.load_cert('cert_512.pem') @@ -132,7 +133,7 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): with open(link, "wb") as f: f.write(kind.encode('ascii') if value is None else value) if kind == "privkey": - os.chmod(link, 0o600) + filesystem.chmod(link, 0o600) def _write_out_ex_kinds(self): for kind in ALL_FOUR: @@ -572,7 +573,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.test_rc.update_all_links_to(1) self.assertTrue(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600)) - os.chmod(self.test_rc.version("privkey", 1), 0o444) + filesystem.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(misc.compare_file_modes( @@ -582,7 +583,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(misc.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) + filesystem.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(misc.compare_file_modes( os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600)) @@ -776,7 +777,7 @@ class RenewableCertTests(BaseRenewableCertTest): with open(temp, "w") as f: f.write("[renewalparams]\nuseful = value # A useful value\n" "useless = value # Not needed\n") - os.chmod(temp, 0o640) + filesystem.chmod(temp, 0o640) target = {} for x in ALL_FOUR: target[x] = "somewhere" diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 49ff6731b..1c5f1d8b0 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -27,6 +27,7 @@ from certbot import lock from certbot import storage from certbot import util from certbot.compat import os +from certbot.compat import filesystem from certbot.display import util as display_util @@ -340,8 +341,13 @@ class TempDirTestCase(unittest.TestCase): def handle_rw_files(_, path, __): """Handle read-only files, that will fail to be removed on Windows.""" - os.chmod(path, stat.S_IWRITE) - os.remove(path) + filesystem.chmod(path, stat.S_IWRITE) + try: + os.remove(path) + except (IOError, OSError): + # TODO: remote the try/except once all logic from windows file permissions is merged + if os.name != 'nt': + raise shutil.rmtree(self.tempdir, onerror=handle_rw_files) diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 6e231b74d..dccd96ab5 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -11,6 +11,7 @@ import certbot.tests.util as test_util from certbot import errors from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem class RunScriptTest(unittest.TestCase): @@ -188,17 +189,19 @@ class CheckPermissionsTest(test_util.TempDirTestCase): return check_permissions(self.tempdir, mode, self.uid) def test_ok_mode(self): - os.chmod(self.tempdir, 0o600) + filesystem.chmod(self.tempdir, 0o600) self.assertTrue(self._call(0o600)) + # TODO: reactivate the test when all logic from windows file permissions is merged. + @test_util.broken_on_windows def test_wrong_mode(self): - os.chmod(self.tempdir, 0o400) + filesystem.chmod(self.tempdir, 0o400) try: self.assertFalse(self._call(0o600)) finally: # Without proper write permissions, Windows is unable to delete a folder, # even with admin permissions. Write access must be explicitly set first. - os.chmod(self.tempdir, 0o700) + filesystem.chmod(self.tempdir, 0o700) class UniqueFileTest(test_util.TempDirTestCase): diff --git a/setup.py b/setup.py index 09ffe8cb5..84c27fce5 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,8 @@ import os import re import sys -from setuptools import find_packages, setup +from distutils.version import StrictVersion +from setuptools import find_packages, setup, __version__ as setuptools_version from setuptools.command.test import test as TestCommand # Workaround for http://bugs.python.org/issue8876, see @@ -52,6 +53,17 @@ install_requires = [ 'zope.interface', ] +# Add pywin32 on Windows platforms to handle low-level system calls. +# This dependency needs to be added using environment markers to avoid its installation on Linux. +# However environment markers are supported only with setuptools >= 36.2. +# So this dependency is not added for old Linux distributions with old setuptools, +# in order to allow these systems to build certbot from sources. +if StrictVersion(setuptools_version) >= StrictVersion('36.2'): + install_requires.append("pywin32 ; sys_platform == 'win32'") +elif 'bdist_wheel' in sys.argv[1:]: + raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' + 'of setuptools. Version 36.2+ of setuptools is required.') + dev_extras = [ 'astroid==1.6.5', 'coverage', diff --git a/tox.cover.py b/tox.cover.py index 65dc4a8a9..0a94cb73f 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -12,7 +12,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] COVER_THRESHOLDS = { - 'certbot': {'linux': 98, 'windows': 93}, + 'certbot': {'linux': 97, 'windows': 96}, 'acme': {'linux': 100, 'windows': 99}, 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, diff --git a/tox.ini b/tox.ini index 404afc165..b86a840ec 100644 --- a/tox.ini +++ b/tox.ini @@ -238,7 +238,7 @@ commands = --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config=certbot-ci/certbot_integration_tests/.coveragerc - coverage report --include 'certbot/*' --show-missing --fail-under=67 + coverage report --include 'certbot/*' --show-missing --fail-under=66 coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 passenv = DOCKER_* -- cgit v1.2.3 From 9a60f6df7886b947dd6890462ede52648a7e6a82 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 21 Jun 2019 21:00:03 +0200 Subject: Fix codecov quality gate since flags have been removed (#7173) Because some users were complaining about staled workflow when flags (https://docs.codecov.io/docs/flags) are enabled, Codecov decided to remove them when calculating the coverage on branches until they improved this functionality. See: https://docs.codecov.io/docs/flags#section-flags-in-the-codecov-ui The flags are still taken into account on PR builds, but not on based branch. This is a problem for us, because we use the flags to compare specifically the coverage of a PR against its base branch for Windows on one side, and Linux on the other side. Without flags taken into account on the base branch, the CI fails because the coverage on Windows is too low. As a temporary fix until the situation is clarified on Codecov side, this PR replaces the validation condition, that was a comparison against the base branch, to a fixed coverage registered in the local .codecov.yml file in Certbot repository. This way, the coverage on PR builds, that takes into account the flags, is validated against an appropriate value. This is a temporary solution, that will require an explicit update of .codecov.yml in the mean time if the coverage significantly increases, or decreases on some developments. But until the situation is fixed, this will allow to have a functional quality gate. --- .codecov.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 55fac56c8..af67ea27f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,11 +4,15 @@ coverage: default: off linux: flags: linux - target: auto + # Fixed target instead of auto set by #7173, can + # be removed when flags in Codecov are added back. + target: 98.0 threshold: 0.1 base: auto windows: flags: windows - target: auto + # Fixed target instead of auto set by #7173, can + # be removed when flags in Codecov are added back. + target: 96.9 threshold: 0.1 base: auto -- cgit v1.2.3 From 249af5c4cd0fa3397f055dd6c2690bc8065b09e8 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 24 Jun 2019 21:03:24 +0200 Subject: Fix integration tests with Pebble v2.1.0 + (#7175) Since Pebble v2.1.0, new controls have been added on ACME specs compliance on Pebble with strict mode enabled. These controls are described here: letsencrypt/pebble@3a2ce1c Currently Certbot is not compliant enough to pass these new controls. One part of the work to do is described here: #7171 As a consequence, our CI is currently broken, both on PR builds and nightly builds. This PR disables the strict mode during integration tests, fixing temporarily our CI. This will give us some time to fix theses deviations, and add back the strict mode in a future PR once it is merged. * Remove -strict mode on Pebble for now. * Refer to relevant Certbot PR * Clean code --- certbot-ci/certbot_integration_tests/utils/acme_server.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index f8f4b2c69..e9226e17c 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -132,13 +132,21 @@ def _prepare_acme_server(workspace, acme_type, acme_xdist): os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), join(instance_path, 'test/rate-limit-policies.yml')) if acme_type == 'pebble': - # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid - # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler: config = yaml.load(file_handler.read()) + # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid + # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. config['services']['pebble'].setdefault('environment', [])\ .extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0']) + + # Also disable strict mode for now, since Pebble v2.1.0 added specs in + # strict mode for which Certbot is not compliant for now. + # See https://github.com/certbot/certbot/pull/7175 + # TODO: Add back -strict mode once Certbot is compliant with Pebble v2.1.0+ + config['services']['pebble']['command'] = config['services']['pebble']['command']\ + .replace('-strict', '') + with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler: file_handler.write(yaml.dump(config)) -- cgit v1.2.3 From 2e3c1d7c7736235fe5fd4d035cb17b6094f68b6b Mon Sep 17 00:00:00 2001 From: Siilike Date: Mon, 24 Jun 2019 22:47:50 +0300 Subject: Add reference to the Standalone DNS Authenticator (#7137) Updated documentation to add a reference to the Standalone DNS Authenticator, https://github.com/siilike/certbot-dns-standalone --- docs/using.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index 8e93bcb41..d8f324fd7 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -268,20 +268,21 @@ There are also a number of third-party plugins for the client, provided by other developers. Many are beta/experimental, but some are already in widespread use: -=========== ==== ==== =============================================================== -Plugin Auth Inst Notes -=========== ==== ==== =============================================================== -plesk_ Y Y Integration with the Plesk web hosting tool -haproxy_ Y Y Integration with the HAProxy load balancer -s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets -gandi_ Y Y Integration with Gandi LiveDNS API -varnish_ Y N Obtain certificates via a Varnish server -external_ Y N A plugin for convenient scripting (See also ticket 2782_) -icecast_ N Y Deploy certificates to Icecast 2 streaming media servers -pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers -proxmox_ N Y Install certificates in Proxmox Virtualization servers -heroku_ Y Y Integration with Heroku SSL -=========== ==== ==== =============================================================== +================== ==== ==== =============================================================== +Plugin Auth Inst Notes +================== ==== ==== =============================================================== +plesk_ Y Y Integration with the Plesk web hosting tool +haproxy_ Y Y Integration with the HAProxy load balancer +s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets +gandi_ Y Y Integration with Gandi LiveDNS API +varnish_ Y N Obtain certificates via a Varnish server +external_ Y N A plugin for convenient scripting (See also ticket 2782_) +icecast_ N Y Deploy certificates to Icecast 2 streaming media servers +pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers +proxmox_ N Y Install certificates in Proxmox Virtualization servers +heroku_ Y Y Integration with Heroku SSL +dns-standalone_ Y N Obtain certificates via an integrated DNS server +================== ==== ==== =============================================================== .. _plesk: https://github.com/plesk/letsencrypt-plesk .. _haproxy: https://github.com/greenhost/certbot-haproxy @@ -294,6 +295,7 @@ heroku_ Y Y Integration with Heroku SSL .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox .. _external: https://github.com/marcan/letsencrypt-external .. _heroku: https://github.com/gboudreau/certbot-heroku +.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone If you're interested, you can also :ref:`write your own plugin `. -- cgit v1.2.3 From a3bbdd52e764335ca746689c665f7b0e46cb7c53 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 24 Jun 2019 16:39:45 -0700 Subject: Improve issue closing behavior. (#7178) --- .github/stale.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 9c095ebd6..2e4106314 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -5,7 +5,8 @@ daysUntilStale: 365 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 +# When changing this value, be sure to also update markComment below. +daysUntilClose: 30 # Ignore issues with an assignee (defaults to false) exemptAssignees: true @@ -18,8 +19,8 @@ markComment: > We've made a lot of changes to Certbot since this issue was opened. If you still have this issue with an up-to-date version of Certbot, can you please add a comment letting us know? This helps us to better see what issues are - still affecting our users. If there is no further activity, this issue will - be automatically closed. + still affecting our users. If there is no activity in the next 30 days, this + issue will be automatically closed. # Comment to post when closing a stale Issue or Pull Request. closeComment: > -- cgit v1.2.3 From 4c95b687ae298921065c54e43fa246194b4c738e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 25 Jun 2019 10:10:14 -0700 Subject: Remove references and tests for Ubuntu Trusty. --- .travis.yml | 6 ----- certbot-apache/certbot_apache/configurator.py | 3 --- letsencrypt-auto-source/Dockerfile.trusty | 36 ------------------------- letsencrypt-auto-source/rebuild_dependencies.py | 2 +- tests/letstest/apache2_targets.yaml | 10 ------- tests/letstest/scripts/boulder_install.sh | 2 -- tests/letstest/targets.yaml | 10 ------- tox.ini | 16 +++-------- 8 files changed, 4 insertions(+), 81 deletions(-) delete mode 100644 letsencrypt-auto-source/Dockerfile.trusty diff --git a/.travis.yml b/.travis.yml index d7d328c8b..4f1905f50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,12 +72,6 @@ matrix: before_install: addons: <<: *not-on-master - - sudo: required - env: TOXENV=le_auto_trusty - services: docker - before_install: - addons: - <<: *not-on-master - python: "2.7" env: TOXENV=apacheconftest-with-pebble sudo: required diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 37f8ba289..8e3dde14d 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -74,9 +74,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # pylint: disable=too-many-instance-attributes,too-many-public-methods """Apache configurator. - State of Configurator: This code has been been tested and built for Ubuntu - 14.04 Apache 2.4 and it works for Ubuntu 12.04 Apache 2.2 - :ivar config: Configuration. :type config: :class:`~certbot.interfaces.IConfig` diff --git a/letsencrypt-auto-source/Dockerfile.trusty b/letsencrypt-auto-source/Dockerfile.trusty deleted file mode 100644 index 3de88f9af..000000000 --- a/letsencrypt-auto-source/Dockerfile.trusty +++ /dev/null @@ -1,36 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -FROM ubuntu:trusty - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups sudo --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' \ - /etc/sudoers - -# Install pip: -RUN apt-get update && \ - apt-get -q -y install python-pip && \ - apt-get clean -# Use pipstrap to update to a stable and tested version of pip -COPY ./pieces/pipstrap.py /opt -RUN /opt/pipstrap.py -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-certificates - -# Copy code: -COPY . /home/lea/certbot/letsencrypt-auto-source - -USER lea -WORKDIR /home/lea - -CMD ["pytest", "-v", "-s", "certbot/letsencrypt-auto-source/tests"] diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index 7096e226c..fb4c1dfb9 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -26,7 +26,7 @@ import argparse # The list of docker distributions to test dependencies against with. DISTRIBUTION_LIST = [ - 'ubuntu:18.04', 'ubuntu:14.04', + 'ubuntu:18.04', 'ubuntu:16.04', 'debian:stretch', 'debian:jessie', 'centos:7', 'centos:6', 'opensuse/leap:15', diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 5ee2632a0..bc87b9dc6 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -16,16 +16,6 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-7b89cc11 - name: ubuntu14.04LTS - type: ubuntu - virt: hvm - user: ubuntu - - ami: ami-9295d0f8 - name: ubuntu14.04LTS_32bit - type: ubuntu - virt: pv - user: ubuntu #----------------------------------------------------------------------------- # Debian - ami: ami-003f19e0e687de1cd diff --git a/tests/letstest/scripts/boulder_install.sh b/tests/letstest/scripts/boulder_install.sh index f997268bd..5161de374 100755 --- a/tests/letstest/scripts/boulder_install.sh +++ b/tests/letstest/scripts/boulder_install.sh @@ -1,7 +1,5 @@ #!/bin/bash -x -# >>>> only tested on Ubuntu 14.04LTS <<<< - # Check out special branch until latest docker changes land in Boulder master. git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH cd $BOULDERPATH diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 547c33ffa..a01d20217 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -16,16 +16,6 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-7b89cc11 - name: ubuntu14.04LTS - type: ubuntu - virt: hvm - user: ubuntu - - ami: ami-9295d0f8 - name: ubuntu14.04LTS_32bit - type: ubuntu - virt: pv - user: ubuntu #----------------------------------------------------------------------------- # Debian - ami: ami-003f19e0e687de1cd diff --git a/tox.ini b/tox.ini index b86a840ec..bbe3c7a05 100644 --- a/tox.ini +++ b/tox.ini @@ -180,12 +180,11 @@ whitelist_externals = passenv = DOCKER_* -[testenv:le_auto_trusty] -# At the moment, this tests under Python 2.7 only, as only that version is -# readily available on the Trusty Docker image. +[testenv:le_auto_xenial] +# At the moment, this tests under Python 2.7 only. commands = python {toxinidir}/tests/modification-check.py - docker build -f letsencrypt-auto-source/Dockerfile.trusty -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.xenial -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker @@ -193,15 +192,6 @@ passenv = DOCKER_* TRAVIS_BRANCH -[testenv:le_auto_xenial] -# At the moment, this tests under Python 2.7 only. -commands = - docker build -f letsencrypt-auto-source/Dockerfile.xenial -t lea letsencrypt-auto-source - docker run --rm -t -i lea -whitelist_externals = - docker -passenv = DOCKER_* - [testenv:le_auto_jessie] # At the moment, this tests under Python 2.7 only, as only that version is # readily available on the Wheezy Docker image. -- cgit v1.2.3 From 9962cf0b8ee40c22bf0693b7ac892596aaebb3e8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 25 Jun 2019 10:13:57 -0700 Subject: Upgrade compatibility tests to stretch. (#7185) Inspired by #7180, there's no reason for these tests to be running on old stable. This upgrades them to the latest stable version of Debian. You can see tests passing with these changes at https://travis-ci.com/certbot/certbot/builds/116844923. --- certbot-compatibility-test/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-compatibility-test/Dockerfile b/certbot-compatibility-test/Dockerfile index 1e9e0d727..2716d6fcb 100644 --- a/certbot-compatibility-test/Dockerfile +++ b/certbot-compatibility-test/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:jessie +FROM debian:stretch MAINTAINER Brad Warren # no need to mkdir anything: -- cgit v1.2.3 From 69cf64079cf0e3ef80429fc128e296d10debd4df Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 25 Jun 2019 10:18:39 -0700 Subject: Mention dropped support in changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dfebb376..ef0a79b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* Support for Ubuntu 14.04 Trusty has been removed. * Update the 'manage your account' help to be more generic. * The error message when Certbot's Apache plugin is unable to modify your Apache configuration has been improved. -- cgit v1.2.3 From 776e939a4c286772ba6bc736e14632c16f91a8dc Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 25 Jun 2019 11:05:28 -0700 Subject: Drop support for quay.io. (#7187) In this spirt of cleaning up some low hanging cruft, this fixes #4343. There are no (recent) release tags on quay.io and the builds are just following master. See https://quay.io/repository/letsencrypt/letsencrypt?tab=tags. Once this lands, I can disable the automated builds on quay.io and we can delete Dockerfile-old and tools/docker-warning.sh. --- tools/docker-warning.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/docker-warning.sh b/tools/docker-warning.sh index e4f5f40ee..cc9a50ee2 100755 --- a/tools/docker-warning.sh +++ b/tools/docker-warning.sh @@ -1,4 +1,5 @@ #!/bin/sh -e -echo "Warning: This Docker image will soon be switching to Alpine Linux." >&2 -echo "You can switch now using the certbot/certbot repo on Docker Hub." >&2 +echo 'Warning: This Docker image is no longer receiving updates!' >&2 +echo 'You should switch to the Docker images on Docker Hub at:' >&2 +echo 'https://hub.docker.com/u/certbot' >&2 exec /opt/certbot/venv/bin/certbot $@ -- cgit v1.2.3 From a37a4486cfda639f0d013227c8bb3838220ce7f6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 25 Jun 2019 14:03:45 -0700 Subject: Add Debian ARM AMI. (#7189) Inspired by the number of ARM users we have (and because I want to rip out the only 32 bit test we have which without this PR would remove all tests we have on non-x86_64 architectures), this test adds an ARM image to the test farm tests. The image ID was taken from https://wiki.debian.org/Cloud/AmazonEC2Image/Stretch, you can see tests passing at https://travis-ci.com/certbot/certbot/builds/116857897, and I ran test_tests.sh locally and it passed. --- tests/letstest/apache2_targets.yaml | 6 ++++++ tests/letstest/multitester.py | 4 +++- tests/letstest/targets.yaml | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 5ee2632a0..c30cc92fa 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -33,6 +33,12 @@ targets: type: ubuntu virt: hvm user: admin + - ami: ami-0ed54dd1b25657636 + name: debian9_arm64 + type: ubuntu + virt: hvm + user: admin + machine_type: a1.medium - ami: ami-077bf3962f29d3fa4 name: debian8.1 type: ubuntu diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 7bc4e034d..2870aaa09 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -307,7 +307,9 @@ def grab_certbot_log(): def create_client_instance(ec2_client, target, security_group_id, subnet_id): """Create a single client instance for running tests.""" - if target['virt'] == 'hvm': + if 'machine_type' in target: + machine_type = target['machine_type'] + elif target['virt'] == 'hvm': machine_type = 't2.medium' if cl_args.fast else 't2.micro' else: # 32 bit systems diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 547c33ffa..73332a468 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -33,6 +33,12 @@ targets: type: ubuntu virt: hvm user: admin + - ami: ami-0ed54dd1b25657636 + name: debian9_arm64 + type: ubuntu + virt: hvm + user: admin + machine_type: a1.medium - ami: ami-077bf3962f29d3fa4 name: debian8.1 type: ubuntu -- cgit v1.2.3 From 0d5bad6c8c859b2fe21284f8ccfb4b00b9afc3b4 Mon Sep 17 00:00:00 2001 From: Hunter Date: Tue, 25 Jun 2019 20:53:31 -0400 Subject: dns-cloudflare: update URL for obtaining API keys (#7052) Updated the ACCOUNT_URL in the Cloudflare-DNS plugin. This uses the new "dash.cloudflare.com" scheme and future-proofs this URL for an upcoming change to Cloudflare API keys (this is not public yet, so no other changes related to this). --- certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py index e3d0d42e0..0bbdf703a 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py @@ -10,7 +10,7 @@ from certbot.plugins import dns_common logger = logging.getLogger(__name__) -ACCOUNT_URL = 'https://www.cloudflare.com/a/account/my-account' +ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens' @zope.interface.implementer(interfaces.IAuthenticator) -- cgit v1.2.3 From f2ab6a338cb2177697b671e4381e8d08a50b5c06 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 26 Jun 2019 02:54:02 -0700 Subject: Remove files for old Docker image. (#7188) --- Dockerfile-old | 75 ------------------------------------------------- tools/docker-warning.sh | 5 ---- 2 files changed, 80 deletions(-) delete mode 100644 Dockerfile-old delete mode 100755 tools/docker-warning.sh diff --git a/Dockerfile-old b/Dockerfile-old deleted file mode 100644 index c52a9937b..000000000 --- a/Dockerfile-old +++ /dev/null @@ -1,75 +0,0 @@ -# https://github.com/letsencrypt/letsencrypt/pull/431#issuecomment-103659297 -# it is more likely developers will already have ubuntu:trusty rather -# than e.g. debian:jessie and image size differences are negligible -FROM ubuntu:trusty -MAINTAINER Jakub Warmuz -MAINTAINER William Budington - -# Note: this only exposes the port to other docker containers. You -# still have to bind to 443@host at runtime, as per the ACME spec. -EXPOSE 443 - -# TODO: make sure --config-dir and --work-dir cannot be changed -# through the CLI (certbot-docker wrapper that uses standalone -# authenticator and text mode only?) -VOLUME /etc/letsencrypt /var/lib/letsencrypt - -WORKDIR /opt/certbot - -# no need to mkdir anything: -# https://docs.docker.com/reference/builder/#copy -# If doesn't exist, it is created along with all missing -# directories in its path. - -ENV DEBIAN_FRONTEND=noninteractive - -COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto -RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* \ - /tmp/* \ - /var/tmp/* - -# the above is not likely to change, so by putting it further up the -# Dockerfile we make sure we cache as much as possible - - -COPY setup.py README.rst CHANGELOG.md MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/ - -# all above files are necessary for setup.py and venv setup, however, -# package source code directory has to be copied separately to a -# subdirectory... -# https://docs.docker.com/reference/builder/#copy: "If is a -# directory, the entire contents of the directory are copied, -# including filesystem metadata. Note: The directory itself is not -# copied, just its contents." Order again matters, three files are far -# more likely to be cached than the whole project directory - -COPY certbot /opt/certbot/src/certbot/ -COPY acme /opt/certbot/src/acme/ -COPY certbot-apache /opt/certbot/src/certbot-apache/ -COPY certbot-nginx /opt/certbot/src/certbot-nginx/ - - -RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv - -# PATH is set now so pipstrap upgrades the correct (v)env -ENV PATH /opt/certbot/venv/bin:$PATH -RUN /opt/certbot/venv/bin/python /opt/certbot/src/pipstrap.py && \ - /opt/certbot/venv/bin/pip install \ - -e /opt/certbot/src/acme \ - -e /opt/certbot/src \ - -e /opt/certbot/src/certbot-apache \ - -e /opt/certbot/src/certbot-nginx - -# install in editable mode (-e) to save space: it's not possible to -# "rm -rf /opt/certbot/src" (it's stays in the underlaying image); -# this might also help in debugging: you can "docker run --entrypoint -# bash" and investigate, apply patches, etc. - -# set up certbot/letsencrypt wrapper to warn people about Dockerfile changes -COPY tools/docker-warning.sh /opt/certbot/bin/certbot -RUN ln -s /opt/certbot/bin/certbot /opt/certbot/bin/letsencrypt -ENV PATH /opt/certbot/bin:$PATH - -ENTRYPOINT [ "certbot" ] diff --git a/tools/docker-warning.sh b/tools/docker-warning.sh deleted file mode 100755 index cc9a50ee2..000000000 --- a/tools/docker-warning.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -e -echo 'Warning: This Docker image is no longer receiving updates!' >&2 -echo 'You should switch to the Docker images on Docker Hub at:' >&2 -echo 'https://hub.docker.com/u/certbot' >&2 -exec /opt/certbot/venv/bin/certbot $@ -- cgit v1.2.3 From a778b5040361e1aa51c69d716b58ac7ac49652a7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 26 Jun 2019 14:54:08 -0700 Subject: Run le_auto_xenial on every PR. (#7195) https://github.com/certbot/certbot/pull/7190/files removed our only le_auto_* tests on PRs. This PR fixes that by running le_auto_xenial on every PR which also includes running modification-check.py like we used to for Trusty. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f1905f50..66ef5ebb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,6 +72,10 @@ matrix: before_install: addons: <<: *not-on-master + - sudo: required + env: TOXENV=le_auto_xenial + services: docker + <<: *not-on-master - python: "2.7" env: TOXENV=apacheconftest-with-pebble sudo: required @@ -205,10 +209,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - sudo: required - env: TOXENV=le_auto_xenial - services: docker - <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_jessie services: docker -- cgit v1.2.3 From a27f3ebd4fd6fb5d110fe32342044818eb53bd45 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 26 Jun 2019 17:24:04 -0700 Subject: s/for for/for (#7196) --- certbot/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/cli.py b/certbot/cli.py index 794242c24..6ffed74fa 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -428,7 +428,7 @@ VERB_HELP = [ }), ("plugins", { "short": "List plugins that are installed and available on your system", - "opts": 'Options for for the "plugins" subcommand', + "opts": 'Options for the "plugins" subcommand', "usage": "\n\n certbot plugins [options]\n\n" }), ("update_symlinks", { -- cgit v1.2.3 From 1c6210ee00b372484c0156f06e13605ed923d1a6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 26 Jun 2019 17:46:51 -0700 Subject: Fix certbot config_changes (#7197) * Remove for_logging parameter. * Remove broken/unused --num parameter. * update changelog --- CHANGELOG.md | 1 + certbot/cli.py | 7 ++----- certbot/client.py | 4 ++-- certbot/main.py | 2 +- certbot/reverter.py | 6 +----- certbot/tests/reverter_test.py | 9 --------- 6 files changed, 7 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef0a79b9e..52a7f6aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Update the 'manage your account' help to be more generic. * The error message when Certbot's Apache plugin is unable to modify your Apache configuration has been improved. +* `certbot config_changes` no longer accepts a --num parameter. ### Fixed diff --git a/certbot/cli.py b/certbot/cli.py index 6ffed74fa..9f11e5d9b 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -418,8 +418,8 @@ VERB_HELP = [ }), ("config_changes", { "short": "Show changes that Certbot has made to server configurations", - "opts": "Options for controlling which changes are displayed", - "usage": "\n\n certbot config_changes --num NUM [options]\n\n" + "opts": "Options for viewing configuration changes", + "usage": "\n\n certbot config_changes [options]\n\n" }), ("rollback", { "short": "Roll back server conf changes made during certificate installation", @@ -1289,9 +1289,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis def _create_subparsers(helpful): - helpful.add("config_changes", "--num", type=int, default=flag_default("num"), - help="How many past revisions you want to be displayed") - from certbot.client import sample_user_agent # avoid import loops helpful.add( None, "--user-agent", default=flag_default("user_agent"), diff --git a/certbot/client.py b/certbot/client.py index 3c4a894ae..4233a6343 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -702,7 +702,7 @@ def rollback(default_installer, checkpoints, config, plugins): installer.restart() -def view_config_changes(config, num=None): +def view_config_changes(config): """View checkpoints and associated configuration changes. .. note:: This assumes that the installation is using a Reverter object. @@ -713,7 +713,7 @@ def view_config_changes(config, num=None): """ rev = reverter.Reverter(config) rev.recovery_routine() - rev.view_config_changes(num) + rev.view_config_changes() def _open_pem_file(cli_arg_path, pem_path): """Open a pem file. diff --git a/certbot/main.py b/certbot/main.py index d5239fa11..d071ee453 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -976,7 +976,7 @@ def config_changes(config, unused_plugins): :rtype: None """ - client.view_config_changes(config, num=config.num) + client.view_config_changes(config) def update_symlinks(config, unused_plugins): """Update the certificate file family symlinks diff --git a/certbot/reverter.py b/certbot/reverter.py index f3088ed7e..a7a8a943d 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -132,7 +132,7 @@ class Reverter(object): "Unable to load checkpoint during rollback") rollback -= 1 - def view_config_changes(self, for_logging=False, num=None): + def view_config_changes(self): """Displays all saved checkpoints. All checkpoints are printed by @@ -145,8 +145,6 @@ class Reverter(object): """ backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) - if num: - backups = backups[:num] if not backups: logger.info("Certbot has not saved backups of your configuration") @@ -182,8 +180,6 @@ class Reverter(object): output.append(os.linesep) - if for_logging: - return os.linesep.join(output) zope.component.getUtility(interfaces.IDisplay).notification( os.linesep.join(output), force_interactive=True, pause=False) return None diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 6a805072b..9e35ad636 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -400,15 +400,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertRaises( errors.ReverterError, self.reverter.view_config_changes) - def test_view_config_changes_for_logging(self): - self._setup_three_checkpoints() - - config_changes = self.reverter.view_config_changes(for_logging=True) - - self.assertTrue("First Checkpoint" in config_changes) - self.assertTrue("Second Checkpoint" in config_changes) - self.assertTrue("Third Checkpoint" in config_changes) - def _setup_three_checkpoints(self): """Generate some finalized checkpoints.""" # Checkpoint1 - config1 -- cgit v1.2.3 From 26a1eddd89615af47936e0543ddd1c6b92e4468d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Jun 2019 15:17:31 -0700 Subject: Remove plesk from the list of 3rd party plugins. (#7200) Our link for the Plesk plugin goes to https://github.com/plesk/letsencrypt-plesk which refers you to https://ext.plesk.com/packages/f6847e61-33a7-4104-8dc9-d26a0183a8dd-letsencrypt and in their changelog for 2.0.0 it says "Replaced Python-based certbot with PHP-based client". --- docs/using.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index d8f324fd7..d778d5bdb 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -271,7 +271,6 @@ widespread use: ================== ==== ==== =============================================================== Plugin Auth Inst Notes ================== ==== ==== =============================================================== -plesk_ Y Y Integration with the Plesk web hosting tool haproxy_ Y Y Integration with the HAProxy load balancer s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets gandi_ Y Y Integration with Gandi LiveDNS API @@ -284,7 +283,6 @@ heroku_ Y Y Integration with Heroku SSL dns-standalone_ Y N Obtain certificates via an integrated DNS server ================== ==== ==== =============================================================== -.. _plesk: https://github.com/plesk/letsencrypt-plesk .. _haproxy: https://github.com/greenhost/certbot-haproxy .. _s3front: https://github.com/dlapiduz/letsencrypt-s3front .. _gandi: https://github.com/obynio/certbot-plugin-gandi -- cgit v1.2.3 From 4fc0ef0fbe61a6a48e881fe40330e812d0673380 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 27 Jun 2019 15:17:45 -0700 Subject: certbot-plugin-gandi is not an installer. (#7201) This [plugin](https://github.com/obynio/certbot-plugin-gandi) is an authenticator but not an installer. It's a DNS authenticator plugin. --- docs/using.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index d778d5bdb..a54e28ec7 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -273,7 +273,7 @@ Plugin Auth Inst Notes ================== ==== ==== =============================================================== haproxy_ Y Y Integration with the HAProxy load balancer s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets -gandi_ Y Y Integration with Gandi LiveDNS API +gandi_ Y N Obtain certificates via the Gandi LiveDNS API varnish_ Y N Obtain certificates via a Varnish server external_ Y N A plugin for convenient scripting (See also ticket 2782_) icecast_ N Y Deploy certificates to Icecast 2 streaming media servers -- cgit v1.2.3 From c08a4dec2d9f15632747a76be88f9b5075fec23d Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 28 Jun 2019 18:39:13 +0300 Subject: Refactor augeas_configurator.py functionality to configurator.py and parser.py accordingly. (#7181) This pull request moves the functionality within `AugeasConfigurator` that previously existed as a parent class of `ApacheConfigurator` to `ApacheConfigurator` and `ApacheParser` accordingly. Most of the methods were moved as-is, and one (`recovery_routine()`) was completely removed. Few of the methods had to be split between the configurator and parser, good example of this is `save()`. The Augeas object now lives completely within the `ApacheParser`. * Remove augeasconfigurator * Fix references * Adjust tests accordingly * Simplify test * Address review comments * Address review comments * Move test_recovery_routine_reload --- .../certbot_apache/augeas_configurator.py | 207 --------------------- certbot-apache/certbot_apache/configurator.py | 155 +++++++++------ certbot-apache/certbot_apache/override_centos.py | 4 +- certbot-apache/certbot_apache/override_fedora.py | 2 +- certbot-apache/certbot_apache/override_gentoo.py | 2 +- certbot-apache/certbot_apache/parser.py | 158 +++++++++++++++- .../tests/augeas_configurator_test.py | 116 ------------ .../certbot_apache/tests/centos6_test.py | 6 +- .../tests/configurator_reverter_test.py | 93 +++++++++ .../certbot_apache/tests/configurator_test.py | 46 +---- certbot-apache/certbot_apache/tests/parser_test.py | 53 +++++- certbot-apache/certbot_apache/tests/util.py | 3 +- 12 files changed, 403 insertions(+), 442 deletions(-) delete mode 100644 certbot-apache/certbot_apache/augeas_configurator.py delete mode 100644 certbot-apache/certbot_apache/tests/augeas_configurator_test.py create mode 100644 certbot-apache/certbot_apache/tests/configurator_reverter_test.py diff --git a/certbot-apache/certbot_apache/augeas_configurator.py b/certbot-apache/certbot_apache/augeas_configurator.py deleted file mode 100644 index a32c65c41..000000000 --- a/certbot-apache/certbot_apache/augeas_configurator.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Class of Augeas Configurators.""" -import logging - - -from certbot import errors -from certbot.plugins import common - -from certbot_apache import constants - -logger = logging.getLogger(__name__) - - -class AugeasConfigurator(common.Installer): - """Base Augeas Configurator class. - - :ivar config: Configuration. - :type config: :class:`~certbot.interfaces.IConfig` - - :ivar aug: Augeas object - :type aug: :class:`augeas.Augeas` - - :ivar str save_notes: Human-readable configuration change notes - :ivar reverter: saves and reverts checkpoints - :type reverter: :class:`certbot.reverter.Reverter` - - """ - def __init__(self, *args, **kwargs): - super(AugeasConfigurator, self).__init__(*args, **kwargs) - - # Placeholder for augeas - self.aug = None - - self.save_notes = "" - - - def init_augeas(self): - """ Initialize the actual Augeas instance """ - import augeas - self.aug = augeas.Augeas( - # specify a directory to load our preferred lens from - loadpath=constants.AUGEAS_LENS_DIR, - # Do not save backup (we do it ourselves), do not load - # anything by default - flags=(augeas.Augeas.NONE | - augeas.Augeas.NO_MODL_AUTOLOAD | - augeas.Augeas.ENABLE_SPAN)) - # See if any temporary changes need to be recovered - # This needs to occur before VirtualHost objects are setup... - # because this will change the underlying configuration and potential - # vhosts - self.recovery_routine() - - def check_parsing_errors(self, lens): - """Verify Augeas can parse all of the lens files. - - :param str lens: lens to check for errors - - :raises .errors.PluginError: If there has been an error in parsing with - the specified lens. - - """ - error_files = self.aug.match("/augeas//error") - - for path in error_files: - # Check to see if it was an error resulting from the use of - # the httpd lens - lens_path = self.aug.get(path + "/lens") - # As aug.get may return null - if lens_path and lens in lens_path: - msg = ( - "There has been an error in parsing the file {0} on line {1}: " - "{2}".format( - # Strip off /augeas/files and /error - path[13:len(path) - 6], - self.aug.get(path + "/line"), - self.aug.get(path + "/message"))) - raise errors.PluginError(msg) - - def ensure_augeas_state(self): - """Makes sure that all Augeas dom changes are written to files to avoid - loss of configuration directives when doing additional augeas parsing, - causing a possible augeas.load() resulting dom reset - """ - - if self.unsaved_files(): - self.save_notes += "(autosave)" - self.save() - - def unsaved_files(self): - """Lists files that have modified Augeas DOM but the changes have not - been written to the filesystem yet, used by `self.save()` and - ApacheConfigurator to check the file state. - - :raises .errors.PluginError: If there was an error in Augeas, in - an attempt to save the configuration, or an error creating a - checkpoint - - :returns: `set` of unsaved files - """ - save_state = self.aug.get("/augeas/save") - self.aug.set("/augeas/save", "noop") - # Existing Errors - ex_errs = self.aug.match("/augeas//error") - try: - # This is a noop save - self.aug.save() - except (RuntimeError, IOError): - self._log_save_errors(ex_errs) - # Erase Save Notes - self.save_notes = "" - raise errors.PluginError( - "Error saving files, check logs for more info.") - - # Return the original save method - self.aug.set("/augeas/save", save_state) - - # Retrieve list of modified files - # Note: Noop saves can cause the file to be listed twice, I used a - # set to remove this possibility. This is a known augeas 0.10 error. - save_paths = self.aug.match("/augeas/events/saved") - - save_files = set() - if save_paths: - for path in save_paths: - save_files.add(self.aug.get(path)[6:]) - return save_files - - def save(self, title=None, temporary=False): - """Saves all changes to the configuration files. - - This function first checks for save errors, if none are found, - all configuration changes made will be saved. According to the - function parameters. If an exception is raised, a new checkpoint - was not created. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (ie. challenges) - - """ - save_files = self.unsaved_files() - if save_files: - self.add_to_checkpoint(save_files, - self.save_notes, temporary=temporary) - - self.save_notes = "" - self.aug.save() - - # Force reload if files were modified - # This is needed to recalculate augeas directive span - if save_files: - for sf in save_files: - self.aug.remove("/files/"+sf) - self.aug.load() - if title and not temporary: - self.finalize_checkpoint(title) - - def _log_save_errors(self, ex_errs): - """Log errors due to bad Augeas save. - - :param list ex_errs: Existing errors before save - - """ - # Check for the root of save problems - new_errs = self.aug.match("/augeas//error") - # logger.error("During Save - %s", mod_conf) - logger.error("Unable to save files: %s. Attempted Save Notes: %s", - ", ".join(err[13:len(err) - 6] for err in new_errs - # Only new errors caused by recent save - if err not in ex_errs), self.save_notes) - - # Wrapper functions for Reverter class - def recovery_routine(self): - """Revert all previously modified files. - - Reverts all modified files that have not been saved as a checkpoint - - :raises .errors.PluginError: If unable to recover the configuration - - """ - super(AugeasConfigurator, self).recovery_routine() - # Need to reload configuration after these changes take effect - self.aug.load() - - def revert_challenge_config(self): - """Used to cleanup challenge configurations. - - :raises .errors.PluginError: If unable to revert the challenge config. - - """ - self.revert_temporary_config() - self.aug.load() - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - - """ - super(AugeasConfigurator, self).rollback_checkpoints(rollback) - self.aug.load() diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 8e3dde14d..c433c5159 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -1,4 +1,4 @@ -"""Apache Configuration based off of Augeas Configurator.""" +"""Apache Configurator.""" # pylint: disable=too-many-lines import copy import fnmatch @@ -29,7 +29,6 @@ from certbot.plugins.util import path_surgery from certbot.plugins.enhancements import AutoHSTSEnhancement from certbot_apache import apache_util -from certbot_apache import augeas_configurator from certbot_apache import constants from certbot_apache import display_ops from certbot_apache import http_01 @@ -70,7 +69,7 @@ logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) -class ApacheConfigurator(augeas_configurator.AugeasConfigurator): +class ApacheConfigurator(common.Installer): # pylint: disable=too-many-instance-attributes,too-many-public-methods """Apache configurator. @@ -198,6 +197,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]] # Temporary state for AutoHSTS enhancement self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]] + # Reverter save notes + self.save_notes = "" # These will be set in the prepare function self._prepared = False @@ -228,12 +229,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :raises .errors.PluginError: If there is any other error """ - # Perform the actual Augeas initialization to be able to react - try: - self.init_augeas() - except ImportError: - raise errors.NoInstallationError("Problem in Augeas installation") - self._prepare_options() # Verify Apache is installed @@ -251,16 +246,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.NotSupportedError( "Apache Version {0} not supported.".format(str(self.version))) - if not self._check_aug_version(): - raise errors.NotSupportedError( - "Apache plugin support requires libaugeas0 and augeas-lenses " - "version 1.2.0 or higher, please make sure you have you have " - "those installed.") - + # Recover from previous crash before Augeas initialization to have the + # correct parse tree from the get go. + self.recovery_routine() + # Perform the actual Augeas initialization to be able to react self.parser = self.get_parser() # Check for errors in parsing files with Augeas - self.check_parsing_errors("httpd.aug") + self.parser.check_parsing_errors("httpd.aug") # Get all of the available vhosts self.vhosts = self.get_virtual_hosts() @@ -279,6 +272,67 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): " Apache configuration?".format(self.option("server_root"))) self._prepared = True + def save(self, title=None, temporary=False): + """Saves all changes to the configuration files. + + This function first checks for save errors, if none are found, + all configuration changes made will be saved. According to the + function parameters. If an exception is raised, a new checkpoint + was not created. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (ie. challenges) + + """ + save_files = self.parser.unsaved_files() + if save_files: + self.add_to_checkpoint(save_files, + self.save_notes, temporary=temporary) + # Handle the parser specific tasks + self.parser.save(save_files) + if title and not temporary: + self.finalize_checkpoint(title) + + def recovery_routine(self): + """Revert all previously modified files. + + Reverts all modified files that have not been saved as a checkpoint + + :raises .errors.PluginError: If unable to recover the configuration + + """ + super(ApacheConfigurator, self).recovery_routine() + # Reload configuration after these changes take effect if needed + # ie. ApacheParser has been initialized. + if self.parser: + # TODO: wrap into non-implementation specific parser interface + self.parser.aug.load() + + def revert_challenge_config(self): + """Used to cleanup challenge configurations. + + :raises .errors.PluginError: If unable to revert the challenge config. + + """ + self.revert_temporary_config() + self.parser.aug.load() + + def rollback_checkpoints(self, rollback=1): + """Rollback saved checkpoints. + + :param int rollback: Number of checkpoints to revert + + :raises .errors.PluginError: If there is a problem with the input or + the function is unable to correctly revert the configuration + + """ + super(ApacheConfigurator, self).rollback_checkpoints(rollback) + self.parser.aug.load() + def _verify_exe_availability(self, exe): """Checks availability of Apache executable""" if not util.exe_exists(exe): @@ -286,26 +340,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.NoInstallationError( 'Cannot find Apache executable {0}'.format(exe)) - def _check_aug_version(self): - """ Checks that we have recent enough version of libaugeas. - If augeas version is recent enough, it will support case insensitive - regexp matching""" - - self.aug.set("/test/path/testing/arg", "aRgUMeNT") - try: - matches = self.aug.match( - "/test//*[self::arg=~regexp('argument', 'i')]") - except RuntimeError: - self.aug.remove("/test/path") - return False - self.aug.remove("/test/path") - return matches - def get_parser(self): """Initializes the ApacheParser""" # If user provided vhost_root value in command line, use it return parser.ApacheParser( - self.aug, self.option("server_root"), self.conf("vhost-root"), + self.option("server_root"), self.conf("vhost-root"), self.version, configurator=self) def _wildcard_domain(self, domain): @@ -484,8 +523,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # install SSLCertificateFile, SSLCertificateKeyFile, # and SSLCertificateChainFile directives set_cert_path = cert_path - self.aug.set(path["cert_path"][-1], cert_path) - self.aug.set(path["cert_key"][-1], key_path) + self.parser.aug.set(path["cert_path"][-1], cert_path) + self.parser.aug.set(path["cert_key"][-1], key_path) if chain_path is not None: self.parser.add_dir(vhost.path, "SSLCertificateChainFile", chain_path) @@ -497,8 +536,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.PluginError("Please provide the --fullchain-path " "option pointing to your full chain file") set_cert_path = fullchain_path - self.aug.set(path["cert_path"][-1], fullchain_path) - self.aug.set(path["cert_key"][-1], key_path) + self.parser.aug.set(path["cert_path"][-1], fullchain_path) + self.parser.aug.set(path["cert_key"][-1], key_path) # Enable the new vhost if needed if not vhost.enabled: @@ -798,7 +837,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ addrs = set() try: - args = self.aug.match(path + "/arg") + args = self.parser.aug.match(path + "/arg") except RuntimeError: logger.warning("Encountered a problem while parsing file: %s, skipping", path) return None @@ -816,7 +855,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): is_ssl = True filename = apache_util.get_file_path( - self.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) + self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) if filename is None: return None @@ -846,7 +885,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Make a list of parser paths because the parser_paths # dictionary may be modified during the loop. for vhost_path in list(self.parser.parser_paths): - paths = self.aug.match( + paths = self.parser.aug.match( ("/files%s//*[label()=~regexp('%s')]" % (vhost_path, parser.case_i("VirtualHost")))) paths = [path for path in paths if @@ -1100,16 +1139,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): avail_fp = nonssl_vhost.filep ssl_fp = self._get_ssl_vhost_path(avail_fp) - orig_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" % + orig_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) self._copy_create_ssl_vhost_skeleton(nonssl_vhost, ssl_fp) # Reload augeas to take into account the new vhost - self.aug.load() + self.parser.aug.load() # Get Vhost augeas path for new vhost - new_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" % + new_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) @@ -1120,7 +1159,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Make Augeas aware of the new vhost self.parser.parse_file(ssl_fp) # Try to search again - new_matches = self.aug.match( + new_matches = self.parser.aug.match( "/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) @@ -1270,8 +1309,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "vhost for your HTTPS site located at {1} because they have " "the potential to create redirection loops.".format( vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY) - self.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") - self.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") def _sift_rewrite_rules(self, contents): """ Helper function for _copy_create_ssl_vhost_skeleton to prepare the @@ -1346,7 +1385,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ try: - span_val = self.aug.span(vhost.path) + span_val = self.parser.aug.span(vhost.path) except ValueError: logger.critical("Error while reading the VirtualHost %s from " "file %s", vhost.name, vhost.filep, exc_info=True) @@ -1381,13 +1420,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _update_ssl_vhosts_addrs(self, vh_path): ssl_addrs = set() - ssl_addr_p = self.aug.match(vh_path + "/arg") + ssl_addr_p = self.parser.aug.match(vh_path + "/arg") for addr in ssl_addr_p: old_addr = obj.Addr.fromstring( str(self.parser.get_arg(addr))) ssl_addr = old_addr.get_addr_obj("443") - self.aug.set(addr, str(ssl_addr)) + self.parser.aug.set(addr, str(ssl_addr)) ssl_addrs.add(ssl_addr) return ssl_addrs @@ -1406,14 +1445,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): vh_path, False)) > 1: directive_path = self.parser.find_dir(directive, None, vh_path, False) - self.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) def _remove_directives(self, vh_path, directives): for directive in directives: while self.parser.find_dir(directive, None, vh_path, False): directive_path = self.parser.find_dir(directive, None, vh_path, False) - self.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) def _add_dummy_ssl_directives(self, vh_path): self.parser.add_dir(vh_path, "SSLCertificateFile", @@ -1452,7 +1491,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ matches = self.parser.find_dir( "ServerAlias", start=vh_path, exclude=False) - aliases = (self.aug.get(match) for match in matches) + aliases = (self.parser.aug.get(match) for match in matches) return self.domain_in_names(aliases, target_name) def _add_name_vhost_if_necessary(self, vhost): @@ -1635,7 +1674,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if header_path: pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' for match in header_path: - if re.search(pat, self.aug.get(match).lower()): + if re.search(pat, self.parser.aug.get(match).lower()): hsts_dirpath = match if not hsts_dirpath: err_msg = ("Certbot was unable to find the existing HSTS header " @@ -1649,7 +1688,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Our match statement was for string strict-transport-security, but # we need to update the value instead. The next index is for the value hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]") - self.aug.set(hsts_dirpath, hsts_maxage) + self.parser.aug.set(hsts_dirpath, hsts_maxage) note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost " "in {1}\n".format(nextstep_value, vhost.filep)) logger.debug(note_msg) @@ -1731,7 +1770,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # We'll simply delete the directive, so that we'll have a # consistent OCSP cache path. if stapling_cache_aug_path: - self.aug.remove( + self.parser.aug.remove( re.sub(r"/\w*$", "", stapling_cache_aug_path[0])) self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path, @@ -1808,7 +1847,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # "Existing Header directive for virtualhost" pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower()) for match in header_path: - if re.search(pat, self.aug.get(match).lower()): + if re.search(pat, self.parser.aug.get(match).lower()): raise errors.PluginEnhancementAlreadyPresent( "Existing %s header" % (header_substring)) @@ -1935,11 +1974,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): constants.REWRITE_HTTPS_ARGS_WITH_END] for dir_path, args_paths in rewrite_args_dict.items(): - arg_vals = [self.aug.get(x) for x in args_paths] + arg_vals = [self.parser.aug.get(x) for x in args_paths] # Search for past redirection rule, delete it, set the new one if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS: - self.aug.remove(dir_path) + self.parser.aug.remove(dir_path) self._set_https_redirection_rewrite_rule(vhost) self.save() raise errors.PluginEnhancementAlreadyPresent( @@ -1995,7 +2034,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): redirect_filepath = self._write_out_redirect(ssl_vhost, text) - self.aug.load() + self.parser.aug.load() # Make a new vhost data structure and add it to the lists new_vhost = self._create_vhost(parser.get_aug_path(self._escape(redirect_filepath))) self.vhosts.append(new_vhost) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index a42e1ac9c..7c7492dbf 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -86,7 +86,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return CentOSParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -155,7 +155,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): for loadmod_path in loadmod_paths: nodir_path = loadmod_path.split("/directive")[0] # Remove the old LoadModule directive - self.aug.remove(loadmod_path) + self.parser.aug.remove(loadmod_path) # Create a new IfModule !mod_ssl.c if not already found on path ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py index cb0bf06d0..786ada0fc 100644 --- a/certbot-apache/certbot_apache/override_fedora.py +++ b/certbot-apache/certbot_apache/override_fedora.py @@ -51,7 +51,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return FedoraParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) def _try_restart_fedora(self): diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index 44b3cac95..c358a10fa 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -44,7 +44,7 @@ class GentooConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return GentooParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index 95b0930a0..b5f0cd81a 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -13,6 +13,8 @@ from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, from certbot import errors from certbot.compat import os +from certbot_apache import constants + logger = logging.getLogger(__name__) @@ -32,7 +34,7 @@ class ApacheParser(object): arg_var_interpreter = re.compile(r"\$\{[^ \}]*}") fnmatch_chars = set(["*", "?", "\\", "[", "]"]) - def __init__(self, aug, root, vhostroot=None, version=(2, 4), + def __init__(self, root, vhostroot=None, version=(2, 4), configurator=None): # Note: Order is important here. @@ -41,11 +43,20 @@ class ApacheParser(object): # issues with aug.load() after adding new files / defines to parse tree self.configurator = configurator + # Initialize augeas + self.aug = None + self.init_augeas() + + if not self.check_aug_version(): + raise errors.NotSupportedError( + "Apache plugin support requires libaugeas0 and augeas-lenses " + "version 1.2.0 or higher, please make sure you have you have " + "those installed.") + self.modules = set() # type: Set[str] self.parser_paths = {} # type: Dict[str, List[str]] self.variables = {} # type: Dict[str, str] - self.aug = aug # Find configuration root and make sure augeas can parse it. self.root = os.path.abspath(root) self.loc = {"root": self._find_config_root()} @@ -77,6 +88,146 @@ class ApacheParser(object): if self.find_dir("Define", exclude=False): raise errors.PluginError("Error parsing runtime variables") + def init_augeas(self): + """ Initialize the actual Augeas instance """ + + try: + import augeas + except ImportError: # pragma: no cover + raise errors.NoInstallationError("Problem in Augeas installation") + + self.aug = augeas.Augeas( + # specify a directory to load our preferred lens from + loadpath=constants.AUGEAS_LENS_DIR, + # Do not save backup (we do it ourselves), do not load + # anything by default + flags=(augeas.Augeas.NONE | + augeas.Augeas.NO_MODL_AUTOLOAD | + augeas.Augeas.ENABLE_SPAN)) + + def check_parsing_errors(self, lens): + """Verify Augeas can parse all of the lens files. + + :param str lens: lens to check for errors + + :raises .errors.PluginError: If there has been an error in parsing with + the specified lens. + + """ + error_files = self.aug.match("/augeas//error") + + for path in error_files: + # Check to see if it was an error resulting from the use of + # the httpd lens + lens_path = self.aug.get(path + "/lens") + # As aug.get may return null + if lens_path and lens in lens_path: + msg = ( + "There has been an error in parsing the file {0} on line {1}: " + "{2}".format( + # Strip off /augeas/files and /error + path[13:len(path) - 6], + self.aug.get(path + "/line"), + self.aug.get(path + "/message"))) + raise errors.PluginError(msg) + + def check_aug_version(self): + """ Checks that we have recent enough version of libaugeas. + If augeas version is recent enough, it will support case insensitive + regexp matching""" + + self.aug.set("/test/path/testing/arg", "aRgUMeNT") + try: + matches = self.aug.match( + "/test//*[self::arg=~regexp('argument', 'i')]") + except RuntimeError: + self.aug.remove("/test/path") + return False + self.aug.remove("/test/path") + return matches + + def unsaved_files(self): + """Lists files that have modified Augeas DOM but the changes have not + been written to the filesystem yet, used by `self.save()` and + ApacheConfigurator to check the file state. + + :raises .errors.PluginError: If there was an error in Augeas, in + an attempt to save the configuration, or an error creating a + checkpoint + + :returns: `set` of unsaved files + """ + save_state = self.aug.get("/augeas/save") + self.aug.set("/augeas/save", "noop") + # Existing Errors + ex_errs = self.aug.match("/augeas//error") + try: + # This is a noop save + self.aug.save() + except (RuntimeError, IOError): + self._log_save_errors(ex_errs) + # Erase Save Notes + self.configurator.save_notes = "" + raise errors.PluginError( + "Error saving files, check logs for more info.") + + # Return the original save method + self.aug.set("/augeas/save", save_state) + + # Retrieve list of modified files + # Note: Noop saves can cause the file to be listed twice, I used a + # set to remove this possibility. This is a known augeas 0.10 error. + save_paths = self.aug.match("/augeas/events/saved") + + save_files = set() + if save_paths: + for path in save_paths: + save_files.add(self.aug.get(path)[6:]) + return save_files + + def ensure_augeas_state(self): + """Makes sure that all Augeas dom changes are written to files to avoid + loss of configuration directives when doing additional augeas parsing, + causing a possible augeas.load() resulting dom reset + """ + + if self.unsaved_files(): + self.configurator.save_notes += "(autosave)" + self.configurator.save() + + def save(self, save_files): + """Saves all changes to the configuration files. + + save() is called from ApacheConfigurator to handle the parser specific + tasks of saving. + + :param list save_files: list of strings of file paths that we need to save. + + """ + self.configurator.save_notes = "" + self.aug.save() + + # Force reload if files were modified + # This is needed to recalculate augeas directive span + if save_files: + for sf in save_files: + self.aug.remove("/files/"+sf) + self.aug.load() + + def _log_save_errors(self, ex_errs): + """Log errors due to bad Augeas save. + + :param list ex_errs: Existing errors before save + + """ + # Check for the root of save problems + new_errs = self.aug.match("/augeas//error") + # logger.error("During Save - %s", mod_conf) + logger.error("Unable to save files: %s. Attempted Save Notes: %s", + ", ".join(err[13:len(err) - 6] for err in new_errs + # Only new errors caused by recent save + if err not in ex_errs), self.configurator.save_notes) + def add_include(self, main_config, inc_path): """Add Include for a new configuration file if one does not exist @@ -658,8 +809,7 @@ class ApacheParser(object): use_new, remove_old = self._check_path_actions(filepath) # Ensure that we have the latest Augeas DOM state on disk before # calling aug.load() which reloads the state from disk - if self.configurator: - self.configurator.ensure_augeas_state() + self.ensure_augeas_state() # Test if augeas included file for Httpd.lens # Note: This works for augeas globs, ie. *.conf if use_new: diff --git a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py b/certbot-apache/certbot_apache/tests/augeas_configurator_test.py deleted file mode 100644 index bfe3b7f16..000000000 --- a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Test for certbot_apache.augeas_configurator.""" -import shutil -import unittest - -import mock - -from certbot import errors -from certbot.compat import os - -from certbot_apache.tests import util - - -class AugeasConfiguratorTest(util.ApacheTest): - """Test for Augeas Configurator base class.""" - - - def setUp(self): # pylint: disable=arguments-differ - super(AugeasConfiguratorTest, self).setUp() - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) - - self.vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multiple_vhosts") - - def tearDown(self): - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - shutil.rmtree(self.temp_dir) - - def test_bad_parse(self): - # pylint: disable=protected-access - self.config.parser.parse_file(os.path.join( - self.config.parser.root, "conf-available", "bad_conf_file.conf")) - self.assertRaises( - errors.PluginError, self.config.check_parsing_errors, "httpd.aug") - - def test_bad_save(self): - mock_save = mock.Mock() - mock_save.side_effect = IOError - self.config.aug.save = mock_save - - self.assertRaises(errors.PluginError, self.config.save) - - def test_bad_save_checkpoint(self): - self.config.reverter.add_to_checkpoint = mock.Mock( - side_effect=errors.ReverterError) - self.config.parser.add_dir( - self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save) - - def test_bad_save_finalize_checkpoint(self): - self.config.reverter.finalize_checkpoint = mock.Mock( - side_effect=errors.ReverterError) - self.config.parser.add_dir( - self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save, "Title") - - def test_finalize_save(self): - mock_finalize = mock.Mock() - self.config.reverter = mock_finalize - self.config.save("Example Title") - - self.assertTrue(mock_finalize.is_called) - - def test_recovery_routine(self): - mock_load = mock.Mock() - self.config.aug.load = mock_load - - self.config.recovery_routine() - self.assertEqual(mock_load.call_count, 1) - - def test_recovery_routine_error(self): - self.config.reverter.recovery_routine = mock.Mock( - side_effect=errors.ReverterError) - - self.assertRaises( - errors.PluginError, self.config.recovery_routine) - - def test_revert_challenge_config(self): - mock_load = mock.Mock() - self.config.aug.load = mock_load - - self.config.revert_challenge_config() - self.assertEqual(mock_load.call_count, 1) - - def test_revert_challenge_config_error(self): - self.config.reverter.revert_temporary_config = mock.Mock( - side_effect=errors.ReverterError) - - self.assertRaises( - errors.PluginError, self.config.revert_challenge_config) - - def test_rollback_checkpoints(self): - mock_load = mock.Mock() - self.config.aug.load = mock_load - - self.config.rollback_checkpoints() - self.assertEqual(mock_load.call_count, 1) - - def test_rollback_error(self): - self.config.reverter.rollback_checkpoints = mock.Mock( - side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) - - def test_view_config_changes(self): - self.config.view_config_changes() - - def test_view_config_changes_error(self): - self.config.reverter.view_config_changes = mock.Mock( - side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.view_config_changes) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/certbot_apache/tests/centos6_test.py index 0d093aca8..3c427ee91 100644 --- a/certbot-apache/certbot_apache/tests/centos6_test.py +++ b/certbot-apache/certbot_apache/tests/centos6_test.py @@ -165,10 +165,6 @@ class CentOS6Tests(util.ApacheTest): "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) self.assertEqual(len(post_loadmods), 1) - - - - def test_loadmod_non_duplicate(self): # the modules/mod_ssl.so exists in ssl.conf sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"] @@ -197,7 +193,7 @@ class CentOS6Tests(util.ApacheTest): exclude=False) for mod in orig_loadmods: noarg_path = mod.rpartition("/")[0] - self.config.aug.remove(noarg_path) + self.config.parser.aug.remove(noarg_path) self.config.save() self.config.deploy_cert( "random.demo", "example/cert.pem", "example/key.pem", diff --git a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py new file mode 100644 index 000000000..114f253e6 --- /dev/null +++ b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py @@ -0,0 +1,93 @@ +"""Test for certbot_apache.configurator implementations of reverter""" +import shutil +import unittest + +import mock + +from certbot import errors + +from certbot_apache.tests import util + + +class ConfiguratorReverterTest(util.ApacheTest): + """Test for ApacheConfigurator reverter methods""" + + + def setUp(self): # pylint: disable=arguments-differ + super(ConfiguratorReverterTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def tearDown(self): + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + shutil.rmtree(self.temp_dir) + + def test_bad_save_checkpoint(self): + self.config.reverter.add_to_checkpoint = mock.Mock( + side_effect=errors.ReverterError) + self.config.parser.add_dir( + self.vh_truth[0].path, "Test", "bad_save_ckpt") + self.assertRaises(errors.PluginError, self.config.save) + + def test_bad_save_finalize_checkpoint(self): + self.config.reverter.finalize_checkpoint = mock.Mock( + side_effect=errors.ReverterError) + self.config.parser.add_dir( + self.vh_truth[0].path, "Test", "bad_save_ckpt") + self.assertRaises(errors.PluginError, self.config.save, "Title") + + def test_finalize_save(self): + mock_finalize = mock.Mock() + self.config.reverter = mock_finalize + self.config.save("Example Title") + + self.assertTrue(mock_finalize.is_called) + + def test_revert_challenge_config(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + + self.config.revert_challenge_config() + self.assertEqual(mock_load.call_count, 1) + + def test_revert_challenge_config_error(self): + self.config.reverter.revert_temporary_config = mock.Mock( + side_effect=errors.ReverterError) + + self.assertRaises( + errors.PluginError, self.config.revert_challenge_config) + + def test_rollback_checkpoints(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + + self.config.rollback_checkpoints() + self.assertEqual(mock_load.call_count, 1) + + def test_rollback_error(self): + self.config.reverter.rollback_checkpoints = mock.Mock( + side_effect=errors.ReverterError) + self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) + + def test_view_config_changes(self): + self.config.view_config_changes() + + def test_view_config_changes_error(self): + self.config.reverter.view_config_changes = mock.Mock( + side_effect=errors.ReverterError) + self.assertRaises(errors.PluginError, self.config.view_config_changes) + + def test_recovery_routine_reload(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + self.config.recovery_routine() + self.assertEqual(mock_load.call_count, 1) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 906232596..f9b6fb186 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -51,25 +51,14 @@ class MultipleVhostsTest(util.ApacheTest): self.config.deploy_cert = mocked_deploy_cert return self.config - @mock.patch("certbot_apache.configurator.ApacheConfigurator.init_augeas") @mock.patch("certbot_apache.configurator.path_surgery") - def test_prepare_no_install(self, mock_surgery, _init_augeas): + def test_prepare_no_install(self, mock_surgery): silly_path = {"PATH": "/tmp/nothingness2342"} mock_surgery.return_value = False with mock.patch.dict('os.environ', silly_path): self.assertRaises(errors.NoInstallationError, self.config.prepare) self.assertEqual(mock_surgery.call_count, 1) - @mock.patch("certbot_apache.augeas_configurator.AugeasConfigurator.init_augeas") - def test_prepare_no_augeas(self, mock_init_augeas): - """ Test augeas initialization ImportError """ - def side_effect_error(): - """ Side effect error for the test """ - raise ImportError - mock_init_augeas.side_effect = side_effect_error - self.assertRaises( - errors.NoInstallationError, self.config.prepare) - @mock.patch("certbot_apache.parser.ApacheParser") @mock.patch("certbot_apache.configurator.util.exe_exists") def test_prepare_version(self, mock_exe_exists, _): @@ -81,16 +70,6 @@ class MultipleVhostsTest(util.ApacheTest): self.assertRaises( errors.NotSupportedError, self.config.prepare) - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") - def test_prepare_old_aug(self, mock_exe_exists, _): - mock_exe_exists.return_value = True - self.config.config_test = mock.Mock() - # pylint: disable=protected-access - self.config._check_aug_version = mock.Mock(return_value=False) - self.assertRaises( - errors.NotSupportedError, self.config.prepare) - def test_prepare_locked(self): server_root = self.config.conf("server-root") self.config.config_test = mock.Mock() @@ -675,7 +654,7 @@ class MultipleVhostsTest(util.ApacheTest): # span excludes the closing tag in older versions # of Augeas return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142] - with mock.patch.object(self.config.aug, 'span') as mock_span: + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: mock_span.return_value = return_value self.test_make_vhost_ssl() @@ -683,7 +662,7 @@ class MultipleVhostsTest(util.ApacheTest): # span includes the closing tag in newer versions # of Augeas return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157] - with mock.patch.object(self.config.aug, 'span') as mock_span: + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: mock_span.return_value = return_value self.test_make_vhost_ssl() @@ -1232,7 +1211,7 @@ class MultipleVhostsTest(util.ApacheTest): except errors.PluginEnhancementAlreadyPresent: args_paths = self.config.parser.find_dir( "RewriteRule", None, http_vhost.path, False) - arg_vals = [self.config.aug.get(x) for x in args_paths] + arg_vals = [self.config.parser.aug.get(x) for x in args_paths] self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) @@ -1335,15 +1314,6 @@ class MultipleVhostsTest(util.ApacheTest): return account_key, (achall1, achall2, achall3) - def test_aug_version(self): - mock_match = mock.Mock(return_value=["something"]) - self.config.aug.match = mock_match - # pylint: disable=protected-access - self.assertEqual(self.config._check_aug_version(), - ["something"]) - self.config.aug.match.side_effect = RuntimeError - self.assertFalse(self.config._check_aug_version()) - def test_enable_site_nondebian(self): inc_path = "/path/to/wherever" vhost = self.vh_truth[0] @@ -1512,7 +1482,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(first_id, second_id) def test_realpath_replaces_symlink(self): - orig_match = self.config.aug.match + orig_match = self.config.parser.aug.match mock_vhost = copy.deepcopy(self.vh_truth[0]) mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') @@ -1526,7 +1496,7 @@ class MultipleVhostsTest(util.ApacheTest): return orig_match(aug_expr) self.config.parser.parser_paths = ["/mocked/path"] - self.config.aug.match = mock_match + self.config.parser.aug.match = mock_match vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 2) self.assertTrue(vhs[0] == self.vh_truth[1]) @@ -1552,8 +1522,8 @@ class AugeasVhostsTest(util.ApacheTest): self.work_dir) def test_choosevhost_with_illegal_name(self): - self.config.aug = mock.MagicMock() - self.config.aug.match.side_effect = RuntimeError + self.config.parser.aug = mock.MagicMock() + self.config.parser.aug.match.side_effect = RuntimeError path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" chosen_vhost = self.config._create_vhost(path) self.assertEqual(None, chosen_vhost) diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index ef4412a58..27d66f680 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -1,8 +1,8 @@ +# pylint: disable=too-many-public-methods """Tests for certbot_apache.parser.""" import shutil import unittest -import augeas import mock from certbot import errors @@ -22,6 +22,27 @@ class BasicParserTest(util.ParserTest): shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) + def test_bad_parse(self): + self.parser.parse_file(os.path.join(self.parser.root, + "conf-available", "bad_conf_file.conf")) + self.assertRaises( + errors.PluginError, self.parser.check_parsing_errors, "httpd.aug") + + def test_bad_save(self): + mock_save = mock.Mock() + mock_save.side_effect = IOError + self.parser.aug.save = mock_save + self.assertRaises(errors.PluginError, self.parser.unsaved_files) + + def test_aug_version(self): + mock_match = mock.Mock(return_value=["something"]) + self.parser.aug.match = mock_match + # pylint: disable=protected-access + self.assertEqual(self.parser.check_aug_version(), + ["something"]) + self.parser.aug.match.side_effect = RuntimeError + self.assertFalse(self.parser.check_aug_version()) + def test_find_config_root_no_root(self): # pylint: disable=protected-access os.remove(self.parser.loc["root"]) @@ -311,21 +332,38 @@ class BasicParserTest(util.ParserTest): class ParserInitTest(util.ApacheTest): def setUp(self): # pylint: disable=arguments-differ super(ParserInitTest, self).setUp() - self.aug = augeas.Augeas( - flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) + @mock.patch("certbot_apache.parser.ApacheParser.init_augeas") + def test_prepare_no_augeas(self, mock_init_augeas): + from certbot_apache.parser import ApacheParser + mock_init_augeas.side_effect = errors.NoInstallationError + self.config.config_test = mock.Mock() + self.assertRaises( + errors.NoInstallationError, ApacheParser, + os.path.relpath(self.config_path), "/dummy/vhostpath", + version=(2, 4, 22), configurator=self.config) + + def test_init_old_aug(self): + from certbot_apache.parser import ApacheParser + with mock.patch("certbot_apache.parser.ApacheParser.check_aug_version") as mock_c: + mock_c.return_value = False + self.assertRaises( + errors.NotSupportedError, + ApacheParser, os.path.relpath(self.config_path), + "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) + @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") def test_unparseable(self, mock_cfg): from certbot_apache.parser import ApacheParser mock_cfg.return_value = ('Define: TEST') self.assertRaises( errors.PluginError, - ApacheParser, self.aug, os.path.relpath(self.config_path), + ApacheParser, os.path.relpath(self.config_path), "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config) def test_root_normalized(self): @@ -337,8 +375,7 @@ class ParserInitTest(util.ApacheTest): self.temp_dir, "debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2") - parser = ApacheParser(self.aug, path, - "/dummy/vhostpath", configurator=self.config) + parser = ApacheParser(path, "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) @@ -347,7 +384,7 @@ class ParserInitTest(util.ApacheTest): with mock.patch("certbot_apache.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( - self.aug, os.path.relpath(self.config_path), + os.path.relpath(self.config_path), "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) @@ -357,7 +394,7 @@ class ParserInitTest(util.ApacheTest): with mock.patch("certbot_apache.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( - self.aug, self.config_path + os.path.sep, + self.config_path + os.path.sep, "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 2ac51540f..8e3de04be 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -78,8 +78,7 @@ class ParserTest(ApacheTest): with mock.patch("certbot_apache.parser.ApacheParser." "update_runtime_variables"): self.parser = ApacheParser( - self.aug, self.config_path, self.vhost_path, - configurator=self.config) + self.config_path, self.vhost_path, configurator=self.config) def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals -- cgit v1.2.3 From 1c75b6dacde82df9acfd21dce0dc2625249a28d5 Mon Sep 17 00:00:00 2001 From: sydneyli Date: Fri, 28 Jun 2019 12:16:52 -0700 Subject: Update Nginx conf file to match Mozilla's security recommendations (#7163) Fixes #7089 --- certbot-nginx/certbot_nginx/constants.py | 2 ++ certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf | 2 +- certbot-nginx/certbot_nginx/options-ssl-nginx.conf | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index cec7acaf5..3f22000eb 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -24,6 +24,7 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" SSL_OPTIONS_HASHES_NEW = [ '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', + '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', ] """SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9""" @@ -34,6 +35,7 @@ ALL_SSL_OPTIONS_HASHES = [ '7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff', '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', + 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', ] + SSL_OPTIONS_HASHES_NEW """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf index 292d42984..627bafadb 100644 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf @@ -4,7 +4,7 @@ # the up-to-date file that you will need to refer to when manually updating # this file. -ssl_session_cache shared:le_nginx_SSL:1m; +ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf index 57a332d2f..3cc2b9b28 100644 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf @@ -4,7 +4,7 @@ # the up-to-date file that you will need to refer to when manually updating # this file. -ssl_session_cache shared:le_nginx_SSL:1m; +ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; -- cgit v1.2.3 From 4fc30f2ecbdf2bec0b5a1e91430e186d2a0ec631 Mon Sep 17 00:00:00 2001 From: dkp Date: Fri, 28 Jun 2019 22:06:52 +0200 Subject: Replace Some Platform-Specific Line Separation (#7203) os.linesep isn't supposed to be used when writing to files opened in text mode, where '\n' is escaped to the platform-specific ASCII sequence. For example, on Windows, os.linesep is '\r\n' and in text mode is escaped to ASCII sequence CR CR LF rather than just CR LF. This is also true for the default logger and IDisplay notifications. Replacing os.linesep with '\n' ensures the right sequence is escaped. Resolves: 6899 --- CHANGELOG.md | 2 +- certbot/display/util.py | 2 +- certbot/reverter.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a7f6aec..1fec8b7c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Replace some unnecessary platform-specific line separation. More details about these changes can be found on our GitHub repo. diff --git a/certbot/display/util.py b/certbot/display/util.py index 91f3bc33c..aa9f0583b 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -114,7 +114,7 @@ class FileDisplay(object): message = _wrap_lines(message) self.outfile.write( "{line}{frame}{line}{msg}{line}{frame}{line}".format( - line=os.linesep, frame=SIDE_FRAME, msg=message)) + line='\n', frame=SIDE_FRAME, msg=message)) self.outfile.flush() if pause: if self._can_interact(force_interactive): diff --git a/certbot/reverter.py b/certbot/reverter.py index a7a8a943d..201bdc03d 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -178,10 +178,10 @@ class Reverter(object): for path in filepaths: output.append(" {0}".format(path)) - output.append(os.linesep) + output.append('\n') zope.component.getUtility(interfaces.IDisplay).notification( - os.linesep.join(output), force_interactive=True, pause=False) + '\n'.join(output), force_interactive=True, pause=False) return None def add_to_temp_checkpoint(self, save_files, save_notes): @@ -497,9 +497,9 @@ class Reverter(object): os.remove(path) else: logger.warning( - "File: %s - Could not be found to be deleted %s - " - "Certbot probably shut down unexpectedly", - os.linesep, path) + "File: %s - Could not be found to be deleted\n" + " - Certbot probably shut down unexpectedly", + path) except (IOError, OSError): logger.critical( "Unable to remove filepaths contained within %s", file_list) -- cgit v1.2.3 From 76b7eb06284c07566d696acde8b329af7b66a906 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 28 Jun 2019 15:53:56 -0700 Subject: Document certbot-auto's code freeze. (#7207) Inspired by #7194, this PR adds a note to our documentation that we're not accepting most changes to certbot-auto. --- docs/contributing.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/contributing.rst b/docs/contributing.rst index 8685a0dd5..14256b15a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -428,6 +428,8 @@ Steps: rewriting commits makes changes harder to track between reviews. 6. Did your tests pass on Travis? If they didn't, fix any errors. +.. _ask for help: + Asking for help =============== @@ -454,6 +456,11 @@ Conduct violation, EFF may review discussion channels or direct messages. Updating certbot-auto and letsencrypt-auto ========================================== +.. note:: We are currently only accepting changes to certbot-auto that fix + regressions on platforms where certbot-auto is the recommended installation + method at https://certbot.eff.org/instructions. If you are unsure if a change + you want to make qualifies, don't hesitate to `ask for help`_! + Updating the scripts -------------------- Developers should *not* modify the ``certbot-auto`` and ``letsencrypt-auto`` files -- cgit v1.2.3 From 3e872627d8dd5c8406ca6d5c3964ed295c7e1f60 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 2 Jul 2019 10:02:00 -0700 Subject: Pin/upgrade virtualenv in our tests (#7211) * Update virtualenv to the latest version. * Use venv from pip and pin more packages. * Pin codecov. * update appveyor config * Write the path separator backwards. * s/pip_install.py install/pip_install.py * Prefix tools\\pip_install.py with python exe. * Upgrade py to fix AppVeyor failures. * add back comment * Update virtualenv with CERTBOT_NO_PIN. * Pass -U to upgrade tox and deps. * Upgrade virtualenv. --- .travis.yml | 8 ++++++-- appveyor.yml | 3 ++- tools/dev_constraints.txt | 5 +++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66ef5ebb1..86a475ca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -257,7 +257,6 @@ addons: apt: packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder. - python-dev - - python-virtualenv - gcc - libaugeas0 - libssl-dev @@ -267,7 +266,12 @@ addons: - nginx-light - openssl -install: "$(command -v pip || command -v pip3) install codecov tox" +# tools/pip_install.py is used to pin packages to a known working version +# except in tests where the environment variable CERTBOT_NO_PIN is set. +# virtualenv is listed here explicitly to make sure it is upgraded when +# CERTBOT_NO_PIN is set to work around failures we've seen when using an older +# version of virtualenv. +install: "tools/pip_install.py -U codecov tox virtualenv" script: tox after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' diff --git a/appveyor.yml b/appveyor.yml index ed3e87c6c..3d58847f8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,8 @@ install: # Upgrade pip to avoid warnings - "python -m pip install --upgrade pip" # Ready to install tox and coverage - - "pip install tox codecov" + # tools/pip_install.py is used to pin packages to a known working version. + - "python tools\\pip_install.py tox codecov" build: off diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index cc0e54185..90b4775c1 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -13,6 +13,7 @@ backports.shutil-get-terminal-size==1.0.0 boto3==1.9.36 botocore==1.12.36 cloudflare==1.5.1 +codecov==2.0.15 configparser==3.7.4 coverage==4.4.2 decorator==4.1.2 @@ -47,7 +48,7 @@ pkginfo==1.4.2 pluggy==0.5.2 prompt-toolkit==1.0.15 ptyprocess==0.5.2 -py==1.4.34 +py==1.8.0 pyasn1==0.1.9 pyasn1-modules==0.0.10 Pygments==2.2.0 @@ -81,6 +82,6 @@ twine==1.11.0 typed-ast==1.1.0 typing==3.6.4 uritemplate==0.6 -virtualenv==15.1.0 +virtualenv==16.6.1 wcwidth==0.1.7 wrapt==1.11.1 -- cgit v1.2.3 From 448d15922367da05a0f95f1729773af7d139b582 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 2 Jul 2019 13:45:57 -0700 Subject: Install Python3 only dev tools with tools/venv3.py (#7215) These packages can be useful and I found that they aren't being installed in our Python 3 development environment. Let's fix that. --- tools/_venv_common.py | 8 +------- tools/venv.py | 4 ++++ tools/venv3.py | 4 ++++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/_venv_common.py b/tools/_venv_common.py index ac3b5f98d..0898f4f50 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -177,12 +177,9 @@ def prepare_venv_path(venv_name): return venv_name -def install_packages(venv_name, pip_args=None): +def install_packages(venv_name, pip_args): """Installs packages in the given venv. - If pip_args is given, they are the arguments given to pip, - otherwise, REQUIREMENTS is used. - :param str venv_name: The name or path at where the virtual environment should be created. :param pip_args: Command line arguments that should be given to @@ -190,9 +187,6 @@ def install_packages(venv_name, pip_args=None): :type pip_args: `list` of `str` """ - if not pip_args: - pip_args = REQUIREMENTS - # Using the python executable from venv, we ensure to execute following commands in this venv. py_venv = get_venv_python_path(venv_name) subprocess_with_print([py_venv, os.path.abspath('letsencrypt-auto-source/pieces/pipstrap.py')]) diff --git a/tools/venv.py b/tools/venv.py index 4e205c3a5..6cd38cfc0 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -25,6 +25,10 @@ def main(pip_args=None): venv_path = _venv_common.prepare_venv_path('venv') create_venv(venv_path) + + if not pip_args: + pip_args = _venv_common.REQUIREMENTS + _venv_common.install_packages(venv_path, pip_args) diff --git a/tools/venv3.py b/tools/venv3.py index dc56a322e..77a30763d 100755 --- a/tools/venv3.py +++ b/tools/venv3.py @@ -19,6 +19,10 @@ def create_venv(venv_path): def main(pip_args=None): venv_path = _venv_common.prepare_venv_path('venv3') create_venv(venv_path) + + if not pip_args: + pip_args = _venv_common.REQUIREMENTS + ['-e .[dev3]'] + _venv_common.install_packages(venv_path, pip_args) -- cgit v1.2.3 From 88876b990178006ee52a4ed2324d6bdf8a78cb9f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 3 Jul 2019 01:21:24 +0200 Subject: [Windows] Security model for files permissions - STEP 3c (#6967) * Implement security.open * Clean lint * Rename security into filesystem * Update certbot/compat/filesystem.py Co-Authored-By: ohemorange * Update certbot/util.py Co-Authored-By: ohemorange * Update certbot/lock.py Co-Authored-By: ohemorange * Update certbot/compat/os.py Co-Authored-By: ohemorange * Update certbot/lock.py Co-Authored-By: ohemorange * Update certbot/compat/os.py Co-Authored-By: ohemorange * Simplify and make more clear comment on os.open. * Secure implementation preventing race conditions * Revert "Secure implementation preventing race conditions" This reverts commit dbb85492195122020ca0b4a685ddb4836fdc6d12. * Simplify the logic on Windows. * Implement os.open to prevent race conditions * Add unit tests * Handle os.O_CREAT and os.O_EXCL directly from the Windows APIs * Improve comments * Use CREATE_ALWAYS * Adapt coverage threshold to new Windows specific LOCs. * Update certbot/compat/os.py Co-Authored-By: ohemorange * Update certbot/compat/os.py Co-Authored-By: ohemorange * Update certbot/compat/os.py Co-Authored-By: ohemorange * Update certbot/compat/filesystem.py Co-Authored-By: ohemorange * Add some comments * Fix pylint * Improve docstring * Added test cases * Improve docstring * Update certbot/lock.py Co-Authored-By: ohemorange * Update certbot/lock.py Co-Authored-By: ohemorange * Fix lint * Adapt coverage * Adapt coverage --- .codecov.yml | 2 +- certbot/compat/filesystem.py | 88 ++++++++++++++++++++++++++++++++- certbot/compat/os.py | 13 ++++- certbot/lock.py | 11 +++-- certbot/plugins/storage.py | 9 ++-- certbot/plugins/storage_test.py | 2 +- certbot/tests/compat/filesystem_test.py | 83 +++++++++++++++++++++++++++++-- certbot/tests/compat/os_test.py | 2 +- certbot/tests/hook_test.py | 2 +- certbot/tests/util_test.py | 2 +- certbot/util.py | 11 ++--- test | 1 + tox.cover.py | 2 +- 13 files changed, 202 insertions(+), 26 deletions(-) create mode 120000 test diff --git a/.codecov.yml b/.codecov.yml index af67ea27f..3a7be503d 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 98.0 + target: 97.8 threshold: 0.1 base: auto windows: diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 5dc01a622..8cdff17d3 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -1,12 +1,20 @@ """Compat module to handle files security on Windows and Linux""" from __future__ import absolute_import +import errno import os # pylint: disable=os-module-forbidden import stat try: - import ntsecuritycon # pylint: disable=import-error - import win32security # pylint: disable=import-error + # pylint: disable=import-error + import ntsecuritycon + import win32security + import win32con + import win32api + import win32file + import pywintypes + import winerror + # pylint: enable=import-error except ImportError: POSIX_MODE = True else: @@ -36,6 +44,68 @@ def chmod(file_path, mode): _apply_win_mode(file_path, mode) +def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin + # type: (str, int, int) -> int + """ + Wrapper of original os.open function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int flags: Flags to apply on file while opened + :param int mode: POSIX mode to apply on file when opened, + Python defaults will be applied if ``None`` + :returns: the file descriptor to the opened file + :rtype: int + :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set, + OSError(errno.EACCES) on Windows if the file already exists and is a directory, and + os.O_CREAT is set. + """ + if POSIX_MODE: + # On Linux, invoke os.open directly. + return os.open(file_path, flags, mode) + + # Windows: handle creation of the file atomically with proper permissions. + if flags & os.O_CREAT: + # If os.O_EXCL is set, we will use the "CREATE_NEW", that will raise an exception if + # file exists, matching the API contract of this bit flag. Otherwise, we use + # "CREATE_ALWAYS" that will always create the file whether it exists or not. + disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + # We set first parameter to 1 (`True`) to say that this security descriptor contains + # a DACL. Otherwise second and third parameters are ignored. + # We set third parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long + security.SetSecurityDescriptorDacl(1, dacl, 0) + + try: + handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, + win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, + attributes, disposition, 0, None) + handle.Close() + except pywintypes.error as err: + # Handle native windows errors into python errors to be consistent with the API + # of os.open in the situation of a file already existing or locked. + if err.winerror == winerror.ERROR_FILE_EXISTS: + raise OSError(errno.EEXIST, err.strerror) + if err.winerror == winerror.ERROR_SHARING_VIOLATION: + raise OSError(errno.EACCES, err.strerror) + raise err + + # At this point, the file that did not exist has been created with proper permissions, + # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to + # avoid a FileExists exception before calling os.open. + return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL) + + # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine. + handle = os.open(file_path, flags) + chmod(file_path, mode) + return handle + + def replace(src, dst): # type: (str, str) -> None """ @@ -173,3 +243,17 @@ def _compare_dacls(dacl1, dacl2): """ return ([dacl1.GetAce(index) for index in range(0, dacl1.GetAceCount())] == [dacl2.GetAce(index) for index in range(0, dacl2.GetAceCount())]) + + +def _get_current_user(): + """ + Return the pySID corresponding to the current user. + """ + account_name = win32api.GetUserNameEx(win32api.NameSamCompatible) + # LookupAccountName() expects the system name as first parameter. By passing None to it, + # we instruct Windows to first search the matching account in the machine local accounts, + # then into the primary domain accounts, if the machine has joined a domain, then finally + # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows + # if there is no reason to use a specific lookup mechanism. + # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea + return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot/compat/os.py b/certbot/compat/os.py index f704055f4..a7de464fa 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -45,12 +45,23 @@ del ourselves, std_os, std_sys # Basically, it states that appropriate permissions will be set for the owner, nothing for the # group, appropriate permissions for the "Everyone" group, and all permissions to the # "Administrators" group + "System" user, as they can do everything anyway. -def chmod(*unused_args, **unused_kwargs): # pylint: disable=function-redefined +def chmod(*unused_args, **unused_kwargs): """Method os.chmod() is forbidden""" raise RuntimeError('Usage of os.chmod() is forbidden. ' 'Use certbot.compat.filesystem.chmod() instead.') +# The os.open function on Windows has the same effect as a call to os.chown concerning the file +# modes: these modes lack the correct control over the permissions given to the file. Instead, +# filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are +# atomically set in case of file creation, or invokes filesystem.chmod to properly set the +# permissions for the other cases. +def open(*unused_args, **unused_kwargs): + """Method os.open() is forbidden""" + raise RuntimeError('Usage of os.open() is forbidden. ' + 'Use certbot.compat.filesystem.open() instead.') + + # Because of the blocking strategy on file handlers on Windows, rename does not behave as expected # with POSIX systems: an exception will be raised if dst already exists. def rename(*unused_args, **unused_kwargs): diff --git a/certbot/lock.py b/certbot/lock.py index fad8a5175..cdb0fbb3c 100644 --- a/certbot/lock.py +++ b/certbot/lock.py @@ -13,6 +13,7 @@ from acme.magic_typing import Optional # pylint: disable=unused-import, no-name from certbot import errors from certbot.compat import os +from certbot.compat import filesystem logger = logging.getLogger(__name__) @@ -131,7 +132,7 @@ class _UnixLockMechanism(_BaseLockMechanism): """Acquire the lock.""" while self._fd is None: # Open the file - fd = os.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) + fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) try: self._try_lock(fd) if self._lock_success(fd): @@ -223,11 +224,15 @@ class _WindowsLockMechanism(_BaseLockMechanism): """Acquire the lock""" open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC - fd = os.open(self._path, open_mode, 0o600) + fd = None try: + # Under Windows, filesystem.open will raise directly an EACCES error + # if the lock file is already locked. + fd = filesystem.open(self._path, open_mode, 0o600) msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) except (IOError, OSError) as err: - os.close(fd) + if fd: + os.close(fd) # Anything except EACCES is unexpected. Raise directly the error in that case. if err.errno != errno.EACCES: raise diff --git a/certbot/plugins/storage.py b/certbot/plugins/storage.py index 51350802c..294dfa0e8 100644 --- a/certbot/plugins/storage.py +++ b/certbot/plugins/storage.py @@ -6,9 +6,11 @@ from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-nam from certbot import errors from certbot.compat import os +from certbot.compat import filesystem logger = logging.getLogger(__name__) + class PluginStorage(object): """Class implementing storage functionality for plugins""" @@ -84,9 +86,10 @@ class PluginStorage(object): logger.error(errmsg) raise errors.PluginStorageError(errmsg) try: - with os.fdopen(os.open(self._storagepath, - os.O_WRONLY | os.O_CREAT | os.O_TRUNC, - 0o600), 'w') as fh: + with os.fdopen(filesystem.open( + self._storagepath, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + 0o600), 'w') as fh: fh.write(serialized) except IOError as e: errmsg = "Could not write PluginStorage data to file {0} : {1}".format( diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py index 2fa2c0345..3251e6b29 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/plugins/storage_test.py @@ -72,7 +72,7 @@ class PluginStorageTest(test_util.ConfigTestCase): def test_save_errors_unable_to_write_file(self): mock_open = mock.mock_open() mock_open.side_effect = IOError - with mock.patch("certbot.compat.os.open", mock_open): + with mock.patch("certbot.compat.filesystem.open", mock_open): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access self.plugin.storage._initialized = True # pylint: disable=protected-access diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 3d1363e6a..fc2b3f80f 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -1,15 +1,19 @@ """Tests for certbot.compat.filesystem""" +import errno import unittest try: - import win32api # pylint: disable=import-error - import win32security # pylint: disable=import-error - import ntsecuritycon # pylint: disable=import-error + # pylint: disable=import-error + import win32api + import win32security + import ntsecuritycon + # pylint: enable=import-error POSIX_MODE = False except ImportError: POSIX_MODE = True import certbot.tests.util as test_util +from certbot import lock from certbot.compat import os from certbot.compat import filesystem from certbot.tests.util import TempDirTestCase @@ -20,7 +24,7 @@ SYSTEM_SID = 'S-1-5-18' ADMINS_SID = 'S-1-5-32-544' -@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') +@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') class WindowsChmodTests(TempDirTestCase): """Unit tests for Windows chmod function in filesystem module""" def setUp(self): @@ -154,6 +158,77 @@ class WindowsChmodTests(TempDirTestCase): self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2) +@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') +class WindowsOpenTest(TempDirTestCase): + def test_new_file_correct_permissions(self): + path = os.path.join(self.tempdir, 'file') + + desc = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o700) + os.close(desc) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_existing_file_correct_permissions(self): + path = os.path.join(self.tempdir, 'file') + open(path, 'w').close() + + desc = filesystem.open(path, os.O_EXCL | os.O_RDWR, 0o700) + os.close(desc) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_create_file_on_open(self): + # os.O_CREAT | os.O_EXCL + file not exists = OK + self._test_one_creation(1, file_exist=False, flags=(os.O_CREAT | os.O_EXCL)) + + # os.O_CREAT | os.O_EXCL + file exists = EEXIST OS exception + with self.assertRaises(OSError) as raised: + self._test_one_creation(2, file_exist=True, flags=(os.O_CREAT | os.O_EXCL)) + self.assertEqual(raised.exception.errno, errno.EEXIST) + + # os.O_CREAT + file not exists = OK + self._test_one_creation(3, file_exist=False, flags=os.O_CREAT) + + # os.O_CREAT + file exists = OK + self._test_one_creation(4, file_exist=True, flags=os.O_CREAT) + + # os.O_CREAT + file exists (locked) = EACCES OS exception + path = os.path.join(self.tempdir, '5') + open(path, 'w').close() + filelock = lock.LockFile(path) + try: + with self.assertRaises(OSError) as raised: + self._test_one_creation(5, file_exist=True, flags=os.O_CREAT) + self.assertEqual(raised.exception.errno, errno.EACCES) + finally: + filelock.release() + + # os.O_CREAT not set + file not exists = OS exception + with self.assertRaises(OSError): + self._test_one_creation(6, file_exist=False, flags=os.O_RDONLY) + + def _test_one_creation(self, num, file_exist, flags): + one_file = os.path.join(self.tempdir, str(num)) + if file_exist and not os.path.exists(one_file): + open(one_file, 'w').close() + + handler = None + try: + handler = filesystem.open(one_file, flags) + except BaseException as err: + if handler: + os.close(handler) + raise err + + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 85a3fa9f4..41b99d06e 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -7,7 +7,7 @@ from certbot.compat import os class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): - for method in ['chmod', 'rename', 'replace']: + for method in ['chmod', 'open', 'rename', 'replace']: self.assertRaises(RuntimeError, getattr(os, method)) diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index ef40d674a..fd2320494 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -42,7 +42,7 @@ class ValidateHookTest(util.TempDirTestCase): def test_not_executable(self): file_path = os.path.join(self.tempdir, "foo") # create a non-executable file - os.close(os.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) + os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) # prevent unnecessary modifications to PATH with mock.patch("certbot.hooks.plug_util.path_surgery"): self.assertRaises(errors.HookCommandNotFound, diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index dccd96ab5..38bf370e5 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -288,7 +288,7 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): f.close() def test_failure(self): - with mock.patch("certbot.util.os.open", side_effect=OSError(errno.EIO)): + with mock.patch("certbot.compat.filesystem.open", side_effect=OSError(errno.EIO)): self.assertRaises(OSError, self._call, "wow") diff --git a/certbot/util.py b/certbot/util.py index 66e5d2524..097edd05d 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -23,6 +23,7 @@ from certbot import errors from certbot import lock from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem logger = logging.getLogger(__name__) @@ -206,24 +207,20 @@ def check_permissions(filepath, mode, uid=0): return misc.compare_file_modes(file_stat.st_mode, mode) and file_stat.st_uid == uid -def safe_open(path, mode="w", chmod=None, buffering=None): +def safe_open(path, mode="w", chmod=None): """Safely open a file. :param str path: Path to a file. :param str mode: Same os `mode` for `open`. - :param int chmod: Same as `mode` for `os.open`, uses Python defaults + :param int chmod: Same as `mode` for `filesystem.open`, uses Python defaults if ``None``. - :param int buffering: Same as `bufsize` for `os.fdopen`, uses Python - defaults if ``None``. """ open_args = () # type: Union[Tuple[()], Tuple[int]] if chmod is not None: open_args = (chmod,) fdopen_args = () # type: Union[Tuple[()], Tuple[int]] - if buffering is not None: - fdopen_args = (buffering,) - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) + fd = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) return os.fdopen(fd, mode, *fdopen_args) diff --git a/test b/test new file mode 120000 index 000000000..bf39a010e --- /dev/null +++ b/test @@ -0,0 +1 @@ +tox.ini \ No newline at end of file diff --git a/tox.cover.py b/tox.cover.py index 0a94cb73f..c313419ed 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -12,7 +12,7 @@ DEFAULT_PACKAGES = [ 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] COVER_THRESHOLDS = { - 'certbot': {'linux': 97, 'windows': 96}, + 'certbot': {'linux': 96, 'windows': 96}, 'acme': {'linux': 100, 'windows': 99}, 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, -- cgit v1.2.3 From 20b595bc9ee271e638cef3d6c7f5e7d1d9864835 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 2 Jul 2019 17:20:12 -0700 Subject: Simplify and deprecate viewing config changes (#7198) * Remove apache and nginx from config_changes help * Deprecate certbot_config changes. * Document config_changes deprecation. * Remove view_config_changes as IInstaller method. * Remove view_config_changes from plugins. * Add view_config_changes warnings. * simplify test_config_changes_deprecation --- CHANGELOG.md | 5 +++ .../tests/configurator_reverter_test.py | 8 ---- .../certbot_nginx/tests/configurator_test.py | 5 --- certbot/cli.py | 4 +- certbot/interfaces.py | 7 ---- certbot/main.py | 2 + certbot/plugins/common.py | 17 ++++++-- certbot/plugins/common_test.py | 47 +++++++++++++--------- certbot/plugins/null.py | 3 -- certbot/reverter.py | 7 ++++ certbot/tests/main_test.py | 7 ++++ pytest.ini | 2 + 12 files changed, 67 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fec8b7c6..fc3acb665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,12 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Update the 'manage your account' help to be more generic. * The error message when Certbot's Apache plugin is unable to modify your Apache configuration has been improved. +* Certbot's config_changes subcommand has been deprecated and will be + removed in a future release. * `certbot config_changes` no longer accepts a --num parameter. +* The functions `certbot.plugins.common.Installer.view_config_changes` and + `certbot.reverter.Reverter.view_config_changes` have been deprecated and will + be removed in a future release. ### Fixed diff --git a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py index 114f253e6..f3c418a3a 100644 --- a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py @@ -74,14 +74,6 @@ class ConfiguratorReverterTest(util.ApacheTest): side_effect=errors.ReverterError) self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) - def test_view_config_changes(self): - self.config.view_config_changes() - - def test_view_config_changes_error(self): - self.config.reverter.view_config_changes = mock.Mock( - side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.view_config_changes) - def test_recovery_routine_reload(self): mock_load = mock.Mock() self.config.parser.aug.load = mock_load diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 5e9d61a44..06c8c53c9 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -427,11 +427,6 @@ class NginxConfiguratorTest(util.NginxTest): mock_recovery_routine.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.recovery_routine) - @mock.patch("certbot.reverter.Reverter.view_config_changes") - def test_view_config_changes_throws_error_from_reverter(self, mock_view_config_changes): - mock_view_config_changes.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.view_config_changes) - @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") diff --git a/certbot/cli.py b/certbot/cli.py index 9f11e5d9b..d22a9a524 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1418,10 +1418,10 @@ def _plugins_parsing(helpful, plugins): help="Authenticator plugin name.") helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), help="Installer plugin name (also used to find domains).") - helpful.add(["plugins", "certonly", "run", "install", "config_changes"], + helpful.add(["plugins", "certonly", "run", "install"], "--apache", action="store_true", default=flag_default("apache"), help="Obtain and install certificates using Apache") - helpful.add(["plugins", "certonly", "run", "install", "config_changes"], + helpful.add(["plugins", "certonly", "run", "install"], "--nginx", action="store_true", default=flag_default("nginx"), help="Obtain and install certificates using Nginx") helpful.add(["plugins", "certonly"], "--standalone", action="store_true", diff --git a/certbot/interfaces.py b/certbot/interfaces.py index 25037a332..2e2df8a73 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -355,13 +355,6 @@ class IInstaller(IPlugin): """ - def view_config_changes(): # type: ignore - """Display all of the LE config changes. - - :raises .PluginError: when config changes cannot be parsed - - """ - def config_test(): # type: ignore """Make sure the configuration is valid. diff --git a/certbot/main.py b/certbot/main.py index d071ee453..6bd47cee3 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -976,6 +976,8 @@ def config_changes(config, unused_plugins): :rtype: None """ + logger.warning("The config_changes subcommand has been deprecated" + " and will be removed in a future release.") client.view_config_changes(config) def update_symlinks(config, unused_plugins): diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 820e86a13..2e2e0f64c 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -3,6 +3,7 @@ import logging import re import shutil import tempfile +import warnings import OpenSSL import pkg_resources @@ -197,10 +198,18 @@ class Installer(Plugin): the checkpoints directories. """ - try: - self.reverter.view_config_changes() - except errors.ReverterError as err: - raise errors.PluginError(str(err)) + warnings.warn( + "The view_config_changes method is no longer part of Certbot's" + " plugin interface, has been deprecated, and will be removed in a" + " future release.", DeprecationWarning, stacklevel=2) + with warnings.catch_warnings(): + # Don't let the reverter code warn about this function. Calling + # this function in the first place is the real problem. + warnings.filterwarnings("ignore", ".*view_config_changes", DeprecationWarning) + try: + self.reverter.view_config_changes() + except errors.ReverterError as err: + raise errors.PluginError(str(err)) @property def ssl_dhparams(self): diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index bce8f833a..ce60046af 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -3,6 +3,7 @@ import functools import shutil import tempfile import unittest +import warnings import OpenSSL import josepy as jose @@ -97,9 +98,8 @@ class InstallerTest(test_util.ConfigTestCase): os.mkdir(self.config.config_dir) from certbot.plugins.common import Installer - with mock.patch("certbot.plugins.common.reverter.Reverter"): - self.installer = Installer(config=self.config, - name="Installer") + self.installer = Installer(config=self.config, + name="Installer") self.reverter = self.installer.reverter def test_add_to_real_checkpoint(self): @@ -121,12 +121,11 @@ class InstallerTest(test_util.ConfigTestCase): temporary=temporary) if temporary: - reverter_func = self.reverter.add_to_temp_checkpoint + reverter_func_name = "add_to_temp_checkpoint" else: - reverter_func = self.reverter.add_to_checkpoint + reverter_func_name = "add_to_checkpoint" - self._test_adapted_method( - installer_func, reverter_func, files, save_notes) + self._test_adapted_method(installer_func, reverter_func_name, files, save_notes) def test_finalize_checkpoint(self): self._test_wrapped_method("finalize_checkpoint", "foo") @@ -143,6 +142,19 @@ class InstallerTest(test_util.ConfigTestCase): def test_view_config_changes(self): self._test_wrapped_method("view_config_changes") + def test_view_config_changes_warning_supression(self): + with warnings.catch_warnings(): + # Without the catch_warnings() code in + # common.Installer.view_config_changes, this would raise an + # exception. The module parameter here is ".*common$" because the + # stacklevel=2 parameter of warnings.warn causes the warning to + # refer to the code in the caller rather than the call to + # warnings.warn. This means the warning in common.Installer refers + # to this module and the warning in the reverter refers to the + # plugins.common module. + warnings.filterwarnings("error", ".*view_config_changes", module=".*common$") + self.installer.view_config_changes() + def _test_wrapped_method(self, name, *args, **kwargs): """Test a wrapped reverter method. @@ -152,28 +164,27 @@ class InstallerTest(test_util.ConfigTestCase): """ installer_func = getattr(self.installer, name) - reverter_func = getattr(self.reverter, name) - self._test_adapted_method( - installer_func, reverter_func, *args, **kwargs) + self._test_adapted_method(installer_func, name, *args, **kwargs) def _test_adapted_method(self, installer_func, - reverter_func, *passed_args, **passed_kwargs): + reverter_func_name, *passed_args, **passed_kwargs): """Test an adapted reverter method :param callable installer_func: installer method to test - :param mock.MagicMock reverter_func: mocked adapated - reverter method + :param str reverter_func_name: name of the method on the + reverter that should be called :param tuple passed_args: positional arguments passed from installer method to the reverter method :param dict passed_kargs: keyword arguments passed from installer method to the reverter method """ - installer_func(*passed_args, **passed_kwargs) - reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) - reverter_func.side_effect = errors.ReverterError - self.assertRaises( - errors.PluginError, installer_func, *passed_args, **passed_kwargs) + with mock.patch.object(self.reverter, reverter_func_name) as reverter_func: + installer_func(*passed_args, **passed_kwargs) + reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) + reverter_func.side_effect = errors.ReverterError + self.assertRaises( + errors.PluginError, installer_func, *passed_args, **passed_kwargs) def test_install_ssl_dhparams(self): self.installer.install_ssl_dhparams() diff --git a/certbot/plugins/null.py b/certbot/plugins/null.py index 87c0737a5..6deb358f1 100644 --- a/certbot/plugins/null.py +++ b/certbot/plugins/null.py @@ -49,9 +49,6 @@ class Installer(common.Plugin): def recovery_routine(self): pass # pragma: no cover - def view_config_changes(self): - pass # pragma: no cover - def config_test(self): pass # pragma: no cover diff --git a/certbot/reverter.py b/certbot/reverter.py index 201bdc03d..21d33806a 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -6,6 +6,7 @@ import shutil import sys import time import traceback +import warnings import six import zope.component @@ -143,6 +144,12 @@ class Reverter(object): :raises .errors.ReverterError: If invalid directory structure. """ + warnings.warn( + "The view_config_changes method has been deprecated and will be" + " removed in a future release. If you were using this method to" + " implement the view_config_changes method of IInstaller, know that" + " that method has been removed from the plugin interface and is no" + " longer used by Certbot.", DeprecationWarning, stacklevel=2) backups = os.listdir(self.config.backup_dir) backups.sort(reverse=True) if not backups: diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index bdc42a62f..2cc42cc88 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -757,6 +757,13 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met _, _, _, client = self._call(['config_changes']) self.assertEqual(1, client.view_config_changes.call_count) + @mock.patch('certbot.main.logger.warning') + def test_config_changes_deprecation(self, mock_warning): + self._call(['config_changes']) + self.assertTrue(mock_warning.called) + msg = mock_warning.call_args[0][0] + self.assertIn("config_changes subcommand has been deprecated", msg) + @mock.patch('certbot.cert_manager.update_live_symlinks') def test_update_symlinks(self, mock_cert_manager): self._call_no_clientmock(['update_symlinks']) diff --git a/pytest.ini b/pytest.ini index 2531e50d2..27a0f2b74 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,9 +10,11 @@ # but it should be corrected to allow Certbot compatiblity with Python >= 3.8 # 4- ipdb uses deprecated functionality of IPython. See # https://github.com/gotcha/ipdb/issues/144. +# 5- ignore our own warnings about deprecated view_config_changes methods filterwarnings = error ignore:decodestring:DeprecationWarning ignore:(TLSSNI01|TLS-SNI-01):DeprecationWarning ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* + ignore:.*view_config_changes:DeprecationWarning -- cgit v1.2.3 From 7d61e9ea56fffea2b6c49bce4f4b21af1cef28ab Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 4 Jul 2019 01:20:43 +0200 Subject: [Windows] Security model for files permissions - STEP 3d (#6968) * Implement security.mkdir and security.makedirs * Fix lint * Correct mock * Rename security into filesystem * Update apache and nginx plugins requirements * Update certbot/plugins/webroot.py Co-Authored-By: ohemorange * Reenable pylint here * Move code * Reimplement mkdir * Control errors on eexist, remove superfluous chmod for makedirs * Add proper skip for windows only tests * Fix lint * Fix mypy * Clean code * Adapt coverage threshold on Linux with addition of LOC specific to Windows * Add forbiden functions to tests * Update certbot/compat/os.py Co-Authored-By: ohemorange * Simplify code * Sync _get_current_user with part3c * Use the simpliest implementation * Remove exist_ok, simplify code. * Simplify inline comment * Update filesystem_test.py * Update certbot/compat/os.py Co-Authored-By: ohemorange * Update certbot/plugins/webroot.py Co-Authored-By: ohemorange * Update certbot/plugins/webroot.py Co-Authored-By: ohemorange * Add a test to check we set back os.mkdir correctly after filesystem.makedirs is called. * Fix lint, adapt coverage --- .codecov.yml | 2 +- certbot-apache/certbot_apache/http_01.py | 3 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot/compat/filesystem.py | 52 +++++++++++++++++++++++++++++ certbot/compat/os.py | 19 +++++++++++ certbot/plugins/common_test.py | 3 +- certbot/plugins/manual_test.py | 3 +- certbot/plugins/storage_test.py | 3 +- certbot/plugins/webroot.py | 7 ++-- certbot/plugins/webroot_test.py | 6 ++-- certbot/storage.py | 6 ++-- certbot/tests/cert_manager_test.py | 11 +++--- certbot/tests/cli_test.py | 5 +-- certbot/tests/compat/filesystem_test.py | 41 +++++++++++++++++++++++ certbot/tests/compat/os_test.py | 2 +- certbot/tests/display/completer_test.py | 3 +- certbot/tests/display/ops_test.py | 3 +- certbot/tests/hook_test.py | 6 ++-- certbot/tests/main_test.py | 7 ++-- certbot/tests/reverter_test.py | 3 +- certbot/tests/storage_test.py | 10 +++--- certbot/tests/util.py | 2 +- certbot/tests/util_test.py | 6 ++-- certbot/util.py | 2 +- tests/lock_test.py | 3 +- 26 files changed, 167 insertions(+), 45 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 3a7be503d..4c0cf93f1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.8 + target: 97.7 threshold: 0.1 base: auto windows: diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py index a6c271aa5..538806de0 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/http_01.py @@ -169,8 +169,7 @@ class ApacheHttp01(common.TLSSNI01): def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): - os.makedirs(self.challenge_dir) - filesystem.chmod(self.challenge_dir, 0o755) + filesystem.makedirs(self.challenge_dir, 0o755) responses = [] for achall in self.achalls: diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 09a1ab8d5..64f343730 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '0.36.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.35.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 8cdff17d3..657f2376c 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -106,6 +106,58 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin return handle +def makedirs(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.makedirs function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on leaf directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.makedirs(file_path, mode) + + orig_mkdir_fn = os.mkdir + try: + # As we know that os.mkdir is called internally by os.makedirs, we will swap the function in + # os module for the time of makedirs execution on Windows. + os.mkdir = mkdir # type: ignore + return os.makedirs(file_path, mode) + finally: + os.mkdir = orig_mkdir_fn + + +def mkdir(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.mkdir function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.mkdir(file_path, mode) + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + security.SetSecurityDescriptorDacl(1, dacl, 0) + + try: + win32file.CreateDirectory(file_path, attributes) + except pywintypes.error as err: + # Handle native windows error into python error to be consistent with the API + # of os.mkdir in the situation of a directory already existing. + if err.winerror == winerror.ERROR_ALREADY_EXISTS: + raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror) + raise err + + return None + + def replace(src, dst): # type: (str, str) -> None """ diff --git a/certbot/compat/os.py b/certbot/compat/os.py index a7de464fa..0c5582ca1 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -62,6 +62,25 @@ def open(*unused_args, **unused_kwargs): 'Use certbot.compat.filesystem.open() instead.') +# Very similarly to os.open, os.mkdir has the same effects on Windows and creates an unsecured +# folder. So a similar mitigation to security.chmod is provided on this platform. +def mkdir(*unused_args, **unused_kwargs): + """Method os.mkdir() is forbidden""" + raise RuntimeError('Usage of os.mkdir() is forbidden. ' + 'Use certbot.compat.filesystem.mkdir() instead.') + + +# As said above, os.makedirs would call the original os.mkdir function recursively on Windows, +# creating the same flaws for every actual folder created. This method is modified to ensure +# that our modified os.mkdir is called on Windows, by monkey patching temporarily the mkdir method +# on the original os module, executing the modified logic to correctly protect newly created +# folders, then restoring original mkdir method in the os module. +def makedirs(*unused_args, **unused_kwargs): + """Method os.makedirs() is forbidden""" + raise RuntimeError('Usage of os.makedirs() is forbidden. ' + 'Use certbot.compat.filesystem.makedirs() instead.') + + # Because of the blocking strategy on file handlers on Windows, rename does not behave as expected # with POSIX systems: an exception will be raised if dst already exists. def rename(*unused_args, **unused_kwargs): diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index ce60046af..1684fb998 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -15,6 +15,7 @@ from certbot import achallenges from certbot import crypto_util from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import acme_util from certbot.tests import util as test_util @@ -95,7 +96,7 @@ class InstallerTest(test_util.ConfigTestCase): def setUp(self): super(InstallerTest, self).setUp() - os.mkdir(self.config.config_dir) + filesystem.mkdir(self.config.config_dir) from certbot.plugins.common import Installer self.installer = Installer(config=self.config, diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index 10dbe73c9..16d240316 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -9,6 +9,7 @@ from acme import challenges from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import acme_util from certbot.tests import util as test_util @@ -23,7 +24,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.dns_achall_2 = acme_util.DNS01_A_2 self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2] for d in ["config_dir", "work_dir", "in_progress"]: - os.mkdir(os.path.join(self.tempdir, d)) + filesystem.mkdir(os.path.join(self.tempdir, d)) # "backup_dir" and "temp_checkpoint_dir" get created in # certbot.util.make_or_verify_dir() during the Reverter # initialization. diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py index 3251e6b29..f72e69d4b 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/plugins/storage_test.py @@ -6,6 +6,7 @@ import mock from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins import common from certbot.tests import util as test_util @@ -16,7 +17,7 @@ class PluginStorageTest(test_util.ConfigTestCase): def setUp(self): super(PluginStorageTest, self).setUp() self.plugin_cls = common.Installer - os.mkdir(self.config.config_dir) + filesystem.mkdir(self.config.config_dir) with mock.patch("certbot.reverter.util"): self.plugin = self.plugin_cls(config=self.config, name="mockplugin") diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index 1c94b34d3..2a42bd5ed 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -19,6 +19,7 @@ from certbot import cli from certbot import errors from certbot import interfaces from certbot.compat import os +from certbot.compat import filesystem from certbot.display import ops from certbot.display import util as display_util from certbot.plugins import common @@ -173,10 +174,10 @@ to serve all files under specified web root ({0}).""" # as it does not correspond to a folder path ('/' or 'C:') for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): try: - # This is coupled with the "umask" call above because - # os.mkdir's "mode" parameter may not always work: + # This is coupled with the "umask" call above because os.mkdir's + # "mode" parameter may not always work under Linux: # https://docs.python.org/3/library/os.html#os.mkdir - os.mkdir(prefix, 0o0755) + filesystem.mkdir(prefix, 0o0755) self._created_dirs.append(prefix) # Set owner as parent directory if possible try: diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index 2d15c46fb..5039aae9e 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -203,7 +203,7 @@ class AuthenticatorTest(unittest.TestCase): self.assertFalse(os.path.exists(self.partial_root_challenge_path)) def test_perform_cleanup_existing_dirs(self): - os.mkdir(self.partial_root_challenge_path) + filesystem.mkdir(self.partial_root_challenge_path) self.auth.prepare() self.auth.perform([self.achall]) self.auth.cleanup([self.achall]) @@ -219,7 +219,7 @@ class AuthenticatorTest(unittest.TestCase): domain="thing.com", account_key=KEY) bingo_validation_path = "YmluZ28" - os.mkdir(self.partial_root_challenge_path) + filesystem.mkdir(self.partial_root_challenge_path) self.auth.prepare() self.auth.perform([bingo_achall, self.achall]) @@ -235,7 +235,7 @@ class AuthenticatorTest(unittest.TestCase): self.auth.perform([self.achall]) leftover_path = os.path.join(self.root_challenge_path, 'leftover') - os.mkdir(leftover_path) + filesystem.mkdir(leftover_path) self.auth.cleanup([self.achall]) self.assertFalse(os.path.exists(self.validation_path)) diff --git a/certbot/storage.py b/certbot/storage.py index bfec72c40..3ab37afe5 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -984,7 +984,7 @@ class RenewableCert(object): for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, cli_config.live_dir): if not os.path.exists(i): - os.makedirs(i, 0o700) + filesystem.makedirs(i, 0o700) logger.debug("Creating directory %s.", i) config_file, config_filename = util.unique_lineage_name( cli_config.renewal_configs_dir, lineagename) @@ -1006,8 +1006,8 @@ class RenewableCert(object): config_file.close() raise errors.CertStorageError( "live directory exists for " + lineagename) - os.mkdir(archive) - os.mkdir(live_dir) + filesystem.mkdir(archive) + filesystem.mkdir(live_dir) logger.debug("Archive directory %s and live " "directory %s created.", archive, live_dir) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 08d7282a7..30ca6f099 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -12,6 +12,7 @@ import mock from certbot import configuration from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.display import util as display_util from certbot.storage import ALL_FOUR from certbot.tests import storage_test @@ -25,7 +26,7 @@ class BaseCertManagerTest(test_util.ConfigTestCase): super(BaseCertManagerTest, self).setUp() self.config.quiet = False - os.makedirs(self.config.renewal_configs_dir) + filesystem.makedirs(self.config.renewal_configs_dir) self.domains = { "example.org": None, @@ -43,14 +44,14 @@ class BaseCertManagerTest(test_util.ConfigTestCase): def _set_up_config(self, domain, custom_archive): # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 - os.makedirs(os.path.join(self.config.live_dir, domain)) + filesystem.makedirs(os.path.join(self.config.live_dir, domain)) config_file = configobj.ConfigObj() if custom_archive is not None: - os.makedirs(custom_archive) + filesystem.makedirs(custom_archive) config_file["archive_dir"] = custom_archive else: - os.makedirs(os.path.join(self.config.default_archive_dir, domain)) + filesystem.makedirs(os.path.join(self.config.default_archive_dir, domain)) for kind in ALL_FOUR: config_file[kind] = os.path.join(self.config.live_dir, domain, @@ -194,7 +195,7 @@ class CertificatesTest(BaseCertManagerTest): quiet=False )) - os.makedirs(empty_config.renewal_configs_dir) + filesystem.makedirs(empty_config.renewal_configs_dir) self._certificates(empty_config) self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member self.assertTrue(mock_utility.called) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 8259d4040..9dd16db2d 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -16,6 +16,7 @@ from certbot import cli from certbot import constants from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins import disco from certbot.tests.util import TempDirTestCase @@ -318,8 +319,8 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.parse(short_args + ['renew']), False) account_dir = os.path.join(config_dir, constants.ACCOUNTS_DIR) - os.mkdir(account_dir) - os.mkdir(os.path.join(account_dir, 'fake_account_dir')) + filesystem.mkdir(account_dir) + filesystem.mkdir(os.path.join(account_dir, 'fake_account_dir')) self._assert_dry_run_flag_worked(self.parse(short_args + ['auth']), True) self._assert_dry_run_flag_worked(self.parse(short_args + ['renew']), True) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index fc2b3f80f..b0edb31ba 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -229,6 +229,47 @@ class WindowsOpenTest(TempDirTestCase): raise err +@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') +class WindowsMkdirTests(test_util.TempDirTestCase): + """Unit tests for Windows mkdir + makedirs functions in filesystem module""" + def test_mkdir_correct_permissions(self): + path = os.path.join(self.tempdir, 'dir') + + filesystem.mkdir(path, 0o700) + + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_makedirs_correct_permissions(self): + path = os.path.join(self.tempdir, 'dir') + subpath = os.path.join(path, 'subpath') + + filesystem.makedirs(subpath, 0o700) + + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + dacl = _get_security_dacl(subpath).GetSecurityDescriptorDacl() + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_makedirs_switch_os_mkdir(self): + path = os.path.join(self.tempdir, 'dir') + import os as std_os # pylint: disable=os-module-forbidden + original_mkdir = std_os.mkdir + + filesystem.makedirs(path) + self.assertEqual(original_mkdir, std_os.mkdir) + + try: + filesystem.makedirs(path) # Will fail because path already exists + except OSError: + pass + self.assertEqual(original_mkdir, std_os.mkdir) + + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 41b99d06e..a2b40a7f8 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -7,7 +7,7 @@ from certbot.compat import os class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): - for method in ['chmod', 'open', 'rename', 'replace']: + for method in ['chmod', 'open', 'mkdir', 'makedirs', 'rename', 'replace']: self.assertRaises(RuntimeError, getattr(os, method)) diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index bbbc50c49..73d17946e 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -13,6 +13,7 @@ from six.moves import reload_module # pylint: disable=import-error from acme.magic_typing import List # pylint: disable=unused-import,no-name-in-module from certbot.compat import os # pylint: disable=ungrouped-imports +from certbot.compat import filesystem # pylint: disable=ungrouped-imports import certbot.tests.util as test_util # pylint: disable=ungrouped-imports @@ -33,7 +34,7 @@ class CompleterTest(test_util.TempDirTestCase): path = os.path.join(self.tempdir, c) self.paths.append(path) if ord(c) % 2: - os.mkdir(path) + filesystem.mkdir(path) else: with open(path, 'w'): pass diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index 95334a9d3..0a80836c0 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -13,6 +13,7 @@ import certbot.tests.util as test_util from certbot import account from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.display import ops from certbot.display import util as display_util @@ -93,7 +94,7 @@ class ChooseAccountTest(test_util.TempDirTestCase): False)) self.account_keys_dir = os.path.join(self.tempdir, "keys") - os.makedirs(self.account_keys_dir, 0o700) + filesystem.makedirs(self.account_keys_dir, 0o700) self.config = mock.MagicMock( accounts_dir=self.tempdir, diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index fd2320494..079bc7f4c 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -96,7 +96,7 @@ class PreHookTest(HookTest): super(PreHookTest, self).setUp() self.config.pre_hook = "foo" - os.makedirs(self.config.renewal_pre_hooks_dir) + filesystem.makedirs(self.config.renewal_pre_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_pre_hooks_dir, "bar") create_hook(self.dir_hook) @@ -174,7 +174,7 @@ class PostHookTest(HookTest): super(PostHookTest, self).setUp() self.config.post_hook = "bar" - os.makedirs(self.config.renewal_post_hooks_dir) + filesystem.makedirs(self.config.renewal_post_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_post_hooks_dir, "foo") create_hook(self.dir_hook) @@ -376,7 +376,7 @@ class RenewHookTest(RenewalHookTest): super(RenewHookTest, self).setUp() self.config.renew_hook = "foo" - os.makedirs(self.config.renewal_deploy_hooks_dir) + filesystem.makedirs(self.config.renewal_deploy_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_deploy_hooks_dir, "bar") create_hook(self.dir_hook) diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 2cc42cc88..ebd9a28e1 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -33,6 +33,7 @@ from certbot import updater from certbot import util from certbot.compat import misc from certbot.compat import os +from certbot.compat import filesystem from certbot.plugins import disco from certbot.plugins import enhancements from certbot.plugins import manual @@ -513,7 +514,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def setUp(self): super(MainTest, self).setUp() - os.mkdir(self.config.logs_dir) + filesystem.mkdir(self.config.logs_dir) self.standard_args = ['--config-dir', self.config.config_dir, '--work-dir', self.config.work_dir, '--logs-dir', self.config.logs_dir, '--text'] @@ -1161,7 +1162,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def test_renew_verb_empty_config(self): rd = os.path.join(self.config.config_dir, 'renewal') if not os.path.exists(rd): - os.makedirs(rd) + filesystem.makedirs(rd) with open(os.path.join(rd, 'empty.conf'), 'w'): pass # leave the file empty args = ["renew", "--dry-run", "-tvv"] @@ -1179,7 +1180,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def _make_dummy_renewal_config(self): renewer_configs_dir = os.path.join(self.config.config_dir, 'renewal') - os.makedirs(renewer_configs_dir) + filesystem.makedirs(renewer_configs_dir) with open(os.path.join(renewer_configs_dir, 'test.conf'), 'w') as f: f.write("My contents don't matter") diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 9e35ad636..31b0ea754 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -10,6 +10,7 @@ import six from certbot import errors from certbot.compat import os +from certbot.compat import filesystem from certbot.tests import util as test_util @@ -395,7 +396,7 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): def test_view_config_changes_bad_backups_dir(self): # There shouldn't be any "in progress directories when this is called # It must just be clean checkpoints - os.makedirs(os.path.join(self.config.backup_dir, "in_progress")) + filesystem.makedirs(os.path.join(self.config.backup_dir, "in_progress")) self.assertRaises( errors.ReverterError, self.reverter.view_config_changes) diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 307c053d8..017807a6d 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -92,10 +92,10 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 - os.makedirs(os.path.join(self.config.config_dir, "live", "example.org")) + filesystem.makedirs(os.path.join(self.config.config_dir, "live", "example.org")) archive_path = os.path.join(self.config.config_dir, "archive", "example.org") - os.makedirs(archive_path) - os.makedirs(os.path.join(self.config.config_dir, "renewal")) + filesystem.makedirs(archive_path) + filesystem.makedirs(os.path.join(self.config.config_dir, "renewal")) config_file = configobj.ConfigObj() for kind in ALL_FOUR: @@ -634,12 +634,12 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(os.path.exists(os.path.join( self.config.live_dir, "the-lineage.com-0001", "README"))) # Now trigger the detection of already existing files - os.mkdir(os.path.join( + filesystem.mkdir(os.path.join( self.config.live_dir, "the-lineage.com-0002")) self.assertRaises(errors.CertStorageError, storage.RenewableCert.new_lineage, "the-lineage.com", b"cert3", b"privkey3", b"chain3", self.config) - os.mkdir(os.path.join(self.config.default_archive_dir, "other-example.com")) + filesystem.mkdir(os.path.join(self.config.default_archive_dir, "other-example.com")) self.assertRaises(errors.CertStorageError, storage.RenewableCert.new_lineage, "other-example.com", b"cert4", diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 1c5f1d8b0..56fbd5458 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -139,7 +139,7 @@ def make_lineage(config_dir, testfile): for directory in (archive_dir, conf_dir, live_dir,): if not os.path.exists(directory): - os.makedirs(directory) + filesystem.makedirs(directory) sample_archive = vector_path('sample-archive') for kind in os.listdir(sample_archive): diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 38bf370e5..41b5cf71d 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -92,7 +92,7 @@ class LockDirUntilExit(test_util.TempDirTestCase): @mock.patch('certbot.util.atexit_register') def test_it(self, mock_register, mock_logger): subdir = os.path.join(self.tempdir, 'subdir') - os.mkdir(subdir) + filesystem.mkdir(subdir) self._call(self.tempdir) self._call(subdir) self._call(subdir) @@ -143,7 +143,7 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): super(MakeOrVerifyDirTest, self).setUp() self.path = os.path.join(self.tempdir, "foo") - os.mkdir(self.path, 0o600) + filesystem.mkdir(self.path, 0o600) self.uid = misc.os_geteuid() @@ -166,7 +166,7 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): self.assertRaises(errors.Error, self._call, self.path, 0o400) def test_reraises_os_error(self): - with mock.patch.object(os, "makedirs") as makedirs: + with mock.patch.object(filesystem, "makedirs") as makedirs: makedirs.side_effect = OSError() self.assertRaises(OSError, self._call, "bar", 12312312) diff --git a/certbot/util.py b/certbot/util.py index 097edd05d..dfdc87346 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -181,7 +181,7 @@ def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False): """ try: - os.makedirs(directory, mode) + filesystem.makedirs(directory, mode) except OSError as exception: if exception.errno == errno.EEXIST: if strict and not check_permissions(directory, mode, uid): diff --git a/tests/lock_test.py b/tests/lock_test.py index aaa8ce2d9..b65442c30 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -21,6 +21,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa from certbot import lock from certbot import util +from certbot.compat import filesystem from certbot.tests import util as test_util @@ -92,7 +93,7 @@ def set_up_dirs(): nginx_dir = os.path.join(temp_dir, 'nginx') for directory in (config_dir, logs_dir, work_dir, nginx_dir,): - os.mkdir(directory) + filesystem.mkdir(directory) test_util.make_lineage(config_dir, 'sample-renewal.conf') set_up_nginx_dir(nginx_dir) -- cgit v1.2.3 From 17f2cabbbf15e6b8f108a1d2b946e7d2636164d6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 8 Jul 2019 10:27:25 -0700 Subject: Replace broken link with archive link. (#7222) --- docs/ciphers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ciphers.rst b/docs/ciphers.rst index c3d6abc42..04b24b526 100644 --- a/docs/ciphers.rst +++ b/docs/ciphers.rst @@ -248,7 +248,7 @@ Dutch National Cyber Security Centre The Dutch National Cyber Security Centre has published guidance on "ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)" ("IT Security Guidelines for Transport Layer Security (TLS)"). These are available only in Dutch at -https://www.ncsc.nl/dienstverlening/expertise-advies/kennisdeling/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html +https://web.archive.org/web/20190516085116/https://www.ncsc.nl/actueel/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html I have access to an English-language summary of the recommendations. -- cgit v1.2.3 From 43f58ca803b3070365e0ba52471544c1caaec605 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 9 Jul 2019 15:07:33 -0700 Subject: Document pytest packaging problems. (#7226) This is probably unlikely to come up again, but this documents that people should run our tests using setuptools rather than calling something like pytest directly. See https://opensource.eff.org/eff-open-source/pl/wdrky4uyzjguppgch3r7t7qjmc for more info. --- docs/packaging.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/packaging.rst b/docs/packaging.rst index c13a14af3..bb26c7ef6 100644 --- a/docs/packaging.rst +++ b/docs/packaging.rst @@ -26,7 +26,7 @@ We release packages and upload them to PyPI (wheels and source tarballs). The following scripts are used in the process: -- https://github.com/letsencrypt/letsencrypt/blob/master/tools/release.sh +- https://github.com/certbot/certbot/blob/master/tools/release.sh We use git tags to identify releases, using `Semantic Versioning`_. For example: `v0.11.1`. @@ -40,11 +40,13 @@ Notes for package maintainers 1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally. -2. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. +2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins. -3. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. +3. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. -4. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. +4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. + +5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. Already ongoing efforts ======================= -- cgit v1.2.3 From 2ac99fefe07355d826abdd0b9eff25398959473c Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 10 Jul 2019 23:29:57 +0200 Subject: [Windows|Linux] Launch integration tests on Pebble without Docker (#7157) This PR is a part of the actions necessary to make Certbot-CI work on Windows, in order to execute the integration tests on this platform. Following #7156, this PR changes how the integration tests are setup against Pebble to not need Docker anymore. As a reminder, one can check #7156 and letsencrypt/pebble#240 to see the rationale about why using Docker is a problem to run the integration tests on Windows. Basically, this PR executes directly Pebble using its executable, since it is build using Go, and Go produces self-contained executable that can run without any installation on Linux and on Windows. During the integration tests setup, Certbot-CI will get the Pebble (and Challtestsrv) executables for the defined target version on the GitHub releases. The binaries are persisted on the filesystem, so it is not needed to download them again on the second integration tests execution. Nonetheless, we are talking about 20MB of executables. Since the setup needs to hold a state, I also took this occasion to refactor the acme_server, in order to use on object oriented approach and improve the readability/maintainability. Once this PR and #7156 are merged, Docker will not be needed anymore for the main integration tests usecase, that is to use Pebble. * Complete process * Fix nginx cert path * Check conditionnally docker * Update gitignore, fix apacheconftest * Full object * Carriage return * Move to official v2.1.0 of pebble * Fix name * Update acme_server.py * Relaunch CI * Update certbot-ci/certbot_integration_tests/utils/acme_server.py Co-Authored-By: Brad Warren * Update certbot-ci/certbot_integration_tests/utils/acme_server.py Co-Authored-By: Brad Warren * Update docstring * Update documentation * Configure a stdout to ACMEServer * Map all process through defined stdout * Remove unused variable * Handle using signals * Use failsafe entering context * Remove failsafe rmtree, that is not needed anymore --- .gitignore | 2 + .../apache-conf-files/apache-conf-test-pebble.py | 2 +- .../certbot_integration_tests/assets/cert.pem | 32 +++ .../certbot_integration_tests/assets/key.pem | 52 ++++ .../assets/nginx_cert.pem | 32 --- .../certbot_integration_tests/assets/nginx_key.pem | 52 ---- certbot-ci/certbot_integration_tests/conftest.py | 25 +- .../nginx_tests/nginx_config.py | 4 +- .../certbot_integration_tests/utils/acme_server.py | 287 ++++++++++----------- .../utils/pebble_artifacts.py | 52 ++++ 10 files changed, 292 insertions(+), 248 deletions(-) create mode 100644 certbot-ci/certbot_integration_tests/assets/cert.pem create mode 100644 certbot-ci/certbot_integration_tests/assets/key.pem delete mode 100644 certbot-ci/certbot_integration_tests/assets/nginx_cert.pem delete mode 100644 certbot-ci/certbot_integration_tests/assets/nginx_key.pem create mode 100644 certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py diff --git a/.gitignore b/.gitignore index 56ec23fbd..68762da6b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ tests/letstest/venv/ # certbot tests .certbot_test_workspace +**/assets/pebble* +**/assets/challtestsrv* diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py index 34f32f2d7..68bd6287d 100755 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py +++ b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py @@ -15,7 +15,7 @@ SCRIPT_DIRNAME = os.path.dirname(__file__) def main(args=None): if not args: args = sys.argv[1:] - with acme_server.setup_acme_server('pebble', [], False) as acme_xdist: + with acme_server.ACMEServer('pebble', [], False) as acme_xdist: environ = os.environ.copy() environ['SERVER'] = acme_xdist['directory_url'] command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')] diff --git a/certbot-ci/certbot_integration_tests/assets/cert.pem b/certbot-ci/certbot_integration_tests/assets/cert.pem new file mode 100644 index 000000000..5aae58f25 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlTCCA32gAwIBAgIUR3wbM8qFE68f8NxfciHhUjR1GeUwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbmdpbngud3RmMCAX +DTE5MDQxODIwMDUwM1oYDzIyOTMwMTMwMjAwNTAzWjBZMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMRIwEAYDVQQDDAluZ2lueC53dGYwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQC/W+yxYE0PWJOS4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OC +HyRm0TEhl6FPUp9tD9YeEHloUZNjHEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf +0go+aVsWelDhapFl8fccw4tWwijVZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+ +rOTruzRzOzlbgKv5WRb4+BxxWonHLkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMH +ZlZvUT6aWRnzFWjAnQGiJpVIw7l9r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXH +aKIR82/xwkcZfFk3sCAM0IcZc8B2SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtE +X5qYlgXRaM2raOn31lxcerk5pjgagQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4 +QHtoO0nyP0RJqxvwX/H98513JTkeqFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquu +k9anv56yvxo77qZ8r80l3z8eMyDA+UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFH +JIUozHoinAg/9lBC/W80fcbILks+Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/ +Shb6R6sWr1YtzUFUc5EH2g9pMriaqT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQAB +o1MwUTAdBgNVHQ4EFgQU46gJeu9ZOfTQ6c4vfbWbSLUpEMowHwYDVR0jBBgwFoAU +46gJeu9ZOfTQ6c4vfbWbSLUpEMowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAgEAcnfkXDUTsEGs0MleegkGbRCVy72a3U7tv1KVTLB8qLPc3tpPJJoT +D4PbOuw9+yIE+HetZTZooOpaZoorLQdiwAEjlQ44RVuXSHSARQ8KW9ZZeiWN/Qvl +Ip4xJ/cHxcKTFKSc/99o8M+kmPKEXF9SUMfKPc5jXarNxCsnA3VriYqJ+CnYEox2 +duNUEe3A9Y2d8ZxjmscBqlcXpk1kFwsCRT5UYVoUYwyjYznLkO5A+GJ0ZnMyRMQp +obUiB34hUrNgyOaBvizk+pNh9EV4rEBPRQwhy4vDMco4AjQcwLWQAQ9G4GSt/E+Q +62XdVDa6CAuOvBCudDPki7kEqNLbj1tMY1K/gsbgb6TYA/xTOVulAnqm4OEZ2svJ +0Jqw3BzMfRTaxsNU6jxm8WehVL15GjoJUzfs7Te+l7Vm/QNc1Dv2pmEhVfBibwMa +YxUZ8ClQtQ1lsOpne97Og0p/Cm93kKELNBLTjzXtpXGGPPYisAyNwe0Hadq8SiOd +pXeNwXa5vHOXHv8xBENzBvFJ3TRN2GmMlHBp/eOfVUx/huNSpcnh2gO3fn5EbMj7 +43IaR133JW5yWbneYAMJOEAMdEB5EthRmEDtLVA7kLqLc/ywFTQ4VbS2b+PsOr5O +501nzt0OTMMEz+UafvGXj5OPJBhe26RtnYXzVwwLfto/F5udM5zglWo= +-----END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/key.pem b/certbot-ci/certbot_integration_tests/assets/key.pem new file mode 100644 index 000000000..1f768f079 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC/W+yxYE0PWJOS +4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OCHyRm0TEhl6FPUp9tD9YeEHloUZNj +HEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf0go+aVsWelDhapFl8fccw4tWwijV +ZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+rOTruzRzOzlbgKv5WRb4+BxxWonH +LkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMHZlZvUT6aWRnzFWjAnQGiJpVIw7l9 +r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXHaKIR82/xwkcZfFk3sCAM0IcZc8B2 +SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtEX5qYlgXRaM2raOn31lxcerk5pjga +gQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4QHtoO0nyP0RJqxvwX/H98513JTke +qFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquuk9anv56yvxo77qZ8r80l3z8eMyDA ++UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFHJIUozHoinAg/9lBC/W80fcbILks+ +Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/Shb6R6sWr1YtzUFUc5EH2g9pMria +qT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQABAoICAAGGL+pxw+tdXz+KQPgmiUnn +aRSrqbUIugIw9Pst67HWjBqUxSkiKl4PSH7mAEjrdY2e1KvEodLs42mkrf04ShAx +0pArfFX8Sx7KrZgLOonGOPPQM+YmfCJnIGybaM2C1cmkFb3K6O81+LFKbr1ZHAYf +SrE2XnufS6cdmItTBMvPPTk6lieqpOAjy5UnYZuS+Muxo/czsrZMbFCD08rOpyiE +kXf94TMCJ2R0UetA7LPxe9N0TzLd485bLU55azV+dCkklwC9oe7EcFPJ9BNEdWdB +UlRcMvxMGdwct+L3QTaEb2QlTwi5kqDl+XxJeduAHA3Pf1Haz1iqjVvj01PvT1di +Cs0+ZeFBsa+BfiGDe9ONwuSQljda1CuI+vDv5bGUExulOSG1dHJ7RK9PBaXFaR/b +/9tRBwAw1Erm7s1JIkjda5Oc46jFb3HzDaZYB1n5hUmEIrYM8HhUOGITyVT3hxDO +AWlaV3aveQ0MmMXLptVXDgbjPGbWDGMLD9d5vUE9R7IyOLeXOmjthYlCH2rj378M +r2PkgX2tD0A/yoEZ8XCFdtBWSVajLdL0/gkm7sKosMABBy3yrSCxbHeq5TFuTAXA +hOdypX4NOZkA6WJU+hn3GkQyIScLqSrvGRA9kzHGoEWVZDKkB9DXg+dmTARZDWXD +mCnHkJo6+FcbhUpXniuZAoIBAQDmE94vvdstB+HEtXxN1uNDY7H8gPc/BUonU6a9 +G5YOIbjByCfEDcXF8AUWekc6lc8DNG3ydx0dnb2ZAkxmdlsaD8GLqHGILzlSsOwR +sez8nR4+4n9vYMfx9Qal8Ren5xEP9Z9sJcNqbKVGta1WFtQzrgYbpVXXf/Luv0xS +YoVK8KaEACciD6XX4wmajrAXPPQgThvqQtXuTn/AxWsUDg1DK0tw1VRUuOJuJwpw +f6qocM9AyqUNvdeVyjFx8Slag34ZI7fmxPtHX/e6opTg3zVXab1Ow8AMICOHMRL6 +m5/+wnWa9xMoKI4kYfk/QFqeTccnLDlwi6kQM8WRfbwr9AyPAoIBAQDU60wrX6Lm +0vIfngv1/4j/w+AGAwjvxiuJ7Q7LwQ2fGsZGOIfMK/lpBxCn543kGbQT+KQKNOjO ++EywObftnJ6Y2+om2NoLkCnCiptsfr5WlN8pxtIPQu2iu5xXA67WpQv4Nc4769PM +wJGVW3pmPKi6H0QjjqYAZd1NAXdN9Au14zZVh3KBWoz82kTHWKSL6Ld1UClG728W +k/moyCFFMMGTXX/LVliQzDVLM6L5jbAOaG317qAuxZIqFJ9NLwHFW9uH/i1S6Qfp ++lOmOfVYKu1O/qh1DUBQfuJkR1XIn2ifZEjxOsxeTmWu1LXpyoZy526JRu49pk8Z +DdEu+w7hsdNjAoIBAD1YWsub8Y6GJXpPcX9HpnzXXiOXN1VEUcs+kJyneFD4SMzS +U1gA3BS0tIaTv94tB28xUYdunwLAhkb/x+Mh95RxUwert+m5va0Ao1DsgeWw9tmJ +hrTptyYaUNV5/Pa1s2Tv9rvdLcd4hHDgDAGCQL4uzk4cvVCiOuHRe8YTorqig6N6 +bvSz+2IelPbyyJzJkcXzTZoei+/oWkPJ340PWhXou0qwdrXIPgdkvXHVeGlE+t2p +qmyJi6vSp3Bb/sy1dq+5SFVtfBpBykmnA88ZdJ2EAge4RcJ150MqoIbVa8l/i9/v +tNnmRlAJF233+LFwx4L4VbBebIt3YlwyjDOj9J0CggEAIknKOGnsV/O8ni7bikAe +leG7X/x5IfPt6wZMDbAHO4oaSBCufcjPH4TNv9xgU014XIb8E9C1dS8zWmXRIujH ++aHgsWTWqGoM75FWukAm8taCob2s8lw63KwN301uiI6HwO8ZSTkPILgaOc1DhtdZ +7K9AT+GXBhVhcBc+WUVl5WKzy05GuGIWtlmIHfo+dXGCqdfA7fV9FEu8NtwTz4qs +gcja3aoIFTltk7C7HCkfIxLaMnK9RQr4IOK1TL63MEs8rUfXkLSKW7m+YtSOmCZB +lSkZg9AgfVYRq0h5nhddx91kicSISN+jLGaA7Sd6Q2LVwDG2CCOSNVyuRTyVBu+W +NQKCAQAWN6vB6oToNIoBLdOThm0HD07cNHcrnBjtaKsYsQDgqbr2m8LRCRzNRML4 +jG0IAOWpuCiEGsgUPxywiI1Ufvyq7ZSNT1QQNzCR47NM3Ve6S2abrQkMIk9VJ+za +CB9c1BH92GokoRxqswb/BiMttG2EIP8L8/pSRYEcVnaaxAkf9QOhEwj4LJPGX0mS +t7kWIUVHPdFJ67F25dYr3mUHgyV+QJupQICkkkgY3nBOU1fS42vAugaxqH0wAP3T +53FlpY3NuE7+kYC3FjfcBer99F1pOac3X9jxhk26w9dr2/QNA33xhDXHKYvoLUCG +RPQylahJByU7IrtQzSCf/RE7q4v0 +-----END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem b/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem deleted file mode 100644 index 5aae58f25..000000000 --- a/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFlTCCA32gAwIBAgIUR3wbM8qFE68f8NxfciHhUjR1GeUwDQYJKoZIhvcNAQEL -BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbmdpbngud3RmMCAX -DTE5MDQxODIwMDUwM1oYDzIyOTMwMTMwMjAwNTAzWjBZMQswCQYDVQQGEwJBVTET -MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMRIwEAYDVQQDDAluZ2lueC53dGYwggIiMA0GCSqGSIb3DQEBAQUAA4IC -DwAwggIKAoICAQC/W+yxYE0PWJOS4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OC -HyRm0TEhl6FPUp9tD9YeEHloUZNjHEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf -0go+aVsWelDhapFl8fccw4tWwijVZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+ -rOTruzRzOzlbgKv5WRb4+BxxWonHLkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMH -ZlZvUT6aWRnzFWjAnQGiJpVIw7l9r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXH -aKIR82/xwkcZfFk3sCAM0IcZc8B2SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtE -X5qYlgXRaM2raOn31lxcerk5pjgagQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4 -QHtoO0nyP0RJqxvwX/H98513JTkeqFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquu -k9anv56yvxo77qZ8r80l3z8eMyDA+UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFH -JIUozHoinAg/9lBC/W80fcbILks+Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/ -Shb6R6sWr1YtzUFUc5EH2g9pMriaqT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQAB -o1MwUTAdBgNVHQ4EFgQU46gJeu9ZOfTQ6c4vfbWbSLUpEMowHwYDVR0jBBgwFoAU -46gJeu9ZOfTQ6c4vfbWbSLUpEMowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B -AQsFAAOCAgEAcnfkXDUTsEGs0MleegkGbRCVy72a3U7tv1KVTLB8qLPc3tpPJJoT -D4PbOuw9+yIE+HetZTZooOpaZoorLQdiwAEjlQ44RVuXSHSARQ8KW9ZZeiWN/Qvl -Ip4xJ/cHxcKTFKSc/99o8M+kmPKEXF9SUMfKPc5jXarNxCsnA3VriYqJ+CnYEox2 -duNUEe3A9Y2d8ZxjmscBqlcXpk1kFwsCRT5UYVoUYwyjYznLkO5A+GJ0ZnMyRMQp -obUiB34hUrNgyOaBvizk+pNh9EV4rEBPRQwhy4vDMco4AjQcwLWQAQ9G4GSt/E+Q -62XdVDa6CAuOvBCudDPki7kEqNLbj1tMY1K/gsbgb6TYA/xTOVulAnqm4OEZ2svJ -0Jqw3BzMfRTaxsNU6jxm8WehVL15GjoJUzfs7Te+l7Vm/QNc1Dv2pmEhVfBibwMa -YxUZ8ClQtQ1lsOpne97Og0p/Cm93kKELNBLTjzXtpXGGPPYisAyNwe0Hadq8SiOd -pXeNwXa5vHOXHv8xBENzBvFJ3TRN2GmMlHBp/eOfVUx/huNSpcnh2gO3fn5EbMj7 -43IaR133JW5yWbneYAMJOEAMdEB5EthRmEDtLVA7kLqLc/ywFTQ4VbS2b+PsOr5O -501nzt0OTMMEz+UafvGXj5OPJBhe26RtnYXzVwwLfto/F5udM5zglWo= ------END CERTIFICATE----- diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_key.pem b/certbot-ci/certbot_integration_tests/assets/nginx_key.pem deleted file mode 100644 index 1f768f079..000000000 --- a/certbot-ci/certbot_integration_tests/assets/nginx_key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC/W+yxYE0PWJOS -4df71Yx596fDjW03I9JZuu9kfP7mneMgy+OCHyRm0TEhl6FPUp9tD9YeEHloUZNj -HEOg/qrnbEOspv3Ha3RFinzrzkMwbzEPR3Xf0go+aVsWelDhapFl8fccw4tWwijV -ZQquhBsWOUnPenS3Txe96kEv2NNJlJ0qFUa+rOTruzRzOzlbgKv5WRb4+BxxWonH -LkAQ5IT87GBlsCerVIyPD+BnZveZGl6e9oMHZlZvUT6aWRnzFWjAnQGiJpVIw7l9 -r4EW0jq1z7wqb37FrqrFbtWrOfUZVE7AlqXHaKIR82/xwkcZfFk3sCAM0IcZc8B2 -SDLi4zNZtDivW6qQgTC/3z5yf1hnJ+j00dtEX5qYlgXRaM2raOn31lxcerk5pjga -gQ7Zj+v3YZS0QnenrgyXJcdnXLDj+cIARzx4QHtoO0nyP0RJqxvwX/H98513JTke -qFBc/Bx11UWYsUv20Qoo9IAuz0VDARu6rquuk9anv56yvxo77qZ8r80l3z8eMyDA -+UjuSD2p1Za09RAHfva7o8rMUqULHNQ4pfFHJIUozHoinAg/9lBC/W80fcbILks+ -Sdi6E9WQ0n8PLl7oFLx9prEDCycKuC0z76J/Shb6R6sWr1YtzUFUc5EH2g9pMria -qT8uGO4CMOeRemXahrdT/H+Xg5m4TQIDAQABAoICAAGGL+pxw+tdXz+KQPgmiUnn -aRSrqbUIugIw9Pst67HWjBqUxSkiKl4PSH7mAEjrdY2e1KvEodLs42mkrf04ShAx -0pArfFX8Sx7KrZgLOonGOPPQM+YmfCJnIGybaM2C1cmkFb3K6O81+LFKbr1ZHAYf -SrE2XnufS6cdmItTBMvPPTk6lieqpOAjy5UnYZuS+Muxo/czsrZMbFCD08rOpyiE -kXf94TMCJ2R0UetA7LPxe9N0TzLd485bLU55azV+dCkklwC9oe7EcFPJ9BNEdWdB -UlRcMvxMGdwct+L3QTaEb2QlTwi5kqDl+XxJeduAHA3Pf1Haz1iqjVvj01PvT1di -Cs0+ZeFBsa+BfiGDe9ONwuSQljda1CuI+vDv5bGUExulOSG1dHJ7RK9PBaXFaR/b -/9tRBwAw1Erm7s1JIkjda5Oc46jFb3HzDaZYB1n5hUmEIrYM8HhUOGITyVT3hxDO -AWlaV3aveQ0MmMXLptVXDgbjPGbWDGMLD9d5vUE9R7IyOLeXOmjthYlCH2rj378M -r2PkgX2tD0A/yoEZ8XCFdtBWSVajLdL0/gkm7sKosMABBy3yrSCxbHeq5TFuTAXA -hOdypX4NOZkA6WJU+hn3GkQyIScLqSrvGRA9kzHGoEWVZDKkB9DXg+dmTARZDWXD -mCnHkJo6+FcbhUpXniuZAoIBAQDmE94vvdstB+HEtXxN1uNDY7H8gPc/BUonU6a9 -G5YOIbjByCfEDcXF8AUWekc6lc8DNG3ydx0dnb2ZAkxmdlsaD8GLqHGILzlSsOwR -sez8nR4+4n9vYMfx9Qal8Ren5xEP9Z9sJcNqbKVGta1WFtQzrgYbpVXXf/Luv0xS -YoVK8KaEACciD6XX4wmajrAXPPQgThvqQtXuTn/AxWsUDg1DK0tw1VRUuOJuJwpw -f6qocM9AyqUNvdeVyjFx8Slag34ZI7fmxPtHX/e6opTg3zVXab1Ow8AMICOHMRL6 -m5/+wnWa9xMoKI4kYfk/QFqeTccnLDlwi6kQM8WRfbwr9AyPAoIBAQDU60wrX6Lm -0vIfngv1/4j/w+AGAwjvxiuJ7Q7LwQ2fGsZGOIfMK/lpBxCn543kGbQT+KQKNOjO -+EywObftnJ6Y2+om2NoLkCnCiptsfr5WlN8pxtIPQu2iu5xXA67WpQv4Nc4769PM -wJGVW3pmPKi6H0QjjqYAZd1NAXdN9Au14zZVh3KBWoz82kTHWKSL6Ld1UClG728W -k/moyCFFMMGTXX/LVliQzDVLM6L5jbAOaG317qAuxZIqFJ9NLwHFW9uH/i1S6Qfp -+lOmOfVYKu1O/qh1DUBQfuJkR1XIn2ifZEjxOsxeTmWu1LXpyoZy526JRu49pk8Z -DdEu+w7hsdNjAoIBAD1YWsub8Y6GJXpPcX9HpnzXXiOXN1VEUcs+kJyneFD4SMzS -U1gA3BS0tIaTv94tB28xUYdunwLAhkb/x+Mh95RxUwert+m5va0Ao1DsgeWw9tmJ -hrTptyYaUNV5/Pa1s2Tv9rvdLcd4hHDgDAGCQL4uzk4cvVCiOuHRe8YTorqig6N6 -bvSz+2IelPbyyJzJkcXzTZoei+/oWkPJ340PWhXou0qwdrXIPgdkvXHVeGlE+t2p -qmyJi6vSp3Bb/sy1dq+5SFVtfBpBykmnA88ZdJ2EAge4RcJ150MqoIbVa8l/i9/v -tNnmRlAJF233+LFwx4L4VbBebIt3YlwyjDOj9J0CggEAIknKOGnsV/O8ni7bikAe -leG7X/x5IfPt6wZMDbAHO4oaSBCufcjPH4TNv9xgU014XIb8E9C1dS8zWmXRIujH -+aHgsWTWqGoM75FWukAm8taCob2s8lw63KwN301uiI6HwO8ZSTkPILgaOc1DhtdZ -7K9AT+GXBhVhcBc+WUVl5WKzy05GuGIWtlmIHfo+dXGCqdfA7fV9FEu8NtwTz4qs -gcja3aoIFTltk7C7HCkfIxLaMnK9RQr4IOK1TL63MEs8rUfXkLSKW7m+YtSOmCZB -lSkZg9AgfVYRq0h5nhddx91kicSISN+jLGaA7Sd6Q2LVwDG2CCOSNVyuRTyVBu+W -NQKCAQAWN6vB6oToNIoBLdOThm0HD07cNHcrnBjtaKsYsQDgqbr2m8LRCRzNRML4 -jG0IAOWpuCiEGsgUPxywiI1Ufvyq7ZSNT1QQNzCR47NM3Ve6S2abrQkMIk9VJ+za -CB9c1BH92GokoRxqswb/BiMttG2EIP8L8/pSRYEcVnaaxAkf9QOhEwj4LJPGX0mS -t7kWIUVHPdFJ67F25dYr3mUHgyV+QJupQICkkkgY3nBOU1fS42vAugaxqH0wAP3T -53FlpY3NuE7+kYC3FjfcBer99F1pOac3X9jxhk26w9dr2/QNA33xhDXHKYvoLUCG -RPQylahJByU7IrtQzSCf/RE7q4v0 ------END PRIVATE KEY----- diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index e84a866b9..d52e4fb58 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -68,17 +68,18 @@ def _setup_primary_node(config): :param config: Configuration of the pytest primary node """ # Check for runtime compatibility: some tools are required to be available in PATH - try: - subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT) - except (subprocess.CalledProcessError, OSError): - raise ValueError('Error: docker is required in PATH to launch the integration tests, ' - 'but is not installed or not available for current user.') - - try: - subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT) - except (subprocess.CalledProcessError, OSError): - raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, ' - 'but is not installed or not available for current user.') + if 'boulder' in config.option.acme_server: + try: + subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker is required in PATH to launch the integration tests on' + 'boulder, but is not installed or not available for current user.') + + try: + subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, ' + 'but is not installed or not available for current user.') # Parameter numprocesses is added to option by pytest-xdist workers = ['primary'] if not config.option.numprocesses\ @@ -86,7 +87,7 @@ def _setup_primary_node(config): # By calling setup_acme_server we ensure that all necessary acme server instances will be # fully started. This runtime is reflected by the acme_xdist returned. - acme_server = acme_lib.setup_acme_server(config.option.acme_server, workers) + acme_server = acme_lib.ACMEServer(config.option.acme_server, workers) config.add_cleanup(acme_server.stop) print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist)) acme_server.start() diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py index a6305c3cc..18991ae62 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py @@ -21,9 +21,9 @@ def construct_nginx_config(nginx_root, nginx_webroot, http_port, https_port, oth :rtype: str """ key_path = key_path if key_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_key.pem') + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/key.pem') cert_path = cert_path if cert_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_cert.pem') + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/cert.pem') return '''\ # This error log will be written regardless of server scope error_log # definitions, so we have to set this here in the main scope. diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index e9226e17c..8e3419f70 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """Module to setup an ACME CA server environment able to run multiple tests in parallel""" from __future__ import print_function +import errno import json import tempfile import time @@ -11,184 +12,178 @@ import sys from os.path import join import requests -import yaml -from certbot_integration_tests.utils import misc, proxy +from certbot_integration_tests.utils import misc, proxy, pebble_artifacts from certbot_integration_tests.utils.constants import * class ACMEServer(object): """ - Handler exposing methods to start and stop the ACME server, and get its configuration - (eg. challenges ports). ACMEServer is also a context manager, and so can be used to - ensure ACME server is started/stopped upon context enter/exit. + ACMEServer configures and handles the lifecycle of an ACME CA server and an HTTP reverse proxy + instance, to allow parallel execution of integration tests against the unique http-01 port + expected by the ACME CA server. + Typically all pytest integration tests will be executed in this context. + ACMEServer gives access the acme_xdist parameter, listing the ports and directory url to use + for each pytest node. It exposes also start and stop methods in order to start the stack, and + stop it with proper resources cleanup. + ACMEServer is also a context manager, and so can be used to ensure ACME server is started/stopped + upon context enter/exit. """ - def __init__(self, acme_xdist, start, server_cleanup): - self._proxy_process = None - self._server_cleanup = server_cleanup - self.acme_xdist = acme_xdist - self.start = start + def __init__(self, acme_server, nodes, http_proxy=True, stdout=False): + """ + Create an ACMEServer instance. + :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) + :param list nodes: list of node names that will be setup by pytest xdist + :param bool http_proxy: if False do not start the HTTP proxy + :param bool stdout: if True stream subprocesses stdout to standard stdout + """ + self._construct_acme_xdist(acme_server, nodes) + + self._acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' + self._proxy = http_proxy + self._workspace = tempfile.mkdtemp() + self._processes = [] + self._stdout = sys.stdout if stdout else open(os.devnull, 'w') + + def start(self): + """Start the test stack""" + try: + if self._proxy: + self._prepare_http_proxy() + if self._acme_type == 'pebble': + self._prepare_pebble_server() + if self._acme_type == 'boulder': + self._prepare_boulder_server() + except BaseException as e: + self.stop() + raise e def stop(self): - if self._proxy_process: - self._proxy_process.terminate() - self._proxy_process.wait() - self._server_cleanup() + """Stop the test stack, and clean its resources""" + print('=> Tear down the test infrastructure...') + try: + for process in self._processes: + try: + process.terminate() + except OSError as e: + # Process may be not started yet, so no PID and terminate fails. + # Then the process never started, and the situation is acceptable. + if e.errno != errno.ESRCH: + raise + for process in self._processes: + process.wait() + + if os.path.exists(os.path.join(self._workspace, 'boulder')): + # Boulder docker generates build artifacts owned by root with 0o744 permissions. + # If we started the acme server from a normal user that has access to the Docker + # daemon, this user will not be able to delete these artifacts from the host. + # We need to do it through a docker. + process = self._launch_process(['docker', 'run', '--rm', '-v', + '{0}:/workspace'.format(self._workspace), + 'alpine', 'rm', '-rf', '/workspace/boulder']) + process.wait() + finally: + shutil.rmtree(self._workspace) + if self._stdout != sys.stdout: + self._stdout.close() + print('=> Test infrastructure stopped and cleaned up.') def __enter__(self): - self._proxy_process = self.start() + self.start() return self.acme_xdist def __exit__(self, exc_type, exc_val, exc_tb): self.stop() + def _construct_acme_xdist(self, acme_server, nodes): + """Generate and return the acme_xdist dict""" + acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT} -def setup_acme_server(acme_server, nodes, proxy=True): - """ - This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel - execution of integration tests against the unique http-01 port expected by the ACME CA server. - Typically all pytest integration tests will be executed in this context. - An ACMEServer instance will be returned, giving access to the ports and directory url to use - for each pytest node, and its start and stop methods are appropriately configured to - respectively start the server, and stop it with proper resources cleanup. - :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) - :param str[] nodes: list of node names that will be setup by pytest xdist - :param bool proxy: set to False to not start the HTTP proxy - :return: a properly configured ACMEServer instance - :rtype: ACMEServer - """ - acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' - acme_xdist = _construct_acme_xdist(acme_server, nodes) - workspace, server_cleanup = _construct_workspace(acme_type) - - def start(): - proxy_process = _prepare_http_proxy(acme_xdist) if proxy else None - _prepare_acme_server(workspace, acme_type, acme_xdist) - - return proxy_process - - return ACMEServer(acme_xdist, start, server_cleanup) + # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. + if acme_server == 'pebble': + acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL + else: # boulder + acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \ + if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL + acme_xdist['http_port'] = {node: port for (node, port) + in zip(nodes, range(5200, 5200 + len(nodes)))} + acme_xdist['https_port'] = {node: port for (node, port) + in zip(nodes, range(5100, 5100 + len(nodes)))} + acme_xdist['other_port'] = {node: port for (node, port) + in zip(nodes, range(5300, 5300 + len(nodes)))} -def _construct_acme_xdist(acme_server, nodes): - """Generate and return the acme_xdist dict""" - acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT} - - # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. - if acme_server == 'pebble': - acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL - else: # boulder - acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \ - if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL + self.acme_xdist = acme_xdist - acme_xdist['http_port'] = {node: port for (node, port) - in zip(nodes, range(5200, 5200 + len(nodes)))} - acme_xdist['https_port'] = {node: port for (node, port) - in zip(nodes, range(5100, 5100 + len(nodes)))} - acme_xdist['other_port'] = {node: port for (node, port) - in zip(nodes, range(5300, 5300 + len(nodes)))} + def _prepare_pebble_server(self): + """Configure and launch the Pebble server""" + print('=> Starting pebble instance deployment...') + pebble_path, challtestsrv_path, pebble_config_path = pebble_artifacts.fetch(self._workspace) - return acme_xdist + # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid + # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. + environ = os.environ.copy() + environ['PEBBLE_VA_NOSLEEP'] = '1' + environ['PEBBLE_WFE_NONCEREJECT'] = '0' + self._launch_process( + [pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'], + env=environ) -def _construct_workspace(acme_type): - """Create a temporary workspace for integration tests stack""" - workspace = tempfile.mkdtemp() + self._launch_process( + [challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""', + '-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""']) - def cleanup(): - """Cleanup function to call that will teardown relevant dockers and their configuration.""" - print('=> Tear down the {0} instance...'.format(acme_type)) - instance_path = join(workspace, acme_type) - try: - if os.path.isfile(join(instance_path, 'docker-compose.yml')): - _launch_command(['docker-compose', 'down'], cwd=instance_path) - except subprocess.CalledProcessError: - pass - print('=> Finished tear down of {0} instance.'.format(acme_type)) + # Wait for the ACME CA server to be up. + print('=> Waiting for pebble instance to respond...') + misc.check_until_timeout(self.acme_xdist['directory_url']) - if acme_type == 'boulder' and os.path.exists(os.path.join(workspace, 'boulder')): - # Boulder docker generates build artifacts owned by root user with 0o744 permissions. - # If we started the acme server from a normal user that has access to the Docker - # daemon, this user will not be able to delete these artifacts from the host. - # We need to do it through a docker. - _launch_command(['docker', 'run', '--rm', '-v', '{0}:/workspace'.format(workspace), - 'alpine', 'rm', '-rf', '/workspace/boulder']) + print('=> Finished pebble instance deployment.') - shutil.rmtree(workspace) + def _prepare_boulder_server(self): + """Configure and launch the Boulder server""" + print('=> Starting boulder instance deployment...') + instance_path = join(self._workspace, 'boulder') - return workspace, cleanup + # Load Boulder from git, that includes a docker-compose.yml ready for production. + process = self._launch_process(['git', 'clone', 'https://github.com/letsencrypt/boulder', + '--single-branch', '--depth=1', instance_path]) + process.wait() + # Allow Boulder to ignore usual limit rate policies, useful for tests. + os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), + join(instance_path, 'test/rate-limit-policies.yml')) -def _prepare_acme_server(workspace, acme_type, acme_xdist): - """Configure and launch the ACME server, Boulder or Pebble""" - print('=> Starting {0} instance deployment...'.format(acme_type)) - instance_path = join(workspace, acme_type) - try: - # Load Boulder/Pebble from git, that includes a docker-compose.yml ready for production. - _launch_command(['git', 'clone', 'https://github.com/letsencrypt/{0}'.format(acme_type), - '--single-branch', '--depth=1', instance_path]) - if acme_type == 'boulder': - # Allow Boulder to ignore usual limit rate policies, useful for tests. - os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), - join(instance_path, 'test/rate-limit-policies.yml')) - if acme_type == 'pebble': - with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler: - config = yaml.load(file_handler.read()) - - # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid - # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. - config['services']['pebble'].setdefault('environment', [])\ - .extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0']) - - # Also disable strict mode for now, since Pebble v2.1.0 added specs in - # strict mode for which Certbot is not compliant for now. - # See https://github.com/certbot/certbot/pull/7175 - # TODO: Add back -strict mode once Certbot is compliant with Pebble v2.1.0+ - config['services']['pebble']['command'] = config['services']['pebble']['command']\ - .replace('-strict', '') - - with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler: - file_handler.write(yaml.dump(config)) - - # Launch the ACME CA server. - _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) + # Launch the Boulder server + self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path) # Wait for the ACME CA server to be up. - print('=> Waiting for {0} instance to respond...'.format(acme_type)) - misc.check_until_timeout(acme_xdist['directory_url']) + print('=> Waiting for boulder instance to respond...') + misc.check_until_timeout(self.acme_xdist['directory_url']) # Configure challtestsrv to answer any A record request with ip of the docker host. - acme_subnet = '10.77.77' if acme_type == 'boulder' else '10.30.50' - response = requests.post('http://localhost:{0}/set-default-ipv4' - .format(acme_xdist['challtestsrv_port']), - json={'ip': '{0}.1'.format(acme_subnet)}) + response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT), + json={'ip': '10.77.77.1'}) response.raise_for_status() - print('=> Finished {0} instance deployment.'.format(acme_type)) - except BaseException: - print('Error while setting up {0} instance.'.format(acme_type)) - raise + print('=> Finished boulder instance deployment.') + def _prepare_http_proxy(self): + """Configure and launch an HTTP proxy""" + print('=> Configuring the HTTP proxy...') + mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) + for node, port in self.acme_xdist['http_port'].items()} + command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)] + self._launch_process(command) + print('=> Finished configuring the HTTP proxy.') -def _prepare_http_proxy(acme_xdist): - """Configure and launch an HTTP proxy""" - print('=> Configuring the HTTP proxy...') - mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) - for node, port in acme_xdist['http_port'].items()} - command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)] - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - print('=> Finished configuring the HTTP proxy.') - - return process - - -def _launch_command(command, cwd=os.getcwd()): - """Launch silently an OS command, output will be displayed in case of failure""" - try: - subprocess.check_output(command, stderr=subprocess.STDOUT, cwd=cwd, universal_newlines=True) - except subprocess.CalledProcessError as e: - sys.stderr.write(e.output) - raise + def _launch_process(self, command, cwd=os.getcwd(), env=None): + """Launch silently an subprocess OS command""" + if not env: + env = os.environ + process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env) + self._processes.append(process) + return process def main(): @@ -199,8 +194,7 @@ def main(): raise ValueError('Invalid server value {0}, should be one of {1}' .format(server_type, possible_values)) - acme_server = setup_acme_server(server_type, [], False) - process = None + acme_server = ACMEServer(server_type, [], http_proxy=False, stdout=True) try: with acme_server as acme_xdist: @@ -208,15 +202,10 @@ def main(): .format(acme_xdist['directory_url'])) print('--> Press CTRL+C to stop the ACME server.') - docker_name = 'pebble_pebble_1' if 'pebble' in server_type else 'boulder_boulder_1' - process = subprocess.Popen(['docker', 'logs', '-f', docker_name]) - while True: time.sleep(3600) except KeyboardInterrupt: - if process: - process.terminate() - process.wait() + pass if __name__ == '__main__': diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py new file mode 100644 index 000000000..9f154bb0e --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py @@ -0,0 +1,52 @@ +import json +import platform +import os +import stat + +import pkg_resources +import requests + +PEBBLE_VERSION = 'v2.1.0' +ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets') + + +def fetch(workspace): + suffix = '{0}-{1}{2}'.format(platform.system().lower(), + platform.machine().lower().replace('x86_64', 'amd64'), + '.exe' if platform.system() == 'Windows' else '') + + pebble_path = _fetch_asset('pebble', suffix) + challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix) + pebble_config_path = _build_pebble_config(workspace) + + return pebble_path, challtestsrv_path, pebble_config_path + + +def _fetch_asset(asset, suffix): + asset_path = os.path.join(ASSETS_PATH, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix)) + if not os.path.exists(asset_path): + asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}' + .format(PEBBLE_VERSION, asset, suffix)) + response = requests.get(asset_url) + response.raise_for_status() + with open(asset_path, 'wb') as file_h: + file_h.write(response.content) + os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC) + + return asset_path + + +def _build_pebble_config(workspace): + config_path = os.path.join(workspace, 'pebble-config.json') + with open(config_path, 'w') as file_h: + file_h.write(json.dumps({ + 'pebble': { + 'listenAddress': '0.0.0.0:14000', + 'certificate': os.path.join(ASSETS_PATH, 'cert.pem'), + 'privateKey': os.path.join(ASSETS_PATH, 'key.pem'), + 'httpPort': 5002, + 'tlsPort': 5001, + }, + })) + + return config_path -- cgit v1.2.3 From 74bf9ef46a4cbef92ba15c9db74788c9dac84c1f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 10 Jul 2019 14:48:34 -0700 Subject: Remove test symlink. (#7232) --- test | 1 - 1 file changed, 1 deletion(-) delete mode 120000 test diff --git a/test b/test deleted file mode 120000 index bf39a010e..000000000 --- a/test +++ /dev/null @@ -1 +0,0 @@ -tox.ini \ No newline at end of file -- cgit v1.2.3 From 74292a10f52c152c321d06d5ca2048d0abee1610 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 11 Jul 2019 01:26:30 +0200 Subject: [Windows] Security model for files permissions - STEP 3e (#7182) This PR implements the filesystem.copy_ownership_and_apply_mode method from #6497. This method is used in two places in Certbot, replacing os.chown, to copy the owner and group owner from a file to another one, and apply to the latter the given POSIX mode. * Implement copy_ownership_and_apply_mode * Update certbot/compat/os.py Co-Authored-By: Brad Warren * Remove default values * Rewrite a comment. * Relaunch CI * Pass as keyword arguments * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Make the private key permissions transfer platform specific * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Rename variable * Fix comment0 * Add unit test for copy_ownership_and_apply_mode * Adapt coverage * Execute unconditionally chmod with copy_ownership_and_apply_mode. Improve doc. --- .codecov.yml | 4 +-- certbot/compat/filesystem.py | 45 +++++++++++++++++++++++++++++++ certbot/compat/misc.py | 13 +++++++++ certbot/compat/os.py | 10 +++++++ certbot/plugins/webroot.py | 12 ++++----- certbot/plugins/webroot_test.py | 8 +++--- certbot/storage.py | 13 ++++----- certbot/tests/compat/filesystem_test.py | 48 +++++++++++++++++++++++++++++++++ certbot/tests/compat/os_test.py | 2 +- certbot/tests/storage_test.py | 9 +++---- 10 files changed, 139 insertions(+), 25 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 4c0cf93f1..86813de6a 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,13 +6,13 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.7 + target: 97.6 threshold: 0.1 base: auto windows: flags: windows # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 96.9 + target: 97.0 threshold: 0.1 base: auto diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 657f2376c..d0d1c262d 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -44,6 +44,39 @@ def chmod(file_path, mode): _apply_win_mode(file_path, mode) +# One could ask why there is no copy_ownership() function, or even a reimplementation +# of os.chown() that would modify the ownership of file without touching the mode itself. +# This is because on Windows, it would require recalculating the existing DACL against +# the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically +# the current owner of a file. This action would be necessary to keep consistency between +# the POSIX mode applied to the file and the current owner of this file. +# Since copying and editing arbitrary DACL is very difficult, and since we actually know +# the mode to apply at the time the owner of a file should change, it is easier to just +# change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does. +def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): + # type: (str, str, int, bool, bool) -> None + """ + Copy ownership (user and optionally group on Linux) from the source to the + destination, then apply given mode in compatible way for Linux and Windows. + This replaces the os.chown command. + :param str src: Path of the source file + :param str dst: Path of the destination file + :param int mode: Permission mode to apply on the destination file + :param bool copy_user: Copy user if `True` + :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) + """ + if POSIX_MODE: + stats = os.stat(src) + user_id = stats.st_uid if copy_user else -1 + group_id = stats.st_gid if copy_group else -1 + os.chown(dst, user_id, group_id) + elif copy_user: + # There is no group handling in Windows + _copy_win_ownership(src, dst) + + chmod(dst, mode) + + def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin # type: (str, int, int) -> int """ @@ -251,6 +284,18 @@ def _analyze_mode(mode): } +def _copy_win_ownership(src, dst): + security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION) + user_src = security_src.GetSecurityDescriptorOwner() + + security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION) + # Second parameter indicates, if `False`, that the owner of the file is not provided by some + # default mechanism, but is explicitly set instead. This is obviously what we are doing here. + security_dst.SetSecurityDescriptorOwner(user_src, False) + + win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst) + + def _generate_windows_flags(rights_desc): # Some notes about how each POSIX right is interpreted. # diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index c61672364..c840c333c 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -17,6 +17,19 @@ except ImportError: # pragma: no cover from certbot import errors from certbot.compat import os + +# MASK_FOR_PRIVATE_KEY_PERMISSIONS defines what are the permissions flags to keep +# when transferring the permissions from an old private key to a new one. +if POSIX_MODE: + # On Linux, we keep read/write/execute permissions + # for group and read permissions for everybody. + MASK_FOR_PRIVATE_KEY_PERMISSIONS = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH +else: + # On Windows, the mode returned by os.stat is not reliable, + # so we do not keep any permission from the previous private key. + MASK_FOR_PRIVATE_KEY_PERMISSIONS = 0 + + def raise_for_non_administrative_windows_rights(): # type: () -> None """ diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 0c5582ca1..6d3c7bb02 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -17,6 +17,7 @@ from os import * # type: ignore # pylint: disable=wildcard-import,unused-wildc # and so not in `from os import *`. import os as std_os # pylint: disable=os-module-forbidden import sys as std_sys + ourselves = std_sys.modules[__name__] for attribute in dir(std_os): # Check if the attribute does not already exist in our module. It could be internal attributes @@ -51,6 +52,15 @@ def chmod(*unused_args, **unused_kwargs): 'Use certbot.compat.filesystem.chmod() instead.') +# Because uid is not a concept on Windows, chown is useless. In fact, it is not even available +# on Python for Windows. So to be consistent on both platforms for Certbot, this method is +# always forbidden. +def chown(*unused_args, **unused_kwargs): + """Method os.chown() is forbidden""" + raise RuntimeError('Usage of os.chown() is forbidden.' + 'Use certbot.compat.filesystem.copy_ownership_and_apply_mode() instead.') + + # The os.open function on Windows has the same effect as a call to os.chown concerning the file # modes: these modes lack the correct control over the permissions given to the file. Instead, # filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index 2a42bd5ed..f767bdf54 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -169,19 +169,19 @@ to serve all files under specified web root ({0}).""" # run as non-root (GH #1795) old_umask = os.umask(0o022) try: - stat_path = os.stat(path) # We ignore the last prefix in the next iteration, # as it does not correspond to a folder path ('/' or 'C:') for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): try: - # This is coupled with the "umask" call above because os.mkdir's - # "mode" parameter may not always work under Linux: + # Set owner as parent directory if possible, apply mode for Linux/Windows. + # For Linux, this is coupled with the "umask" call above because + # os.mkdir's "mode" parameter may not always work: # https://docs.python.org/3/library/os.html#os.mkdir - filesystem.mkdir(prefix, 0o0755) + filesystem.mkdir(prefix, 0o755) self._created_dirs.append(prefix) - # Set owner as parent directory if possible try: - os.chown(prefix, stat_path.st_uid, stat_path.st_gid) + filesystem.copy_ownership_and_apply_mode( + path, prefix, 0o755, copy_user=True, copy_group=True) except (OSError, AttributeError) as exception: logger.info("Unable to change owner and uid of webroot directory") logger.debug("Error was: %s", exception) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index 5039aae9e..ba9950fae 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -142,13 +142,11 @@ class AuthenticatorTest(unittest.TestCase): self.assertRaises(errors.PluginError, self.auth.perform, []) filesystem.chmod(self.path, 0o700) - @test_util.skip_on_windows('On Windows, there is no chown.') - @mock.patch("certbot.plugins.webroot.os.chown") - def test_failed_chown(self, mock_chown): - mock_chown.side_effect = OSError(errno.EACCES, "msg") + @mock.patch("certbot.plugins.webroot.filesystem.copy_ownership_and_apply_mode") + def test_failed_chown(self, mock_ownership): + mock_ownership.side_effect = OSError(errno.EACCES, "msg") self.auth.perform([self.achall]) # exception caught and logged - @test_util.patch_get_utility() def test_perform_new_webroot_not_in_map(self, mock_get_utility): new_webroot = tempfile.mkdtemp() diff --git a/certbot/storage.py b/certbot/storage.py index 3ab37afe5..518ba9464 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -18,6 +18,7 @@ from certbot import crypto_util from certbot import error_handler from certbot import errors from certbot import util +from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem from certbot.plugins import common as plugins_common @@ -1104,13 +1105,13 @@ class RenewableCert(object): 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) + # Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS) + # from previous privkey in this lineage. + old_mode = (stat.S_IMODE(os.stat(old_privkey).st_mode) & + misc.MASK_FOR_PRIVATE_KEY_PERMISSIONS) mode = BASE_PRIVKEY_MODE | old_mode - os.chown(target["privkey"], -1, os.stat(old_privkey).st_gid) - filesystem.chmod(target["privkey"], mode) + filesystem.copy_ownership_and_apply_mode( + old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) # Save everything else with open(target["cert"], "wb") as f: diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index b0edb31ba..27c48c19a 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -2,6 +2,8 @@ import errno import unittest +import mock + try: # pylint: disable=import-error import win32api @@ -270,6 +272,52 @@ class WindowsMkdirTests(test_util.TempDirTestCase): self.assertEqual(original_mkdir, std_os.mkdir) +class CopyOwnershipTest(test_util.TempDirTestCase): + """Tests about replacement of chown: copy_ownership_and_apply_mode""" + def setUp(self): + super(CopyOwnershipTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') + def test_windows(self): + system = win32security.ConvertStringSidToSid(SYSTEM_SID) + security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR + security.SetSecurityDescriptorOwner(system, False) + + with mock.patch('win32security.GetFileSecurity') as mock_get: + with mock.patch('win32security.SetFileSecurity') as mock_set: + mock_get.return_value = security + filesystem.copy_ownership_and_apply_mode( + 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=False) + + self.assertEqual(mock_set.call_count, 2) + + first_call = mock_set.call_args_list[0] + security = first_call[0][2] + self.assertEqual(system, security.GetSecurityDescriptorOwner()) + + second_call = mock_set.call_args_list[1] + security = second_call[0][2] + dacl = security.GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + self.assertTrue(dacl.GetAceCount()) + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') + def test_linux(self): + with mock.patch('os.chown') as mock_chown: + with mock.patch('os.chmod') as mock_chmod: + with mock.patch('os.stat') as mock_stat: + mock_stat.return_value.st_uid = 50 + mock_stat.return_value.st_gid = 51 + filesystem.copy_ownership_and_apply_mode( + 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=True) + + mock_chown.assert_called_once_with(self.probe_path, 50, 51) + mock_chmod.assert_called_once_with(self.probe_path, 0o700) + + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index a2b40a7f8..9e25548f0 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -7,7 +7,7 @@ from certbot.compat import os class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): - for method in ['chmod', 'open', 'mkdir', 'makedirs', 'rename', 'replace']: + for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', 'replace']: self.assertRaises(RuntimeError, getattr(os, method)) diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 017807a6d..fceef804a 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -588,10 +588,9 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(misc.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.patch("certbot.storage.filesystem.copy_ownership_and_apply_mode") + def test_save_successor_maintains_gid(self, mock_ownership, mock_rv): # Mock relevant_values() to claim that all values are relevant here # (to avoid instantiating parser) mock_rv.side_effect = lambda x: x @@ -599,9 +598,9 @@ class RenewableCertTests(BaseRenewableCertTest): 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.assertFalse(mock_ownership.called) self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(mock_chown.called) + self.assertTrue(mock_ownership.called) @mock.patch("certbot.storage.relevant_values") def test_new_lineage(self, mock_rv): -- cgit v1.2.3 From add24d48613c86daf192572613452051bc81a1bc Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 10 Jul 2019 16:30:06 -0700 Subject: Run tests on apache-parser-v2 (#7231) We're planning on using the branch apache-parser-v2 allowing us to incrementally work on the new Apache parser and feel comfortable landing temporary test code that we don't really want in master. The apache-parser-v2 branch is created and locked down, but neither Travis or AppVeyor are configured to run tests on it. See #7230. This PR fixes that problem. This could probably just land in the apache-parser-v2 branch, but why unnecessarily deviate the branch from master? It doesn't hurt anything there. Once it lands, I'll get this added to the apache-parser-v2 branch too. * Run tests on apache-parser-v2. * add comment * Don't run full test suite on apache-parser-v2. --- .travis.yml | 8 ++++++-- appveyor.yml | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 86a475ca8..baf385604 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,9 @@ before_script: # is a cap of on the number of simultaneous runs. branches: only: + # apache-parser-v2 is a temporary branch for doing work related to + # rewriting the parser in the Apache plugin. + - apache-parser-v2 - master - /^\d+\.\d+\.x$/ - /^test-.*$/ @@ -24,9 +27,10 @@ branches: not-on-master: ¬-on-master if: NOT (type = push AND branch = master) -# Jobs for the extended test suite are executed for cron jobs and pushes on non-master branches. +# Jobs for the extended test suite are executed for cron jobs and pushes to +# non-development branches. See the explanation for apache-parser-v2 above. extended-test-suite: &extended-test-suite - if: type = cron OR (type = push AND branch != master) + if: type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master)) matrix: include: diff --git a/appveyor.yml b/appveyor.yml index 3d58847f8..33f522df1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,9 @@ environment: branches: only: + # apache-parser-v2 is a temporary branch for doing work related to + # rewriting the parser in the Apache plugin. + - apache-parser-v2 - master - /^\d+\.\d+\.x$/ # Version branches like X.X.X - /^test-.*$/ -- cgit v1.2.3 From d0a9695b0963eb06e6405115657471b3a37103ed Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 10 Jul 2019 18:14:01 -0700 Subject: Make PR template a checklist and suggest mypy. (#7223) --- pull_request_template.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pull_request_template.md b/pull_request_template.md index 9ab07e3aa..77d65499f 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,4 +1,8 @@ -Be sure to edit the `master` section of `CHANGELOG.md` to include a description -of the change being made in this PR. +## Pull Request Checklist -You are also welcome to add your name to `AUTHORS.md` if you like. +- [ ] Edit the `master` section of `CHANGELOG.md` to include a description of + the change being made. +- [ ] Add [mypy type + annotations](https://certbot.eff.org/docs/contributing.html#mypy-type-annotations) + for any functions that were added or modified. +- [ ] Include your name in `AUTHORS.md` if you like. -- cgit v1.2.3 From 89f52ca9f921133918c26581f6fa91ed8204ad4a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 10 Jul 2019 18:14:12 -0700 Subject: Add mypy to contributing checklist. (#7224) --- docs/contributing.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 14256b15a..715788332 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -375,6 +375,8 @@ As a developer, when working on Certbot or its plugins, you must use ``certbot.c in every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of ``import os``). Otherwise the tests will fail when your PR is submitted. +.. _type annotations: + Mypy type annotations ===================== @@ -414,7 +416,10 @@ Submitting a pull request Steps: -1. Write your code! +1. Write your code! When doing this, you should add :ref:`mypy type annotations + ` for any functions you add or modify. You can check that + you've done this correctly by running ``tox -e mypy`` on a machine that has + Python 3 installed. 2. Make sure your environment is set up properly and that you're in your virtualenv. You can do this by following the instructions in the :ref:`Getting Started ` section. -- cgit v1.2.3 From 13c44a059580d5dfa7d79570bdb55e3a98945d01 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 11 Jul 2019 12:12:24 -0700 Subject: Update changelog for 0.36.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc3acb665..50855c942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.36.0 - master +## 0.36.0 - 2019-07-11 ### Added -- cgit v1.2.3 From cbd0a37c7a5b277b2d69edb03b56f24f244fdf61 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 11 Jul 2019 12:31:51 -0700 Subject: Release 0.36.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 ++-- certbot-auto | 26 +++++++++++----------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 4 ++-- certbot/__init__.py | 2 +- docs/cli-help.txt | 17 ++++++-------- letsencrypt-auto | 26 +++++++++++----------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------- letsencrypt-auto-source/letsencrypt-auto | 26 +++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.sig | 3 +-- .../pieces/certbot-requirements.txt | 24 ++++++++++---------- 28 files changed, 90 insertions(+), 94 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 9fca57e01..6ab8b4930 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.36.0.dev0' +version = '0.36.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da509406e..a959ad44f 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.36.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 6e6f0277f..b1cd344bf 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.36.0.dev0', + 'certbot>=0.36.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index 756b8e247..fd2cfb29b 100755 --- a/certbot-auto +++ b/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.35.1" +LE_AUTO_VERSION="0.36.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==0.36.0 \ + --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ + --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 +acme==0.36.0 \ + --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ + --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 +certbot-apache==0.36.0 \ + --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ + --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd +certbot-nginx==0.36.0 \ + --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ + --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 362043531..f3dfac291 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index bd201aca2..8c6e8e434 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index d8d7aa9b8..13c9cf5e0 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 92a9b2b14..3b6d8ce2e 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 709ca8330..cc106a7ac 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 1d55b0fe4..04c4c33a4 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 6dac126d0..64999b851 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 5c31a81f8..f8989c340 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index b70a6a39c..94675e939 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 5f5322319..7d1bd28b2 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 00ed64032..e024f4676 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index bff394bf9..ef272f190 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 25c6ae1d1..2bd6df51e 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index b8af58b30..dede8e7c4 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index b674b7199..9d79fab9c 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0.dev0' +version = '0.36.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index da509406e..a959ad44f 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.36.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 64f343730..d3b5c2132 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.36.0.dev0' +version = '0.36.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.35.0.dev0', + 'certbot>=0.35.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/__init__.py b/certbot/__init__.py index 32ab75aaa..0d23d2084 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.36.0.dev0' +__version__ = '0.36.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 3499acf19..9532b7b22 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -27,10 +27,10 @@ manage certificates: revoke Revoke a certificate (supply --cert-path or --cert-name) delete Delete a certificate -manage your account with Let's Encrypt: - register Create a Let's Encrypt ACME account - unregister Deactivate a Let's Encrypt ACME account - update_account Update a Let's Encrypt ACME account +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account --agree-tos Agree to the ACME server's Subscriber Agreement -m EMAIL Email address for important account notifications @@ -113,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.35.1 + "". (default: CertbotACMEClient/0.36.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -393,10 +393,7 @@ install: Options for modifying how a certificate is deployed config_changes: - Options for controlling which changes are displayed - - --num NUM How many past revisions you want to be displayed - (default: None) + Options for viewing configuration changes rollback: Options for rolling back server configuration changes @@ -405,7 +402,7 @@ rollback: (default: 1) plugins: - Options for for the "plugins" subcommand + Options for the "plugins" subcommand --init Initialize plugins. (default: False) --prepare Initialize and prepare plugins. (default: False) diff --git a/letsencrypt-auto b/letsencrypt-auto index 756b8e247..fd2cfb29b 100755 --- a/letsencrypt-auto +++ b/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.35.1" +LE_AUTO_VERSION="0.36.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==0.36.0 \ + --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ + --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 +acme==0.36.0 \ + --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ + --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 +certbot-apache==0.36.0 \ + --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ + --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd +certbot-nginx==0.36.0 \ + --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ + --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 0abdfdfdb..e102e3720 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlz+2KgACgkQTRfJlc2X -dfKLvwf/XUnWRyKldk/d8Egx514mpjzV38grCcZTZrY0O/Rd3YMv5KtrxnTnmvxJ -zAkTfNIo7Y1894mZ6XUIF3D7BiPoRqLj6F8tYLV6jbdsPJKC75dQY/6rcttTSJab -Zbqcw+WTXYNZ72AlHw0uTaxNT+S31KvrJ0pNmuj2ezKzZcDfgcxeeqmI1pJYozbQ -+AfqJMFgP9qHtfZVnmRO5UFBW2qPM522E02wWCtkaQSybI9ikRTvJOtilQgsLFJK -vBdD/MgGHm/xQC6lxG6l/SD4pebvNBaIPCiPAo2XC8ML+4tpjuJ5lPoK/aFCvfYQ -wSMHbDbse/Ndw+ssjmriiBreKB37Mg== -=flRo +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl0njnkACgkQTRfJlc2X +dfIqjwf/XBEzOEtwi84v97jZjHi9bqeFBzAt+n6YXleKySk8anxFMFmFIvrc2w/U +eyskpn0mmJDX2LjaXcsJji+l5yAWbm3p8M2J2toaPI2TLznhM6+uEWP62BHJiQYi +1ORBJYATSfLxA541CwXXW3VTYDu+CLq0w1nr5mHg1Y20ZFBrPIlt04mkh9o70fD4 +qv6MsMXKZxglhH1ORyLMVn5Jze22awmJ944pP8aI54ZEkTl2XT9DsZt3QpZ1muOy +IRg6sU86ukgWK66zWjTyd1AOddDL2OY3+U7JachFd5eb7dnnaCGeZhCjfVide7a3 +Fk8NrXwlrpKKJYkbqDfRkT4Pba47VQ== +=gf1k -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 24e9b295e..fd2cfb29b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.36.0.dev0" +LE_AUTO_VERSION="0.36.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==0.36.0 \ + --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ + --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 +acme==0.36.0 \ + --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ + --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 +certbot-apache==0.36.0 \ + --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ + --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd +certbot-nginx==0.36.0 \ + --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ + --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index d3824cf47..9430fd293 100644 --- a/letsencrypt-auto-source/letsencrypt-auto.sig +++ b/letsencrypt-auto-source/letsencrypt-auto.sig @@ -1,2 +1 @@ -{¡Ü–=Çà’¯Q\0Åÿ<*ÝOÛÍ#ýå›pýÚ£í²5wtl]ýÓ  pT—éM!ͺC¿˜püÂ!”Õ³ÿÍ ÍÍ ³ìÚ_ÁÒF…–õë@íìrÀ솑=Î^%†Ò~x~û:0ŸÏ×7¢”Éo'7œª “™ª#šnnRèg:SC·Y®—b¡ÆŒÇÛéx™pajoýûó­Oj¶IÛqQ¾;6ôÙèò,ƒ=^ƒ/ƒ¤©{³ÚTJΩ^õòþÓ#L”TÉŠ§±+Ü1XcYw„ß _mÒrñìã :D–ŽOßäÎûá*¤ß@»’té‡+¥`jû¾F˜ -;Ii» \ No newline at end of file +Å„–Üp|?Ê¢5ªÇgÒl,ÂzCHvöW3–tœÌºZê| Þ3µ‚­^¹Ç 7Iª‹Âœ2¦¾ä^m¡(Þ`…X™í²ËÅq. U“±DDR»À]À£ÁGý{ìÁ¨"øËâòÐæÆ°”9]æ{×}ºMž g/åAüA"ºš³ø×£×ûÆáêñ³¬¼B c˜R{÷¶²Gâ}dÎ$H&”[éðµÉâiÀ­C^Y­˜¡îl ÒS·nüE?7¼TÙ¡†BÓò{ÊÅv—!n/…q~‘Hh•#R£9Èb€×}j5:}šO®3_»ìaª}¬Í­þ \ No newline at end of file diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 71c041934..ee5705766 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==0.36.0 \ + --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ + --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 +acme==0.36.0 \ + --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ + --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 +certbot-apache==0.36.0 \ + --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ + --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd +certbot-nginx==0.36.0 \ + --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ + --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 -- cgit v1.2.3 From 15b1d8e5a7a4035199bc88eb3b90def75b46763a Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 11 Jul 2019 12:31:53 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50855c942..091ec0493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.37.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.36.0 - 2019-07-11 ### Added -- cgit v1.2.3 From d1934e36fe9e919b67857dd4af0d2523d15c25d4 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 11 Jul 2019 12:31:53 -0700 Subject: Bump version to 0.37.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6ab8b4930..5493df0cc 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.36.0' +version = '0.37.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b1cd344bf..1003ad678 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f3dfac291..8c04a96ae 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 8c6e8e434..8a7a36c2e 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 13c9cf5e0..196a3641f 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 3b6d8ce2e..466ab60ec 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index cc106a7ac..26eaaf7c2 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 04c4c33a4..db36f256f 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 64999b851..975d79231 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f8989c340..a7cae3bbe 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 94675e939..81329ee8d 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 7d1bd28b2..4ee89f076 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index e024f4676..73fbf768b 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index ef272f190..b18dc5057 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 2bd6df51e..f78f7a2c7 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index dede8e7c4..43f8081fe 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 9d79fab9c..17e419ae0 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.36.0' +version = '0.37.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index d3b5c2132..c30202272 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.36.0' +version = '0.37.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 0d23d2084..f5bd37e3c 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.36.0' +__version__ = '0.37.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index fd2cfb29b..b5f8500ad 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.36.0" +LE_AUTO_VERSION="0.37.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 82ad73612097146b019b1a803eb1f53bd16ac1c9 Mon Sep 17 00:00:00 2001 From: Lucid One Date: Thu, 11 Jul 2019 17:40:24 -0400 Subject: Fixes #7220 to allow config to be loaded from <(envsubst < template) (#7221) * Fixes #7220 to allow config to be loaded from <(envsubst < template) --- certbot/plugins/dns_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py index 9be2868b0..e7fbd3889 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/plugins/dns_common.py @@ -303,8 +303,8 @@ def validate_file(filename): if not os.path.exists(filename): raise errors.PluginError('File not found: {0}'.format(filename)) - if not os.path.isfile(filename): - raise errors.PluginError('Path is not a file: {0}'.format(filename)) + if os.path.isdir(filename): + raise errors.PluginError('Path is a directory: {0}'.format(filename)) def validate_file_permissions(filename): -- cgit v1.2.3 From c4684f187a4ce4ef13425cfba607dec9d8bfa963 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 12 Jul 2019 02:49:52 +0200 Subject: Add a test for the default directories on Windows (#7238) There is a unit test to check that the default directories for Certbot are not diverging, in certbot.tests.cli_test:FlagDefaultTests:test_linux_directories. But this test is not done on Windows. This PR fixes that. --- certbot/tests/cli_test.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 9dd16db2d..c1a489267 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -1,7 +1,6 @@ """Tests for certbot.cli.""" import argparse import copy -import sys import tempfile import unittest @@ -44,11 +43,15 @@ class TestReadFile(TempDirTestCase): class FlagDefaultTest(unittest.TestCase): """Tests cli.flag_default""" - def test_linux_directories(self): - if 'fcntl' in sys.modules: + def test_default_directories(self): + if os.name != 'nt': 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') + else: + self.assertEqual(cli.flag_default('config_dir'), 'C:\\Certbot') + self.assertEqual(cli.flag_default('work_dir'), 'C:\\Certbot\\lib') + self.assertEqual(cli.flag_default('logs_dir'), 'C:\\Certbot\\log') class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods -- cgit v1.2.3 From 750d6a9686a98204c3ea46208999f6ae0ee15479 Mon Sep 17 00:00:00 2001 From: Po-Chuan Hsieh Date: Sat, 13 Jul 2019 03:53:43 +0800 Subject: Unify license filename (LICENSE.txt) (#7239) * Unify license filename (LICENSE.txt) --- certbot-dns-route53/LICENSE | 201 ---------------------------------------- certbot-dns-route53/LICENSE.txt | 201 ++++++++++++++++++++++++++++++++++++++++ certbot-dns-route53/MANIFEST.in | 2 +- 3 files changed, 202 insertions(+), 202 deletions(-) delete mode 100644 certbot-dns-route53/LICENSE create mode 100644 certbot-dns-route53/LICENSE.txt diff --git a/certbot-dns-route53/LICENSE b/certbot-dns-route53/LICENSE deleted file mode 100644 index 8dada3eda..000000000 --- a/certbot-dns-route53/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/certbot-dns-route53/LICENSE.txt b/certbot-dns-route53/LICENSE.txt new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/certbot-dns-route53/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/certbot-dns-route53/MANIFEST.in b/certbot-dns-route53/MANIFEST.in index c48c07e59..ca37a7baf 100644 --- a/certbot-dns-route53/MANIFEST.in +++ b/certbot-dns-route53/MANIFEST.in @@ -1,3 +1,3 @@ -include LICENSE +include LICENSE.txt include README recursive-include docs * -- cgit v1.2.3 From 41a17f913e7d710c584105a4f16cc13d0640b88e Mon Sep 17 00:00:00 2001 From: J0WI Date: Wed, 17 Jul 2019 22:05:02 +0200 Subject: Use Buster as base image (#7251) --- Dockerfile-dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile-dev b/Dockerfile-dev index 1ab56e081..86f5f04e7 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,5 +1,5 @@ # This Dockerfile builds an image for development. -FROM ubuntu:xenial +FROM debian:buster # Note: this only exposes the port to other docker containers. EXPOSE 80 443 -- cgit v1.2.3 From 71ff47daad97dab3c2659a705d036428275c7bd2 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 18 Jul 2019 23:31:39 +0200 Subject: Implement a consistent realpath function in certbot.compat.filesystem (#7242) Fixes #7115 This PR creates a `realpath` method in `filesystem`, whose goal is to replace any call to `os.path.realpath` in Certbot. The reason is that `os.path.realpath` is broken on some versions of Python for Windows. See https://bugs.python.org/issue9949. The function created here works consistently across Linux and Windows. As for the other forbidden functions in `os` module, our `certbot.compat.os` will raise an exception if its `path.realpath` function is invoked, and using the `os` module from Python is forbidden from the pylint check implemented in our CI. Every call to `os.path.realpath` is corrected in `certbot` and `certbot-apache` modules. * Forbid os.path.realpath * Finish implementation * Use filesystem.realpath * Control symlink loops also for Linux * Add a test for forbidden method * Import a new object from os.path module * Use same approach of wrapping than certbot.compat.os * Correct errors * Fix dependencies * Make path module internal --- certbot-apache/certbot_apache/configurator.py | 7 +-- certbot-apache/certbot_apache/override_debian.py | 3 +- certbot-apache/certbot_apache/tests/centos_test.py | 3 +- .../certbot_apache/tests/configurator_test.py | 5 +- certbot-apache/certbot_apache/tests/debian_test.py | 4 +- certbot-apache/certbot_apache/tests/fedora_test.py | 3 +- certbot-apache/certbot_apache/tests/gentoo_test.py | 3 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 2 +- certbot/compat/_path.py | 31 +++++++++++++ certbot/compat/filesystem.py | 28 ++++++++++-- certbot/compat/os.py | 4 +- certbot/main.py | 9 ++-- certbot/plugins/common.py | 2 +- certbot/plugins/storage_test.py | 2 +- certbot/tests/cert_manager_test.py | 4 +- certbot/tests/compat/filesystem_test.py | 53 ++++++++++++++++------ certbot/tests/compat/os_test.py | 4 ++ certbot/tests/main_test.py | 2 +- certbot/tests/util_test.py | 8 ++-- 20 files changed, 134 insertions(+), 45 deletions(-) create mode 100644 certbot/compat/_path.py diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index c433c5159..f7c27bf76 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -23,6 +23,7 @@ from certbot import interfaces from certbot import util from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import +from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common from certbot.plugins.util import path_surgery @@ -895,7 +896,7 @@ class ApacheConfigurator(common.Installer): if not new_vhost: continue internal_path = apache_util.get_internal_aug_path(new_vhost.path) - realpath = os.path.realpath(new_vhost.filep) + realpath = filesystem.realpath(new_vhost.filep) if realpath not in file_paths: file_paths[realpath] = new_vhost.filep internal_paths[realpath].add(internal_path) @@ -1221,11 +1222,11 @@ class ApacheConfigurator(common.Installer): """ if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")): - fp = os.path.join(os.path.realpath(self.option("vhost_root")), + fp = os.path.join(filesystem.realpath(self.option("vhost_root")), os.path.basename(non_ssl_vh_fp)) else: # Use non-ssl filepath - fp = os.path.realpath(non_ssl_vh_fp) + fp = filesystem.realpath(non_ssl_vh_fp) if fp.endswith(".conf"): return fp[:-(len(".conf"))] + self.option("le_vhost_ext") diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index 5706ce760..58492bd01 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -7,6 +7,7 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import filesystem from certbot.compat import os from certbot_apache import apache_util @@ -65,7 +66,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): try: os.symlink(vhost.filep, enabled_path) except OSError as err: - if os.path.islink(enabled_path) and os.path.realpath( + if os.path.islink(enabled_path) and filesystem.realpath( enabled_path) == vhost.filep: # Already in shape vhost.enabled = True diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index 5d16c2b55..dddbf489e 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -4,6 +4,7 @@ import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot_apache import obj @@ -160,7 +161,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = os.path.realpath( + self.config.parser.sysconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../sysconfig/httpd")) self.config.parser.variables = {} diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index f9b6fb186..1eafae982 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -675,8 +675,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_make_vhost_ssl_nonexistent_vhost_path(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) self.assertEqual(os.path.dirname(ssl_vhost.filep), - os.path.dirname(os.path.realpath( - self.vh_truth[1].filep))) + os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) @@ -1336,7 +1335,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules.add("ssl_module") self.config.parser.modules.add("mod_ssl.c") self.config.parser.modules.add("socache_shmcb_module") - tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot")) + tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) filesystem.chmod(tmp_path, 0o755) mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path" mock_a = "certbot_apache.parser.ApacheParser.add_include" diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py index f1d5843a5..54ced2d0b 100644 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ b/certbot-apache/certbot_apache/tests/debian_test.py @@ -79,9 +79,9 @@ class MultipleVhostsTestDebian(util.ApacheTest): def test_enable_site_failure(self): self.config.parser.root = "/tmp/nonexistent" - with mock.patch("os.path.isdir") as mock_dir: + with mock.patch("certbot.compat.os.path.isdir") as mock_dir: mock_dir.return_value = True - with mock.patch("os.path.islink") as mock_link: + with mock.patch("certbot.compat.os.path.islink") as mock_link: mock_link.return_value = False self.assertRaises( errors.NotSupportedError, diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/certbot_apache/tests/fedora_test.py index 67533fe1d..4d3f3a313 100644 --- a/certbot-apache/certbot_apache/tests/fedora_test.py +++ b/certbot-apache/certbot_apache/tests/fedora_test.py @@ -4,6 +4,7 @@ import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot_apache import obj @@ -160,7 +161,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = os.path.realpath( + self.config.parser.sysconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../sysconfig/httpd")) self.config.parser.variables = {} diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py index dd01e9170..d0d3ba0dd 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/certbot_apache/tests/gentoo_test.py @@ -4,6 +4,7 @@ import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot_apache import obj @@ -81,7 +82,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): """Make sure we read the Gentoo APACHE2_OPTS variable correctly""" defines = ['DEFAULT_VHOST', 'INFO', 'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE'] - self.config.parser.apacheconfig_filep = os.path.realpath( + self.config.parser.apacheconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../conf.d/apache2")) self.config.parser.variables = {} with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"): diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index a959ad44f..da509406e 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.36.0 +-e .[dev] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 1003ad678..98b7382db 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '0.37.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.36.0', + 'certbot>=0.37.0.dev0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot/compat/_path.py b/certbot/compat/_path.py new file mode 100644 index 000000000..fe2d2d1d2 --- /dev/null +++ b/certbot/compat/_path.py @@ -0,0 +1,31 @@ +"""This compat module wraps os.path to forbid some functions.""" +# pylint: disable=function-redefined +from __future__ import absolute_import + +# First round of wrapping: we import statically all public attributes exposed by the os.path +# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path +# members are available in certbot.compat.path. +from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden + +# Second round of wrapping: we import dynamically all attributes from the os.path module that have +# not yet been imported by the first round (static star import). +import os.path as std_os_path # pylint: disable=os-module-forbidden +import sys as std_sys + +ourselves = std_sys.modules[__name__] +for attribute in dir(std_os_path): + # Check if the attribute does not already exist in our module. It could be internal attributes + # of the module (__name__, __doc__), or attributes from standard os.path already imported with + # `from os.path import *`. + if not hasattr(ourselves, attribute): + setattr(ourselves, attribute, getattr(std_os_path, attribute)) + +# Clean all remaining importables that are not from the core os.path module. +del ourselves, std_os_path, std_sys + + +# Function os.path.realpath is broken on some versions of Python for Windows. +def realpath(*unused_args, **unused_kwargs): + """Method os.path.realpath() is forbidden""" + raise RuntimeError('Usage of os.path.realpath() is forbidden. ' + 'Use certbot.compat.filesystem.realpath() instead.') diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index d0d1c262d..e2757eb2f 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -207,13 +207,22 @@ def replace(src, dst): os.rename(src, dst) -def _apply_win_mode(file_path, mode): +def realpath(file_path): """ - This function converts the given POSIX mode into a Windows ACL list, and applies it to the - file given its path. If the given path is a symbolic link, it will resolved to apply the - mode on the targeted file. + Find the real path for the given path. This method resolves symlinks, including + recursive symlinks, and is protected against symlinks that creates an infinite loop. """ original_path = file_path + + if POSIX_MODE: + path = os.path.realpath(file_path) + if os.path.islink(path): + # If path returned by realpath is still a link, it means that it failed to + # resolve the symlink because of a loop. + # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + return path + inspected_paths = [] # type: List[str] while os.path.islink(file_path): link_path = file_path @@ -223,6 +232,17 @@ def _apply_win_mode(file_path, mode): if file_path in inspected_paths: raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) inspected_paths.append(file_path) + + return os.path.abspath(file_path) + + +def _apply_win_mode(file_path, mode): + """ + This function converts the given POSIX mode into a Windows ACL list, and applies it to the + file given its path. If the given path is a symbolic link, it will resolved to apply the + mode on the targeted file. + """ + file_path = realpath(file_path) # Get owner sid of the file security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) user = security.GetSecurityDescriptorOwner() diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 6d3c7bb02..bb0e02eb3 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -26,7 +26,9 @@ for attribute in dir(std_os): if not hasattr(ourselves, attribute): setattr(ourselves, attribute, getattr(std_os, attribute)) -# Similar to os.path, allow certbot.compat.os.path to behave as a module +# Import our internal path module, then allow certbot.compat.os.path +# to behave as a module (similarly to os.path). +from certbot.compat import _path as path # type: ignore # pylint: disable=wrong-import-position std_sys.modules[__name__ + '.path'] = path # Clean all remaining importables that are not from the core os module. diff --git a/certbot/main.py b/certbot/main.py index 6bd47cee3..50470438f 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -31,6 +31,7 @@ from certbot import reporter from certbot import storage from certbot import updater from certbot import util +from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os from certbot.display import util as display_util, ops as display_ops @@ -841,12 +842,12 @@ def _populate_from_certname(config): return config def _check_certificate_and_key(config): - if not os.path.isfile(os.path.realpath(config.cert_path)): + if not os.path.isfile(filesystem.realpath(config.cert_path)): raise errors.ConfigurationError("Error while reading certificate from path " - "{0}".format(config.cert_path)) - if not os.path.isfile(os.path.realpath(config.key_path)): + "{0}".format(config.cert_path)) + if not os.path.isfile(filesystem.realpath(config.key_path)): raise errors.ConfigurationError("Error while reading private key from path " - "{0}".format(config.key_path)) + "{0}".format(config.key_path)) def plugins_cmd(config, plugins): """List server software plugins. diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 2e2e0f64c..5c292bfad 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -486,7 +486,7 @@ def dir_setup(test_dir, pkg): # pragma: no cover link, (ex: OS X) such plugins will be confused. This function prevents such a case. """ - return os.path.realpath(tempfile.mkdtemp(prefix)) + return filesystem.realpath(tempfile.mkdtemp(prefix)) temp_dir = expanded_tempdir("temp") config_dir = expanded_tempdir("config") diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py index f72e69d4b..9d08cc7ef 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/plugins/storage_test.py @@ -31,7 +31,7 @@ class PluginStorageTest(test_util.ConfigTestCase): self.plugin.storage.storagepath = os.path.join(self.config.config_dir, ".pluginstorage.json") with mock.patch("six.moves.builtins.open", mock_open): - with mock.patch('os.path.isfile', return_value=True): + with mock.patch('certbot.compat.os.path.isfile', return_value=True): with mock.patch("certbot.reverter.util"): self.assertRaises(errors.PluginStorageError, self.plugin.storage._load) # pylint: disable=protected-access diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 30ca6f099..b4cd946ee 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -97,8 +97,8 @@ class UpdateLiveSymlinksTest(BaseCertManagerTest): for kind in ALL_FOUR: os.chdir(os.path.dirname(self.config_files[domain][kind])) self.assertEqual( - os.path.realpath(os.readlink(self.config_files[domain][kind])), - os.path.realpath(archive_paths[domain][kind])) + filesystem.realpath(os.readlink(self.config_files[domain][kind])), + filesystem.realpath(archive_paths[domain][kind])) finally: os.chdir(prev_dir) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 27c48c19a..5d112b652 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -48,18 +48,6 @@ class WindowsChmodTests(TempDirTestCase): self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link)) # pylint: disable=protected-access - def test_symlink_loop_mitigation(self): - link1_path = os.path.join(self.tempdir, 'link1') - link2_path = os.path.join(self.tempdir, 'link2') - link3_path = os.path.join(self.tempdir, 'link3') - os.symlink(link1_path, link2_path) - os.symlink(link2_path, link3_path) - os.symlink(link3_path, link1_path) - - with self.assertRaises(RuntimeError) as error: - filesystem.chmod(link1_path, 0o755) - self.assertTrue('link1 is a loop!' in str(error.exception)) - def test_world_permission(self): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) @@ -320,7 +308,6 @@ class CopyOwnershipTest(test_util.TempDirTestCase): class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" - def test_os_replace_to_existing_file(self): """Ensure that replace will effectively rename src into dst for all platforms.""" src = os.path.join(self.tempdir, 'src') @@ -335,6 +322,46 @@ class OsReplaceTest(test_util.TempDirTestCase): self.assertTrue(os.path.exists(dst)) +class RealpathTest(test_util.TempDirTestCase): + """Tests for realpath method""" + def setUp(self): + super(RealpathTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_symlink_resolution(self): + # Absolute resolution + link_path = os.path.join(self.tempdir, 'link_abs') + os.symlink(self.probe_path, link_path) + + self.assertEqual(self.probe_path, filesystem.realpath(self.probe_path)) + self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + + # Relative resolution + curdir = os.getcwd() + link_path = os.path.join(self.tempdir, 'link_rel') + probe_name = os.path.basename(self.probe_path) + try: + os.chdir(os.path.dirname(self.probe_path)) + os.symlink(probe_name, link_path) + + self.assertEqual(self.probe_path, filesystem.realpath(probe_name)) + self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + finally: + os.chdir(curdir) + + def test_symlink_loop_mitigation(self): + link1_path = os.path.join(self.tempdir, 'link1') + link2_path = os.path.join(self.tempdir, 'link2') + link3_path = os.path.join(self.tempdir, 'link3') + os.symlink(link1_path, link2_path) + os.symlink(link2_path, link3_path) + os.symlink(link3_path, link1_path) + + with self.assertRaises(RuntimeError) as error: + filesystem.realpath(link1_path) + self.assertTrue('link1 is a loop!' in str(error.exception)) + + def _get_security_dacl(target): return win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION) diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 9e25548f0..754e97a10 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -7,8 +7,12 @@ from certbot.compat import os class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): + # Checks for os module for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', 'replace']: self.assertRaises(RuntimeError, getattr(os, method)) + # Checks for os.path module + for method in ['realpath']: + self.assertRaises(RuntimeError, getattr(os.path, method)) if __name__ == "__main__": diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index ebd9a28e1..ff8a800ab 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -542,7 +542,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met return True return orig_open(fn) - with mock.patch("os.path.isfile") as mock_if: + with mock.patch("certbot.compat.os.path.isfile") as mock_if: mock_if.side_effect = mock_isfile with mock.patch('certbot.main.client') as client: ret, stdout, stderr = self._call_no_clientmock(args, stdout) diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 41b5cf71d..b96f8afd2 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -528,7 +528,7 @@ class OsInfoTest(unittest.TestCase): from certbot.util import (get_os_info, get_systemd_os_info, get_os_info_ua) - with mock.patch('os.path.isfile', return_value=True): + with mock.patch('certbot.compat.os.path.isfile', return_value=True): self.assertEqual(get_os_info( test_util.vector_path("os-release"))[0], 'systemdos') self.assertEqual(get_os_info( @@ -536,13 +536,13 @@ class OsInfoTest(unittest.TestCase): self.assertEqual(get_systemd_os_info(os.devnull), ("", "")) self.assertEqual(get_os_info_ua( test_util.vector_path("os-release")), "SystemdOS") - with mock.patch('os.path.isfile', return_value=False): + with mock.patch('certbot.compat.os.path.isfile', return_value=False): self.assertEqual(get_systemd_os_info(), ("", "")) def test_systemd_os_release_like(self): from certbot.util import get_systemd_os_like - with mock.patch('os.path.isfile', return_value=True): + with mock.patch('certbot.compat.os.path.isfile', return_value=True): id_likes = get_systemd_os_like(test_util.vector_path( "os-release")) self.assertEqual(len(id_likes), 3) @@ -552,7 +552,7 @@ class OsInfoTest(unittest.TestCase): def test_non_systemd_os_info(self, popen_mock): from certbot.util import (get_os_info, get_python_os_info, get_os_info_ua) - with mock.patch('os.path.isfile', return_value=False): + with mock.patch('certbot.compat.os.path.isfile', return_value=False): with mock.patch('platform.system_alias', return_value=('NonSystemD', '42', '42')): self.assertEqual(get_os_info()[0], 'nonsystemd') -- cgit v1.2.3 From f7c736da6fa04b3db6520a11152e0a4c6a6400cf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 18 Jul 2019 15:44:01 -0700 Subject: Update pexpect to fix Python 3.7 dev venvs. (#7259) --- tools/dev_constraints.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 90b4775c1..1aa8414f9 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -42,12 +42,12 @@ mypy==0.600 ndg-httpsclient==0.3.2 oauth2client==2.0.0 pathlib2==2.3.0 -pexpect==4.2.1 +pexpect==4.7.0 pickleshare==0.7.4 pkginfo==1.4.2 pluggy==0.5.2 prompt-toolkit==1.0.15 -ptyprocess==0.5.2 +ptyprocess==0.6.0 py==1.8.0 pyasn1==0.1.9 pyasn1-modules==0.0.10 -- cgit v1.2.3 From 47f64c7280ec547561e8f92c022e57d82061c29e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 19 Jul 2019 10:44:17 -0700 Subject: Remove list of packaging efforts. (#7258) I think this list maybe had value when distros were first starting to package Certbot, but now I don't think it does. What function does this list serve? The instruction generator at https://certbot.eff.org/instructions does a much better job telling users how to use these packages. On the packaging side, I think anyone capable of packaging Certbot at the various distros would be able to search their repositories to see if a Certbot package is available. Since this list is hard to maintain as links semi-regularly break and keeping it up to date with all distros and all Certbot components is a fair bit of work, let's just remove it. This PR was motivated by the Travis failures at https://travis-ci.com/certbot/website/builds/119588518 due to GNU Guix changing the layout of their site. --- docs/packaging.rst | 79 ------------------------------------------------------ 1 file changed, 79 deletions(-) diff --git a/docs/packaging.rst b/docs/packaging.rst index bb26c7ef6..7b0b1d41a 100644 --- a/docs/packaging.rst +++ b/docs/packaging.rst @@ -47,82 +47,3 @@ Notes for package maintainers 4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. 5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. - -Already ongoing efforts -======================= - - -Arch ----- - -From our official releases: - -- https://www.archlinux.org/packages/community/any/python-acme -- https://www.archlinux.org/packages/community/any/certbot -- https://www.archlinux.org/packages/community/any/certbot-apache -- https://www.archlinux.org/packages/community/any/certbot-nginx -- https://www.archlinux.org/packages/community/any/certbot-dns-cloudflare -- https://www.archlinux.org/packages/community/any/certbot-dns-cloudxns -- https://www.archlinux.org/packages/community/any/certbot-dns-digitalocean -- https://www.archlinux.org/packages/community/any/certbot-dns-dnsimple -- https://www.archlinux.org/packages/community/any/certbot-dns-dnsmadeeasy -- https://www.archlinux.org/packages/community/any/certbot-dns-google -- https://www.archlinux.org/packages/community/any/certbot-dns-luadns -- https://www.archlinux.org/packages/community/any/certbot-dns-nsone -- https://www.archlinux.org/packages/community/any/certbot-dns-rfc2136 -- https://www.archlinux.org/packages/community/any/certbot-dns-route53 - -From ``master``: https://aur.archlinux.org/packages/certbot-git - -Debian (and its derivatives, including Ubuntu) ----------------------------------------------- - -- https://packages.debian.org/sid/certbot -- https://packages.debian.org/sid/python-certbot -- https://packages.debian.org/sid/python-certbot-apache - -Fedora ------- - -In Fedora 23+. - -- https://apps.fedoraproject.org/packages/python-acme -- https://apps.fedoraproject.org/packages/certbot -- https://apps.fedoraproject.org/packages/python-certbot-apache -- https://apps.fedoraproject.org/packages/python-certbot-dns-cloudflare -- https://apps.fedoraproject.org/packages/python-certbot-dns-cloudxns -- https://apps.fedoraproject.org/packages/python-certbot-dns-digitalocean -- https://apps.fedoraproject.org/packages/python-certbot-dns-dnsimple -- https://apps.fedoraproject.org/packages/python-certbot-dns-dnsmadeeasy -- https://apps.fedoraproject.org/packages/python-certbot-dns-google -- https://apps.fedoraproject.org/packages/python-certbot-dns-luadns -- https://apps.fedoraproject.org/packages/python-certbot-dns-nsone -- https://apps.fedoraproject.org/packages/python-certbot-dns-rfc2136 -- https://apps.fedoraproject.org/packages/python-certbot-dns-route53 -- https://apps.fedoraproject.org/packages/python-certbot-nginx - -FreeBSD -------- - -- https://www.freshports.org/security/py-acme/ -- https://www.freshports.org/security/py-certbot/ - -Gentoo ------- - -Currently, all ``certbot`` related packages are in the testing branch: - -- https://packages.gentoo.org/packages/app-crypt/certbot -- https://packages.gentoo.org/packages/app-crypt/certbot-apache -- https://packages.gentoo.org/packages/app-crypt/certbot-nginx -- https://packages.gentoo.org/packages/app-crypt/acme - -GNU Guix --------- - -- https://www.gnu.org/software/guix/package-list.html#certbot - -OpenBSD -------- - -- http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/security/letsencrypt/client/ -- cgit v1.2.3 From a35470292e272fc6b0a99a0d1c47dda51e797f1f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 22 Jul 2019 12:43:58 +0200 Subject: Remove Dockerfiles (#7257) --- Dockerfile | 35 ----------------------------------- certbot-dns-cloudflare/Dockerfile | 5 ----- certbot-dns-cloudxns/Dockerfile | 5 ----- certbot-dns-digitalocean/Dockerfile | 5 ----- certbot-dns-dnsimple/Dockerfile | 5 ----- certbot-dns-dnsmadeeasy/Dockerfile | 5 ----- certbot-dns-gehirn/Dockerfile | 5 ----- certbot-dns-google/Dockerfile | 5 ----- certbot-dns-linode/Dockerfile | 5 ----- certbot-dns-luadns/Dockerfile | 5 ----- certbot-dns-nsone/Dockerfile | 5 ----- certbot-dns-ovh/Dockerfile | 5 ----- certbot-dns-rfc2136/Dockerfile | 5 ----- certbot-dns-route53/Dockerfile | 5 ----- certbot-dns-sakuracloud/Dockerfile | 5 ----- 15 files changed, 105 deletions(-) delete mode 100644 Dockerfile delete mode 100644 certbot-dns-cloudflare/Dockerfile delete mode 100644 certbot-dns-cloudxns/Dockerfile delete mode 100644 certbot-dns-digitalocean/Dockerfile delete mode 100644 certbot-dns-dnsimple/Dockerfile delete mode 100644 certbot-dns-dnsmadeeasy/Dockerfile delete mode 100644 certbot-dns-gehirn/Dockerfile delete mode 100644 certbot-dns-google/Dockerfile delete mode 100644 certbot-dns-linode/Dockerfile delete mode 100644 certbot-dns-luadns/Dockerfile delete mode 100644 certbot-dns-nsone/Dockerfile delete mode 100644 certbot-dns-ovh/Dockerfile delete mode 100644 certbot-dns-rfc2136/Dockerfile delete mode 100644 certbot-dns-route53/Dockerfile delete mode 100644 certbot-dns-sakuracloud/Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 828f5ec94..000000000 --- a/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM python:2-alpine3.9 - -ENTRYPOINT [ "certbot" ] -EXPOSE 80 443 -VOLUME /etc/letsencrypt /var/lib/letsencrypt -WORKDIR /opt/certbot - -COPY CHANGELOG.md README.rst setup.py src/ - -# Generate constraints file to pin dependency versions -COPY letsencrypt-auto-source/pieces/dependency-requirements.txt . -COPY tools /opt/certbot/tools -RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt' -RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt' - -COPY acme src/acme -COPY certbot src/certbot - -RUN apk add --no-cache --virtual .certbot-deps \ - libffi \ - libssl1.1 \ - openssl \ - ca-certificates \ - binutils -RUN apk add --no-cache --virtual .build-deps \ - gcc \ - linux-headers \ - openssl-dev \ - musl-dev \ - libffi-dev \ - && pip install -r /opt/certbot/dependency-requirements.txt \ - && pip install --no-cache-dir --no-deps \ - --editable /opt/certbot/src/acme \ - --editable /opt/certbot/src \ - && apk del .build-deps diff --git a/certbot-dns-cloudflare/Dockerfile b/certbot-dns-cloudflare/Dockerfile deleted file mode 100644 index adbf715fa..000000000 --- a/certbot-dns-cloudflare/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-cloudflare - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudflare diff --git a/certbot-dns-cloudxns/Dockerfile b/certbot-dns-cloudxns/Dockerfile deleted file mode 100644 index 48c88c35c..000000000 --- a/certbot-dns-cloudxns/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-cloudxns - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudxns diff --git a/certbot-dns-digitalocean/Dockerfile b/certbot-dns-digitalocean/Dockerfile deleted file mode 100644 index 342e0e876..000000000 --- a/certbot-dns-digitalocean/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-digitalocean - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-digitalocean diff --git a/certbot-dns-dnsimple/Dockerfile b/certbot-dns-dnsimple/Dockerfile deleted file mode 100644 index 724675339..000000000 --- a/certbot-dns-dnsimple/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-dnsimple - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsimple diff --git a/certbot-dns-dnsmadeeasy/Dockerfile b/certbot-dns-dnsmadeeasy/Dockerfile deleted file mode 100644 index 1480baf4f..000000000 --- a/certbot-dns-dnsmadeeasy/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-dnsmadeeasy - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsmadeeasy diff --git a/certbot-dns-gehirn/Dockerfile b/certbot-dns-gehirn/Dockerfile deleted file mode 100644 index 7dce0e521..000000000 --- a/certbot-dns-gehirn/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-gehirn - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-gehirn diff --git a/certbot-dns-google/Dockerfile b/certbot-dns-google/Dockerfile deleted file mode 100644 index 5750b31d9..000000000 --- a/certbot-dns-google/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-google - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-google diff --git a/certbot-dns-linode/Dockerfile b/certbot-dns-linode/Dockerfile deleted file mode 100644 index 6db8b59fb..000000000 --- a/certbot-dns-linode/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-linode - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-linode diff --git a/certbot-dns-luadns/Dockerfile b/certbot-dns-luadns/Dockerfile deleted file mode 100644 index efc9f36d6..000000000 --- a/certbot-dns-luadns/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-luadns - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-luadns diff --git a/certbot-dns-nsone/Dockerfile b/certbot-dns-nsone/Dockerfile deleted file mode 100644 index de541e850..000000000 --- a/certbot-dns-nsone/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-nsone - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-nsone diff --git a/certbot-dns-ovh/Dockerfile b/certbot-dns-ovh/Dockerfile deleted file mode 100644 index 37e488dc4..000000000 --- a/certbot-dns-ovh/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-ovh - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-ovh diff --git a/certbot-dns-rfc2136/Dockerfile b/certbot-dns-rfc2136/Dockerfile deleted file mode 100644 index 3ebb6a72e..000000000 --- a/certbot-dns-rfc2136/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-rfc2136 - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-rfc2136 diff --git a/certbot-dns-route53/Dockerfile b/certbot-dns-route53/Dockerfile deleted file mode 100644 index e1825c11d..000000000 --- a/certbot-dns-route53/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-route53 - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-route53 diff --git a/certbot-dns-sakuracloud/Dockerfile b/certbot-dns-sakuracloud/Dockerfile deleted file mode 100644 index 9fa9b3c22..000000000 --- a/certbot-dns-sakuracloud/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-sakuracloud - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-sakuracloud -- cgit v1.2.3 From 06a0dae67f7497075e6da1b0e51d4fcab2c679c6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 23 Jul 2019 11:01:29 -0700 Subject: Fix test_symlink_resolution on macOS. (#7263) This fixes the test failures which can be seen at https://travis-ci.com/certbot/certbot/builds/120123338. The problem here is the path returned by tempfile.mkdtemp() contains a symlink. For instance, one run of the function produced '/var/folders/3b/zg8fdh5j71x92yyzc1tyllfw0000gp/T/tmp3k9ytfj1' which is a symlink to '/private/var/folders/3b/zg8fdh5j71x92yyzc1tyllfw0000gp/T/tmp3k9ytfj1'. Removing this symlink before testing filesystem.realpath solves the problem. You can see the macOS tests passing with this change at https://travis-ci.com/certbot/certbot/builds/120250667. --- certbot/tests/compat/filesystem_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 5d112b652..48ac04670 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -329,6 +329,8 @@ class RealpathTest(test_util.TempDirTestCase): self.probe_path = _create_probe(self.tempdir) def test_symlink_resolution(self): + # Remove any symlinks already in probe_path + self.probe_path = filesystem.realpath(self.probe_path) # Absolute resolution link_path = os.path.join(self.tempdir, 'link_abs') os.symlink(self.probe_path, link_path) -- cgit v1.2.3 From 391f301dd88906c3c8320096cf412b468918df6f Mon Sep 17 00:00:00 2001 From: alexzorin Date: Thu, 25 Jul 2019 11:04:59 +1000 Subject: acme: Implement authz deactivation (#7254) Resolves #4945. First PR in order to address #5116. * acme: Implement authz deactivation Resolves #4945 * update AUTHORS and CHANGELOG * typos in mypy annotations * formatting: missing newline * improve test_deactivate_authorization * improve deactivate_authorization * test: s/STATUS_INVALID/STATUS_DEACTIVATED/ * simplify dict to keyword argument * acme: add UpdateAuthorization * acme: use UpdateAuthorization in deactivate_authz and add mypy annotation This allows deactivate_authorization to succeed for both ACME v1 and v2 servers. --- AUTHORS.md | 1 + CHANGELOG.md | 2 +- acme/acme/client.py | 15 +++++++++++++++ acme/acme/client_test.py | 8 ++++++++ acme/acme/messages.py | 9 ++++++++- 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 340a0a94b..45d00eda7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ Authors * [Alex Gaynor](https://github.com/alex) * [Alex Halderman](https://github.com/jhalderm) * [Alex Jordan](https://github.com/strugee) +* [Alex Zorin](https://github.com/alexzorin) * [Amjad Mashaal](https://github.com/TheNavigat) * [Andrew Murray](https://github.com/radarhere) * [Anselm Levskaya](https://github.com/levskaya) diff --git a/CHANGELOG.md b/CHANGELOG.md index 091ec0493..6f0da8ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* acme: Authz deactivation added to `acme` module. ### Changed diff --git a/acme/acme/client.py b/acme/acme/client.py index 5a8fd88ae..9698c7609 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -123,6 +123,21 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes """ return self.update_registration(regr, update={'status': 'deactivated'}) + def deactivate_authorization(self, authzr): + # type: (messages.AuthorizationResource) -> messages.AuthorizationResource + """Deactivate authorization. + + :param messages.AuthorizationResource authzr: The Authorization resource + to be deactivated. + + :returns: The Authorization resource that was deactivated. + :rtype: `.AuthorizationResource` + + """ + body = messages.UpdateAuthorization(status='deactivated') + response = self._post(authzr.uri, body) + return self._authzr_from_response(response) + def _authzr_from_response(self, response, identifier=None, uri=None): authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index 406201751..4c762031b 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -637,6 +637,14 @@ class ClientTest(ClientTestBase): errors.PollError, self.client.poll_and_request_issuance, csr, authzrs, mintime=mintime, max_attempts=2) + def test_deactivate_authorization(self): + authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) + self.response.json.return_value = authzb.to_json() + authzr = self.client.deactivate_authorization(self.authzr) + self.assertEqual(authzb, authzr.body) + self.assertEqual(self.client.net.post.call_count, 1) + self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) + def test_check_cert(self): self.response.headers['Location'] = self.certr.uri self.response.content = CERT_DER diff --git a/acme/acme/messages.py b/acme/acme/messages.py index df96b5f2b..2bfe688d2 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -168,6 +168,7 @@ STATUS_VALID = Status('valid') STATUS_INVALID = Status('invalid') STATUS_REVOKED = Status('revoked') STATUS_READY = Status('ready') +STATUS_DEACTIVATED = Status('deactivated') class IdentifierType(_Constant): @@ -471,7 +472,7 @@ class Authorization(ResourceBody): :ivar datetime.datetime expires: """ - identifier = jose.Field('identifier', decoder=Identifier.from_json) + identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True) challenges = jose.Field('challenges', omitempty=True) combinations = jose.Field('combinations', omitempty=True) @@ -501,6 +502,12 @@ class NewAuthorization(Authorization): resource = fields.Resource(resource_type) +class UpdateAuthorization(Authorization): + """Update authorization.""" + resource_type = 'authz' + resource = fields.Resource(resource_type) + + class AuthorizationResource(ResourceWithURI): """Authorization Resource. -- cgit v1.2.3 From bf9c681c4f3ad444a74cb935b59965c2f1edfb32 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 25 Jul 2019 01:20:52 -0700 Subject: fix backwards logic (#7265) --- tests/letstest/multitester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 2870aaa09..d63b7ab5a 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -375,7 +375,7 @@ def cleanup(cl_args, instances, targetlist): # If lengths of instances and targetlist aren't equal, instances failed to # start before running tests so leaving instances running for debugging # isn't very useful. Let's cleanup after ourselves instead. - if len(instances) == len(targetlist) or not cl_args.saveinstances: + if len(instances) != len(targetlist) or not cl_args.saveinstances: print('Terminating EC2 Instances') if cl_args.killboulder: boulder_server.terminate() -- cgit v1.2.3 From 40da709792d0e5907207ce1346020c72d8ec34be Mon Sep 17 00:00:00 2001 From: alexzorin Date: Thu, 25 Jul 2019 18:23:28 +1000 Subject: docs: s/certbot_tests/certbot_test/ (#7267) --- docs/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 715788332..8aeef54cc 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -176,7 +176,7 @@ that can be used once the virtual environment is activated: .. code-block:: shell - certbot_tests [ARGS...] + certbot_test [ARGS...] - Execute certbot with the provided arguments and other arguments useful for testing purposes, such as: verbose output, full tracebacks in case Certbot crashes, *etc.* -- cgit v1.2.3 From e6bf3fe7f81ff7651b3e8be3d530be725090ed2c Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 26 Jul 2019 00:25:36 +0200 Subject: [Windows] Security model for files permissions - STEP 3f (#7233) * Correct file permissions on TempHandler * Forbid os.chown and os.geteuid, as theses functions can be harmful to the security model on Windows. * Implement copy_ownership * Apply copy_ownership * Correct webroot tests (and activate another broken test !) * Correct lint and mypy * Ensure to apply mode in makedirs * Apply strict permissions on directories created with tempfile.mkdtemp(), like on Unix. * Ensure streamHandler has 0600 on Windows * Reactivate a test on windows * Pin oldest requirements to current internal libraries (acme and certbot) * Add dynamically pywin32 in dependencies: always except for certbot-oldest to avoid to break the relevant tests. * Administrative privileges are always required. * Correct security implementation (not the logic yet) * First correction. Allow to manipulate finely file permissions during their generation * Align to master + fix lint + resolve correctly symbolic links * Add a test for windows about default paths * Strenghthen the detection of Linux/Windows to check the standard files layout. * Fix lint and mypy * Reflect non usage of cache discovery from dns google plugin to its tests, solving Windows tests on the way * Apply suggestions from code review Co-Authored-By: adferrand * Add more details in a comment * Retrigger build. * Add documentation. * Fix a test * Correct RW clear down * Update util.py * Remove unused code * Fix code style * Adapt certbot coverage threshold on Linux due to Windows specific LOC addition. * Various optimizations around file owner and file mode * Fix last error * Fix copy_ownership_and_apply_mode * Fix lint * Correct mypy * Extract out first part from windows-file-permissions * Ignore new_compat in coverage for now * Create test package for compat * Add unit tests for security module. * Add pywin32 * Adapt linux coverages to the windows-specific LOCs added * Clean imports * Correct import * Trigger CI * Reactivate a test * Create the certbot.compat package. Move logic in certbot.compat.misc * Clean comment * Add doc * Fix lint * Correct mypy * Add executable permissions * Add the delegate certbot.compat.os module, add check coding style to enforce usage of certbot.compat.os instead of standard os * Load certbot.compat.os instead of os * Move existing compat test * Update local oldest requirements * Import sys * Fix some mocks * Update account_test.py * Update os.py * Update os.py * Update local oldest requirements * Implement the new linter_plugin * Fix remaining linting errors * Fix local oldest for nginx * Remove custom check in favor of pylint plugin * Remove check coding style * Update linter_plugin.py Co-Authored-By: adferrand * Add several comments * Update the setup.py * Add documentation * Update acme dependencies * Update certbot/compat/os.py Co-Authored-By: adferrand * Update certbot/compat/os.py Co-Authored-By: adferrand * Update certbot/compat/os.py Co-Authored-By: adferrand * Update docs/contributing.rst Co-Authored-By: adferrand * Update linter_plugin.py Co-Authored-By: adferrand * Update linter_plugin.py Co-Authored-By: adferrand * Update docs/contributing.rst Co-Authored-By: adferrand * Update docs/contributing.rst Co-Authored-By: adferrand * Corrections * Handle os.path. Simplify checker. * Add a comment to a reference implementation * Update changelog * Fix module registering * Update docs/contributing.rst Co-Authored-By: adferrand * Update docs/contributing.rst Co-Authored-By: adferrand * Update docs/contributing.rst Co-Authored-By: adferrand * Update config and changelog * Correction * Correct os * Fix merge * Disable pylint checks * Normalize imports * Simplify security * Corrections * Reorganize module * Clean code * Clean code * Remove coverage * No cover * Implement security.chmod * Disable a test for now * Disable hard error for now * Add a first test. Remove unused import * Recalibrate coverage * Modifications for misc * Correct function call * Add some types * Remove newline * Use os_rename * Implement security.open * Revert to windows-files-permissions approach * Fix lint * Implement security.mkdir and security.makedirs * Fix lint * Clean lint * Clean lint * Revert "Clean lint" This reverts commit 83bf81960ac6bf3f76c286ca065a5ac850c6870b. * Correct mock * Conditionally add pywin32 on setuptools versions that support environment markers. * Fix separator * Fix separator * Rename security into filesystem * Change module security to filesystem * Move rename into filesystem * Rename security into filesystem * Rename security into filesystem * Rerun CI * Fix import * Fix pylint * Implement copy_ownership_and_apply_mode * Fix pylint * Update certbot/compat/os.py Co-Authored-By: Brad Warren * Remove default values * Rewrite a comment. * Relaunch CI * Pass as keyword arguments * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Make the private key permissions transfer platform specific * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Rename variable * Fix comment0 * Add unit test for copy_ownership_and_apply_mode * Adapt coverage * Implement new methods. * Remove the old method * Reimplement make_or_verify_dir * Finish migration * Start to fix tests * Fix ownership when creating a file with filesystem.open * Fix security on TempHandler * Fix validation path permissions * Fix owner on mkdir * Use a proper workdir for crypto tests * Fix pylint * Adapt coverage * Update storage_test.py * Update util_test.py * Clean code * Update certbot/compat/filesystem.py Co-Authored-By: ohemorange * Add comment * Update certbot/compat/filesystem.py Co-Authored-By: ohemorange * Check permissions * Change test mode * Add unit test for filesystem.check_* functions * Update filesystem_test.py * Better logic for TempHandler * Adapt coverage --- .codecov.yml | 4 +- certbot-nginx/certbot_nginx/configurator.py | 11 ++--- certbot/account.py | 7 +-- certbot/cert_manager.py | 5 +- certbot/client.py | 5 +- certbot/compat/filesystem.py | 75 +++++++++++++++++++++++++++++ certbot/compat/misc.py | 26 ---------- certbot/crypto_util.py | 7 +-- certbot/log.py | 12 ++--- certbot/main.py | 10 ++-- certbot/plugins/webroot.py | 3 +- certbot/plugins/webroot_test.py | 5 +- certbot/reverter.py | 10 ++-- certbot/tests/cert_manager_test.py | 30 +++++------- certbot/tests/compat/filesystem_test.py | 49 ++++++++++++++++++- certbot/tests/crypto_util_test.py | 10 ++-- certbot/tests/log_test.py | 5 +- certbot/tests/main_test.py | 10 ++-- certbot/tests/storage_test.py | 19 +++----- certbot/tests/util_test.py | 52 +++----------------- certbot/util.py | 30 +++--------- 21 files changed, 194 insertions(+), 191 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 86813de6a..f4d4d1d6c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,13 +6,13 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.6 + target: 97.5 threshold: 0.1 base: auto windows: flags: windows # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.0 + target: 97.2 threshold: 0.1 base: auto diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index e078ad4cb..0c6eabf57 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -20,7 +20,6 @@ from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.plugins import common @@ -903,13 +902,9 @@ class NginxConfigurator(common.Installer): have permissions of root. """ - uid = misc.os_geteuid() - util.make_or_verify_dir( - self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) - util.make_or_verify_dir( - self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) - util.make_or_verify_dir( - self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid) + util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE) + util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE) + util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE) def get_version(self): """Return version of Nginx Server. diff --git a/certbot/account.py b/certbot/account.py index bf5c131db..7a1e2de7a 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -20,7 +20,6 @@ from certbot import constants from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc from certbot.compat import os logger = logging.getLogger(__name__) @@ -139,8 +138,7 @@ class AccountFileStorage(interfaces.AccountStorage): """ def __init__(self, config): self.config = config - util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) def _account_dir_path(self, account_id): return self._account_dir_path_for_server_path(account_id, self.config.server_path) @@ -322,8 +320,7 @@ class AccountFileStorage(interfaces.AccountStorage): def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) - util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index ab929b597..6d6d2e2e6 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -15,7 +15,6 @@ from certbot import interfaces from certbot import ocsp from certbot import storage from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.display import util as display_util @@ -106,7 +105,7 @@ def lineage_for_certname(cli_config, certname): """Find a lineage object with name certname.""" configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755) try: renewal_file = storage.renewal_file_for_certname(cli_config, certname) except errors.CertStorageError: @@ -375,7 +374,7 @@ def _search_lineages(cli_config, func, initial_rv, *args): """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): diff --git a/certbot/client.py b/certbot/client.py index 4233a6343..7372d6d9d 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -30,7 +30,6 @@ from certbot import interfaces from certbot import reverter from certbot import storage from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.display import enhancements from certbot.display import ops as display_ops @@ -459,9 +458,7 @@ class Client(object): """ for path in cert_path, chain_path, fullchain_path: - util.make_or_verify_dir( - os.path.dirname(path), 0o755, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index e2757eb2f..a38fbe760 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -77,6 +77,54 @@ def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): chmod(dst, mode) +def check_mode(file_path, mode): + # type: (str, int) -> bool + """ + Check if the given mode matches the permissions of the given file. + On Linux, will make a direct comparison, on Windows, mode will be compared against + the security model. + :param str file_path: Path of the file + :param int mode: POSIX mode to test + :rtype: bool + :return: True if the POSIX mode matches the file permissions + """ + if POSIX_MODE: + return stat.S_IMODE(os.stat(file_path).st_mode) == mode + + return _check_win_mode(file_path, mode) + + +def check_owner(file_path): + # type: (str) -> bool + """ + Check if given file is owned by current user. + :param str file_path: File path to check + :rtype: bool + :return: True if given file is owned by current user, False otherwise. + """ + if POSIX_MODE: + return os.stat(file_path).st_uid == os.getuid() + + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # Compare sids + return _get_current_user() == user + + +def check_permissions(file_path, mode): + # type: (str, int) -> bool + """ + Check if given file has the given mode and is owned by current user. + :param str file_path: File path to check + :param int mode: POSIX mode to check + :rtype: bool + :return: True if file has correct mode and owner, False otherwise. + """ + return check_owner(file_path) and check_mode(file_path, mode) + + def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin # type: (str, int, int) -> int """ @@ -107,6 +155,10 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin security = attributes.SECURITY_DESCRIPTOR user = _get_current_user() dacl = _generate_dacl(user, mode) + # We set second parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long + security.SetSecurityDescriptorOwner(user, 0) # We set first parameter to 1 (`True`) to say that this security descriptor contains # a DACL. Otherwise second and third parameters are ignored. # We set third parameter to 0 (`False`) to say that this security descriptor is @@ -177,6 +229,7 @@ def mkdir(file_path, mode=0o777): security = attributes.SECURITY_DESCRIPTOR user = _get_current_user() dacl = _generate_dacl(user, mode) + security.SetSecurityDescriptorOwner(user, False) security.SetSecurityDescriptorDacl(1, dacl, 0) try: @@ -353,6 +406,28 @@ def _generate_windows_flags(rights_desc): return flag +def _check_win_mode(file_path, mode): + # Resolve symbolic links + file_path = realpath(file_path) + # Get current dacl file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION + | win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + # Get current file owner sid + user = security.GetSecurityDescriptorOwner() + + if not dacl: + # No DACL means full control to everyone + # This is not a deterministic permissions set. + return False + + # Calculate the target dacl + ref_dacl = _generate_dacl(user, mode) + + return _compare_dacls(dacl, ref_dacl) + + def _compare_dacls(dacl1, dacl2): """ This method compare the two given DACLs to check if they are identical. diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index c840c333c..693fceefb 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -42,22 +42,6 @@ def raise_for_non_administrative_windows_rights(): raise errors.Error('Error, certbot must be run on a shell with administrative rights.') -def os_geteuid(): - """ - Get current user uid - - :returns: The current user uid. - :rtype: int - - """ - try: - # Linux specific - return os.geteuid() - except AttributeError: - # Windows specific - return 0 - - def readline_with_timeout(timeout, prompt): # type: (float, str) -> str """ @@ -88,16 +72,6 @@ def readline_with_timeout(timeout, prompt): return sys.stdin.readline() -def compare_file_modes(mode1, mode2): - """Return true if the two modes can be considered as equals for this platform""" - if os.name != 'nt': - # Linux specific: standard compare - return oct(stat.S_IMODE(mode1)) == oct(stat.S_IMODE(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', diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 281a76668..12291af38 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -28,7 +28,6 @@ from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-mo from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc from certbot.compat import os logger = logging.getLogger(__name__) @@ -61,8 +60,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): config = zope.component.getUtility(interfaces.IConfig) # Save file - util.make_or_verify_dir(key_dir, 0o700, misc.os_geteuid(), - config.strict_permissions) + util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions) key_f, key_path = util.unique_file( os.path.join(key_dir, keyname), 0o600, "wb") with key_f: @@ -92,8 +90,7 @@ def init_save_csr(privkey, names, path): privkey.pem, names, must_staple=config.must_staple) # Save CSR - util.make_or_verify_dir(path, 0o755, misc.os_geteuid(), - config.strict_permissions) + util.make_or_verify_dir(path, 0o755, config.strict_permissions) csr_f, csr_filename = util.unique_file( os.path.join(path, "csr-certbot.pem"), 0o644, "wb") with csr_f: diff --git a/certbot/log.py b/certbot/log.py index bf444de07..a16e2ef7e 100644 --- a/certbot/log.py +++ b/certbot/log.py @@ -17,6 +17,7 @@ from __future__ import print_function import functools import logging import logging.handlers +import shutil import sys import tempfile import traceback @@ -26,7 +27,6 @@ from acme import messages from certbot import constants from certbot import errors from certbot import util -from certbot.compat import misc from certbot.compat import os # Logging format @@ -134,8 +134,7 @@ def setup_log_file_handler(config, logfile, fmt): """ # TODO: logs might contain sensitive data such as contents of the # private key! #525 - util.set_up_core_dir( - config.logs_dir, 0o700, misc.os_geteuid(), config.strict_permissions) + util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) log_file_path = os.path.join(config.logs_dir, logfile) try: handler = logging.handlers.RotatingFileHandler( @@ -240,9 +239,10 @@ class TempHandler(logging.StreamHandler): """ def __init__(self): - stream = tempfile.NamedTemporaryFile('w', delete=False) + self._workdir = tempfile.mkdtemp() + self.path = os.path.join(self._workdir, 'log') + stream = util.safe_open(self.path, mode='w', chmod=0o600) super(TempHandler, self).__init__(stream) - self.path = stream.name self._delete = True def emit(self, record): @@ -266,7 +266,7 @@ class TempHandler(logging.StreamHandler): # stream like stderr to be used self.stream.close() if self._delete: - os.remove(self.path) + shutil.rmtree(self._workdir) self._delete = False super(TempHandler, self).close() finally: diff --git a/certbot/main.py b/certbot/main.py index 50470438f..fc91aca5f 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1299,18 +1299,14 @@ def make_or_verify_needed_dirs(config): :rtype: None """ - util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), config.strict_permissions) - util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), config.strict_permissions) + util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) hook_dirs = (config.renewal_pre_hooks_dir, config.renewal_deploy_hooks_dir, config.renewal_post_hooks_dir,) for hook_dir in hook_dirs: - util.make_or_verify_dir(hook_dir, - uid=misc.os_geteuid(), - strict=config.strict_permissions) + util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) def set_displayer(config): diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py index f767bdf54..33e6530a1 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/plugins/webroot.py @@ -24,6 +24,7 @@ from certbot.display import ops from certbot.display import util as display_util from certbot.plugins import common from certbot.plugins import util +from certbot.util import safe_open logger = logging.getLogger(__name__) @@ -207,7 +208,7 @@ to serve all files under specified web root ({0}).""" old_umask = os.umask(0o022) try: - with open(validation_path, "wb") as validation_file: + with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: validation_file.write(validation.encode()) finally: os.umask(old_umask) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index ba9950fae..a0b701cac 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -17,7 +17,6 @@ from acme import challenges from certbot import achallenges from certbot import errors -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem from certbot.display import util as display_util @@ -168,14 +167,14 @@ class AuthenticatorTest(unittest.TestCase): # Remove exec bit from permission check, so that it # matches the file self.auth.perform([self.achall]) - self.assertTrue(misc.compare_file_modes(os.stat(self.validation_path).st_mode, 0o644)) + self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) # Check permissions of the directories for dirpath, dirnames, _ in os.walk(self.path): for directory in dirnames: full_path = os.path.join(dirpath, directory) - self.assertTrue(misc.compare_file_modes(os.stat(full_path).st_mode, 0o755)) + self.assertTrue(filesystem.check_mode(full_path, 0o755)) parent_gid = os.stat(self.path).st_gid parent_uid = os.stat(self.path).st_uid diff --git a/certbot/reverter.py b/certbot/reverter.py index 21d33806a..f4f1c8c67 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -15,7 +15,6 @@ from certbot import constants from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem @@ -68,8 +67,7 @@ class Reverter(object): self.config = config util.make_or_verify_dir( - config.backup_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) def revert_temporary_config(self): """Reload users original configuration files after a temporary save. @@ -225,8 +223,7 @@ class Reverter(object): """ util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) op_fd, existing_filepaths = self._read_and_append( os.path.join(cp_dir, "FILEPATHS")) @@ -445,8 +442,7 @@ class Reverter(object): cp_dir = self.config.in_progress_dir util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) return cp_dir diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index b4cd946ee..83946a0f7 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -277,13 +277,12 @@ class SearchLineagesTest(BaseCertManagerTest): @mock.patch('certbot.storage.renewal_conf_files') @mock.patch('certbot.storage.RenewableCert') def test_cert_storage_error(self, mock_renewable_cert, mock_renewal_conf_files, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_files.return_value = ["badfile"] mock_renewable_cert.side_effect = errors.CertStorageError from certbot import cert_manager # pylint: disable=protected-access - self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, "check"), - "check") + self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, "check"), "check") self.assertTrue(mock_make_or_verify_dir.called) @@ -294,33 +293,28 @@ class LineageForCertnameTest(BaseCertManagerTest): @mock.patch('certbot.storage.renewal_file_for_certname') @mock.patch('certbot.storage.RenewableCert') def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" mock_match = mock.Mock(lineagename="example.com") mock_renewable_cert.return_value = mock_match from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - mock_match) + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), mock_match) self.assertTrue(mock_make_or_verify_dir.called) @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_match(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "other.com.conf" from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - None) + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), None) self.assertTrue(mock_make_or_verify_dir.called) @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_renewal_file(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + def test_no_renewal_file(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.side_effect = errors.CertStorageError() from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - None) + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), None) self.assertTrue(mock_make_or_verify_dir.called) @@ -331,7 +325,7 @@ class DomainsForCertnameTest(BaseCertManagerTest): @mock.patch('certbot.storage.renewal_file_for_certname') @mock.patch('certbot.storage.RenewableCert') def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" mock_match = mock.Mock(lineagename="example.com") domains = ["example.com", "example.org"] @@ -344,12 +338,10 @@ class DomainsForCertnameTest(BaseCertManagerTest): @mock.patch('certbot.util.make_or_verify_dir') @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_match(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" from certbot import cert_manager - self.assertEqual(cert_manager.domains_for_certname(self.config, "other.com"), - None) + self.assertEqual(cert_manager.domains_for_certname(self.config, "other.com"), None) self.assertTrue(mock_make_or_verify_dir.called) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 48ac04670..90ecd13d7 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -16,6 +16,7 @@ except ImportError: import certbot.tests.util as test_util from certbot import lock +from certbot import util from certbot.compat import os from certbot.compat import filesystem from certbot.tests.util import TempDirTestCase @@ -306,6 +307,52 @@ class CopyOwnershipTest(test_util.TempDirTestCase): mock_chmod.assert_called_once_with(self.probe_path, 0o700) +class CheckPermissionsTest(test_util.TempDirTestCase): + def setUp(self): + super(CheckPermissionsTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_check_mode(self): + self.assertTrue(filesystem.check_mode(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.check_mode(self.probe_path, 0o744)) + + @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') + def test_check_owner_windows(self): + self.assertTrue(filesystem.check_owner(self.probe_path)) + + system = win32security.ConvertStringSidToSid(SYSTEM_SID) + security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR + security.SetSecurityDescriptorOwner(system, False) + + with mock.patch('win32security.GetFileSecurity') as mock_get: + mock_get.return_value = security + self.assertFalse(filesystem.check_owner(self.probe_path)) + + @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') + def test_check_owner_linux(self): + self.assertTrue(filesystem.check_owner(self.probe_path)) + + import os as std_os # pylint: disable=os-module-forbidden + uid = std_os.getuid() + + with mock.patch('os.getuid') as mock_uid: + mock_uid.return_value = uid + 1 + self.assertFalse(filesystem.check_owner(self.probe_path)) + + def test_check_permissions(self): + self.assertTrue(filesystem.check_permissions(self.probe_path, 0o744)) + + with mock.patch('certbot.compat.filesystem.check_mode') as mock_mode: + mock_mode.return_value = False + self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + + with mock.patch('certbot.compat.filesystem.check_owner') as mock_owner: + mock_owner.return_value = False + self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" def test_os_replace_to_existing_file(self): @@ -381,7 +428,7 @@ def _set_owner(target, security_owner, user): def _create_probe(tempdir): filesystem.chmod(tempdir, 0o744) probe_path = os.path.join(tempdir, 'probe') - open(probe_path, 'w').close() + util.safe_open(probe_path, 'w', chmod=0o744).close() return probe_path diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 5c1a07f8d..2673f4219 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -11,6 +11,7 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import os +from certbot.compat import filesystem RSA256_KEY = test_util.load_vector('rsa256_key.pem') RSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem') @@ -29,6 +30,9 @@ class InitSaveKeyTest(test_util.TempDirTestCase): def setUp(self): super(InitSaveKeyTest, self).setUp() + self.workdir = os.path.join(self.tempdir, 'workdir') + filesystem.mkdir(self.workdir, mode=0o700) + logging.disable(logging.CRITICAL) zope.component.provideUtility( mock.Mock(strict_permissions=True), interfaces.IConfig) @@ -46,15 +50,15 @@ class InitSaveKeyTest(test_util.TempDirTestCase): @mock.patch('certbot.crypto_util.make_key') def test_success(self, mock_make): mock_make.return_value = b'key_pem' - key = self._call(1024, self.tempdir) + key = self._call(1024, self.workdir) self.assertEqual(key.pem, b'key_pem') self.assertTrue('key-certbot.pem' in key.file) - self.assertTrue(os.path.exists(os.path.join(self.tempdir, key.file))) + self.assertTrue(os.path.exists(os.path.join(self.workdir, key.file))) @mock.patch('certbot.crypto_util.make_key') def test_key_failure(self, mock_make): mock_make.side_effect = ValueError - self.assertRaises(ValueError, self._call, 431, self.tempdir) + self.assertRaises(ValueError, self._call, 431, self.workdir) class InitSaveCSRTest(test_util.TempDirTestCase): diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index d203635a2..807c7ecaa 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -14,7 +14,7 @@ from acme.magic_typing import Optional # pylint: disable=unused-import, no-name from certbot import constants from certbot import errors from certbot import util -from certbot.compat import misc +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import util as test_util @@ -260,8 +260,7 @@ class TempHandlerTest(unittest.TestCase): self.handler.close() def test_permissions(self): - self.assertTrue( - util.check_permissions(self.handler.path, 0o600, misc.os_geteuid())) + self.assertTrue(filesystem.check_permissions(self.handler.path, 0o600)) def test_delete(self): self.handler.close() diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index ff8a800ab..b7477e355 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -31,7 +31,6 @@ from certbot import interfaces # pylint: disable=unused-import from certbot import main from certbot import updater from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem from certbot.plugins import disco @@ -809,9 +808,9 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met ifaces = [] # type: List[interfaces.IPlugin] plugins = mock_disco.PluginsRegistry.find_all() - def throw_error(directory, mode, uid, strict): + def throw_error(directory, mode, strict): """Raises error.Error.""" - _, _, _, _ = directory, mode, uid, strict + _, _, _ = directory, mode, strict raise errors.Error() stdout = six.StringIO() @@ -1593,7 +1592,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): for core_dir in (self.config.config_dir, self.config.work_dir,): mock_util.set_up_core_dir.assert_any_call( core_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), self.config.strict_permissions + self.config.strict_permissions ) hook_dirs = (self.config.renewal_pre_hooks_dir, @@ -1602,8 +1601,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): for hook_dir in hook_dirs: # default mode of 755 is used mock_util.make_or_verify_dir.assert_any_call( - hook_dir, uid=misc.os_geteuid(), - strict=self.config.strict_permissions) + hook_dir, strict=self.config.strict_permissions) class EnhanceTest(test_util.ConfigTestCase): diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index fceef804a..2bc45873c 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -13,7 +13,6 @@ import six import certbot import certbot.tests.util as test_util from certbot import errors -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem from certbot.storage import ALL_FOUR @@ -21,7 +20,6 @@ from certbot.storage import ALL_FOUR CERT = test_util.load_cert('cert_512.pem') - def unlink_all(rc_object): """Unlink all four items associated with this RenewableCert.""" for kind in ALL_FOUR: @@ -498,7 +496,6 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(self.test_rc.should_autorenew()) mock_ocsp.return_value = False - @test_util.broken_on_windows @mock.patch("certbot.storage.relevant_values") def test_save_successor(self, mock_rv): # Mock relevant_values() to claim that all values are relevant here @@ -562,7 +559,7 @@ 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 + @test_util.skip_on_windows('Group/everybody permissions are not maintained 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 @@ -571,22 +568,18 @@ class RenewableCertTests(BaseRenewableCertTest): for kind in ALL_FOUR: self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) - self.assertTrue(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 1), 0o600)) filesystem.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(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 2)).st_mode, 0o444)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 2), 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(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 3)).st_mode, 0o644)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 3), 0o644)) # If permissions reverted, next renewal will also revert permissions of new key filesystem.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(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 4), 0o600)) @mock.patch("certbot.storage.relevant_values") @mock.patch("certbot.storage.filesystem.copy_ownership_and_apply_mode") @@ -622,7 +615,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(misc.compare_file_modes(os.stat(result.key_path).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(result.key_path, 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 diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index b96f8afd2..e65de3349 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -9,7 +9,6 @@ from six.moves import reload_module # pylint: disable=import-error import certbot.tests.util as test_util from certbot import errors -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem @@ -120,15 +119,14 @@ class SetUpCoreDirTest(test_util.TempDirTestCase): @mock.patch('certbot.util.lock_dir_until_exit') def test_success(self, mock_lock): new_dir = os.path.join(self.tempdir, 'new') - self._call(new_dir, 0o700, misc.os_geteuid(), False) + self._call(new_dir, 0o700, False) self.assertTrue(os.path.exists(new_dir)) self.assertEqual(mock_lock.call_count, 1) @mock.patch('certbot.util.make_or_verify_dir') def test_failure(self, mock_make_or_verify): mock_make_or_verify.side_effect = OSError - self.assertRaises(errors.Error, self._call, - self.tempdir, 0o700, misc.os_geteuid(), False) + self.assertRaises(errors.Error, self._call, self.tempdir, 0o700, False) class MakeOrVerifyDirTest(test_util.TempDirTestCase): @@ -145,23 +143,20 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): self.path = os.path.join(self.tempdir, "foo") filesystem.mkdir(self.path, 0o600) - self.uid = misc.os_geteuid() - def _call(self, directory, mode): from certbot.util import make_or_verify_dir - return make_or_verify_dir(directory, mode, self.uid, strict=True) + return make_or_verify_dir(directory, mode, strict=True) def test_creates_dir_when_missing(self): path = os.path.join(self.tempdir, "bar") self._call(path, 0o650) self.assertTrue(os.path.isdir(path)) - self.assertTrue(misc.compare_file_modes(os.stat(path).st_mode, 0o650)) + self.assertTrue(filesystem.check_mode(path, 0o650)) def test_existing_correct_mode_does_not_fail(self): self._call(self.path, 0o600) - self.assertTrue(misc.compare_file_modes(os.stat(self.path).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.path, 0o600)) - @test_util.skip_on_windows('Umask modes are mostly ignored on Windows.') def test_existing_wrong_mode_fails(self): self.assertRaises(errors.Error, self._call, self.path, 0o400) @@ -171,39 +166,6 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): self.assertRaises(OSError, self._call, "bar", 12312312) -class CheckPermissionsTest(test_util.TempDirTestCase): - """Tests for certbot.util.check_permissions. - - Note that it is not possible to test for a wrong file owner, - as this testing script would have to be run as root. - - """ - - def setUp(self): - super(CheckPermissionsTest, self).setUp() - - self.uid = misc.os_geteuid() - - def _call(self, mode): - from certbot.util import check_permissions - return check_permissions(self.tempdir, mode, self.uid) - - def test_ok_mode(self): - filesystem.chmod(self.tempdir, 0o600) - self.assertTrue(self._call(0o600)) - - # TODO: reactivate the test when all logic from windows file permissions is merged. - @test_util.broken_on_windows - def test_wrong_mode(self): - filesystem.chmod(self.tempdir, 0o400) - try: - self.assertFalse(self._call(0o600)) - finally: - # Without proper write permissions, Windows is unable to delete a folder, - # even with admin permissions. Write access must be explicitly set first. - filesystem.chmod(self.tempdir, 0o700) - - class UniqueFileTest(test_util.TempDirTestCase): """Tests for certbot.util.unique_file.""" @@ -226,8 +188,8 @@ class UniqueFileTest(test_util.TempDirTestCase): def test_right_mode(self): fd1, name1 = self._call(0o700) fd2, name2 = self._call(0o600) - self.assertTrue(misc.compare_file_modes(0o700, os.stat(name1).st_mode)) - self.assertTrue(misc.compare_file_modes(0o600, os.stat(name2).st_mode)) + self.assertTrue(filesystem.check_mode(name1, 0o700)) + self.assertTrue(filesystem.check_mode(name2, 0o600)) fd1.close() fd2.close() diff --git a/certbot/util.py b/certbot/util.py index dfdc87346..8f553e51a 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -21,7 +21,6 @@ from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no- from certbot import constants from certbot import errors from certbot import lock -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem @@ -144,12 +143,11 @@ def _release_locks(): _LOCKS.clear() -def set_up_core_dir(directory, mode, uid, strict): +def set_up_core_dir(directory, mode, strict): """Ensure directory exists with proper permissions and is locked. :param str directory: Path to a directory. :param int mode: Directory mode. - :param int uid: Directory owner. :param bool strict: require directory to be owned by current user :raises .errors.LockError: if the directory cannot be locked @@ -157,19 +155,18 @@ def set_up_core_dir(directory, mode, uid, strict): """ try: - make_or_verify_dir(directory, mode, uid, strict) + make_or_verify_dir(directory, mode, strict) lock_dir_until_exit(directory) except OSError as error: logger.debug("Exception was:", exc_info=True) raise errors.Error(PERM_ERR_FMT.format(error)) -def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False): +def make_or_verify_dir(directory, mode=0o755, strict=False): """Make sure directory exists with proper permissions. :param str directory: Path to a directory. :param int mode: Directory mode. - :param int uid: Directory owner. :param bool strict: require directory to be owned by current user :raises .errors.Error: if a directory already exists, @@ -184,29 +181,14 @@ def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False): filesystem.makedirs(directory, mode) except OSError as exception: if exception.errno == errno.EEXIST: - if strict and not check_permissions(directory, mode, uid): + if strict and not filesystem.check_permissions(directory, mode): raise errors.Error( - "%s exists, but it should be owned by user %d with" - "permissions %s" % (directory, uid, oct(mode))) + "%s exists, but it should be owned by current user with" + " permissions %s" % (directory, oct(mode))) else: raise -def check_permissions(filepath, mode, uid=0): - """Check file or directory permissions. - - :param str filepath: Path to the tested file (or directory). - :param int mode: Expected file mode. - :param int uid: Expected file owner. - - :returns: True if `mode` and `uid` match, False otherwise. - :rtype: bool - - """ - file_stat = os.stat(filepath) - return misc.compare_file_modes(file_stat.st_mode, mode) and file_stat.st_uid == uid - - def safe_open(path, mode="w", chmod=None): """Safely open a file. -- cgit v1.2.3 From d3da19919fe8c8a86bd1b1580f8142e35b37fe3d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 Jul 2019 13:37:16 -0700 Subject: Remove duplicate, failing oldest tests. (#7272) Nightly tests failed last night at https://travis-ci.com/certbot/certbot/builds/120816454. The cause was the oldest the version of Ubuntu used in the tests suddenly changed from Trusty to Xenial. You can see Xenial being used in the failing test at https://travis-ci.com/certbot/certbot/jobs/219873088#L9 and Trusty being used at the last passing test at https://travis-ci.com/certbot/certbot/jobs/218936290#L9. The change in the default doesn't seem to be documented (yet) at https://docs.travis-ci.com/user/reference/overview/. I started to pin Trusty in these tests, however, I noticed that we are running these same unit tests at https://github.com/certbot/certbot/blob/e6bf3fe7f81ff7651b3e8be3d530be725090ed2c/.travis.yml#L58. These other tests are still succeeding because it appears that including `sudo: required` causes Travis to still default to Trusty. Deleting these duplicated tests fixes our Travis failures and speeds things up ever so slightly. * Remove duplicate, failing oldest tests. * pin trusty --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index baf385604..94eaf693e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,10 @@ matrix: env: TOXENV=mypy <<: *not-on-master - python: "2.7" + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' sudo: required services: docker @@ -132,12 +136,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "2.7" - env: TOXENV=py27-certbot-oldest - <<: *extended-test-suite - - python: "2.7" - env: TOXENV=py27-nginx-oldest - <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest sudo: required -- cgit v1.2.3 From 81e0b92b4383d0f659f608e5e9d2ef9a9db5fb76 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 29 Jul 2019 19:27:09 +0200 Subject: Refer to ubuntu in install.rst (#6986) Fixes #5758 --- docs/install.rst | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/install.rst b/docs/install.rst index 3ae7fa397..93a122e80 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -42,7 +42,7 @@ client as root, either `letsencrypt-nosudo The Apache plugin currently requires an OS with augeas version 1.0; currently `it supports `_ -modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. +modern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin. Additional integrity verification of certbot-auto script can be done by verifying its digital signature. @@ -218,6 +218,31 @@ repo, if you have not already done so. Then run: sudo apt-get install certbot python-certbot-apache -t jessie-backports +**Ubuntu** + +If you run Ubuntu Trusty, Xenial, or Bionic, certbot is available through the official PPA, +that can be installed as followed: + +.. code-block:: shell + + sudo apt-get update + sudo apt-get install software-properties-common + sudo add-apt-repository universe + sudo add-apt-repository ppa:certbot/certbot + sudo apt-get update + +Then, certbot can be installed using: + +.. code-block:: shell + + sudo apt-get install certbot + +Optionally to install the Certbot Apache plugin, you can use: + +.. code-block:: shell + + sudo apt-get install python-certbot-apache + **Fedora** .. code-block:: shell -- cgit v1.2.3 From 9174c631d9965834f263ea7ff842d8d2087f47c7 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 29 Jul 2019 21:54:51 +0200 Subject: Disable TLS session tickets for Apache 2.4.11+ (#7191) * Implement the logic * Update tests * Fix lint and changelog * Update configurator.py * Move the TLS configs in a dedicated folder. Fix the formalism of their naming and location. * Improve existing test to check all TLS config have their hash registered in Certbot * Corrections after review * Improve a test * Remove commented useless lines in TLS configs * Add a nice warning. Because I am nice. * Fix lint * Add a test --- CHANGELOG.md | 1 + certbot-apache/MANIFEST.in | 1 + certbot-apache/certbot_apache/apache_util.py | 14 +++++++++++ .../certbot_apache/centos-options-ssl-apache.conf | 25 -------------------- certbot-apache/certbot_apache/configurator.py | 20 ++++++++++++---- certbot-apache/certbot_apache/constants.py | 5 ++++ .../certbot_apache/options-ssl-apache.conf | 26 --------------------- certbot-apache/certbot_apache/override_arch.py | 4 ---- certbot-apache/certbot_apache/override_centos.py | 15 +++++++++--- certbot-apache/certbot_apache/override_darwin.py | 4 ---- certbot-apache/certbot_apache/override_debian.py | 3 --- certbot-apache/certbot_apache/override_fedora.py | 4 ---- certbot-apache/certbot_apache/override_gentoo.py | 4 ---- certbot-apache/certbot_apache/override_suse.py | 4 ---- certbot-apache/certbot_apache/tests/centos_test.py | 7 ++++++ .../certbot_apache/tests/configurator_test.py | 27 ++++++++++++++++------ .../centos-current-options-ssl-apache.conf | 19 +++++++++++++++ .../tls_configs/centos-old-options-ssl-apache.conf | 18 +++++++++++++++ .../tls_configs/current-options-ssl-apache.conf | 20 ++++++++++++++++ .../tls_configs/old-options-ssl-apache.conf | 19 +++++++++++++++ 20 files changed, 151 insertions(+), 89 deletions(-) delete mode 100644 certbot-apache/certbot_apache/centos-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f0da8ce1..0960eb4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added +* Turn off session tickets for apache plugin by default * acme: Authz deactivation added to `acme` module. ### Changed diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index 3e594a953..a34e215e7 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -5,3 +5,4 @@ recursive-include certbot_apache/tests/testdata * include certbot_apache/centos-options-ssl-apache.conf include certbot_apache/options-ssl-apache.conf recursive-include certbot_apache/augeas_lens *.aug +recursive-include certbot_apache/tls_configs *.conf diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index 7a2ecf49b..f338c0407 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,6 +1,8 @@ """ Utility functions for certbot-apache plugin """ import binascii +import pkg_resources + from certbot import util from certbot.compat import os @@ -105,3 +107,15 @@ def parse_define_file(filepath, varname): def unique_id(): """ Returns an unique id to be used as a VirtualHost identifier""" return binascii.hexlify(os.urandom(16)).decode("utf-8") + + +def find_ssl_apache_conf(prefix): + """ + Find a TLS Apache config file in the dedicated storage. + :param str prefix: prefix of the TLS Apache config file to find + :return: the path the TLS Apache config file + :rtype: str + """ + return pkg_resources.resource_filename( + "certbot_apache", + os.path.join("tls_configs", "{0}-options-ssl-apache.conf".format(prefix))) diff --git a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf deleted file mode 100644 index 56c946a4e..000000000 --- a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf +++ /dev/null @@ -1,25 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index f7c27bf76..ecc7c83ab 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -9,7 +9,6 @@ import time from collections import defaultdict -import pkg_resources import six import zope.component @@ -110,14 +109,24 @@ class ApacheConfigurator(common.Installer): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) def option(self, key): """Get a value from options""" return self.options.get(key) + def pick_apache_config(self): + """ + Pick the appropriate TLS Apache configuration file for current version of Apache and OS. + :return: the path to the TLS Apache configuration file to use + :rtype: str + """ + # Disabling TLS session tickets is supported by Apache 2.4.11+. + # So for old versions of Apache we pick a configuration without this option. + if self.version < (2, 4, 11): + return apache_util.find_ssl_apache_conf("old") + return apache_util.find_ssl_apache_conf("current") + def _prepare_options(self): """ Set the values possibly changed by command line parameters to @@ -2339,8 +2348,9 @@ class ApacheConfigurator(common.Installer): # XXX if we ever try to enforce a local privilege boundary (eg, running # certbot for unprivileged users via setuid), this function will need # to be modified. - return common.install_version_controlled_file(options_ssl, options_ssl_digest, - self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES) + apache_config_path = self.pick_apache_config() + return common.install_version_controlled_file( + options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES) def enable_autohsts(self, _unused_lineage, domains): """ diff --git a/certbot-apache/certbot_apache/constants.py b/certbot-apache/certbot_apache/constants.py index 23a7b7afd..f8184a42f 100644 --- a/certbot-apache/certbot_apache/constants.py +++ b/certbot-apache/certbot_apache/constants.py @@ -9,6 +9,7 @@ MOD_SSL_CONF_DEST = "options-ssl-apache.conf" UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" +# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING! ALL_SSL_OPTIONS_HASHES = [ '2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a', '4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a', @@ -18,6 +19,10 @@ ALL_SSL_OPTIONS_HASHES = [ 'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b', '80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791', 'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082', + '717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20', + '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', + '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', + '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', ] """SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" diff --git a/certbot-apache/certbot_apache/options-ssl-apache.conf b/certbot-apache/certbot_apache/options-ssl-apache.conf deleted file mode 100644 index 8113ee81e..000000000 --- a/certbot-apache/certbot_apache/options-ssl-apache.conf +++ /dev/null @@ -1,26 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/override_arch.py index c5620e9f9..02891548d 100644 --- a/certbot-apache/certbot_apache/override_arch.py +++ b/certbot-apache/certbot_apache/override_arch.py @@ -1,6 +1,4 @@ """ Distribution specific override class for Arch Linux """ -import pkg_resources - import zope.interface from certbot import interfaces @@ -26,6 +24,4 @@ class ArchConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index 7c7492dbf..d4a7d7137 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -1,7 +1,6 @@ """ Distribution specific override class for CentOS family (RHEL, Fedora) """ import logging -import pkg_resources import zope.interface from certbot import errors @@ -39,8 +38,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): @@ -75,6 +72,18 @@ class CentOSConfigurator(configurator.ApacheConfigurator): # Finish with actual config check to see if systemctl restart helped super(CentOSConfigurator, self).config_test() + def pick_apache_config(self): + """ + Pick the appropriate TLS Apache configuration file for current version of Apache and OS. + :return: the path to the TLS Apache configuration file to use + :rtype: str + """ + # Disabling TLS session tickets is supported by Apache 2.4.11+. + # So for old versions of Apache we pick a configuration without this option. + if self.version < (2, 4, 11): + return apache_util.find_ssl_apache_conf("centos-old") + return apache_util.find_ssl_apache_conf("centos-current") + def _prepare_options(self): """ Override the options dictionary initialization in order to support diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/override_darwin.py index 4e2a6acac..e825b66b8 100644 --- a/certbot-apache/certbot_apache/override_darwin.py +++ b/certbot-apache/certbot_apache/override_darwin.py @@ -1,6 +1,4 @@ """ Distribution specific override class for macOS """ -import pkg_resources - import zope.interface from certbot import interfaces @@ -26,6 +24,4 @@ class DarwinConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/other", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index 58492bd01..1fc32670b 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -1,7 +1,6 @@ """ Distribution specific override class for Debian family (Ubuntu/Debian) """ import logging -import pkg_resources import zope.interface from certbot import errors @@ -35,8 +34,6 @@ class DebianConfigurator(configurator.ApacheConfigurator): handle_modules=True, handle_sites=True, challenge_location="/etc/apache2", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) def enable_site(self, vhost): diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py index 786ada0fc..77f31efe8 100644 --- a/certbot-apache/certbot_apache/override_fedora.py +++ b/certbot-apache/certbot_apache/override_fedora.py @@ -1,5 +1,4 @@ """ Distribution specific override class for Fedora 29+ """ -import pkg_resources import zope.interface from certbot import errors @@ -31,9 +30,6 @@ class FedoraConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - # TODO: eventually newest version of Fedora will need their own config - "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index c358a10fa..6fa033857 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -1,6 +1,4 @@ """ Distribution specific override class for Gentoo Linux """ -import pkg_resources - import zope.interface from certbot import interfaces @@ -29,8 +27,6 @@ class GentooConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) def _prepare_options(self): diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/override_suse.py index 3d0043afe..4baa57497 100644 --- a/certbot-apache/certbot_apache/override_suse.py +++ b/certbot-apache/certbot_apache/override_suse.py @@ -1,6 +1,4 @@ """ Distribution specific override class for OpenSUSE """ -import pkg_resources - import zope.interface from certbot import interfaces @@ -26,6 +24,4 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index dddbf489e..5c8cff3b3 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -190,6 +190,13 @@ class MultipleVhostsTestCentOS(util.ApacheTest): errors.SubprocessError] self.assertRaises(errors.MisconfigurationError, self.config.restart) + def test_pick_correct_tls_config(self): + self.config.version = (2, 4, 10) + self.assertTrue('centos-old' in self.config.pick_apache_config()) + + self.config.version = (2, 4, 11) + self.assertTrue('centos-current' in self.config.pick_apache_config()) + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 1eafae982..2bc2271a1 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1706,7 +1706,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.config.updated_mod_ssl_conf_digest) def _current_ssl_options_hash(self): - return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC")) + return crypto_util.sha256sum(self.config.pick_apache_config()) def _assert_current_file(self): self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) @@ -1742,7 +1742,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.assertFalse(mock_logger.warning.called) self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) self.assertEqual(crypto_util.sha256sum( - self.config.option("MOD_SSL_CONF_SRC")), + self.config.pick_apache_config()), self._current_ssl_options_hash()) self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) @@ -1758,18 +1758,31 @@ class InstallSslOptionsConfTest(util.ApacheTest): "%s has been manually modified; updated file " "saved to %s. We recommend updating %s for security purposes.") self.assertEqual(crypto_util.sha256sum( - self.config.option("MOD_SSL_CONF_SRC")), + self.config.pick_apache_config()), self._current_ssl_options_hash()) # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertFalse(mock_logger.warning.called) - def test_current_file_hash_in_all_hashes(self): + def test_ssl_config_files_hash_in_all_hashes(self): + """ + It is really critical that all TLS Apache config files have their SHA256 hash registered in + constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config + file has been manually edited by the user, and will refuse to update it. + This test ensures that all necessary hashes are present. + """ from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES - self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended" - " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") + import pkg_resources + tls_configs_dir = pkg_resources.resource_filename("certbot_apache", "tls_configs") + all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) + if name.endswith('options-ssl-apache.conf')] + self.assertTrue(all_files) + for one_file in all_files: + file_hash = crypto_util.sha256sum(one_file) + self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + "hash of {0} when it is updated.".format(one_file)) if __name__ == "__main__": diff --git a/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf new file mode 100644 index 000000000..2d99f6219 --- /dev/null +++ b/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf @@ -0,0 +1,19 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLSessionTickets off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf new file mode 100644 index 000000000..277c8954a --- /dev/null +++ b/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf @@ -0,0 +1,18 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf new file mode 100644 index 000000000..c32e83148 --- /dev/null +++ b/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf @@ -0,0 +1,20 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLCompression off +SSLSessionTickets off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf new file mode 100644 index 000000000..cd7c9bc4b --- /dev/null +++ b/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf @@ -0,0 +1,19 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common -- cgit v1.2.3 From bfd4955bad0ea1771bb1d27be1359ec8798f9e6c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 30 Jul 2019 12:28:18 -0700 Subject: Bump timeout waiting for ACME server to 4 minutes. (#7284) * Bump timeout to 4 minutes. * address review comments --- certbot-ci/certbot_integration_tests/utils/acme_server.py | 2 +- certbot-ci/certbot_integration_tests/utils/misc.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index 8e3419f70..a41f6366a 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -159,7 +159,7 @@ class ACMEServer(object): # Wait for the ACME CA server to be up. print('=> Waiting for boulder instance to respond...') - misc.check_until_timeout(self.acme_xdist['directory_url']) + misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=240) # Configure challtestsrv to answer any A record request with ip of the docker host. response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT), diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 2144b9cee..0237bc01e 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -28,12 +28,13 @@ RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' -def check_until_timeout(url): +def check_until_timeout(url, attempts=30): """ Wait and block until given url responds with status 200, or raise an exception - after 150 attempts. + after the specified number of attempts. :param str url: the URL to test - :raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL + :param int attempts: the number of times to try to connect to the URL + :raise ValueError: exception raised if unable to reach the URL """ try: import urllib3 @@ -43,7 +44,7 @@ def check_until_timeout(url): from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - for _ in range(0, 150): + for _ in range(attempts): time.sleep(1) try: if requests.get(url, verify=False).status_code == 200: @@ -51,7 +52,7 @@ def check_until_timeout(url): except requests.exceptions.ConnectionError: pass - raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url)) + raise ValueError('Error, url did not respond after {0} attempts: {1}'.format(attempts, url)) class GracefulTCPServer(socketserver.TCPServer): -- cgit v1.2.3 From 2d3f3a042af6c5a4d2fb1a258e27868d34aef5ae Mon Sep 17 00:00:00 2001 From: Mikel Kew Date: Wed, 31 Jul 2019 18:31:05 +1000 Subject: Update dns-cloudflare docs regarding API Tokens (#7285) A quick update to the docs to explicitly mention that the Cloudflare Global API Key must me used instead of an API Token. --- certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py index 7e53f83ce..b08bc0968 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py @@ -22,7 +22,9 @@ Credentials Use of this plugin requires a configuration file containing Cloudflare API credentials, obtained from your Cloudflare -`account page `_. +`account page `_. This plugin +does not currently support Cloudflare's "API Tokens", so please ensure you use +the "Global API Key" for authentication. .. code-block:: ini :name: credentials.ini -- cgit v1.2.3 From 56f609d4f582f61274f93a807ec4b8a0bba43828 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 1 Aug 2019 19:39:46 +0200 Subject: Fix unit tests on Windows (#7270) Fixes #6850 This PR makes the last corrections needed to run all unit tests on Windows: add a function to check if a hook is executable in a cross-platform compatible way handle correctly the PATH surgery for Windows during hook execution handle correctly an account compatibility over both ACMEv1 and ACMEv2 remove (finally!) the @broken_on_windows decorator. * Fix account_tests * Fix hook executable test * Remove the temporary decorator @broken_on_windows * Fix util_test * No broken unit test on Windows anymore * More elegant mock * Fix context manager * Adapt coverage * Corrections * Adapt coverage * Forbid os.access --- .codecov.yml | 2 +- certbot/compat/filesystem.py | 36 ++++++++++++++++++ certbot/compat/misc.py | 4 ++ certbot/compat/os.py | 9 +++++ certbot/constants.py | 7 ++-- certbot/hooks.py | 5 ++- certbot/plugins/util.py | 6 ++- certbot/plugins/util_test.py | 17 +++++---- certbot/tests/account_test.py | 17 ++------- certbot/tests/compat/filesystem_test.py | 65 +++++++++++++++++++++++++++++++++ certbot/tests/compat/os_test.py | 3 +- certbot/tests/hook_test.py | 29 +++++++-------- certbot/tests/util.py | 9 ----- certbot/tests/util_test.py | 27 ++++---------- certbot/util.py | 16 +------- 15 files changed, 164 insertions(+), 88 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index f4d4d1d6c..8a1503da8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,6 +13,6 @@ coverage: flags: windows # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.2 + target: 97.6 threshold: 0.1 base: auto diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index a38fbe760..7a48e24f1 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -289,6 +289,42 @@ def realpath(file_path): return os.path.abspath(file_path) +# On Windows is_executable run from an unprivileged shell may claim that a path is +# executable when it is excutable only if run from a privileged shell. This result +# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights +# without taking into consideration if the target user has currently required the +# elevated privileges or not. However this is not a problem since certbot always +# requires to be run under a privileged shell, so the user will always benefit +# from the highest (privileged one) set of permissions on a given file. +def is_executable(path): + """ + Is path an executable file? + :param str path: path to test + :returns: True if path is an executable file + :rtype: bool + """ + if POSIX_MODE: + return os.path.isfile(path) and os.access(path, os.X_OK) + + return _win_is_executable(path) + + +def _win_is_executable(path): + if not os.path.isfile(path): + return False + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': _get_current_user(), + }) + + return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE + + def _apply_win_mode(file_path, mode): """ This function converts the given POSIX mode into a Windows ACL list, and applies it to the diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 693fceefb..5151e7156 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -30,6 +30,10 @@ else: MASK_FOR_PRIVATE_KEY_PERMISSIONS = 0 +# For Linux: define OS specific standard binary directories +STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] + + def raise_for_non_administrative_windows_rights(): # type: () -> None """ diff --git a/certbot/compat/os.py b/certbot/compat/os.py index bb0e02eb3..2857ea408 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -107,3 +107,12 @@ def replace(*unused_args, **unused_kwargs): """Method os.replace() is forbidden""" raise RuntimeError('Usage of os.replace() is forbidden. ' 'Use certbot.compat.filesystem.replace() instead.') + + +# Results given by os.access are inconsistent or partial on Windows, because this platform is not +# following the POSIX approach. +def access(*unused_args, **unused_kwargs): + """Method os.access() is forbidden""" + raise RuntimeError('Usage of os.access() is forbidden. ' + 'Use certbot.compat.filesystem.check_mode() or ' + 'certbot.compat.filesystem.is_executable() instead.') diff --git a/certbot/constants.py b/certbot/constants.py index 5b268e157..10cd58ca1 100644 --- a/certbot/constants.py +++ b/certbot/constants.py @@ -169,9 +169,10 @@ ACCOUNTS_DIR = "accounts" """Directory where all accounts are saved.""" LE_REUSE_SERVERS = { - 'acme-v02.api.letsencrypt.org/directory': 'acme-v01.api.letsencrypt.org/directory', - 'acme-staging-v02.api.letsencrypt.org/directory': - 'acme-staging.api.letsencrypt.org/directory' + os.path.normpath('acme-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-v01.api.letsencrypt.org/directory'), + os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-staging.api.letsencrypt.org/directory') } """Servers that can reuse accounts from other servers.""" diff --git a/certbot/hooks.py b/certbot/hooks.py index 34e06e0a3..1bb3a2eab 100644 --- a/certbot/hooks.py +++ b/certbot/hooks.py @@ -8,6 +8,7 @@ from acme.magic_typing import Set, List # pylint: disable=unused-import, no-nam from certbot import errors from certbot import util +from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import util as plug_util @@ -254,7 +255,7 @@ def execute(cmd_name, shell_cmd): cmd_name, shell_cmd, cmd.returncode) if err: logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) - return (err, out) + return err, out def list_hooks(dir_path): @@ -267,5 +268,5 @@ def list_hooks(dir_path): """ allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) - hooks = [path for path in allpaths if util.is_exe(path) and not path.endswith('~')] + hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] return sorted(hooks) diff --git a/certbot/plugins/util.py b/certbot/plugins/util.py index 61f811280..87eb45fe9 100644 --- a/certbot/plugins/util.py +++ b/certbot/plugins/util.py @@ -3,9 +3,11 @@ import logging from certbot import util from certbot.compat import os +from certbot.compat.misc import STANDARD_BINARY_DIRS logger = logging.getLogger(__name__) + def get_prefixes(path): """Retrieves all possible path prefixes of a path, in descending order of length. For instance, @@ -26,6 +28,7 @@ def get_prefixes(path): break return prefixes + def path_surgery(cmd): """Attempt to perform PATH surgery to find cmd @@ -35,10 +38,9 @@ def path_surgery(cmd): :returns: True if the operation succeeded, False otherwise """ - dirs = ("/usr/sbin", "/usr/local/bin", "/usr/local/sbin") path = os.environ["PATH"] added = [] - for d in dirs: + for d in STANDARD_BINARY_DIRS: if d not in path: path += os.pathsep + d added.append(d) diff --git a/certbot/plugins/util_test.py b/certbot/plugins/util_test.py index 6ec6f62f4..c41e55222 100644 --- a/certbot/plugins/util_test.py +++ b/certbot/plugins/util_test.py @@ -16,6 +16,7 @@ class GetPrefixTest(unittest.TestCase): self.assertEqual(get_prefixes('/'), [os.path.normpath('/')]) self.assertEqual(get_prefixes('a'), ['a']) + class PathSurgeryTest(unittest.TestCase): """Tests for certbot.plugins.path_surgery.""" @@ -29,13 +30,15 @@ class PathSurgeryTest(unittest.TestCase): self.assertEqual(path_surgery("eg"), True) self.assertEqual(mock_debug.call_count, 0) self.assertEqual(os.environ["PATH"], all_path["PATH"]) - no_path = {"PATH": "/tmp/"} - with mock.patch.dict('os.environ', no_path): - path_surgery("thingy") - self.assertEqual(mock_debug.call_count, 2) - self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) - self.assertTrue("/usr/local/bin" in os.environ["PATH"]) - self.assertTrue("/tmp" in os.environ["PATH"]) + if os.name != 'nt': + # This part is specific to Linux since on Windows no PATH surgery is ever done. + no_path = {"PATH": "/tmp/"} + with mock.patch.dict('os.environ', no_path): + path_surgery("thingy") + self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1) + self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) + self.assertTrue("/usr/local/bin" in os.environ["PATH"]) + self.assertTrue("/tmp" in os.environ["PATH"]) if __name__ == "__main__": diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 24a092cc8..c14500798 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -2,7 +2,6 @@ import datetime import json import shutil -import stat import unittest import josepy as jose @@ -13,6 +12,7 @@ from acme import messages import certbot.tests.util as test_util from certbot import errors +from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os @@ -116,7 +116,6 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self.assertTrue(os.path.isdir( misc.underscores_for_unsupported_characters_in_path(self.config.accounts_dir))) - @test_util.broken_on_windows def test_save_and_restore(self): self.storage.save(self.acc, self.mock_client) account_path = os.path.join(self.config.accounts_dir, self.acc.id) @@ -124,8 +123,8 @@ class AccountFileStorageTest(test_util.ConfigTestCase): for file_name in "regr.json", "meta.json", "private_key.json": self.assertTrue(os.path.exists( os.path.join(account_path, file_name))) - self.assertTrue(oct(os.stat(os.path.join( - account_path, "private_key.json"))[stat.ST_MODE] & 0o777) in ("0400", "0o400")) + self.assertTrue( + filesystem.check_mode(os.path.join(account_path, "private_key.json"), 0o400)) # restore loaded = self.storage.load(self.acc.id) @@ -219,14 +218,12 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self._set_server('https://acme-staging.api.letsencrypt.org/directory') self.assertEqual([], self.storage.find_all()) - @test_util.broken_on_windows def test_upgrade_version_staging(self): self._set_server('https://acme-staging.api.letsencrypt.org/directory') self.storage.save(self.acc, self.mock_client) self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory') self.assertEqual([self.acc], self.storage.find_all()) - @test_util.broken_on_windows def test_upgrade_version_production(self): self._set_server('https://acme-v01.api.letsencrypt.org/directory') self.storage.save(self.acc, self.mock_client) @@ -244,7 +241,6 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory') self.assertEqual([], self.storage.find_all()) - @test_util.broken_on_windows def test_upgrade_load(self): self._set_server('https://acme-staging.api.letsencrypt.org/directory') self.storage.save(self.acc, self.mock_client) @@ -253,7 +249,6 @@ class AccountFileStorageTest(test_util.ConfigTestCase): account = self.storage.load(self.acc.id) self.assertEqual(prev_account, account) - @test_util.broken_on_windows def test_upgrade_load_single_account(self): self._set_server('https://acme-staging.api.letsencrypt.org/directory') self.storage.save(self.acc, self.mock_client) @@ -278,7 +273,6 @@ class AccountFileStorageTest(test_util.ConfigTestCase): errors.AccountStorageError, self.storage.save, self.acc, self.mock_client) - @test_util.broken_on_windows def test_delete(self): self.storage.save(self.acc, self.mock_client) self.storage.delete(self.acc.id) @@ -313,12 +307,10 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory') self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id) - @test_util.broken_on_windows def test_delete_folders_up(self): self._test_delete_folders('https://acme-staging.api.letsencrypt.org/directory') self._assert_symlinked_account_removed() - @test_util.broken_on_windows def test_delete_folders_down(self): self._test_delete_folders('https://acme-staging-v02.api.letsencrypt.org/directory') self._assert_symlinked_account_removed() @@ -328,15 +320,14 @@ class AccountFileStorageTest(test_util.ConfigTestCase): with open(os.path.join(self.config.accounts_dir, 'foo'), 'w') as f: f.write('bar') - @test_util.broken_on_windows def test_delete_shared_account_up(self): self._set_server_and_stop_symlink('https://acme-staging-v02.api.letsencrypt.org/directory') self._test_delete_folders('https://acme-staging.api.letsencrypt.org/directory') - @test_util.broken_on_windows def test_delete_shared_account_down(self): self._set_server_and_stop_symlink('https://acme-staging-v02.api.letsencrypt.org/directory') self._test_delete_folders('https://acme-staging-v02.api.letsencrypt.org/directory') + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 90ecd13d7..11293fbfe 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -1,4 +1,5 @@ """Tests for certbot.compat.filesystem""" +import contextlib import errno import unittest @@ -411,6 +412,70 @@ class RealpathTest(test_util.TempDirTestCase): self.assertTrue('link1 is a loop!' in str(error.exception)) +class IsExecutableTest(test_util.TempDirTestCase): + """Tests for is_executable method""" + def test_not_executable(self): + file_path = os.path.join(self.tempdir, "foo") + + # On Windows a file created within Certbot will always have all permissions to the + # Administrators group set. Since the unit tests are typically executed under elevated + # privileges, it means that current user will always have effective execute rights on the + # hook script, and so the test will fail. To prevent that and represent a file created + # outside Certbot as typically a hook file is, we mock the _generate_dacl function in + # certbot.compat.filesystem to give rights only to the current user. This implies removing + # all ACEs except the first one from the DACL created by original _generate_dacl function. + + from certbot.compat.filesystem import _generate_dacl + + def _execute_mock(user_sid, mode): + dacl = _generate_dacl(user_sid, mode) + for _ in range(1, dacl.GetAceCount()): + dacl.DeleteAce(1) # DeleteAce dynamically updates the internal index mapping. + return dacl + + # create a non-executable file + with mock.patch("certbot.compat.filesystem._generate_dacl", side_effect=_execute_mock): + os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) + + self.assertFalse(filesystem.is_executable(file_path)) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_full_path(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = True + self.assertTrue(filesystem.is_executable("/path/to/exe")) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_rel_path(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = True + self.assertTrue(filesystem.is_executable("exe")) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_not_found(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = False + self.assertFalse(filesystem.is_executable("exe")) + + +@contextlib.contextmanager +def _fix_windows_runtime(): + if os.name != 'nt': + yield + else: + with mock.patch('win32security.GetFileSecurity') as mock_get: + dacl_mock = mock_get.return_value.GetSecurityDescriptorDacl + mode_mock = dacl_mock.return_value.GetEffectiveRightsFromAcl + mode_mock.return_value = ntsecuritycon.FILE_GENERIC_EXECUTE + yield + + def _get_security_dacl(target): return win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION) diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 754e97a10..e4928e4fb 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -8,7 +8,8 @@ class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): # Checks for os module - for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', 'replace']: + for method in ['chmod', 'chown', 'open', 'mkdir', + 'makedirs', 'rename', 'replace', 'access']: self.assertRaises(RuntimeError, getattr(os, method)) # Checks for os.path module for method in ['realpath']: diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 079bc7f4c..017edc560 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -1,14 +1,14 @@ """Tests for certbot.hooks.""" -import stat import unittest import mock from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors +from certbot import util from certbot.compat import os from certbot.compat import filesystem -from certbot.tests import util +from certbot.tests import util as test_util class ValidateHooksTest(unittest.TestCase): @@ -30,7 +30,7 @@ class ValidateHooksTest(unittest.TestCase): self.assertEqual("renew", types[-1]) -class ValidateHookTest(util.TempDirTestCase): +class ValidateHookTest(test_util.TempDirTestCase): """Tests for certbot.hooks.validate_hook.""" @classmethod @@ -38,22 +38,20 @@ class ValidateHookTest(util.TempDirTestCase): from certbot.hooks import validate_hook return validate_hook(*args, **kwargs) - @util.broken_on_windows - def test_not_executable(self): - file_path = os.path.join(self.tempdir, "foo") - # create a non-executable file - os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) + def test_hook_not_executable(self): # prevent unnecessary modifications to PATH with mock.patch("certbot.hooks.plug_util.path_surgery"): - self.assertRaises(errors.HookCommandNotFound, - self._call, file_path, "foo") + # We just mock out filesystem.is_executable since on Windows, it is difficult + # to get a fully working test around executable permissions. See + # certbot.tests.compat.filesystem::NotExecutableTest for more in-depth tests. + with mock.patch("certbot.hooks.filesystem.is_executable", return_value=False): + self.assertRaises(errors.HookCommandNotFound, self._call, 'dummy', "foo") @mock.patch("certbot.hooks.util.exe_exists") def test_not_found(self, mock_exe_exists): mock_exe_exists.return_value = False with mock.patch("certbot.hooks.plug_util.path_surgery") as mock_ps: - self.assertRaises(errors.HookCommandNotFound, - self._call, "foo", "bar") + self.assertRaises(errors.HookCommandNotFound, self._call, "foo", "bar") self.assertTrue(mock_ps.called) @mock.patch("certbot.hooks._prog") @@ -62,7 +60,7 @@ class ValidateHookTest(util.TempDirTestCase): self.assertFalse(mock_prog.called) -class HookTest(util.ConfigTestCase): +class HookTest(test_util.ConfigTestCase): """Common base class for hook tests.""" @classmethod @@ -454,7 +452,7 @@ class ExecuteTest(unittest.TestCase): self.assertTrue(mock_logger.error.called) -class ListHooksTest(util.TempDirTestCase): +class ListHooksTest(test_util.TempDirTestCase): """Tests for certbot.hooks.list_hooks.""" @classmethod @@ -494,8 +492,7 @@ def create_hook(file_path): :param str file_path: path to create the file at """ - open(file_path, "w").close() - filesystem.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR) + util.safe_open(file_path, mode="w", chmod=0o744).close() if __name__ == '__main__': diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 56fbd5458..7ee215c66 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -421,15 +421,6 @@ def skip_on_windows(reason): return wrapper -def broken_on_windows(function): - """Decorator to skip temporarily a broken test on Windows.""" - reason = 'Test is broken and ignored on windows but should be fixed.' - return unittest.skipIf( - sys.platform == 'win32' - and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', - reason)(function) - - def temp_join(path): """ Return the given path joined to the tempdir path for the current platform diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index e65de3349..cf4f31647 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -52,26 +52,13 @@ class ExeExistsTest(unittest.TestCase): from certbot.util import exe_exists return exe_exists(exe) - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_full_path(self, mock_access, mock_isfile): - mock_access.return_value = True - mock_isfile.return_value = True - self.assertTrue(self._call("/path/to/exe")) - - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_on_path(self, mock_access, mock_isfile): - mock_access.return_value = True - mock_isfile.return_value = True - self.assertTrue(self._call("exe")) - - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_not_found(self, mock_access, mock_isfile): - mock_access.return_value = False - mock_isfile.return_value = True - self.assertFalse(self._call("exe")) + def test_exe_exists(self): + with mock.patch("certbot.util.filesystem.is_executable", return_value=True): + self.assertTrue(self._call("/path/to/exe")) + + def test_exe_not_exists(self): + with mock.patch("certbot.util.filesystem.is_executable", return_value=False): + self.assertFalse(self._call("/path/to/exe")) class LockDirUntilExit(test_util.TempDirTestCase): diff --git a/certbot/util.py b/certbot/util.py index 8f553e51a..d3297507e 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -86,18 +86,6 @@ def run_script(params, log=logger.error): return stdout, stderr -def is_exe(path): - """Is path an executable file? - - :param str path: path to test - - :returns: True iff path is an executable file - :rtype: bool - - """ - return os.path.isfile(path) and os.access(path, os.X_OK) - - def exe_exists(exe): """Determine whether path/name refers to an executable. @@ -109,10 +97,10 @@ def exe_exists(exe): """ path, _ = os.path.split(exe) if path: - return is_exe(exe) + return filesystem.is_executable(exe) else: for path in os.environ["PATH"].split(os.pathsep): - if is_exe(os.path.join(path, exe)): + if filesystem.is_executable(os.path.join(path, exe)): return True return False -- cgit v1.2.3 From 36b4c312c6c802c13dfde6f13f2b2a3004c913e0 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 2 Aug 2019 18:47:36 +0200 Subject: Upgrade virtualenv in dev/tests environments (#7287) AppVeyor recently upgrade the Python 3.7.x installed in their VM to 3.7.4. However, virtualenv 16.6.1 is broken on that specific version of Python for Windows. This PR upgrade virtualenv installed for a dev/test environment from 16.6.1 to 16.6.2 in order to fix this issue, and repair the CI jobs execute by AppVeyor on PRs. --- tools/dev_constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 1aa8414f9..660986da9 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -82,6 +82,6 @@ twine==1.11.0 typed-ast==1.1.0 typing==3.6.4 uritemplate==0.6 -virtualenv==16.6.1 +virtualenv==16.6.2 wcwidth==0.1.7 wrapt==1.11.1 -- cgit v1.2.3 From 1c7105a940267fc83e71c8955bb2b9f2109a46a9 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 2 Aug 2019 20:46:12 +0200 Subject: Create a mock OCSP server for Pebble integration tests (#7281) * Implement a logic, miss the private key of pebble * Complete process * Fix nginx cert path * Check conditionnally docker * Update gitignore, fix apacheconftest * Full object * Carriage return * Work in progress * Move to official v2.1.0 of pebble * Fix name * Update acme_server.py * Link things together with new version of pebble * Plug the logic to tests * Update config * Reinitiate config * Add OCSP config to pebble * Working. * Simplify logic * Clean code * Use forked pebble for now # Conflicts: # certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py * Move full logic of mock at the acme server config * Continue work * Finish fixing the date parsing * Update module name * Use again official pebble * Activate mock OCSP server * Clean code * Update pebble_artifacts.py * Remove OCSP stale test * Add executable permissions * Clean code * Update setup.py * Simplify code * On-demand import of pebble_ocsp_server * Revert "Remove OCSP stale test" This reverts commit 2e4c985b427120cc15526bbcfd15806d02a6f3fc. # Conflicts: # certbot-ci/certbot_integration_tests/utils/misc.py * Fix for virtualenv on Python 3.7.4 for Windows * Update acme_server.py --- .../certbot_tests/context.py | 4 +- .../certbot_tests/test_main.py | 4 +- .../certbot_integration_tests/utils/acme_server.py | 7 +++ .../certbot_integration_tests/utils/constants.py | 2 + certbot-ci/certbot_integration_tests/utils/misc.py | 1 - .../utils/pebble_artifacts.py | 6 +- .../utils/pebble_ocsp_server.py | 71 ++++++++++++++++++++++ certbot-ci/setup.py | 1 + 8 files changed, 89 insertions(+), 7 deletions(-) create mode 100755 certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index c4c02a25e..562748fb6 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -4,7 +4,7 @@ import shutil import sys import tempfile -from certbot_integration_tests.utils import misc, certbot_call +from certbot_integration_tests.utils import certbot_call class IntegrationTestsContext(object): @@ -19,7 +19,7 @@ class IntegrationTestsContext(object): self.worker_id = 'primary' acme_xdist = request.config.acme_xdist - self.acme_server =acme_xdist['acme_server'] + self.acme_server = acme_xdist['acme_server'] self.directory_url = acme_xdist['directory_url'] self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id] self.http_01_port = acme_xdist['http_port'][self.worker_id] diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 5428f1a09..a2d1e7123 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -555,11 +555,9 @@ def test_ocsp_status_stale(context): def test_ocsp_status_live(context): """Test retrieval of OCSP statuses for live config""" - if context.acme_server == 'pebble': - pytest.skip('Pebble does not support OCSP status requests.') + cert = context.get_domain('ocsp-check') # OSCP 1: Check live certificate OCSP status (VALID) - cert = context.get_domain('ocsp-check') context.certbot(['--domains', cert]) output = context.certbot(['certificates']) diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index a41f6366a..f13855d1e 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -134,6 +134,13 @@ class ACMEServer(object): [challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""', '-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""']) + # pebble_ocsp_server is imported here and not at the top of module in order to avoid a useless + # ImportError, in the case where cryptography dependency is too old to support ocsp, but + # Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is the typical + # situation of integration-certbot-oldest tox testenv. + from certbot_integration_tests.utils import pebble_ocsp_server + self._launch_process([sys.executable, pebble_ocsp_server.__file__]) + # Wait for the ACME CA server to be up. print('=> Waiting for pebble instance to respond...') misc.check_until_timeout(self.acme_xdist['directory_url']) diff --git a/certbot-ci/certbot_integration_tests/utils/constants.py b/certbot-ci/certbot_integration_tests/utils/constants.py index 9266e25ea..dfdeda411 100644 --- a/certbot-ci/certbot_integration_tests/utils/constants.py +++ b/certbot-ci/certbot_integration_tests/utils/constants.py @@ -5,3 +5,5 @@ CHALLTESTSRV_PORT = 8055 BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory' BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory' PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir' +PEBBLE_MANAGEMENT_URL = 'https://localhost:15000' +MOCK_OCSP_SERVER_PORT = 4002 diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 0237bc01e..14e2b232e 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -23,7 +23,6 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption from six.moves import socketserver, SimpleHTTPServer - RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py index 9f154bb0e..fcb393f1e 100644 --- a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py +++ b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py @@ -6,7 +6,9 @@ import stat import pkg_resources import requests -PEBBLE_VERSION = 'v2.1.0' +from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT + +PEBBLE_VERSION = 'v2.2.1' ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets') @@ -42,10 +44,12 @@ def _build_pebble_config(workspace): file_h.write(json.dumps({ 'pebble': { 'listenAddress': '0.0.0.0:14000', + 'managementListenAddress': '0.0.0.0:15000', 'certificate': os.path.join(ASSETS_PATH, 'cert.pem'), 'privateKey': os.path.join(ASSETS_PATH, 'key.pem'), 'httpPort': 5002, 'tlsPort': 5001, + 'ocspResponderURL': 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT), }, })) diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py new file mode 100755 index 000000000..2c5dea4a2 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +""" +This runnable module interfaces itself with the Pebble management interface in order +to serve a mock OCSP responder during integration tests against Pebble. +""" +import datetime +import re + +import requests +from dateutil import parser + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization, hashes +from cryptography import x509 +from cryptography.x509 import ocsp +from six.moves import BaseHTTPServer + +from certbot_integration_tests.utils.misc import GracefulTCPServer +from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT, PEBBLE_MANAGEMENT_URL + + +class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_POST(self): + request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediate-keys/0', verify=False) + issuer_key = serialization.load_pem_private_key(request.content, None, default_backend()) + + request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediates/0', verify=False) + issuer_cert = x509.load_pem_x509_certificate(request.content, default_backend()) + + try: + content_len = int(self.headers.getheader('content-length', 0)) + except AttributeError: + content_len = int(self.headers.get('Content-Length')) + + ocsp_request = ocsp.load_der_ocsp_request(self.rfile.read(content_len)) + response = requests.get('{0}/cert-status-by-serial/{1}'.format( + PEBBLE_MANAGEMENT_URL, str(hex(ocsp_request.serial_number)).replace('0x', '')), verify=False) + + if not response.ok: + ocsp_response = ocsp.OCSPResponseBuilder.build_unsuccessful(ocsp.OCSPResponseStatus.UNAUTHORIZED) + else: + data = response.json() + + now = datetime.datetime.utcnow() + cert = x509.load_pem_x509_certificate(data['Certificate'].encode(), default_backend()) + if data['Status'] != 'Revoked': + ocsp_status, revocation_time, revocation_reason = ocsp.OCSPCertStatus.GOOD, None, None + else: + ocsp_status, revocation_reason = ocsp.OCSPCertStatus.REVOKED, x509.ReasonFlags.unspecified + revoked_at = re.sub(r'( \+\d{4}).*$', r'\1', data['RevokedAt']) # "... +0000 UTC" => "+0000" + revocation_time = parser.parse(revoked_at) + + ocsp_response = ocsp.OCSPResponseBuilder().add_response( + cert=cert, issuer=issuer_cert, algorithm=hashes.SHA1(), + cert_status=ocsp_status, + this_update=now, next_update=now + datetime.timedelta(hours=1), + revocation_time=revocation_time, revocation_reason=revocation_reason + ).responder_id( + ocsp.OCSPResponderEncoding.NAME, issuer_cert + ).sign(issuer_key, hashes.SHA256()) + + self.send_response(200) + self.end_headers() + self.wfile.write(ocsp_response.public_bytes(serialization.Encoding.DER)) + + +if __name__ == '__main__': + try: + GracefulTCPServer(('', MOCK_OCSP_SERVER_PORT), _ProxyHandler).serve_forever() + except KeyboardInterrupt: + pass diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 2adf137b8..91ee1403b 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -11,6 +11,7 @@ install_requires = [ 'pytest', 'pytest-cov', 'pytest-xdist', + 'python-dateutil', 'pyyaml', 'requests', 'six', -- cgit v1.2.3 From 14e10f40e5cabc3377e49385871b347f5723c8b2 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 2 Aug 2019 12:25:40 -0700 Subject: Follow Mozilla recs for Nginx ssl_protocols, ssl_ciphers, and ssl_prefer_server_ciphers (#7274) * Follow Mozilla recs for Nginx ssl_protocols, ssl_ciphers, and ssl_prefer_server_ciphers * Add tests and fix if statement * Update CHANGELOG.md Co-Authored-By: Brad Warren * Test that the hashes of all of the current configuration files are in ALL_SSL_OPTIONS_HASHES * Remove conditioning on OpenSSL version, since Nginx behaves cleanly if its linked OpenSSL doesn't support TLS1.3 --- CHANGELOG.md | 3 ++- certbot-nginx/certbot_nginx/configurator.py | 2 ++ certbot-nginx/certbot_nginx/constants.py | 12 ++++++++-- .../certbot_nginx/options-ssl-nginx-old.conf | 6 ++--- .../options-ssl-nginx-tls12-only.conf | 14 +++++++++++ certbot-nginx/certbot_nginx/options-ssl-nginx.conf | 6 ++--- .../certbot_nginx/tests/configurator_test.py | 28 +++++++++++++++++++++- 7 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 0960eb4a1..9715d8ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers, + and ssl_prefer_server_ciphers ### Fixed diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 0c6eabf57..4fd5056a3 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -127,6 +127,8 @@ class NginxConfigurator(common.Installer): config_filename = "options-ssl-nginx.conf" if self.version < (1, 5, 9): config_filename = "options-ssl-nginx-old.conf" + elif self.version < (1, 13, 0): + config_filename = "options-ssl-nginx-tls12-only.conf" return pkg_resources.resource_filename("certbot_nginx", config_filename) @property diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index 3f22000eb..c90b6b52f 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -23,10 +23,17 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" SSL_OPTIONS_HASHES_NEW = [ + '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', +] +"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0""" + +SSL_OPTIONS_HASHES_MEDIUM = [ '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', + '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', ] -"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9""" +"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9 + and nginx < 1.13.0""" ALL_SSL_OPTIONS_HASHES = [ '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', @@ -36,7 +43,8 @@ ALL_SSL_OPTIONS_HASHES = [ '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', -] + SSL_OPTIONS_HASHES_NEW + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', +] + SSL_OPTIONS_HASHES_MEDIUM + SSL_OPTIONS_HASHES_NEW """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" def os_constant(key): diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf index 627bafadb..a678b0507 100644 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf @@ -7,7 +7,7 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_prefer_server_ciphers on; +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf new file mode 100644 index 000000000..1933cbc4f --- /dev/null +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf index 3cc2b9b28..978e6e8ab 100644 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf @@ -8,7 +8,7 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_session_tickets off; -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_prefer_server_ciphers on; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 06c8c53c9..35113aa98 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -963,13 +963,39 @@ class InstallSslOptionsConfTest(util.NginxTest): "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") - def test_old_nginx_version_uses_old_config(self): + def test_ssl_config_files_hash_in_all_hashes(self): + """ + It is really critical that all TLS Nginx config files have their SHA256 hash registered in + constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config + file has been manually edited by the user, and will refuse to update it. + This test ensures that all necessary hashes are present. + """ + from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + import pkg_resources + all_files = [pkg_resources.resource_filename("certbot_nginx", x) for x in ( + "options-ssl-nginx.conf", + "options-ssl-nginx-old.conf", + "options-ssl-nginx-tls12-only.conf" + )] + self.assertTrue(all_files) + for one_file in all_files: + file_hash = crypto_util.sha256sum(one_file) + self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + "hash of {0} when it is updated.".format(one_file)) + + def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-old.conf") self._call() self._assert_current_file() self.config.version = (1, 5, 9) + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls12-only.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx.conf") -- cgit v1.2.3 From 8bcb04af4a1aec87fa838283eb5c6a4e2d1a625c Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 6 Aug 2019 00:45:08 +0200 Subject: Move Nginx TLS configuration files into a specific folder (#7300) Following discussions in #7298. This PR moves the three Nginx TLS configuration files into a specific folder, tls_configs, update the MANIFEST to include this folder and its content into the certbot-nginx package, and update tests accordingly. * Move tls configuration files in a specific folder * Move new file --- certbot-nginx/MANIFEST.in | 3 +-- certbot-nginx/certbot_nginx/configurator.py | 3 ++- certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf | 13 ------------- .../certbot_nginx/options-ssl-nginx-tls12-only.conf | 14 -------------- certbot-nginx/certbot_nginx/options-ssl-nginx.conf | 14 -------------- certbot-nginx/certbot_nginx/tests/configurator_test.py | 11 ++++++----- .../certbot_nginx/tls_configs/options-ssl-nginx-old.conf | 13 +++++++++++++ .../tls_configs/options-ssl-nginx-tls12-only.conf | 14 ++++++++++++++ .../certbot_nginx/tls_configs/options-ssl-nginx.conf | 14 ++++++++++++++ 9 files changed, 50 insertions(+), 49 deletions(-) delete mode 100644 certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf delete mode 100644 certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf delete mode 100644 certbot-nginx/certbot_nginx/options-ssl-nginx.conf create mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf create mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf create mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index 8707f9443..3a22c4873 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -2,5 +2,4 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_nginx/tests/testdata * -include certbot_nginx/options-ssl-nginx.conf -include certbot_nginx/options-ssl-nginx-old.conf +recursive-include certbot_nginx/tls_configs *.conf diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 4fd5056a3..d3de83593 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -129,7 +129,8 @@ class NginxConfigurator(common.Installer): config_filename = "options-ssl-nginx-old.conf" elif self.version < (1, 13, 0): config_filename = "options-ssl-nginx-tls12-only.conf" - return pkg_resources.resource_filename("certbot_nginx", config_filename) + return pkg_resources.resource_filename( + "certbot_nginx", os.path.join("tls_configs", config_filename)) @property def mod_ssl_conf(self): diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf deleted file mode 100644 index a678b0507..000000000 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; - -ssl_protocols TLSv1.2; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf deleted file mode 100644 index 1933cbc4f..000000000 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx-tls12-only.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; -ssl_session_tickets off; - -ssl_protocols TLSv1.2; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf deleted file mode 100644 index 978e6e8ab..000000000 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; -ssl_session_tickets off; - -ssl_protocols TLSv1.2 TLSv1.3; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 35113aa98..8db202785 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -972,11 +972,12 @@ class InstallSslOptionsConfTest(util.NginxTest): """ from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES import pkg_resources - all_files = [pkg_resources.resource_filename("certbot_nginx", x) for x in ( - "options-ssl-nginx.conf", - "options-ssl-nginx-old.conf", - "options-ssl-nginx-tls12-only.conf" - )] + all_files = [ + pkg_resources.resource_filename("certbot_nginx", os.path.join("tls_configs", x)) + for x in ("options-ssl-nginx.conf", + "options-ssl-nginx-old.conf", + "options-ssl-nginx-tls12-only.conf") + ] self.assertTrue(all_files) for one_file in all_files: file_hash = crypto_util.sha256sum(one_file) diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf new file mode 100644 index 000000000..a678b0507 --- /dev/null +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf new file mode 100644 index 000000000..1933cbc4f --- /dev/null +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf new file mode 100644 index 000000000..978e6e8ab --- /dev/null +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; -- cgit v1.2.3 From f4d17d9a6b0f2ba997cdc8da75dd60c1bbf5ebf1 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 6 Aug 2019 00:57:20 +0200 Subject: Clean the useless entries in MANIFEST.in (#7299) Since #7191, TLS configuration files for Apache have been moved to a dedicated folder tls_configs. Then the entries in MANIFEST.in removed by this PR do not correspond to an existing path, and so are not useful anymore. --- certbot-apache/MANIFEST.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index a34e215e7..c1f79fcc7 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -2,7 +2,5 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_apache/tests/testdata * -include certbot_apache/centos-options-ssl-apache.conf -include certbot_apache/options-ssl-apache.conf recursive-include certbot_apache/augeas_lens *.aug recursive-include certbot_apache/tls_configs *.conf -- cgit v1.2.3 From d6e6d64848cd9a3dc3953b9e4385b1d6ec898122 Mon Sep 17 00:00:00 2001 From: Michael Watters Date: Fri, 12 Jul 2019 13:00:44 -0400 Subject: Update certbot-auto script to work with RHEL 8 /usr/bin/python no longer exists in RHEL 8. This patch updates the certbot-auto script to use python3 on nodes running RHEL 8. Also fixed a bug in the RPM_DIST_VERSION logic which would cause letsencrypt-auto to fail on servers running CentOS/RHEL 6. --- AUTHORS.md | 1 + CHANGELOG.md | 2 +- letsencrypt-auto-source/letsencrypt-auto | 27 +++++++++++++++++++---- letsencrypt-auto-source/letsencrypt-auto.template | 27 +++++++++++++++++++---- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 340a0a94b..e753fb693 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -161,6 +161,7 @@ Authors * [Michael Schumacher](https://github.com/schumaml) * [Michael Strache](https://github.com/Jarodiv) * [Michael Sverdlin](https://github.com/sveder) +* [Michael Watters](https://github.com/blackknight36) * [Michal Moravec](https://github.com/https://github.com/Majkl578) * [Michal Papis](https://github.com/mpapis) * [Minn Soe](https://github.com/MinnSoe) diff --git a/CHANGELOG.md b/CHANGELOG.md index 091ec0493..73ca822d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fix certbot-auto failures on RHEL 8. More details about these changes can be found on our GitHub repo. diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index b5f8500ad..7f3e06398 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -755,13 +755,31 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +793,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index c064580bd..b38aa7017 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -330,13 +330,31 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -350,6 +368,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { -- cgit v1.2.3 From b67fda88324c2489228983b32a2b4a1cc204d564 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 7 Aug 2019 00:02:16 +0200 Subject: Fix integration tests on Windows (#7271) * Fix account_tests * Fix hook executable test * Remove the temporary decorator @broken_on_windows * Fix util_test * No broken unit test on Windows anymore * More elegant mock * Fix context manager * Fix lint * Fix mypy * Adapt coverage * Corrections * Fix lint * Adapt coverage * Update certbot/tests/compat/filesystem_test.py Co-Authored-By: Brad Warren * Update util_test.py * Fix pylint * Forbid os.access * Update os_test.py * Update os.py * Fix lint * Update filesystem.py * Update filesystem.py * Update filesystem.py * Update os.py * Start fixing tests * Platform independent hooks * Fix probe fd close * Add broken_on_windows for integration tests * Fix a lot of tests * Use a python hook script, to prepare cross-platform * New approach to be compliant with Linux and Windows on hook scripts * New tests fixed * Test for permissions on Windows * Permissions comparison for Windows * No broken tests in certbot core anymore * Change mode * Specific config for appveyor * Use forked pebble for now * Various fixes * Assert file permissions for world on private keys * Clean code * Fix several things * Add integration target * Optimize integration env * Re-enable all AppVeyor envs * Use again official pebble * Update pebble_artifacts.py * Set PYTEST_ADDOPTS silently * Update appveyor.yml * Pin pywin32 for tests, give a minimal requirement for certbot. * Remove injection of nginx in PATH * Clean debug code * Various cleanup, ensure to remove workspace after tests * Update tox target * Improve assertions. Control the keyword echoed in hooks * Fix for virtualenv on Python 3.7.4 for Windows * Update certbot-ci/certbot_integration_tests/certbot_tests/assertions.py Co-Authored-By: Brad Warren * Add conditionally pywin in certbot-ci like in certbot --- appveyor.yml | 11 +- .../certbot_integration_tests/assets/hook.py | 11 ++ .../certbot_tests/assertions.py | 124 +++++++++++++++++---- .../certbot_tests/context.py | 6 +- .../certbot_tests/test_main.py | 107 +++++++++++------- certbot-ci/certbot_integration_tests/utils/misc.py | 88 +++++++++------ .../utils/pebble_artifacts.py | 5 +- certbot-ci/setup.py | 17 ++- setup.py | 2 +- tools/dev_constraints.txt | 1 + tox.ini | 9 ++ 11 files changed, 274 insertions(+), 107 deletions(-) create mode 100755 certbot-ci/certbot_integration_tests/assets/hook.py diff --git a/appveyor.yml b/appveyor.yml index 33f522df1..53f29a5e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,7 @@ environment: matrix: - TOXENV: py35 - TOXENV: py37-cover + - TOXENV: integration-certbot branches: only: @@ -24,14 +25,16 @@ init: install: # Use Python 3.7 by default - - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" + - SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH% + # Using 4 processes is proven to be the most efficient integration tests config for AppVeyor + - IF %TOXENV%==integration-certbot SET PYTEST_ADDOPTS=--numprocesses=4 # Check env - - "python --version" + - python --version # Upgrade pip to avoid warnings - - "python -m pip install --upgrade pip" + - python -m pip install --upgrade pip # Ready to install tox and coverage # tools/pip_install.py is used to pin packages to a known working version. - - "python tools\\pip_install.py tox codecov" + - python tools\\pip_install.py tox codecov build: off diff --git a/certbot-ci/certbot_integration_tests/assets/hook.py b/certbot-ci/certbot_integration_tests/assets/hook.py new file mode 100755 index 000000000..ff735a216 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/hook.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +import sys +import os + +hook_script_type = os.path.basename(os.path.dirname(sys.argv[1])) +if hook_script_type == 'deploy' and ('RENEWED_DOMAINS' not in os.environ or 'RENEWED_LINEAGE' not in os.environ): + sys.stderr.write('Environment variables not properly set!\n') + sys.exit(1) + +with open(sys.argv[2], 'a') as file_h: + file_h.write(hook_script_type + '\n') diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py index b82c0b5f0..5177ffbd2 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -1,6 +1,17 @@ """This module contains advanced assertions for the certbot integration tests.""" import os -import grp +try: + import grp + POSIX_MODE = True +except ImportError: + import win32api + import win32security + import ntsecuritycon + POSIX_MODE = False + +EVERYBODY_SID = 'S-1-1-0' +SYSTEM_SID = 'S-1-5-18' +ADMINS_SID = 'S-1-5-32-544' def assert_hook_execution(probe_path, probe_content): @@ -10,9 +21,10 @@ def assert_hook_execution(probe_path, probe_content): :param probe_content: content expected when the hook is executed """ with open(probe_path, 'r') as file: - lines = file.readlines() + data = file.read() - assert '{0}{1}'.format(probe_content, os.linesep) in lines + lines = [line.strip() for line in data.splitlines()] + assert probe_content in lines def assert_saved_renew_hook(config_dir, lineage): @@ -38,16 +50,51 @@ def assert_cert_count_for_lineage(config_dir, lineage, count): assert len(certs) == count -def assert_equals_permissions(file1, file2, mask): +def assert_equals_group_permissions(file1, file2): """ - Assert that permissions on two files are identical in respect to a given umask. + Assert that two files have the same permissions for group owner. :param file1: first file path to compare :param file2: second file path to compare - :param mask: 3-octal representation of a POSIX umask under which the two files mode - should match (eg. 0o074 will test RWX on group and R on world) """ - mode_file1 = os.stat(file1).st_mode & mask - mode_file2 = os.stat(file2).st_mode & mask + # On Windows there is no group, so this assertion does nothing on this platform + if POSIX_MODE: + mode_file1 = os.stat(file1).st_mode & 0o070 + mode_file2 = os.stat(file2).st_mode & 0o070 + + assert mode_file1 == mode_file2 + + +def assert_equals_world_read_permissions(file1, file2): + """ + Assert that two files have the same read permissions for everyone. + :param file1: first file path to compare + :param file2: second file path to compare + """ + if POSIX_MODE: + mode_file1 = os.stat(file1).st_mode & 0o004 + mode_file2 = os.stat(file2).st_mode & 0o004 + else: + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + security1 = win32security.GetFileSecurity(file1, win32security.DACL_SECURITY_INFORMATION) + dacl1 = security1.GetSecurityDescriptorDacl() + + mode_file1 = dacl1.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': everybody, + }) + mode_file1 = mode_file1 & ntsecuritycon.FILE_GENERIC_READ + + security2 = win32security.GetFileSecurity(file2, win32security.DACL_SECURITY_INFORMATION) + dacl2 = security2.GetSecurityDescriptorDacl() + + mode_file2 = dacl2.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': everybody, + }) + mode_file2 = mode_file2 & ntsecuritycon.FILE_GENERIC_READ assert mode_file1 == mode_file2 @@ -57,20 +104,57 @@ def assert_equals_group_owner(file1, file2): Assert that two files have the same group owner. :param file1: first file path to compare :param file2: second file path to compare - :return: """ - group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0] - group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0] + # On Windows there is no group, so this assertion does nothing on this platform + if POSIX_MODE: + group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0] + group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0] - assert group_owner_file1 == group_owner_file2 + assert group_owner_file1 == group_owner_file2 -def assert_world_permissions(file, mode): +def assert_world_no_permissions(file): """ - Assert that a file has the expected world permission. - :param file: file path to check - :param mode: world permissions mode expected + Assert that the given file is not world-readable. + :param file: path of the file to check """ - mode_file_all = os.stat(file).st_mode & 0o007 - - assert mode_file_all == mode + if POSIX_MODE: + mode_file_all = os.stat(file).st_mode & 0o007 + assert mode_file_all == 0 + else: + security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID), + }) + + assert not mode + + +def assert_world_read_permissions(file): + """ + Assert that the given file is world-readable, but not world-writable or world-executable. + :param file: path of the file to check + """ + if POSIX_MODE: + mode_file_all = os.stat(file).st_mode & 0o007 + assert mode_file_all == 4 + else: + security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID), + }) + + assert not mode & ntsecuritycon.FILE_GENERIC_WRITE + assert not mode & ntsecuritycon.FILE_GENERIC_EXECUTE + assert mode & ntsecuritycon.FILE_GENERIC_READ == ntsecuritycon.FILE_GENERIC_READ + + +def _get_current_user(): + account_name = win32api.GetUserNameEx(win32api.NameSamCompatible) + return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index 562748fb6..6f8670000 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -1,4 +1,5 @@ """Module to handle the context of integration tests.""" +import logging import os import shutil import sys @@ -30,7 +31,10 @@ class IntegrationTestsContext(object): self.workspace = tempfile.mkdtemp() self.config_dir = os.path.join(self.workspace, 'conf') - self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1] + + probe = tempfile.mkstemp(dir=self.workspace) + os.close(probe[0]) + self.hook_probe = probe[1] self.manual_dns_auth_hook = ( '{0} -c "import os; import requests; import json; ' diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index a2d1e7123..80fa15584 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -11,8 +11,11 @@ from os.path import join, exists import pytest from certbot_integration_tests.certbot_tests import context as certbot_context from certbot_integration_tests.certbot_tests.assertions import ( - assert_hook_execution, assert_saved_renew_hook, assert_cert_count_for_lineage, - assert_world_permissions, assert_equals_group_owner, assert_equals_permissions, + assert_hook_execution, assert_saved_renew_hook, + assert_cert_count_for_lineage, + assert_world_no_permissions, assert_world_read_permissions, + assert_equals_group_owner, assert_equals_group_permissions, assert_equals_world_read_permissions, + EVERYBODY_SID ) from certbot_integration_tests.utils import misc @@ -84,9 +87,9 @@ def test_http_01(context): context.certbot([ '--domains', certname, '--preferred-challenges', 'http-01', 'run', '--cert-name', certname, - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--deploy-hook', misc.echo('deploy', context.hook_probe), ]) assert_hook_execution(context.hook_probe, 'deploy') @@ -104,9 +107,9 @@ def test_manual_http_auth(context): '--cert-name', certname, '--manual-auth-hook', scripts[0], '--manual-cleanup-hook', scripts[1], - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--renew-hook', misc.echo('renew', context.hook_probe), ]) with pytest.raises(AssertionError): @@ -122,9 +125,9 @@ def test_manual_dns_auth(context): 'run', '--cert-name', certname, '--manual-auth-hook', context.manual_dns_auth_hook, '--manual-cleanup-hook', context.manual_dns_cleanup_hook, - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--renew-hook', misc.echo('renew', context.hook_probe), ]) with pytest.raises(AssertionError): @@ -173,21 +176,19 @@ def test_renew_files_permissions(context): certname = context.get_domain('renew') context.certbot(['-d', certname]) + privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem') + privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem') + assert_cert_count_for_lineage(context.config_dir, certname, 1) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0) + assert_world_no_permissions(privkey1) context.certbot(['renew']) assert_cert_count_for_lineage(context.config_dir, certname, 2) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0) - assert_equals_group_owner( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem')) - assert_equals_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + assert_world_no_permissions(privkey2) + assert_equals_group_owner(privkey1, privkey2) + assert_equals_world_read_permissions(privkey1, privkey2) + assert_equals_group_permissions(privkey1, privkey2) def test_renew_with_hook_scripts(context): @@ -211,15 +212,35 @@ def test_renew_files_propagate_permissions(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - os.chmod(join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0o444) + privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem') + privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem') + + if os.name != 'nt': + os.chmod(privkey1, 0o444) + else: + import win32security + import ntsecuritycon + # Get the current DACL of the private key + security = win32security.GetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + # Create a read permission for Everybody group + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_GENERIC_READ, everybody) + # Apply the updated DACL to the private key + security.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION, security) + context.certbot(['renew']) assert_cert_count_for_lineage(context.config_dir, certname, 2) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 4) - assert_equals_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + if os.name != 'nt': + # On Linux, read world permissions + all group permissions will be copied from the previous private key + assert_world_read_permissions(privkey2) + assert_equals_world_read_permissions(privkey1, privkey2) + assert_equals_group_permissions(privkey1, privkey2) + else: + # On Windows, world will never have any permissions, and group permission is irrelevant for this platform + assert_world_no_permissions(privkey2) def test_graceful_renew_it_is_not_time(context): @@ -229,7 +250,7 @@ def test_graceful_renew_it_is_not_time(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)], force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 1) @@ -250,7 +271,7 @@ def test_graceful_renew_it_is_time(context): with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'w') as file: file.writelines(lines) - context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)], force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 2) @@ -317,9 +338,9 @@ def test_renew_hook_override(context): context.certbot([ 'certonly', '-d', certname, '--preferred-challenges', 'http-01', - '--pre-hook', 'echo pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('pre', context.hook_probe), + '--post-hook', misc.echo('post', context.hook_probe), + '--deploy-hook', misc.echo('deploy', context.hook_probe), ]) assert_hook_execution(context.hook_probe, 'pre') @@ -330,14 +351,14 @@ def test_renew_hook_override(context): open(context.hook_probe, 'w').close() context.certbot([ 'renew', '--cert-name', certname, - '--pre-hook', 'echo pre-override >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo post-override >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy-override >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('pre_override', context.hook_probe), + '--post-hook', misc.echo('post_override', context.hook_probe), + '--deploy-hook', misc.echo('deploy_override', context.hook_probe), ]) - assert_hook_execution(context.hook_probe, 'pre-override') - assert_hook_execution(context.hook_probe, 'post-override') - assert_hook_execution(context.hook_probe, 'deploy-override') + assert_hook_execution(context.hook_probe, 'pre_override') + assert_hook_execution(context.hook_probe, 'post_override') + assert_hook_execution(context.hook_probe, 'deploy_override') with pytest.raises(AssertionError): assert_hook_execution(context.hook_probe, 'pre') with pytest.raises(AssertionError): @@ -349,11 +370,11 @@ def test_renew_hook_override(context): open(context.hook_probe, 'w').close() context.certbot(['renew', '--cert-name', certname]) - assert_hook_execution(context.hook_probe, 'pre-override') - assert_hook_execution(context.hook_probe, 'post-override') - assert_hook_execution(context.hook_probe, 'deploy-override') + assert_hook_execution(context.hook_probe, 'pre_override') + assert_hook_execution(context.hook_probe, 'post_override') + assert_hook_execution(context.hook_probe, 'deploy_override') + - def test_invalid_domain_with_dns_challenge(context): """Test certificate issuance failure with DNS-01 challenge.""" # Manual dns auth hooks from misc are designed to fail if the domain contains 'fail-*'. @@ -512,7 +533,7 @@ def test_revoke_multiple_lineages(context): data = file.read() data = re.sub('archive_dir = .*\n', - 'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1)), + 'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1).replace('\\', '\\\\')), data) with open(join(context.config_dir, 'renewal', '{0}.conf'.format(cert2)), 'w') as file: diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 14e2b232e..c7d92a4e6 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -3,9 +3,11 @@ Misc module contains stateless functions that could be used during pytest execut or outside during setup/teardown of the integration tests environment. """ import contextlib +import logging import errno import multiprocessing import os +import re import shutil import stat import subprocess @@ -62,6 +64,10 @@ class GracefulTCPServer(socketserver.TCPServer): allow_reuse_address = True +def _run_server(port): + GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever() + + @contextlib.contextmanager def create_http_server(port): """ @@ -74,10 +80,7 @@ def create_http_server(port): current_cwd = os.getcwd() webroot = tempfile.mkdtemp() - def run(): - GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever() - - process = multiprocessing.Process(target=run) + process = multiprocessing.Process(target=_run_server, args=(port,)) try: # SimpleHTTPServer is designed to serve files from the current working directory at the @@ -119,15 +122,9 @@ def generate_test_file_hooks(config_dir, hook_probe): :param str config_dir: current certbot config directory :param hook_probe: path to the hook probe to test hook scripts execution """ - if sys.platform == 'win32': - extension = 'bat' - else: - extension = 'sh' + hook_path = pkg_resources.resource_filename('certbot_integration_tests', 'assets/hook.py') - renewal_hooks_dirs = list_renewal_hooks_dirs(config_dir) - renewal_deploy_hook_path = os.path.join(renewal_hooks_dirs[1], 'hook.sh') - - for hook_dir in renewal_hooks_dirs: + for hook_dir in list_renewal_hooks_dirs(config_dir): # We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of # the hierarchy already exists. It is not the case of os.makedirs. Python 3 has an # optional parameter `exists_ok` to not fail on existing dir, but Python 2.7 does not. @@ -137,26 +134,25 @@ def generate_test_file_hooks(config_dir, hook_probe): except OSError as error: if error.errno != errno.EEXIST: raise - hook_path = os.path.join(hook_dir, 'hook.{0}'.format(extension)) - if extension == 'sh': - data = '''\ -#!/bin/bash -xe -if [ "$0" = "{0}" ]; then - if [ -z "$RENEWED_DOMAINS" -o -z "$RENEWED_LINEAGE" ]; then - echo "Environment variables not properly set!" >&2 - exit 1 - fi -fi -echo $(basename $(dirname "$0")) >> "{1}"\ -'''.format(renewal_deploy_hook_path, hook_probe) + + if os.name != 'nt': + entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.sh') + entrypoint_script = '''\ +#!/usr/bin/env bash +set -e +"{0}" "{1}" "{2}" "{3}" +'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe) else: - # TODO: Write the equivalent bat file for Windows - data = '''\ + entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.bat') + entrypoint_script = '''\ +@echo off +"{0}" "{1}" "{2}" "{3}" + '''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe) + + with open(entrypoint_script_path, 'w') as file_h: + file_h.write(entrypoint_script) -''' - with open(hook_path, 'w') as file: - file.write(data) - os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IEXEC) + os.chmod(entrypoint_script_path, os.stat(entrypoint_script_path).st_mode | stat.S_IEXEC) @contextlib.contextmanager @@ -193,7 +189,7 @@ for _ in range(0, 10): except requests.exceptions.ConnectionError: pass raise ValueError('Error, url did not respond after 10 attempts: {{0}}'.format(url)) -'''.format(http_server_root, http_port)) +'''.format(http_server_root.replace('\\', '\\\\'), http_port)) os.chmod(auth_script_path, 0o755) cleanup_script_path = os.path.join(tempdir, 'cleanup.py') @@ -204,7 +200,7 @@ import os import shutil well_known = os.path.join('{0}', '.well-known') shutil.rmtree(well_known) -'''.format(http_server_root)) +'''.format(http_server_root.replace('\\', '\\\\'))) os.chmod(cleanup_script_path, 0o755) yield ('{0} {1}'.format(sys.executable, auth_script_path), @@ -287,4 +283,32 @@ def load_sample_data_path(workspace): original = pkg_resources.resource_filename('certbot_integration_tests', 'assets/sample-config') copied = os.path.join(workspace, 'sample-config') shutil.copytree(original, copied, symlinks=True) + + if os.name == 'nt': + # Fix the symlinks on Windows since GIT is not creating them upon checkout + for lineage in ['a.encryption-example.com', 'b.encryption-example.com']: + current_live = os.path.join(copied, 'live', lineage) + for name in os.listdir(current_live): + if name != 'README': + current_file = os.path.join(current_live, name) + with open(current_file) as file_h: + src = file_h.read() + os.unlink(current_file) + os.symlink(os.path.join(current_live, src), current_file) + return copied + + +def echo(keyword, path=None): + """ + Generate a platform independent executable command + that echoes the given keyword into the given file. + :param keyword: the keyword to echo (must be a single keyword) + :param path: path to the file were keyword is echoed + :return: the executable command + """ + if not re.match(r'^\w+$', keyword): + raise ValueError('Error, keyword `{0}` is not a single keyword.' + .format(keyword)) + return '{0} -c "from __future__ import print_function; print(\'{1}\')"{2}'.format( + os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '') diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py index fcb393f1e..2b1557928 100644 --- a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py +++ b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py @@ -1,5 +1,4 @@ import json -import platform import os import stat @@ -13,9 +12,7 @@ ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'asse def fetch(workspace): - suffix = '{0}-{1}{2}'.format(platform.system().lower(), - platform.machine().lower().replace('x86_64', 'amd64'), - '.exe' if platform.system() == 'Windows' else '') + suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe' pebble_path = _fetch_asset('pebble', suffix) challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix) diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 91ee1403b..8ab9b9659 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -1,5 +1,7 @@ -from setuptools import setup -from setuptools import find_packages +import sys + +from distutils.version import StrictVersion +from setuptools import setup, find_packages, __version__ as setuptools_version version = '0.32.0.dev0' @@ -17,6 +19,17 @@ install_requires = [ 'six', ] +# Add pywin32 on Windows platforms to handle low-level system calls. +# This dependency needs to be added using environment markers to avoid its installation on Linux. +# However environment markers are supported only with setuptools >= 36.2. +# So this dependency is not added for old Linux distributions with old setuptools, +# in order to allow these systems to build certbot from sources. +if StrictVersion(setuptools_version) >= StrictVersion('36.2'): + install_requires.append("pywin32>=224 ; sys_platform == 'win32'") +elif 'bdist_wheel' in sys.argv[1:]: + raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' + 'of setuptools. Version 36.2+ of setuptools is required.') + setup( name='certbot-ci', version=version, diff --git a/setup.py b/setup.py index 84c27fce5..017b66619 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ install_requires = [ # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. if StrictVersion(setuptools_version) >= StrictVersion('36.2'): - install_requires.append("pywin32 ; sys_platform == 'win32'") + install_requires.append("pywin32>=224 ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' 'of setuptools. Version 36.2+ of setuptools is required.') diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 660986da9..0db06a1f1 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -61,6 +61,7 @@ pytest-sugar==0.9.2 pytest-rerunfailures==4.2 python-dateutil==2.6.1 python-digitalocean==1.11 +pywin32==224 PyYAML==3.13 repoze.sphinx.autointerface==0.8 requests-file==1.4.2 diff --git a/tox.ini b/tox.ini index bbe3c7a05..a4f4bd3e3 100644 --- a/tox.ini +++ b/tox.ini @@ -232,6 +232,15 @@ commands = coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 passenv = DOCKER_* +[testenv:integration-certbot] +commands = + {[base]pip_install} acme . certbot-ci + pytest certbot-ci/certbot_integration_tests/certbot_tests \ + --acme-server={env:ACME_SERVER:pebble} \ + --cov=acme --cov=certbot --cov-report= \ + --cov-config=certbot-ci/certbot_integration_tests/.coveragerc + coverage report --include 'certbot/*' --show-missing --fail-under=62 + [testenv:integration-certbot-oldest] commands = {[base]pip_install} . -- cgit v1.2.3 From e2844bd0ad9e3c032c156dc020dbf41d23539055 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 23 Jul 2019 16:56:20 -0700 Subject: Add RHEL8 to test farm targets * Add RHEL 8 to targets * Use latest certbot-auto to bootstrap. * Workaround leauto failures. --- tests/letstest/scripts/test_leauto_upgrades.sh | 11 +++++++++++ tests/letstest/scripts/test_sdists.sh | 2 +- tests/letstest/targets.yaml | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 49606b49c..1e1784883 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -26,6 +26,17 @@ else # 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. INITIAL_VERSION="0.33.1" fi + +# If we're on RHEL 8, the initial version of certbot-auto will fail until we do +# a release including https://github.com/certbot/certbot/pull/7240 and update +# INITIAL_VERSION above to use a version containing this fix. This works around +# the problem for now so we can successfully run tests on RHEL 8. +RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` +RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) | cut -d '.' -f1 || echo "0"` +if [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + sudo yum install python3-virtualenv -y +fi + git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index e48e95848..347589e04 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,7 +1,7 @@ #!/bin/sh -xe cd letsencrypt -./certbot-auto --install-only -n --debug +letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index c3906209d..d592e058a 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -45,6 +45,11 @@ targets: type: centos virt: hvm user: ec2-user + - ami: ami-0c322300a1dd5dc79 + name: RHEL8 + type: centos + virt: hvm + user: ec2-user - ami: ami-00bbc6858140f19ed name: fedora30 type: centos -- cgit v1.2.3 From dded9290b74ed36259cf9a5bf11f9beabd9c9aed Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 7 Aug 2019 10:26:34 -0700 Subject: Update changelog for 0.37.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39891c8b2..4e0533bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.37.0 - master +## 0.37.0 - 2019-08-07 ### Added -- cgit v1.2.3 From 987ce2c6b29d30d898e8032f3f19d174adbd4c48 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 7 Aug 2019 10:35:11 -0700 Subject: Release 0.37.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 +- certbot-auto | 53 +++++++++++++++------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 53 +++++++++++++++------- letsencrypt-auto-source/certbot-auto.asc | 16 +++---- letsencrypt-auto-source/letsencrypt-auto | 26 +++++------ letsencrypt-auto-source/letsencrypt-auto.sig | 3 +- .../pieces/certbot-requirements.txt | 24 +++++----- 27 files changed, 129 insertions(+), 90 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 5493df0cc..ec9a26981 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.0.dev0' +version = '0.37.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da509406e..aafd37702 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.37.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 98b7382db..4dcf9c899 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.37.0.dev0', + 'certbot>=0.37.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index fd2cfb29b..90b7c951e 100755 --- a/certbot-auto +++ b/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.36.0" +LE_AUTO_VERSION="0.37.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -755,13 +755,31 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +793,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { @@ -1314,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.36.0 \ - --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ - --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 -acme==0.36.0 \ - --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ - --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 -certbot-apache==0.36.0 \ - --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ - --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd -certbot-nginx==0.36.0 \ - --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ - --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 +certbot==0.37.0 \ + --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ + --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a +acme==0.37.0 \ + --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ + --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 +certbot-apache==0.37.0 \ + --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ + --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 +certbot-nginx==0.37.0 \ + --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ + --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8c04a96ae..bd6c4ed9f 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 8a7a36c2e..a0f7b3aec 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 196a3641f..cf037f653 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 466ab60ec..0f3dd14dc 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 26eaaf7c2..c8ffcb5b1 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index db36f256f..082d48d24 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 975d79231..a9f6fceef 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a7cae3bbe..a42f91830 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 81329ee8d..7ec80dcb7 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 4ee89f076..9e6c3f6aa 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 73fbf768b..a69490708 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index b18dc5057..0ae9d5a03 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index f78f7a2c7..388b63a3d 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 43f8081fe..c8bcf2e4c 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 17e419ae0..e57297036 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0.dev0' +version = '0.37.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index c30202272..3137760a0 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0.dev0' +version = '0.37.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index f5bd37e3c..99f77cec6 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.0.dev0' +__version__ = '0.37.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 9532b7b22..09075e61a 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.36.0 + "". (default: CertbotACMEClient/0.37.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index fd2cfb29b..90b7c951e 100755 --- a/letsencrypt-auto +++ b/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.36.0" +LE_AUTO_VERSION="0.37.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -755,13 +755,31 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +793,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { @@ -1314,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.36.0 \ - --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ - --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 -acme==0.36.0 \ - --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ - --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 -certbot-apache==0.36.0 \ - --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ - --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd -certbot-nginx==0.36.0 \ - --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ - --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 +certbot==0.37.0 \ + --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ + --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a +acme==0.37.0 \ + --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ + --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 +certbot-apache==0.37.0 \ + --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ + --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 +certbot-nginx==0.37.0 \ + --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ + --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index e102e3720..d015c7cc8 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl0njnkACgkQTRfJlc2X -dfIqjwf/XBEzOEtwi84v97jZjHi9bqeFBzAt+n6YXleKySk8anxFMFmFIvrc2w/U -eyskpn0mmJDX2LjaXcsJji+l5yAWbm3p8M2J2toaPI2TLznhM6+uEWP62BHJiQYi -1ORBJYATSfLxA541CwXXW3VTYDu+CLq0w1nr5mHg1Y20ZFBrPIlt04mkh9o70fD4 -qv6MsMXKZxglhH1ORyLMVn5Jze22awmJ944pP8aI54ZEkTl2XT9DsZt3QpZ1muOy -IRg6sU86ukgWK66zWjTyd1AOddDL2OY3+U7JachFd5eb7dnnaCGeZhCjfVide7a3 -Fk8NrXwlrpKKJYkbqDfRkT4Pba47VQ== -=gf1k +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1LC8MACgkQTRfJlc2X +dfLT8gf/aBYJDQQcRyS9p72t2e7hHgbDuLDIS8GeBSwMxNp9caDr7m5O1eLDOHIq +VtbCRR2c1CIWq2WlJ/WJLIf9I5MHjkr7/+xFkYuybU0waEceC1RgkI6d49gPhg2V +iTbDErAOW9Ito2TNbJJX+VZfTHI1JH0BXNL+TIl7nLWoXTvI+65gzSiv/ng8WD+6 +Dd2ibZGV7AlcHS1+i3iNEFPNc9qALkImfAvZssU3CfRgO/WQphlgvXDkSYRP8cDx +esjyUniHjd4N6tr2WSaD9do2ZVbMuJtPWt7JrIMJHs+UgB8BaAQCSzmv2ItWXD9t +o1zHyEUEgMuV49GZRdnNM+6aEZt7xA== +=vP3K -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 7f3e06398..90b7c951e 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.0.dev0" +LE_AUTO_VERSION="0.37.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.36.0 \ - --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ - --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 -acme==0.36.0 \ - --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ - --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 -certbot-apache==0.36.0 \ - --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ - --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd -certbot-nginx==0.36.0 \ - --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ - --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 +certbot==0.37.0 \ + --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ + --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a +acme==0.37.0 \ + --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ + --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 +certbot-apache==0.37.0 \ + --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ + --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 +certbot-nginx==0.37.0 \ + --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ + --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 9430fd293..015f6741e 100644 --- a/letsencrypt-auto-source/letsencrypt-auto.sig +++ b/letsencrypt-auto-source/letsencrypt-auto.sig @@ -1 +1,2 @@ -Å„–Üp|?Ê¢5ªÇgÒl,ÂzCHvöW3–tœÌºZê| Þ3µ‚­^¹Ç 7Iª‹Âœ2¦¾ä^m¡(Þ`…X™í²ËÅq. U“±DDR»À]À£ÁGý{ìÁ¨"øËâòÐæÆ°”9]æ{×}ºMž g/åAüA"ºš³ø×£×ûÆáêñ³¬¼B c˜R{÷¶²Gâ}dÎ$H&”[éðµÉâiÀ­C^Y­˜¡îl ÒS·nüE?7¼TÙ¡†BÓò{ÊÅv—!n/…q~‘Hh•#R£9Èb€×}j5:}šO®3_»ìaª}¬Í­þ \ No newline at end of file +8êOJ$ô=´:ê_­ìãz–fT!vhÛ‰˜²°Äeâ «†òˆLùÑ ²5Môɨ~Çìû#lþªÁ;QY:@&]tœ ‡6Iš?7ã}¹µª8è \o/A‚yVÞ…­ù#p·•!s'–O|Š#9t½Ç×ÃØryû´©T¨ÎŽ€Mà¦G%YžŸ¹UQ¸AÓÓ¸þœãä¬Ú.Éá|{OVóSh>ÎÙÙlïæ» íCòSeKæÎeÀu+Ô?â_’É] +p<àŒO;$2ïxØæÏUth§A±¼{?ÄŒá@Ðçëó&ÅZM \ No newline at end of file diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index ee5705766..59892c207 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.36.0 \ - --hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \ - --hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2 -acme==0.36.0 \ - --hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \ - --hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668 -certbot-apache==0.36.0 \ - --hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \ - --hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd -certbot-nginx==0.36.0 \ - --hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \ - --hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322 +certbot==0.37.0 \ + --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ + --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a +acme==0.37.0 \ + --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ + --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 +certbot-apache==0.37.0 \ + --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ + --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 +certbot-nginx==0.37.0 \ + --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ + --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe -- cgit v1.2.3 From 0c04ce3c32d954d63db4f54c12d6c1467dd7869e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 7 Aug 2019 10:35:13 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0533bae..fbe7e0706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.38.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.37.0 - 2019-08-07 ### Added -- cgit v1.2.3 From d978440cb5b096c65db33d838b95d5810f27dfde Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 7 Aug 2019 10:35:13 -0700 Subject: Bump version to 0.38.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index ec9a26981..445886ac4 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 4dcf9c899..810c00594 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index bd6c4ed9f..33d353423 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index a0f7b3aec..31d70e72a 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index cf037f653..85f24bb9d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 0f3dd14dc..e12c7fad9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index c8ffcb5b1..8bb303b6b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 082d48d24..6ee65fded 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index a9f6fceef..2ffbaa128 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a42f91830..adee66a48 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 7ec80dcb7..9f239f6c8 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 9e6c3f6aa..8d83d08b5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index a69490708..59d2feb51 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 0ae9d5a03..0982f08dc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 388b63a3d..416f221f0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index c8bcf2e4c..a4bbd8c60 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index e57297036..901ed3060 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 3137760a0..64e24666e 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 99f77cec6..c800bda3f 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.0' +__version__ = '0.38.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 90b7c951e..73832faee 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.0" +LE_AUTO_VERSION="0.38.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 3d3cbc0d16733cbc8c0dd373a9918bceb7d7aa00 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 7 Aug 2019 15:07:37 -0700 Subject: Don't run tox -e cover. (#7312) --- docs/contributing.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 8aeef54cc..1051413ae 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -114,9 +114,9 @@ Once you are done with your code changes, and the tests in ``foo_test.py`` pass, run all of the unittests for Certbot with ``tox -e py27`` (this uses Python 2.7). -Once all the unittests pass, check for sufficient test coverage using -``tox -e cover``, and then check for code style with ``tox -e lint`` (all files) -or ``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). +Once all the unittests pass, check for sufficient test coverage using ``tox -e +py27-cover``, and then check for code style with ``tox -e lint`` (all files) or +``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). Once all of the above is successful, you may run the full test suite using ``tox --skip-missing-interpreters``. We recommend running the commands above -- cgit v1.2.3 From 2911eda3bda95340b12d6deb096e7c15bdd5f44b Mon Sep 17 00:00:00 2001 From: Matt Nordhoff Date: Thu, 8 Aug 2019 18:44:21 +0000 Subject: Update link to the Server forum category (#7309) Let's Encrypt closed it in favor of the Help category. https://community.letsencrypt.org/t/closing-the-server-category/93016 --- CHANGELOG.md | 4 +++- certbot/client.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe7e0706..de2fdfb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* If Certbot fails to rollback your server configuration, the error message + links to the Let's Encrypt forum. Change the link to the Help category now + that the Server category has been closed. ### Fixed diff --git a/certbot/client.py b/certbot/client.py index 7372d6d9d..c1199daac 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -624,7 +624,7 @@ class Client(object): reporter.add_message( "An error occurred and we failed to restore your config and " "restart your server. Please post to " - "https://community.letsencrypt.org/c/server-config " + "https://community.letsencrypt.org/c/help " "with details about your configuration and this error you received.", reporter.HIGH_PRIORITY) raise -- cgit v1.2.3 From 120137eb8dc0640cc4bd8c66e710415bf9b164c2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 8 Aug 2019 16:23:37 -0700 Subject: Revert disabling TLS session tickets in Apache (#7315) See https://community.letsencrypt.org/t/ssl-error-after-cert-renew/99430. The first commit of this PR is a simple, clean revert of #7191. Subsequent commits add back pieces of that PR we want to keep. I also reverted #7299 which landed in a separate PR, but needs to be reverted to keep including the TLS config files in the certbot-apache package when it is built. I tested this on Ubuntu 18.04 by installing a cert to Apache using Certbot master and then running certbot renew with this branch. I watched the Apache plugin update the configuration file to remove SSLSessionTickets off. * Revert "Disable TLS session tickets for Apache 2.4.11+ (#7191)" This reverts commit 9174c631d9965834f263ea7ff842d8d2087f47c7. * Keep hashes with TLS session tickets disabled. * dont delete changelog entries * add changelog entry * Revert "Clean the useless entries in MANIFEST.in (#7299)" This reverts commit f4d17d9a6b0f2ba997cdc8da75dd60c1bbf5ebf1. --- CHANGELOG.md | 3 ++- certbot-apache/MANIFEST.in | 3 ++- certbot-apache/certbot_apache/apache_util.py | 14 ----------- .../certbot_apache/centos-options-ssl-apache.conf | 25 ++++++++++++++++++++ certbot-apache/certbot_apache/configurator.py | 20 ++++------------ .../certbot_apache/options-ssl-apache.conf | 26 +++++++++++++++++++++ certbot-apache/certbot_apache/override_arch.py | 4 ++++ certbot-apache/certbot_apache/override_centos.py | 15 +++--------- certbot-apache/certbot_apache/override_darwin.py | 4 ++++ certbot-apache/certbot_apache/override_debian.py | 3 +++ certbot-apache/certbot_apache/override_fedora.py | 4 ++++ certbot-apache/certbot_apache/override_gentoo.py | 4 ++++ certbot-apache/certbot_apache/override_suse.py | 4 ++++ certbot-apache/certbot_apache/tests/centos_test.py | 7 ------ .../certbot_apache/tests/configurator_test.py | 27 ++++++---------------- .../centos-current-options-ssl-apache.conf | 19 --------------- .../tls_configs/centos-old-options-ssl-apache.conf | 18 --------------- .../tls_configs/current-options-ssl-apache.conf | 20 ---------------- .../tls_configs/old-options-ssl-apache.conf | 19 --------------- 19 files changed, 93 insertions(+), 146 deletions(-) create mode 100644 certbot-apache/certbot_apache/centos-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index de2fdfb03..9d96f2f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Stop disabling TLS session tickets in Apache as it caused TLS failures on + some systems. More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index c1f79fcc7..3e594a953 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -2,5 +2,6 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_apache/tests/testdata * +include certbot_apache/centos-options-ssl-apache.conf +include certbot_apache/options-ssl-apache.conf recursive-include certbot_apache/augeas_lens *.aug -recursive-include certbot_apache/tls_configs *.conf diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index f338c0407..7a2ecf49b 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,8 +1,6 @@ """ Utility functions for certbot-apache plugin """ import binascii -import pkg_resources - from certbot import util from certbot.compat import os @@ -107,15 +105,3 @@ def parse_define_file(filepath, varname): def unique_id(): """ Returns an unique id to be used as a VirtualHost identifier""" return binascii.hexlify(os.urandom(16)).decode("utf-8") - - -def find_ssl_apache_conf(prefix): - """ - Find a TLS Apache config file in the dedicated storage. - :param str prefix: prefix of the TLS Apache config file to find - :return: the path the TLS Apache config file - :rtype: str - """ - return pkg_resources.resource_filename( - "certbot_apache", - os.path.join("tls_configs", "{0}-options-ssl-apache.conf".format(prefix))) diff --git a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf new file mode 100644 index 000000000..56c946a4e --- /dev/null +++ b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf @@ -0,0 +1,25 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index ecc7c83ab..f7c27bf76 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -9,6 +9,7 @@ import time from collections import defaultdict +import pkg_resources import six import zope.component @@ -109,24 +110,14 @@ class ApacheConfigurator(common.Installer): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def option(self, key): """Get a value from options""" return self.options.get(key) - def pick_apache_config(self): - """ - Pick the appropriate TLS Apache configuration file for current version of Apache and OS. - :return: the path to the TLS Apache configuration file to use - :rtype: str - """ - # Disabling TLS session tickets is supported by Apache 2.4.11+. - # So for old versions of Apache we pick a configuration without this option. - if self.version < (2, 4, 11): - return apache_util.find_ssl_apache_conf("old") - return apache_util.find_ssl_apache_conf("current") - def _prepare_options(self): """ Set the values possibly changed by command line parameters to @@ -2348,9 +2339,8 @@ class ApacheConfigurator(common.Installer): # XXX if we ever try to enforce a local privilege boundary (eg, running # certbot for unprivileged users via setuid), this function will need # to be modified. - apache_config_path = self.pick_apache_config() - return common.install_version_controlled_file( - options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES) + return common.install_version_controlled_file(options_ssl, options_ssl_digest, + self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES) def enable_autohsts(self, _unused_lineage, domains): """ diff --git a/certbot-apache/certbot_apache/options-ssl-apache.conf b/certbot-apache/certbot_apache/options-ssl-apache.conf new file mode 100644 index 000000000..8113ee81e --- /dev/null +++ b/certbot-apache/certbot_apache/options-ssl-apache.conf @@ -0,0 +1,26 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/override_arch.py index 02891548d..c5620e9f9 100644 --- a/certbot-apache/certbot_apache/override_arch.py +++ b/certbot-apache/certbot_apache/override_arch.py @@ -1,4 +1,6 @@ """ Distribution specific override class for Arch Linux """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class ArchConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index d4a7d7137..7c7492dbf 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -1,6 +1,7 @@ """ Distribution specific override class for CentOS family (RHEL, Fedora) """ import logging +import pkg_resources import zope.interface from certbot import errors @@ -38,6 +39,8 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): @@ -72,18 +75,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator): # Finish with actual config check to see if systemctl restart helped super(CentOSConfigurator, self).config_test() - def pick_apache_config(self): - """ - Pick the appropriate TLS Apache configuration file for current version of Apache and OS. - :return: the path to the TLS Apache configuration file to use - :rtype: str - """ - # Disabling TLS session tickets is supported by Apache 2.4.11+. - # So for old versions of Apache we pick a configuration without this option. - if self.version < (2, 4, 11): - return apache_util.find_ssl_apache_conf("centos-old") - return apache_util.find_ssl_apache_conf("centos-current") - def _prepare_options(self): """ Override the options dictionary initialization in order to support diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/override_darwin.py index e825b66b8..4e2a6acac 100644 --- a/certbot-apache/certbot_apache/override_darwin.py +++ b/certbot-apache/certbot_apache/override_darwin.py @@ -1,4 +1,6 @@ """ Distribution specific override class for macOS """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class DarwinConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/other", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index 1fc32670b..58492bd01 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -1,6 +1,7 @@ """ Distribution specific override class for Debian family (Ubuntu/Debian) """ import logging +import pkg_resources import zope.interface from certbot import errors @@ -34,6 +35,8 @@ class DebianConfigurator(configurator.ApacheConfigurator): handle_modules=True, handle_sites=True, challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def enable_site(self, vhost): diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py index 77f31efe8..786ada0fc 100644 --- a/certbot-apache/certbot_apache/override_fedora.py +++ b/certbot-apache/certbot_apache/override_fedora.py @@ -1,4 +1,5 @@ """ Distribution specific override class for Fedora 29+ """ +import pkg_resources import zope.interface from certbot import errors @@ -30,6 +31,9 @@ class FedoraConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + # TODO: eventually newest version of Fedora will need their own config + "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index 6fa033857..c358a10fa 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -1,4 +1,6 @@ """ Distribution specific override class for Gentoo Linux """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -27,6 +29,8 @@ class GentooConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def _prepare_options(self): diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/override_suse.py index 4baa57497..3d0043afe 100644 --- a/certbot-apache/certbot_apache/override_suse.py +++ b/certbot-apache/certbot_apache/override_suse.py @@ -1,4 +1,6 @@ """ Distribution specific override class for OpenSUSE """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index 5c8cff3b3..dddbf489e 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -190,13 +190,6 @@ class MultipleVhostsTestCentOS(util.ApacheTest): errors.SubprocessError] self.assertRaises(errors.MisconfigurationError, self.config.restart) - def test_pick_correct_tls_config(self): - self.config.version = (2, 4, 10) - self.assertTrue('centos-old' in self.config.pick_apache_config()) - - self.config.version = (2, 4, 11) - self.assertTrue('centos-current' in self.config.pick_apache_config()) - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 2bc2271a1..1eafae982 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1706,7 +1706,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.config.updated_mod_ssl_conf_digest) def _current_ssl_options_hash(self): - return crypto_util.sha256sum(self.config.pick_apache_config()) + return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC")) def _assert_current_file(self): self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) @@ -1742,7 +1742,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.assertFalse(mock_logger.warning.called) self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), + self.config.option("MOD_SSL_CONF_SRC")), self._current_ssl_options_hash()) self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) @@ -1758,31 +1758,18 @@ class InstallSslOptionsConfTest(util.ApacheTest): "%s has been manually modified; updated file " "saved to %s. We recommend updating %s for security purposes.") self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), + self.config.option("MOD_SSL_CONF_SRC")), self._current_ssl_options_hash()) # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertFalse(mock_logger.warning.called) - def test_ssl_config_files_hash_in_all_hashes(self): - """ - It is really critical that all TLS Apache config files have their SHA256 hash registered in - constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config - file has been manually edited by the user, and will refuse to update it. - This test ensures that all necessary hashes are present. - """ + def test_current_file_hash_in_all_hashes(self): from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES - import pkg_resources - tls_configs_dir = pkg_resources.resource_filename("certbot_apache", "tls_configs") - all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) - if name.endswith('options-ssl-apache.conf')] - self.assertTrue(all_files) - for one_file in all_files: - file_hash = crypto_util.sha256sum(one_file) - self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " - "hash of {0} when it is updated.".format(one_file)) + self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended" + " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") if __name__ == "__main__": diff --git a/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf deleted file mode 100644 index 2d99f6219..000000000 --- a/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLSessionTickets off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf deleted file mode 100644 index 277c8954a..000000000 --- a/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf deleted file mode 100644 index c32e83148..000000000 --- a/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf +++ /dev/null @@ -1,20 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off -SSLSessionTickets off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf deleted file mode 100644 index cd7c9bc4b..000000000 --- a/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common -- cgit v1.2.3 From e21401004b9a57c5c721ee0b914a73b39e6e4a42 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 8 Aug 2019 16:36:45 -0700 Subject: Revert disabling TLS session tickets in Apache (#7315) (#7316) See https://community.letsencrypt.org/t/ssl-error-after-cert-renew/99430. The first commit of this PR is a simple, clean revert of #7191. Subsequent commits add back pieces of that PR we want to keep. I also reverted #7299 which landed in a separate PR, but needs to be reverted to keep including the TLS config files in the certbot-apache package when it is built. I tested this on Ubuntu 18.04 by installing a cert to Apache using Certbot master and then running certbot renew with this branch. I watched the Apache plugin update the configuration file to remove SSLSessionTickets off. * Revert "Disable TLS session tickets for Apache 2.4.11+ (#7191)" This reverts commit 9174c631d9965834f263ea7ff842d8d2087f47c7. * Keep hashes with TLS session tickets disabled. * dont delete changelog entries * add changelog entry * Revert "Clean the useless entries in MANIFEST.in (#7299)" This reverts commit f4d17d9a6b0f2ba997cdc8da75dd60c1bbf5ebf1. (cherry picked from commit 120137eb8dc0640cc4bd8c66e710415bf9b164c2) --- CHANGELOG.md | 9 ++++++++ certbot-apache/MANIFEST.in | 3 ++- certbot-apache/certbot_apache/apache_util.py | 14 ----------- .../certbot_apache/centos-options-ssl-apache.conf | 25 ++++++++++++++++++++ certbot-apache/certbot_apache/configurator.py | 20 ++++------------ .../certbot_apache/options-ssl-apache.conf | 26 +++++++++++++++++++++ certbot-apache/certbot_apache/override_arch.py | 4 ++++ certbot-apache/certbot_apache/override_centos.py | 15 +++--------- certbot-apache/certbot_apache/override_darwin.py | 4 ++++ certbot-apache/certbot_apache/override_debian.py | 3 +++ certbot-apache/certbot_apache/override_fedora.py | 4 ++++ certbot-apache/certbot_apache/override_gentoo.py | 4 ++++ certbot-apache/certbot_apache/override_suse.py | 4 ++++ certbot-apache/certbot_apache/tests/centos_test.py | 7 ------ .../certbot_apache/tests/configurator_test.py | 27 ++++++---------------- .../centos-current-options-ssl-apache.conf | 19 --------------- .../tls_configs/centos-old-options-ssl-apache.conf | 18 --------------- .../tls_configs/current-options-ssl-apache.conf | 20 ---------------- .../tls_configs/old-options-ssl-apache.conf | 19 --------------- 19 files changed, 100 insertions(+), 145 deletions(-) create mode 100644 certbot-apache/certbot_apache/centos-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0533bae..a52ee7585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.37.1 - master + +### Fixed + +* Stop disabling TLS session tickets in Apache as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + ## 0.37.0 - 2019-08-07 ### Added diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index c1f79fcc7..3e594a953 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -2,5 +2,6 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_apache/tests/testdata * +include certbot_apache/centos-options-ssl-apache.conf +include certbot_apache/options-ssl-apache.conf recursive-include certbot_apache/augeas_lens *.aug -recursive-include certbot_apache/tls_configs *.conf diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py index f338c0407..7a2ecf49b 100644 --- a/certbot-apache/certbot_apache/apache_util.py +++ b/certbot-apache/certbot_apache/apache_util.py @@ -1,8 +1,6 @@ """ Utility functions for certbot-apache plugin """ import binascii -import pkg_resources - from certbot import util from certbot.compat import os @@ -107,15 +105,3 @@ def parse_define_file(filepath, varname): def unique_id(): """ Returns an unique id to be used as a VirtualHost identifier""" return binascii.hexlify(os.urandom(16)).decode("utf-8") - - -def find_ssl_apache_conf(prefix): - """ - Find a TLS Apache config file in the dedicated storage. - :param str prefix: prefix of the TLS Apache config file to find - :return: the path the TLS Apache config file - :rtype: str - """ - return pkg_resources.resource_filename( - "certbot_apache", - os.path.join("tls_configs", "{0}-options-ssl-apache.conf".format(prefix))) diff --git a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf new file mode 100644 index 000000000..56c946a4e --- /dev/null +++ b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf @@ -0,0 +1,25 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index ecc7c83ab..f7c27bf76 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -9,6 +9,7 @@ import time from collections import defaultdict +import pkg_resources import six import zope.component @@ -109,24 +110,14 @@ class ApacheConfigurator(common.Installer): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def option(self, key): """Get a value from options""" return self.options.get(key) - def pick_apache_config(self): - """ - Pick the appropriate TLS Apache configuration file for current version of Apache and OS. - :return: the path to the TLS Apache configuration file to use - :rtype: str - """ - # Disabling TLS session tickets is supported by Apache 2.4.11+. - # So for old versions of Apache we pick a configuration without this option. - if self.version < (2, 4, 11): - return apache_util.find_ssl_apache_conf("old") - return apache_util.find_ssl_apache_conf("current") - def _prepare_options(self): """ Set the values possibly changed by command line parameters to @@ -2348,9 +2339,8 @@ class ApacheConfigurator(common.Installer): # XXX if we ever try to enforce a local privilege boundary (eg, running # certbot for unprivileged users via setuid), this function will need # to be modified. - apache_config_path = self.pick_apache_config() - return common.install_version_controlled_file( - options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES) + return common.install_version_controlled_file(options_ssl, options_ssl_digest, + self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES) def enable_autohsts(self, _unused_lineage, domains): """ diff --git a/certbot-apache/certbot_apache/options-ssl-apache.conf b/certbot-apache/certbot_apache/options-ssl-apache.conf new file mode 100644 index 000000000..8113ee81e --- /dev/null +++ b/certbot-apache/certbot_apache/options-ssl-apache.conf @@ -0,0 +1,26 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/override_arch.py index 02891548d..c5620e9f9 100644 --- a/certbot-apache/certbot_apache/override_arch.py +++ b/certbot-apache/certbot_apache/override_arch.py @@ -1,4 +1,6 @@ """ Distribution specific override class for Arch Linux """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class ArchConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py index d4a7d7137..7c7492dbf 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/override_centos.py @@ -1,6 +1,7 @@ """ Distribution specific override class for CentOS family (RHEL, Fedora) """ import logging +import pkg_resources import zope.interface from certbot import errors @@ -38,6 +39,8 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): @@ -72,18 +75,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator): # Finish with actual config check to see if systemctl restart helped super(CentOSConfigurator, self).config_test() - def pick_apache_config(self): - """ - Pick the appropriate TLS Apache configuration file for current version of Apache and OS. - :return: the path to the TLS Apache configuration file to use - :rtype: str - """ - # Disabling TLS session tickets is supported by Apache 2.4.11+. - # So for old versions of Apache we pick a configuration without this option. - if self.version < (2, 4, 11): - return apache_util.find_ssl_apache_conf("centos-old") - return apache_util.find_ssl_apache_conf("centos-current") - def _prepare_options(self): """ Override the options dictionary initialization in order to support diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/override_darwin.py index e825b66b8..4e2a6acac 100644 --- a/certbot-apache/certbot_apache/override_darwin.py +++ b/certbot-apache/certbot_apache/override_darwin.py @@ -1,4 +1,6 @@ """ Distribution specific override class for macOS """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class DarwinConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/other", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py index 1fc32670b..58492bd01 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/override_debian.py @@ -1,6 +1,7 @@ """ Distribution specific override class for Debian family (Ubuntu/Debian) """ import logging +import pkg_resources import zope.interface from certbot import errors @@ -34,6 +35,8 @@ class DebianConfigurator(configurator.ApacheConfigurator): handle_modules=True, handle_sites=True, challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def enable_site(self, vhost): diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py index 77f31efe8..786ada0fc 100644 --- a/certbot-apache/certbot_apache/override_fedora.py +++ b/certbot-apache/certbot_apache/override_fedora.py @@ -1,4 +1,5 @@ """ Distribution specific override class for Fedora 29+ """ +import pkg_resources import zope.interface from certbot import errors @@ -30,6 +31,9 @@ class FedoraConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + # TODO: eventually newest version of Fedora will need their own config + "certbot_apache", "centos-options-ssl-apache.conf") ) def config_test(self): diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py index 6fa033857..c358a10fa 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/override_gentoo.py @@ -1,4 +1,6 @@ """ Distribution specific override class for Gentoo Linux """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -27,6 +29,8 @@ class GentooConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) def _prepare_options(self): diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/override_suse.py index 4baa57497..3d0043afe 100644 --- a/certbot-apache/certbot_apache/override_suse.py +++ b/certbot-apache/certbot_apache/override_suse.py @@ -1,4 +1,6 @@ """ Distribution specific override class for OpenSUSE """ +import pkg_resources + import zope.interface from certbot import interfaces @@ -24,4 +26,6 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator): handle_modules=False, handle_sites=False, challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", "options-ssl-apache.conf") ) diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index 5c8cff3b3..dddbf489e 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -190,13 +190,6 @@ class MultipleVhostsTestCentOS(util.ApacheTest): errors.SubprocessError] self.assertRaises(errors.MisconfigurationError, self.config.restart) - def test_pick_correct_tls_config(self): - self.config.version = (2, 4, 10) - self.assertTrue('centos-old' in self.config.pick_apache_config()) - - self.config.version = (2, 4, 11) - self.assertTrue('centos-current' in self.config.pick_apache_config()) - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 2bc2271a1..1eafae982 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1706,7 +1706,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.config.updated_mod_ssl_conf_digest) def _current_ssl_options_hash(self): - return crypto_util.sha256sum(self.config.pick_apache_config()) + return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC")) def _assert_current_file(self): self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) @@ -1742,7 +1742,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.assertFalse(mock_logger.warning.called) self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), + self.config.option("MOD_SSL_CONF_SRC")), self._current_ssl_options_hash()) self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), self._current_ssl_options_hash()) @@ -1758,31 +1758,18 @@ class InstallSslOptionsConfTest(util.ApacheTest): "%s has been manually modified; updated file " "saved to %s. We recommend updating %s for security purposes.") self.assertEqual(crypto_util.sha256sum( - self.config.pick_apache_config()), + self.config.option("MOD_SSL_CONF_SRC")), self._current_ssl_options_hash()) # only print warning once with mock.patch("certbot.plugins.common.logger") as mock_logger: self._call() self.assertFalse(mock_logger.warning.called) - def test_ssl_config_files_hash_in_all_hashes(self): - """ - It is really critical that all TLS Apache config files have their SHA256 hash registered in - constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config - file has been manually edited by the user, and will refuse to update it. - This test ensures that all necessary hashes are present. - """ + def test_current_file_hash_in_all_hashes(self): from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES - import pkg_resources - tls_configs_dir = pkg_resources.resource_filename("certbot_apache", "tls_configs") - all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) - if name.endswith('options-ssl-apache.conf')] - self.assertTrue(all_files) - for one_file in all_files: - file_hash = crypto_util.sha256sum(one_file) - self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " - "hash of {0} when it is updated.".format(one_file)) + self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended" + " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") if __name__ == "__main__": diff --git a/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf deleted file mode 100644 index 2d99f6219..000000000 --- a/certbot-apache/certbot_apache/tls_configs/centos-current-options-ssl-apache.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLSessionTickets off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf deleted file mode 100644 index 277c8954a..000000000 --- a/certbot-apache/certbot_apache/tls_configs/centos-old-options-ssl-apache.conf +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf deleted file mode 100644 index c32e83148..000000000 --- a/certbot-apache/certbot_apache/tls_configs/current-options-ssl-apache.conf +++ /dev/null @@ -1,20 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off -SSLSessionTickets off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf b/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf deleted file mode 100644 index cd7c9bc4b..000000000 --- a/certbot-apache/certbot_apache/tls_configs/old-options-ssl-apache.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common -- cgit v1.2.3 From c5e1be4fd7092bd859e7b306a3ee744d6b3323f2 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 8 Aug 2019 16:39:43 -0700 Subject: Update changelog for 0.37.1 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52ee7585..5a5e7207f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.37.1 - master +## 0.37.1 - 2019-08-08 ### Fixed -- cgit v1.2.3 From 48d9715bd5893c464d04b3bf7aec677116989a50 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 8 Aug 2019 17:01:32 -0700 Subject: Release 0.37.1 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 26 ++++++++++----------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------- letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++++---------- 26 files changed, 79 insertions(+), 79 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index ec9a26981..4e37b7547 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.37.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 4dcf9c899..900bf1086 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 90b7c951e..15623463b 100755 --- a/certbot-auto +++ b/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.37.0" +LE_AUTO_VERSION="0.37.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.0 \ - --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ - --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a -acme==0.37.0 \ - --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ - --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 -certbot-apache==0.37.0 \ - --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ - --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 -certbot-nginx==0.37.0 \ - --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ - --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe +certbot==0.37.1 \ + --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ + --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 +acme==0.37.1 \ + --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ + --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 +certbot-apache==0.37.1 \ + --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ + --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c +certbot-nginx==0.37.1 \ + --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ + --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index bd6c4ed9f..6806b0040 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index a0f7b3aec..f53fd9376 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index cf037f653..3e8a0de8f 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 0f3dd14dc..4697d7984 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index c8ffcb5b1..1d13a7adc 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 082d48d24..7fc711921 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index a9f6fceef..853b52c3a 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a42f91830..7fc5d127b 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 7ec80dcb7..2260518fd 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 9e6c3f6aa..c07faf9f6 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index a69490708..4a1bf25cd 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 0ae9d5a03..a9d562ea9 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 388b63a3d..ae975a81a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index c8bcf2e4c..af09b3ee4 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index e57297036..76e1d8224 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.0' +version = '0.37.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 3137760a0..2701bb373 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.0' +version = '0.37.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 99f77cec6..9fbd657b3 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.0' +__version__ = '0.37.1' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 09075e61a..e7aa03d11 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.37.0 + "". (default: CertbotACMEClient/0.37.1 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 90b7c951e..15623463b 100755 --- a/letsencrypt-auto +++ b/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.37.0" +LE_AUTO_VERSION="0.37.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.0 \ - --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ - --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a -acme==0.37.0 \ - --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ - --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 -certbot-apache==0.37.0 \ - --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ - --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 -certbot-nginx==0.37.0 \ - --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ - --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe +certbot==0.37.1 \ + --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ + --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 +acme==0.37.1 \ + --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ + --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 +certbot-apache==0.37.1 \ + --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ + --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c +certbot-nginx==0.37.1 \ + --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ + --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index d015c7cc8..a9f7e1e9f 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1LC8MACgkQTRfJlc2X -dfLT8gf/aBYJDQQcRyS9p72t2e7hHgbDuLDIS8GeBSwMxNp9caDr7m5O1eLDOHIq -VtbCRR2c1CIWq2WlJ/WJLIf9I5MHjkr7/+xFkYuybU0waEceC1RgkI6d49gPhg2V -iTbDErAOW9Ito2TNbJJX+VZfTHI1JH0BXNL+TIl7nLWoXTvI+65gzSiv/ng8WD+6 -Dd2ibZGV7AlcHS1+i3iNEFPNc9qALkImfAvZssU3CfRgO/WQphlgvXDkSYRP8cDx -esjyUniHjd4N6tr2WSaD9do2ZVbMuJtPWt7JrIMJHs+UgB8BaAQCSzmv2ItWXD9t -o1zHyEUEgMuV49GZRdnNM+6aEZt7xA== -=vP3K +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1Mt7UACgkQTRfJlc2X +dfIALggAhyS29bqwp7L2u31uJalZbZQzK2jb86+YyxYzJ/TNAOVHghZNrF7krXAV +GCYEV6SXNHlScAtv7eIVbMcbiaSh/+6/1K3HsPBNP/7nR2sTZ/AOSQNPKdgUia5E +jypTdGYcOiQBCqyP0yDKFXIKxJFOP63tIvidfuT0rBcyusrJ/QPJs6uhKLggOiFv +9kNgZQsOhE3LpA9Yaqf0lsbKhA154c2Q662JiGCzQ2AST36bdzNEwsUeVoTbJda3 +o3qN5kg+mWZNrc9qgYjDA3gXxepNGxjXmFasJc1k1uVx9gxYhEO+/WC1UKMQJR1O +Y/7Qrv3sR3KJ/Q/guhEB4jTKOnvXvw== +=+61j -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 90b7c951e..15623463b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.0" +LE_AUTO_VERSION="0.37.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.0 \ - --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ - --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a -acme==0.37.0 \ - --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ - --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 -certbot-apache==0.37.0 \ - --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ - --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 -certbot-nginx==0.37.0 \ - --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ - --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe +certbot==0.37.1 \ + --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ + --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 +acme==0.37.1 \ + --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ + --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 +certbot-apache==0.37.1 \ + --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ + --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c +certbot-nginx==0.37.1 \ + --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ + --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 015f6741e..20d7b4570 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 59892c207..c7a8a50f5 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.37.0 \ - --hash=sha256:940a7c5902d45c222bf977477d6898d2d1112181252bf998a4b41f6078093b65 \ - --hash=sha256:34c5a832b43f41438bd84eb247a64607228a865e3cdc5272d7a27c3943a94d8a -acme==0.37.0 \ - --hash=sha256:c68f37ac2cbc230af1efd39f258a8fab73a6211d87dd7af56ca6b4651e5c99a6 \ - --hash=sha256:9178b725ad1f282d3ccab89fbdf36a403447eab7c0c52669279fb33df6fbe161 -certbot-apache==0.37.0 \ - --hash=sha256:6737355b54fee44552a9c9374cb0ec532cbd0215506994c8f0d73ee0eeb1c36f \ - --hash=sha256:6d1c413937c0a9419fbd2c67f110cad860e87afdd358650707f32cde71d4fc21 -certbot-nginx==0.37.0 \ - --hash=sha256:e173f523ab21ce6bd290bc0e842ca435e5ff6b325b5ad6ea257d517aeac697bb \ - --hash=sha256:93c9f8d934b886e5632e1658ea411fbbded9fa55dc07a5bc35d493e2a6cc4ffe +certbot==0.37.1 \ + --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ + --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 +acme==0.37.1 \ + --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ + --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 +certbot-apache==0.37.1 \ + --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ + --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c +certbot-nginx==0.37.1 \ + --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ + --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 -- cgit v1.2.3 From 794ce5735672d67951504d023d0511da76c8ed17 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 8 Aug 2019 17:01:38 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5e7207f..83d5f555d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.38.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.37.1 - 2019-08-08 ### Fixed -- cgit v1.2.3 From 44eb04809898dea75d4db0d9bc3fc60331ea7e98 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Thu, 8 Aug 2019 17:01:39 -0700 Subject: Bump version to 0.38.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 4e37b7547..445886ac4 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 900bf1086..810c00594 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 6806b0040..33d353423 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index f53fd9376..31d70e72a 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 3e8a0de8f..85f24bb9d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 4697d7984..e12c7fad9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1d13a7adc..8bb303b6b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 7fc711921..6ee65fded 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 853b52c3a..2ffbaa128 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 7fc5d127b..adee66a48 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 2260518fd..9f239f6c8 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index c07faf9f6..8d83d08b5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 4a1bf25cd..59d2feb51 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a9d562ea9..0982f08dc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index ae975a81a..416f221f0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index af09b3ee4..a4bbd8c60 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 76e1d8224..901ed3060 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 2701bb373..64e24666e 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 9fbd657b3..c800bda3f 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.1' +__version__ = '0.38.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 15623463b..29282dfc0 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.1" +LE_AUTO_VERSION="0.38.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From a1aef4c15cecab291066444a28011362faf11047 Mon Sep 17 00:00:00 2001 From: tyborr Date: Mon, 12 Aug 2019 21:59:29 +0200 Subject: Fix Certbot's Apache plugin doesn't work on Scientific Linux (#7294) This PR adds OVERRIDE_CLASS in certbot-apache/entrypoint.py for Scientific Linux. Fixes #7248. * add OVERRIDE_CLASS for Scientific Linux os name * add entry for Scientific Linux using "scientific" as key * Update changelog --- CHANGELOG.md | 2 +- certbot-apache/certbot_apache/entrypoint.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70cb3f8ed..2275d41dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fixed OS detection in the Apache plugin on Scientific Linux. More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py index df7297d3e..0b875add3 100644 --- a/certbot-apache/certbot_apache/entrypoint.py +++ b/certbot-apache/certbot_apache/entrypoint.py @@ -31,6 +31,8 @@ OVERRIDE_CLASSES = { "gentoo base system": override_gentoo.GentooConfigurator, "opensuse": override_suse.OpenSUSEConfigurator, "suse": override_suse.OpenSUSEConfigurator, + "scientific": override_centos.CentOSConfigurator, + "scientific linux": override_centos.CentOSConfigurator, } -- cgit v1.2.3 From a8bd83922366697452f8aa321dece88a9732982d Mon Sep 17 00:00:00 2001 From: Matthias Bilger Date: Thu, 15 Aug 2019 23:43:14 +0200 Subject: Added DNS plugin for ISPConfig to list (#7332) --- docs/using.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/using.rst b/docs/using.rst index a54e28ec7..700fcf92a 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -281,6 +281,7 @@ pritunl_ N Y Install certificates in pritunl distributed OpenVPN proxmox_ N Y Install certificates in Proxmox Virtualization servers heroku_ Y Y Integration with Heroku SSL dns-standalone_ Y N Obtain certificates via an integrated DNS server +dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server ================== ==== ==== =============================================================== .. _haproxy: https://github.com/greenhost/certbot-haproxy @@ -294,6 +295,7 @@ dns-standalone_ Y N Obtain certificates via an integrated DNS server .. _external: https://github.com/marcan/letsencrypt-external .. _heroku: https://github.com/gboudreau/certbot-heroku .. _dns-standalone: https://github.com/siilike/certbot-dns-standalone +.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig If you're interested, you can also :ref:`write your own plugin `. -- cgit v1.2.3 From 9a047a6996f9aa3db591bb212fb02dc10bc05b3d Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 16 Aug 2019 01:41:51 +0200 Subject: Clean travis config (#7328) This PR removes some useless capabilities in .travis.yml that are associated to the jobs. This concerns mainly sudo and docker. --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 94eaf693e..718261f81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,8 +37,6 @@ matrix: # Main test suite - python: "2.7" env: ACME_SERVER=pebble TOXENV=integration - sudo: required - services: docker <<: *not-on-master # This job is always executed, including on master @@ -60,19 +58,13 @@ matrix: # OpenSSL in Xenial or newer. dist: trusty env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' - sudo: required - services: docker <<: *not-on-master - python: "3.4" env: TOXENV=py34 - sudo: required - services: docker <<: *not-on-master - python: "3.7" dist: xenial env: TOXENV=py37 - sudo: required - services: docker <<: *not-on-master - sudo: required env: TOXENV=apache_compat @@ -86,8 +78,6 @@ matrix: <<: *not-on-master - python: "2.7" env: TOXENV=apacheconftest-with-pebble - sudo: required - services: docker <<: *not-on-master - python: "2.7" env: TOXENV=nginxroundtrip @@ -123,7 +113,6 @@ matrix: - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" <<: *extended-test-suite - python: "3.7" - dist: xenial env: TOXENV=py37 CERTBOT_NO_PIN=1 <<: *extended-test-suite - python: "2.7" -- cgit v1.2.3 From 6882f006ac28753e7d51e1d3a3ecab766d9dd067 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 16 Aug 2019 11:08:42 +0200 Subject: [Windows] Fix closing files descriptors during unit tests (#7326) * Fix file descriptor cleanup during tests on Windows * Fix lint * Remove useless tearDown * Clean pylint --- certbot-nginx/certbot_nginx/tests/util.py | 26 +++++++++++--------------- certbot/compat/filesystem.py | 5 ++++- certbot/tests/compat/filesystem_test.py | 6 +++--- certbot/tests/util.py | 12 +----------- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index 5476333e0..c46ddabc9 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -3,7 +3,6 @@ import copy import shutil import tempfile import unittest -import warnings import josepy as jose import mock @@ -11,6 +10,7 @@ import pkg_resources import zope.component from certbot import configuration +from certbot import util from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util @@ -34,20 +34,16 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods "rsa512_key.pem")) def tearDown(self): - # On Windows we have various files which are not correctly closed at the time of tearDown. - # For know, we log them until a proper file close handling is written. - # Useful for development only, so no warning when we are on a CI process. - def onerror_handler(_, path, excinfo): - """On error handler""" - if not os.environ.get('APPVEYOR'): # pragma: no cover - message = ('Following error occurred when deleting path {0}' - 'during tearDown process: {1}'.format(path, str(excinfo))) - warnings.warn(message) - - shutil.rmtree(self.temp_dir, onerror=onerror_handler) - shutil.rmtree(self.config_dir, onerror=onerror_handler) - shutil.rmtree(self.work_dir, onerror=onerror_handler) - shutil.rmtree(self.logs_dir, onerror=onerror_handler) + # Cleanup opened resources after a test. This is usually done through atexit handlers in + # Certbot, but during tests, atexit will not run registered functions before tearDown is + # called and instead will run them right before the entire test process exits. + # It is a problem on Windows, that does not accept to clean resources before closing them. + util._release_locks() # pylint: disable=protected-access + + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + shutil.rmtree(self.logs_dir) def get_data_filename(filename): diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 7a48e24f1..0649f9bad 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -166,11 +166,11 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long security.SetSecurityDescriptorDacl(1, dacl, 0) + handle = None try: handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, attributes, disposition, 0, None) - handle.Close() except pywintypes.error as err: # Handle native windows errors into python errors to be consistent with the API # of os.open in the situation of a file already existing or locked. @@ -179,6 +179,9 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin if err.winerror == winerror.ERROR_SHARING_VIOLATION: raise OSError(errno.EACCES, err.strerror) raise err + finally: + if handle: + handle.Close() # At this point, the file that did not exist has been created with proper permissions, # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 11293fbfe..c808a5238 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -210,15 +210,15 @@ class WindowsOpenTest(TempDirTestCase): def _test_one_creation(self, num, file_exist, flags): one_file = os.path.join(self.tempdir, str(num)) if file_exist and not os.path.exists(one_file): - open(one_file, 'w').close() + with open(one_file, 'w'): + pass handler = None try: handler = filesystem.open(one_file, flags) - except BaseException as err: + finally: if handler: os.close(handler) - raise err @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 7ee215c66..c46623e0a 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -5,7 +5,6 @@ """ import logging import shutil -import stat import sys import tempfile import unittest @@ -339,16 +338,7 @@ class TempDirTestCase(unittest.TestCase): logging.getLogger().handlers = [] util._release_locks() # pylint: disable=protected-access - def handle_rw_files(_, path, __): - """Handle read-only files, that will fail to be removed on Windows.""" - filesystem.chmod(path, stat.S_IWRITE) - try: - os.remove(path) - except (IOError, OSError): - # TODO: remote the try/except once all logic from windows file permissions is merged - if os.name != 'nt': - raise - shutil.rmtree(self.tempdir, onerror=handle_rw_files) + shutil.rmtree(self.tempdir) class ConfigTestCase(TempDirTestCase): -- cgit v1.2.3 From d39f63feca9827aa9267d14869dcb72b85e40c6f Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 16 Aug 2019 14:55:45 +0200 Subject: Use travis_retry for farm tests (#7327) * Use travis_retry in travis builds to retry the farm tests * travis_retry is a bash function, so it can be called only from current bash * Update .travis.yml * Update .travis.yml --- .travis.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 718261f81..ee3d99104 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ before_script: - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi' # On Travis, the fastest parallelization for integration tests has proved to be 4. - 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi' + # Use Travis retry feature for farm tests since they are flaky + - 'if [[ "$TOXENV" == "travis-test-farm"* ]]; then export TRAVIS_RETRY=travis_retry; fi' - export TOX_TESTENV_PASSENV=TRAVIS # Only build pushes to the master branch, PRs, and branches beginning with @@ -262,8 +264,12 @@ addons: # virtualenv is listed here explicitly to make sure it is upgraded when # CERTBOT_NO_PIN is set to work around failures we've seen when using an older # version of virtualenv. -install: "tools/pip_install.py -U codecov tox virtualenv" -script: tox +install: 'tools/pip_install.py -U codecov tox virtualenv' +# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the +# script command. It is set only to `travis_retry` during farm tests, in +# order to trigger the Travis retry feature, and compensate the inherent +# flakiness of these specific tests. +script: '$TRAVIS_RETRY tox' after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' -- cgit v1.2.3 From 70ed79170926e12145ae0511b585c08a01d49419 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 16 Aug 2019 11:42:34 -0700 Subject: Update Debian instructions in docs. --- docs/install.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 93a122e80..671a95c1f 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -200,23 +200,36 @@ Operating System Packages **Debian** -If you run Debian Stretch or Debian Sid, you can install certbot packages. +If you run Debian Buster or Debian testing/sid, you can easily install certbot +packages through commands like: .. code-block:: shell sudo apt-get update - sudo apt-get install certbot python-certbot-apache + sudo apt-get install certbot + +If you run Debian Stretch, we recommend you use the packages in Debian +backports repository. First you'll have to follow the instructions at +http://backports.debian.org/Instructions/ to enable the Stretch backports repo, +if you have not already done so. Then run: + +.. code-block:: shell -If you don't want to use the Apache plugin, you can omit the -``python-certbot-apache`` package. Or you can install ``python-certbot-nginx`` instead. + sudo apt-get install certbot -t stretch-backports -Packages exist for Debian Jessie via backports. First you'll have to follow the -instructions at http://backports.debian.org/Instructions/ to enable the Jessie backports -repo, if you have not already done so. Then run: +In all of these cases, there also packages available to help Certbot integrate +with Apache, nginx, or various DNS services. These packages can be +found through a command like: .. code-block:: shell - sudo apt-get install certbot python-certbot-apache -t jessie-backports + apt search python-certbot* + +They can be installed by running the same installation command above but +replace ``certbot`` with the name of the desired package. + +There are no Certbot packages available for Debian Jessie and you should +instead use certbot-auto_. **Ubuntu** -- cgit v1.2.3 From 17c1d016c16dc9d41516308dadb8cd6812959981 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 21 Aug 2019 14:29:10 -0700 Subject: Stop turning session tickets off in Nginx (#7344) Related to #7322. * Stop turning session tickets off in Nginx * update changelog --- CHANGELOG.md | 2 ++ certbot-nginx/certbot_nginx/constants.py | 2 ++ .../certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf | 1 - certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2275d41dd..146a45533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed * Fixed OS detection in the Apache plugin on Scientific Linux. +* Stop disabling TLS session tickets in Nginx as it caused TLS failures on + some systems. More details about these changes can be found on our GitHub repo. diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index c90b6b52f..2b22729a8 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -24,6 +24,7 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" SSL_OPTIONS_HASHES_NEW = [ '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', + 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', ] """SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0""" @@ -31,6 +32,7 @@ SSL_OPTIONS_HASHES_MEDIUM = [ '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', ] """SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9 and nginx < 1.13.0""" diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf index 1933cbc4f..a678b0507 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf @@ -6,7 +6,6 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; -ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf index 978e6e8ab..52fdfde24 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf @@ -6,7 +6,6 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; -ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -- cgit v1.2.3 From 46a2ef8ba1c4c74f1fa9e033d982687cfaaf82f7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 21 Aug 2019 14:44:09 -0700 Subject: Stop turning session tickets off in Nginx (#7344) (#7345) Related to #7322. * Stop turning session tickets off in Nginx * update changelog (cherry picked from commit 17c1d016c16dc9d41516308dadb8cd6812959981) --- CHANGELOG.md | 9 +++++++++ certbot-nginx/certbot_nginx/constants.py | 2 ++ .../certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf | 1 - certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf | 1 - 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5e7207f..591356e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.37.2 - master + +### Fixed + +* Stop disabling TLS session tickets in Nginx as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + ## 0.37.1 - 2019-08-08 ### Fixed diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index c90b6b52f..2b22729a8 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -24,6 +24,7 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" SSL_OPTIONS_HASHES_NEW = [ '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', + 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', ] """SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0""" @@ -31,6 +32,7 @@ SSL_OPTIONS_HASHES_MEDIUM = [ '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', ] """SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9 and nginx < 1.13.0""" diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf index 1933cbc4f..a678b0507 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf @@ -6,7 +6,6 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; -ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf index 978e6e8ab..52fdfde24 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf @@ -6,7 +6,6 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; -ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -- cgit v1.2.3 From c17f2ff6b094355cb71a258ec86da7264fc06f94 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 21 Aug 2019 14:48:40 -0700 Subject: Update changelog for 0.37.2 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 591356e6c..c78bcdcfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.37.2 - master +## 0.37.2 - 2019-08-21 ### Fixed -- cgit v1.2.3 From 83200189787b9181419fe19894cc9e2ab8e1cc27 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 21 Aug 2019 15:23:14 -0700 Subject: Release 0.37.2 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 26 ++++++++++----------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------- letsencrypt-auto-source/letsencrypt-auto | 26 ++++++++++----------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++++---------- 26 files changed, 79 insertions(+), 79 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 4e37b7547..6aa745538 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.37.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 900bf1086..31155da78 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 15623463b..90d7fb8ef 100755 --- a/certbot-auto +++ b/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.37.1" +LE_AUTO_VERSION="0.37.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.1 \ - --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ - --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 -acme==0.37.1 \ - --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ - --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 -certbot-apache==0.37.1 \ - --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ - --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c -certbot-nginx==0.37.1 \ - --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ - --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 +certbot==0.37.2 \ + --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ + --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 +acme==0.37.2 \ + --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ + --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c +certbot-apache==0.37.2 \ + --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ + --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 +certbot-nginx==0.37.2 \ + --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ + --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 6806b0040..0ec2c0107 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index f53fd9376..0f7020da3 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 3e8a0de8f..7321cd4aa 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 4697d7984..d158f43e4 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1d13a7adc..4a24e360b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 7fc711921..9a5e35437 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 853b52c3a..04e71d803 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 7fc5d127b..3912699c3 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 2260518fd..02e94d9ed 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index c07faf9f6..1e73c4f34 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 4a1bf25cd..4e1f380fb 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a9d562ea9..0dd48e9c0 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index ae975a81a..d2e97cf14 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index af09b3ee4..64e1e63f5 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 76e1d8224..109eb70b1 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.1' +version = '0.37.2' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 2701bb373..a4e63d788 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.1' +version = '0.37.2' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 9fbd657b3..423f99e75 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.1' +__version__ = '0.37.2' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index e7aa03d11..02f8c3e4c 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.37.1 + "". (default: CertbotACMEClient/0.37.2 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 15623463b..90d7fb8ef 100755 --- a/letsencrypt-auto +++ b/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.37.1" +LE_AUTO_VERSION="0.37.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.1 \ - --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ - --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 -acme==0.37.1 \ - --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ - --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 -certbot-apache==0.37.1 \ - --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ - --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c -certbot-nginx==0.37.1 \ - --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ - --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 +certbot==0.37.2 \ + --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ + --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 +acme==0.37.2 \ + --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ + --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c +certbot-apache==0.37.2 \ + --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ + --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 +certbot-nginx==0.37.2 \ + --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ + --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index a9f7e1e9f..4d72cae0b 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1Mt7UACgkQTRfJlc2X -dfIALggAhyS29bqwp7L2u31uJalZbZQzK2jb86+YyxYzJ/TNAOVHghZNrF7krXAV -GCYEV6SXNHlScAtv7eIVbMcbiaSh/+6/1K3HsPBNP/7nR2sTZ/AOSQNPKdgUia5E -jypTdGYcOiQBCqyP0yDKFXIKxJFOP63tIvidfuT0rBcyusrJ/QPJs6uhKLggOiFv -9kNgZQsOhE3LpA9Yaqf0lsbKhA154c2Q662JiGCzQ2AST36bdzNEwsUeVoTbJda3 -o3qN5kg+mWZNrc9qgYjDA3gXxepNGxjXmFasJc1k1uVx9gxYhEO+/WC1UKMQJR1O -Y/7Qrv3sR3KJ/Q/guhEB4jTKOnvXvw== -=+61j +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1dxDAACgkQTRfJlc2X +dfIoRAf/RY18bXoZNDuihCEz2zM3OIwXalOk6sPfFAGDyQ2Wh6rJhUWeV5btqItJ +uCAl707fwYZW4aYVZO8HxrZW2nNaSGk0xGQsnfMsCmiKJqj0C7MN5Ib46JTejT16 +uxB329CvYsARez0CkKzu0EosZHToZFZWXyeXboCCbPzOfyhKkzBfWS+AIclvBswJ +ytPO9K7Kgu4mpKDZNvqZTSLr5atOPgIyW1+FX677ildiCLt/OUT90OVAfDGkyv86 +Tv7HdIClgUsYog2xNuOqLxXoqMK/qsoPrkGr2+xpz2FvU6oX69zq1REyU+N1qPFh +XfPmX0c2m1zIeJ2wA7NH/25srEnr1w== +=6ueH -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 15623463b..90d7fb8ef 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.1" +LE_AUTO_VERSION="0.37.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1333,18 +1333,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.1 \ - --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ - --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 -acme==0.37.1 \ - --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ - --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 -certbot-apache==0.37.1 \ - --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ - --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c -certbot-nginx==0.37.1 \ - --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ - --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 +certbot==0.37.2 \ + --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ + --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 +acme==0.37.2 \ + --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ + --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c +certbot-apache==0.37.2 \ + --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ + --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 +certbot-nginx==0.37.2 \ + --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ + --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 20d7b4570..e1367a528 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index c7a8a50f5..fa97d9374 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.37.1 \ - --hash=sha256:84dbdad204327b8d8ef9ab5b040f2be1e427a9f7e087affcc9a6051ea1b03fe7 \ - --hash=sha256:aace73e63b0c11cdb4b0bd33e1780c1fbe0ce5669dc72e80c3aa9500145daf16 -acme==0.37.1 \ - --hash=sha256:83a4f6f3c5eb6a85233d5ba87714b426f2d096df58d711f8a2fc4071eb3fd3fc \ - --hash=sha256:c069a761990751f7c4bf51d2e87ae10319bf460de6629d2908c9fa6f69e97111 -certbot-apache==0.37.1 \ - --hash=sha256:3ea832408877b12b3a60d17e8b2ee3387364f8c3023ac267161c25b99087cd42 \ - --hash=sha256:e46c2644451101c0e216aa1f525a577cc903efaf871e0e4da277224a4439040c -certbot-nginx==0.37.1 \ - --hash=sha256:1f9af389d26f06634e2eefaace3354e7679dabb4295e1d55d05a4ee7e23a64bd \ - --hash=sha256:02a7ec15bd388d0f0e94a34c86a8f8d618ec7d5ffde0c206039bb4c46b294ce4 +certbot==0.37.2 \ + --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ + --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 +acme==0.37.2 \ + --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ + --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c +certbot-apache==0.37.2 \ + --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ + --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 +certbot-nginx==0.37.2 \ + --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ + --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 -- cgit v1.2.3 From 3dd918b024d7cda930c8e74f0962763b66832f60 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 21 Aug 2019 15:23:15 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c78bcdcfc..fe0a579f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.38.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.37.2 - 2019-08-21 ### Fixed -- cgit v1.2.3 From 4f19d516d67c067e61e1b559d1b71aa727bfb8fb Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Wed, 21 Aug 2019 15:23:15 -0700 Subject: Bump version to 0.38.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6aa745538..445886ac4 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.37.2' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 31155da78..810c00594 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 0ec2c0107..33d353423 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 0f7020da3..31d70e72a 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 7321cd4aa..85f24bb9d 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index d158f43e4..e12c7fad9 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 4a24e360b..8bb303b6b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 9a5e35437..6ee65fded 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 04e71d803..2ffbaa128 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 3912699c3..adee66a48 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 02e94d9ed..9f239f6c8 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 1e73c4f34..8d83d08b5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 4e1f380fb..59d2feb51 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 0dd48e9c0..0982f08dc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d2e97cf14..416f221f0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 64e1e63f5..a4bbd8c60 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 109eb70b1..901ed3060 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.37.2' +version = '0.38.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index a4e63d788..64e24666e 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.37.2' +version = '0.38.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 423f99e75..c800bda3f 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.37.2' +__version__ = '0.38.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 90d7fb8ef..5e65c2619 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.37.2" +LE_AUTO_VERSION="0.38.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 74e6736c79ac76dd34cda2f7bfdc3021efe2fb5e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 22 Aug 2019 09:28:57 -0700 Subject: use latest RHEL 7 AMI (#7349) --- tests/letstest/targets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index d592e058a..8821cbf3b 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -40,7 +40,7 @@ targets: # - [ apt-get, install, -y, curl ] #----------------------------------------------------------------------------- # Other Redhat Distros - - ami: ami-a8d369c0 + - ami: ami-0916c408cb02e310b name: RHEL7 type: centos virt: hvm -- cgit v1.2.3 From ce325db4e42de1e8792ccea279ccf244dc13c085 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 23 Aug 2019 12:43:05 -0700 Subject: address review comments --- docs/install.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 671a95c1f..1e709e2ee 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -200,7 +200,7 @@ Operating System Packages **Debian** -If you run Debian Buster or Debian testing/sid, you can easily install certbot +If you run Debian Buster or Debian testing/Sid, you can easily install certbot packages through commands like: .. code-block:: shell @@ -210,7 +210,7 @@ packages through commands like: If you run Debian Stretch, we recommend you use the packages in Debian backports repository. First you'll have to follow the instructions at -http://backports.debian.org/Instructions/ to enable the Stretch backports repo, +https://backports.debian.org/Instructions/ to enable the Stretch backports repo, if you have not already done so. Then run: .. code-block:: shell @@ -218,18 +218,21 @@ if you have not already done so. Then run: sudo apt-get install certbot -t stretch-backports In all of these cases, there also packages available to help Certbot integrate -with Apache, nginx, or various DNS services. These packages can be -found through a command like: +with Apache, nginx, or various DNS services. If you are using Apache or nginx, +we strongly recommend that you install the ``python-certbot-apache`` or +``python-certbot-nginx`` package so that Certbot can fully automate HTTPS +configuration for your server. A full list of these packages can be found +through a command like: .. code-block:: shell - apt search python-certbot* + apt search 'python-certbot*' They can be installed by running the same installation command above but -replace ``certbot`` with the name of the desired package. +replacing ``certbot`` with the name of the desired package. -There are no Certbot packages available for Debian Jessie and you should -instead use certbot-auto_. +There are no Certbot packages available for Debian Jessie and Jessie users +should instead use certbot-auto_. **Ubuntu** -- cgit v1.2.3 From 0324d1740e987922673593607f2aabc195253d6e Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 23 Aug 2019 21:53:31 +0200 Subject: Ensure relpath is executed on paths in the same drive (#7335) On Windows you can have several drives (`C:`, `D:`, ...), that is the roughly (really roughly) equivalent of mount points, since each drive is usually associated to a specific physical partition. So you can have paths like `C:\one\path`, `D:\another\path`. In parallel, `os.path.relpath(path, start='.')` calculates the relative path between the given `path` and a `start` path (current directory if not provided). In recent versions of Python, `os.path.relpath` will fail if `path` and `start` are not on the same drive, because a relative path between two paths like `C:\one\path`, `D:\another\path` is not possible. In saw unit tests failing because of this in two locations. This occurs when the certbot codebase that is tested is on a given drive (like `D:`) while the default temporary directory used by `tempfile` is on another drive (most of the time located in `C:` drive). This PR fixes that. --- certbot-nginx/certbot_nginx/tests/parser_test.py | 12 ++++++++-- certbot/tests/cli_test.py | 30 ++++++++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index 97c542532..396f996bf 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -30,8 +30,16 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods self.assertEqual(nparser.root, self.config_path) def test_root_absolute(self): - nparser = parser.NginxParser(os.path.relpath(self.config_path)) - self.assertEqual(nparser.root, self.config_path) + curr_dir = os.getcwd() + try: + # On Windows current directory may be on a different drive than self.tempdir. + # However a relative path between two different drives is invalid. So we move to + # self.tempdir to ensure that we stay on the same drive. + os.chdir(self.temp_dir) + nparser = parser.NginxParser(os.path.relpath(self.config_path)) + self.assertEqual(nparser.root, self.config_path) + finally: + os.chdir(curr_dir) def test_root_no_trailing_slash(self): nparser = parser.NginxParser(self.config_path + os.path.sep) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index c1a489267..87b074a81 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -23,21 +23,27 @@ PLUGINS = disco.PluginsRegistry.find_all() class TestReadFile(TempDirTestCase): - '''Test cli.read_file''' - - + """Test cli.read_file""" def test_read_file(self): - rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo')) - self.assertRaises( - argparse.ArgumentTypeError, cli.read_file, rel_test_path) + curr_dir = os.getcwd() + try: + # On Windows current directory may be on a different drive than self.tempdir. + # However a relative path between two different drives is invalid. So we move to + # self.tempdir to ensure that we stay on the same drive. + os.chdir(self.tempdir) + rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo')) + self.assertRaises( + argparse.ArgumentTypeError, cli.read_file, rel_test_path) - test_contents = b'bar\n' - with open(rel_test_path, 'wb') as f: - f.write(test_contents) + test_contents = b'bar\n' + with open(rel_test_path, 'wb') as f: + f.write(test_contents) - path, contents = cli.read_file(rel_test_path) - self.assertEqual(path, os.path.abspath(path)) - self.assertEqual(contents, test_contents) + path, contents = cli.read_file(rel_test_path) + self.assertEqual(path, os.path.abspath(path)) + self.assertEqual(contents, test_contents) + finally: + os.chdir(curr_dir) class FlagDefaultTest(unittest.TestCase): -- cgit v1.2.3 From aaeb4582e28339026a85bbd016716ce45fd79aae Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 28 Aug 2019 01:25:31 +0200 Subject: Fix PYTHONPATH in integration tests (#7357) This PR supersedes #7353. It fixes the execution of nginx oldest tests when these tests are executed on top of the modifications made in #7337. This execution failure revealed the fact that in some cases, the wrong version of certbot logic was used during integration tests (namely the logic lying in the codebase of the branch built, instead of the logic from the version of certbot declared by certbot-nginx for instance). I let you appreciate my inline comment for the explanation and the workaround. Thanks a lot to @bmw who found this python/pytest madness. You can see the oldest tests succeeding with the logic of #7337 + this PR here: https://travis-ci.com/certbot/certbot/builds/124816254 * Remove certbot root from PYTHONPATH during integration tests * Add a biiiiig comment. --- .../utils/certbot_call.py | 48 ++++++++++++++++++++-- certbot-ci/certbot_integration_tests/utils/misc.py | 12 ------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index 1bff94e75..949852c0a 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -6,7 +6,7 @@ import subprocess import sys import os -from certbot_integration_tests.utils import misc +import certbot_integration_tests from certbot_integration_tests.utils.constants import * @@ -33,18 +33,58 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port, return subprocess.check_output(command, universal_newlines=True, cwd=workspace, env=env) -def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, - config_dir, workspace, force_renew): +def _prepare_environ(workspace): new_environ = os.environ.copy() new_environ['TMPDIR'] = workspace + # So, pytest is nice, and a little too nice for our usage. + # In order to help user to call seamlessly any piece of python code without requiring to + # install it as a full-fledged setuptools distribution for instance, it may inject the path + # to the test files into the PYTHONPATH. This allows the python interpreter to import + # as modules any python file available at this path. + # See https://docs.pytest.org/en/3.2.5/pythonpath.html for the explanation and description. + # However this behavior is not good in integration tests, in particular the nginx oldest ones. + # Indeed during these kind of tests certbot is installed as a transitive dependency to + # certbot-nginx. Here is the trick: this certbot version is not necessarily the same as + # the certbot codebase lying in current working directory. For instance in oldest tests + # certbot==0.36.0 may be installed while the codebase corresponds to certbot==0.37.0.dev0. + # Then during a pytest run, PYTHONPATH contains the path to the Certbot codebase, so invoking + # certbot will import the modules from the codebase (0.37.0.dev0), not from the + # required/installed version (0.36.0). + # This will lead to funny and totally incomprehensible errors. To avoid that, we ensure that + # if PYTHONPATH is set, it does not contain the path to the root of the codebase. + if new_environ.get('PYTHONPATH'): + # certbot_integration_tests.__file__ is: + # '/path/to/certbot/certbot-ci/certbot_integration_tests/__init__.pyc' + # ... and we want '/path/to/certbot' + certbot_root = os.path.dirname(os.path.dirname(os.path.dirname(certbot_integration_tests.__file__))) + python_paths = [path for path in new_environ['PYTHONPATH'].split(':') if path != certbot_root] + new_environ['PYTHONPATH'] = ':'.join(python_paths) + + return new_environ + + +def _compute_additional_args(workspace, environ, force_renew): additional_args = [] - if misc.get_certbot_version() >= LooseVersion('0.30.0'): + output = subprocess.check_output(['certbot', '--version'], + universal_newlines=True, stderr=subprocess.STDOUT, + cwd=workspace, env=environ) + version_str = output.split(' ')[1].strip() # Typical response is: output = 'certbot 0.31.0.dev0' + if LooseVersion(version_str) >= LooseVersion('0.30.0'): additional_args.append('--no-random-sleep-on-renew') if force_renew: additional_args.append('--renew-by-default') + return additional_args + + +def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, force_renew): + + new_environ = _prepare_environ(workspace) + additional_args = _compute_additional_args(workspace, new_environ, force_renew) + command = [ 'certbot', '--server', directory_url, diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index c7d92a4e6..db910b9ec 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -209,18 +209,6 @@ shutil.rmtree(well_known) shutil.rmtree(tempdir) -def get_certbot_version(): - """ - Find the version of the certbot available in PATH. - :return str: the certbot version - """ - output = subprocess.check_output(['certbot', '--version'], - universal_newlines=True, stderr=subprocess.STDOUT) - # Typical response is: output = 'certbot 0.31.0.dev0' - version_str = output.split(' ')[1].strip() - return LooseVersion(version_str) - - def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE): """ Generate a private key, and a CSR for the given domains using this key. -- cgit v1.2.3 From 0fe28a64598dcddafaeb0b1acb6fafc56fc391ea Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 27 Aug 2019 18:31:35 -0700 Subject: Replace platform.linux_distribution with distro.linux_distribution (#7337) Smallest possible fix for #7106 * Replace platform.linux_dependencies with distro.linux_dependencies * run build.py * Add minimum version of 1.0.1 * Pin back requests package * Update changelog --- CHANGELOG.md | 2 + certbot/tests/util_test.py | 4 +- certbot/util.py | 5 +- letsencrypt-auto-source/letsencrypt-auto | 175 +++++++++++---------- .../pieces/dependency-requirements.txt | 175 +++++++++++---------- letsencrypt-auto-source/rebuild_dependencies.py | 7 +- setup.py | 1 + tools/oldest_constraints.txt | 1 + 8 files changed, 192 insertions(+), 178 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb051a22d..13e621ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * If Certbot fails to rollback your server configuration, the error message links to the Let's Encrypt forum. Change the link to the Help category now that the Server category has been closed. +* Replace platform.linux_distribution with distro.linux_distribution as a step + towards Python 3.8 support in Certbot. ### Fixed diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index cf4f31647..0ed6511f3 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -520,11 +520,11 @@ class OsInfoTest(unittest.TestCase): with mock.patch('platform.system_alias', return_value=('linux', '', '')): - with mock.patch('platform.linux_distribution', + with mock.patch('distro.linux_distribution', return_value=('', '', '')): self.assertEqual(get_python_os_info(), ("linux", "")) - with mock.patch('platform.linux_distribution', + with mock.patch('distro.linux_distribution', return_value=('testdist', '42', '')): self.assertEqual(get_python_os_info(), ("testdist", "42")) diff --git a/certbot/util.py b/certbot/util.py index d3297507e..7d82eca8c 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -14,6 +14,7 @@ import socket import subprocess import configargparse +import distro import six from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module @@ -391,8 +392,8 @@ def get_python_os_info(): os_type, os_ver, _ = info os_type = os_type.lower() if os_type.startswith('linux'): - info = platform.linux_distribution() - # On arch, platform.linux_distribution() is reportedly ('','',''), + info = distro.linux_distribution() + # On arch, distro.linux_distribution() is reportedly ('','',''), # so handle it defensively if info[0]: os_type = info[0] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 5e65c2619..af2228ff4 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1134,73 +1134,76 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 asn1crypto==0.24.0 \ --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.6.16 \ + --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ + --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 +cffi==1.12.3 \ + --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ + --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ + --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ + --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ + --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ + --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ + --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ + --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ + --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ + --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ + --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ + --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ + --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ + --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ + --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ + --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ + --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ + --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ + --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ + --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ + --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ + --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ + --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ + --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ + --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ + --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ + --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ + --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.7 \ + --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ + --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ + --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ + --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ + --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ + --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ + --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ + --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ + --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ + --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ + --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ + --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ + --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ + --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ + --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ + --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1216,18 +1219,18 @@ idna==2.8 \ ipaddress==1.0.22 \ --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.2 \ + --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ + --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1236,14 +1239,14 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.2 \ + --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ + --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.2 \ + --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ + --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b @@ -1253,15 +1256,15 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1309,18 +1312,18 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.2 \ + --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ + --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ + --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ + --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ + --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ + --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ + --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ + --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ + --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ + --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ + --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 # Contains the requirements for the letsencrypt package. # diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 48c2afd93..2d683eb48 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -2,73 +2,76 @@ # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 asn1crypto==0.24.0 \ --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.6.16 \ + --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ + --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 +cffi==1.12.3 \ + --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ + --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ + --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ + --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ + --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ + --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ + --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ + --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ + --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ + --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ + --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ + --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ + --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ + --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ + --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ + --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ + --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ + --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ + --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ + --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ + --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ + --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ + --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ + --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ + --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ + --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ + --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ + --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.7 \ + --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ + --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ + --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ + --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ + --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ + --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ + --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ + --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ + --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ + --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ + --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ + --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ + --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ + --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ + --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ + --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -84,18 +87,18 @@ idna==2.8 \ ipaddress==1.0.22 \ --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.2 \ + --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ + --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -104,14 +107,14 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.2 \ + --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ + --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.2 \ + --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ + --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b @@ -121,15 +124,15 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -177,15 +180,15 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.2 \ + --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ + --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ + --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ + --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ + --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ + --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ + --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ + --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ + --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ + --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ + --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index fb4c1dfb9..e5acf7db5 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -33,7 +33,7 @@ DISTRIBUTION_LIST = [ 'fedora:29', ] -# Theses constraints will be added while gathering dependencies on each distribution. +# These constraints will be added while gathering dependencies on each distribution. # It can be used because a particular version for a package is required for any reason, # or to solve a version conflict between two distributions requirements. AUTHORITATIVE_CONSTRAINTS = { @@ -45,7 +45,10 @@ AUTHORITATIVE_CONSTRAINTS = { # Package enum34 needs to be explicitly limited to Python2.x, in order to avoid # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. - 'enum34': '1.1.6 ; python_version < \'3.4\'' + 'enum34': '1.1.6 ; python_version < \'3.4\'', + # Newer versions of requests dropped support for python 3.4. Once Certbot does as well, + # we should unpin the dependency. + 'requests': '2.21.0', } diff --git a/setup.py b/setup.py index 017b66619..4bcdf78c3 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ install_requires = [ 'ConfigArgParse>=0.9.3', 'configobj', 'cryptography>=1.2.3', # load_pem_x509_certificate + 'distro>=1.0.1', # 1.1.0+ is required to avoid the warnings described at # https://github.com/certbot/josepy/issues/13. 'josepy>=1.1.0', diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index e48d6b13c..73465639f 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -51,6 +51,7 @@ funcsigs==0.4 zope.hookable==4.0.4 # Ubuntu Bionic constraints. +distro==1.0.1 # Lexicon oldest constraint is overridden appropriately on relevant DNS provider plugins # using their local-oldest-requirements.txt dns-lexicon==2.2.1 -- cgit v1.2.3 From 4eaa06d58e9d148bf2cb89231889e2709e86b71b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 28 Aug 2019 11:10:13 -0700 Subject: list py37 support (#7360) These plugins also support Python 3.7. You can see tests passing at https://travis-ci.com/certbot/certbot/jobs/228820500. --- certbot-dns-gehirn/setup.py | 1 + certbot-dns-ovh/setup.py | 1 + certbot-dns-sakuracloud/setup.py | 1 + 3 files changed, 3 insertions(+) diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 2ffbaa128..8a301eb3c 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -41,6 +41,7 @@ setup( 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 0982f08dc..ed086ec0e 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -42,6 +42,7 @@ setup( 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 901ed3060..19a9c0a5b 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -41,6 +41,7 @@ setup( 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', -- cgit v1.2.3 From 6d4baec955da4eebbffb42ac5a897c0ef0c20942 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Sep 2019 12:42:35 -0700 Subject: Update changelog for 0.38.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e621ae7..099e3d7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.38.0 - master +## 0.38.0 - 2019-09-03 ### Added -- cgit v1.2.3 From 46a12d01273d98f8994c3493251fa4f55327a77c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Sep 2019 12:49:28 -0700 Subject: Release 0.38.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 201 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 201 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 26 files changed, 257 insertions(+), 251 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 445886ac4..2fb552ad9 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.38.0.dev0' +version = '0.38.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 810c00594..45d3a487a 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 90d7fb8ef..122654d35 100755 --- a/certbot-auto +++ b/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.37.2" +LE_AUTO_VERSION="0.38.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1134,73 +1134,76 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 asn1crypto==0.24.0 \ --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.6.16 \ + --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ + --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 +cffi==1.12.3 \ + --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ + --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ + --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ + --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ + --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ + --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ + --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ + --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ + --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ + --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ + --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ + --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ + --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ + --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ + --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ + --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ + --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ + --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ + --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ + --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ + --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ + --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ + --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ + --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ + --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ + --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ + --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ + --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.7 \ + --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ + --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ + --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ + --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ + --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ + --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ + --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ + --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ + --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ + --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ + --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ + --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ + --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ + --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ + --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ + --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1216,18 +1219,18 @@ idna==2.8 \ ipaddress==1.0.22 \ --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.2 \ + --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ + --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1236,14 +1239,14 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.2 \ + --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ + --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.2 \ + --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ + --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b @@ -1253,15 +1256,15 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1309,18 +1312,18 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.2 \ + --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ + --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ + --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ + --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ + --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ + --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ + --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ + --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ + --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ + --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ + --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 # Contains the requirements for the letsencrypt package. # @@ -1333,18 +1336,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.2 \ - --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ - --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 -acme==0.37.2 \ - --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ - --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c -certbot-apache==0.37.2 \ - --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ - --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 -certbot-nginx==0.37.2 \ - --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ - --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 +certbot==0.38.0 \ + --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ + --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b +acme==0.38.0 \ + --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ + --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 +certbot-apache==0.38.0 \ + --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ + --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 +certbot-nginx==0.38.0 \ + --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ + --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 33d353423..66db22861 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 31d70e72a..5c038ef10 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 85f24bb9d..edc8f2930 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index e12c7fad9..dfc828eba 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 8bb303b6b..d61f199b9 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 6ee65fded..e640c4ae0 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 8a301eb3c..8714aeae3 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index adee66a48..6cbe3e944 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 9f239f6c8..1bb2b9ed3 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 8d83d08b5..91bccb1e5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 59d2feb51..68cf021f7 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index ed086ec0e..b6c12daa2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 416f221f0..72b738c09 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index a4bbd8c60..7a2ce99c6 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 19a9c0a5b..e21b3de94 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0.dev0' +version = '0.38.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 64e24666e..a72810219 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.38.0.dev0' +version = '0.38.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index c800bda3f..563c8998c 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.38.0.dev0' +__version__ = '0.38.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 02f8c3e4c..1ec584e6b 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.37.2 + "". (default: CertbotACMEClient/0.38.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 90d7fb8ef..122654d35 100755 --- a/letsencrypt-auto +++ b/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.37.2" +LE_AUTO_VERSION="0.38.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1134,73 +1134,76 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 asn1crypto==0.24.0 \ --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.6.16 \ + --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ + --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 +cffi==1.12.3 \ + --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ + --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ + --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ + --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ + --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ + --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ + --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ + --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ + --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ + --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ + --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ + --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ + --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ + --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ + --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ + --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ + --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ + --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ + --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ + --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ + --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ + --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ + --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ + --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ + --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ + --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ + --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ + --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.7 \ + --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ + --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ + --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ + --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ + --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ + --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ + --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ + --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ + --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ + --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ + --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ + --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ + --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ + --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ + --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ + --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1216,18 +1219,18 @@ idna==2.8 \ ipaddress==1.0.22 \ --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.2 \ + --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ + --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1236,14 +1239,14 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.2 \ + --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ + --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.2 \ + --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ + --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b @@ -1253,15 +1256,15 @@ requests-toolbelt==0.9.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb zope.component==4.5 \ --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1309,18 +1312,18 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.2 \ + --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ + --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ + --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ + --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ + --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ + --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ + --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ + --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ + --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ + --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ + --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 # Contains the requirements for the letsencrypt package. # @@ -1333,18 +1336,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.2 \ - --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ - --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 -acme==0.37.2 \ - --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ - --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c -certbot-apache==0.37.2 \ - --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ - --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 -certbot-nginx==0.37.2 \ - --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ - --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 +certbot==0.38.0 \ + --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ + --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b +acme==0.38.0 \ + --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ + --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 +certbot-apache==0.38.0 \ + --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ + --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 +certbot-nginx==0.38.0 \ + --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ + --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 4d72cae0b..181452990 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1dxDAACgkQTRfJlc2X -dfIoRAf/RY18bXoZNDuihCEz2zM3OIwXalOk6sPfFAGDyQ2Wh6rJhUWeV5btqItJ -uCAl707fwYZW4aYVZO8HxrZW2nNaSGk0xGQsnfMsCmiKJqj0C7MN5Ib46JTejT16 -uxB329CvYsARez0CkKzu0EosZHToZFZWXyeXboCCbPzOfyhKkzBfWS+AIclvBswJ -ytPO9K7Kgu4mpKDZNvqZTSLr5atOPgIyW1+FX677ildiCLt/OUT90OVAfDGkyv86 -Tv7HdIClgUsYog2xNuOqLxXoqMK/qsoPrkGr2+xpz2FvU6oX69zq1REyU+N1qPFh -XfPmX0c2m1zIeJ2wA7NH/25srEnr1w== -=6ueH +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1uw5wACgkQTRfJlc2X +dfLRQggAium36If8RkfNxvNnKCpBteWx+wbPHhldn5gadRofFTyKXPaYpgtQ5e0P +2BIOZTwpXLBR3uAS3Rxfw4ZdoMYyuhD0Cz6SjBFHYA8ChjtCBKdeToA4e2QEV9Vi +42hBcacL7k3HhWQh+LZfu4D6pfr0ZZbZmkPWBjliEyN+g5Alfms3vzZ2aywcqoSv +iXWVwBfTk3NzVktsJVDIq2uZ1CItmYr3SyF/KRDNXTt/TL7689UF7xD7vm0RmlCZ +e6A5Si1q7RdS+OvPjyD4oKnJgJowWpFqIajOpgLVS4Z2pY3dEhe7eY7KVK5tDKhq +fTC7Elp3OKjzTXv98cEMhG6Oo67jKw== +=bbfh -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index af2228ff4..122654d35 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.38.0.dev0" +LE_AUTO_VERSION="0.38.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1336,18 +1336,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.37.2 \ - --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ - --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 -acme==0.37.2 \ - --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ - --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c -certbot-apache==0.37.2 \ - --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ - --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 -certbot-nginx==0.37.2 \ - --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ - --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 +certbot==0.38.0 \ + --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ + --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b +acme==0.38.0 \ + --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ + --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 +certbot-apache==0.38.0 \ + --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ + --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 +certbot-nginx==0.38.0 \ + --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ + --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index e1367a528..7ea174475 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index fa97d9374..791a8bd86 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.37.2 \ - --hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \ - --hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5 -acme==0.37.2 \ - --hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \ - --hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c -certbot-apache==0.37.2 \ - --hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \ - --hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031 -certbot-nginx==0.37.2 \ - --hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \ - --hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061 +certbot==0.38.0 \ + --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ + --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b +acme==0.38.0 \ + --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ + --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 +certbot-apache==0.38.0 \ + --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ + --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 +certbot-nginx==0.38.0 \ + --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ + --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac -- cgit v1.2.3 From deb0168c09d268599c099593cbd817349001e422 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Sep 2019 12:49:29 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 099e3d7bb..0aff9670a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.39.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.38.0 - 2019-09-03 ### Added -- cgit v1.2.3 From 8a570b18e91b53b2e598f0255c726eee7f62456c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Sep 2019 12:49:30 -0700 Subject: Bump version to 0.39.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 2fb552ad9..a02fd6199 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.38.0' +version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 45d3a487a..21d11ea72 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 66db22861..0de11b671 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 5c038ef10..0de6ac2fb 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index edc8f2930..37b77c8de 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index dfc828eba..3b88276a2 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index d61f199b9..860c4819e 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index e640c4ae0..2b110d042 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 8714aeae3..f9a818fdf 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 6cbe3e944..83ec28253 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 1bb2b9ed3..c8d453e49 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 91bccb1e5..0bccca2d4 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 68cf021f7..cb4963c17 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index b6c12daa2..c3f1ea636 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 72b738c09..1e480b046 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 7a2ce99c6..2f49e77f2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index e21b3de94..a87fbb147 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.38.0' +version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index a72810219..70ecab695 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.38.0' +version = '0.39.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 563c8998c..2021c56cc 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.38.0' +__version__ = '0.39.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 122654d35..ae7dc1d16 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.38.0" +LE_AUTO_VERSION="0.39.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From ed0b8e4af529958d33a5bd1f8ee5284fb1ab444b Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 4 Sep 2019 01:30:13 +0200 Subject: [Windows] Create an installer for Certbot (#7324) This PR is the first step to create an official distribution channel of Certbot for Windows. It consists essentially in creating a proper Certbot Windows installer. Usually distributing an application requires, in a way or another, to stabilize the application logic and its dependencies around a given version. On Windows, this usually takes the form of a freezed application, that vendors its dependencies into a single executable. There are two well-known solutions to create an executable shipping a Python application on Windows: [py2exe](http://www.py2exe.org/) and [pyinstaller](https://www.pyinstaller.org/). However these solutions create self-executable `.EXE` files: you run the `.EXE` file that launches immediately the software. This is not a end-user solution. Indeed when a Windows user wants to install a piece of software, he expects to find and download an installer. When run the installer would interface with Windows to setup configuration entries in the Registry, update the environment variable, add shortcuts in the Start Menu, and declare a uninstaller entry into the Uninstaller Manager. Quite similarly, this is what you would get from a `.deb` or `.rpm` package. A solution that builds proper installers is [pynsis](https://pynsist.readthedocs.io/en/latest/). It is a Python project that constructs installers for Python software using [NSIS](https://sourceforge.net/projects/nsis/), the most known free Windows installer builder solution. This PR uses pynsist to build a Windows installer. The Python script to launch the installer build is `.\windows-installer\construct.py`. Once finished, the installer is located in `.\windows-installer\build\nsis`. This installer will do the following operations during the installation: * copy in the install path a full python distribution used exclusively for Certbot * copy all Python requirements gathered from the `setup.py` of relevant certbot projects * copy `certbot` and `acme` * pre-build python binary assets * register the existence of the application correctly in Windows Registry * prepare a procedure to uninstall Certbot * and of course, expose `certbot` executable to the Windows command line, like on Linux, to be able to launch it as any CLI application from Batch or Powershell This installer support updates: downloading a new version of it and running it on a Windows with existing installation of Certbot will replace it with the new version. Future capabilities not included in this PR: * auto-update of Certbot when a new release is available * online documentation for Windows * register a scheduled task for certificate renewal * installer distribution (continuous deployment + distribution channels) * method to check the downloaded installer is untampered * Setup config * Fix shortcut * Various improvments * Update windows-installer/construct.py Co-Authored-By: Brad Warren * Split into several method * Change installer name * Remove DNS plugins for now * Add a comment about administrator privileges * Update welcome * Control python version * Control bitness * Update windows-installer/construct.py Co-Authored-By: Brad Warren * Update windows-installer/construct.py Co-Authored-By: Brad Warren * Update windows-installer/construct.py Co-Authored-By: Brad Warren --- windows-installer/.gitignore | 2 + windows-installer/certbot.ico | Bin 0 -> 183198 bytes windows-installer/construct.py | 137 +++++++++++++++++++++++++++++++++++++++++ windows-installer/run.bat | 31 ++++++++++ 4 files changed, 170 insertions(+) create mode 100644 windows-installer/.gitignore create mode 100644 windows-installer/certbot.ico create mode 100644 windows-installer/construct.py create mode 100644 windows-installer/run.bat diff --git a/windows-installer/.gitignore b/windows-installer/.gitignore new file mode 100644 index 000000000..a1a48d6b8 --- /dev/null +++ b/windows-installer/.gitignore @@ -0,0 +1,2 @@ +build +build.* diff --git a/windows-installer/certbot.ico b/windows-installer/certbot.ico new file mode 100644 index 000000000..364c32098 Binary files /dev/null and b/windows-installer/certbot.ico differ diff --git a/windows-installer/construct.py b/windows-installer/construct.py new file mode 100644 index 000000000..15296d559 --- /dev/null +++ b/windows-installer/construct.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import ctypes +import struct +import subprocess +import os +import sys +import shutil +import time + + +PYTHON_VERSION = (3, 7, 4) +PYTHON_BITNESS = 32 + + +def main(): + build_path, repo_path, venv_path, venv_python = _prepare_environment() + + _copy_assets(build_path, repo_path) + + installer_cfg_path = _generate_pynsist_config(repo_path, build_path) + + _prepare_build_tools(venv_path, venv_python) + _compile_wheels(repo_path, build_path, venv_python) + _build_installer(installer_cfg_path, venv_path) + + print('Done') + + +def _build_installer(installer_cfg_path, venv_path): + print('Build the installer') + subprocess.check_call([os.path.join(venv_path, 'Scripts', 'pynsist.exe'), installer_cfg_path]) + + +def _compile_wheels(repo_path, build_path, venv_python): + print('Compile wheels') + + wheels_path = os.path.join(build_path, 'wheels') + os.makedirs(wheels_path) + + certbot_packages = ['acme', '.'] + # Uncomment following line to include all DNS plugins in the installer + # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')]) + wheels_project = [os.path.join(repo_path, package) for package in certbot_packages] + + command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path] + command.extend(wheels_project) + subprocess.check_call(command) + + +def _prepare_build_tools(venv_path, venv_python): + print('Prepare build tools') + subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) + subprocess.check_call(['choco', 'upgrade', '-y', 'nsis']) + subprocess.check_call([venv_python, '-m', 'pip', 'install', '--upgrade', 'pip']) + subprocess.check_call([venv_python, '-m', 'pip', 'install', 'wheel', 'pynsist']) + + +def _copy_assets(build_path, repo_path): + print('Copy assets') + if os.path.exists(build_path): + os.rename(build_path, '{0}.{1}.bak'.format(build_path, int(time.time()))) + os.makedirs(build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'certbot.ico'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'run.bat'), build_path) + + +def _generate_pynsist_config(repo_path, build_path): + print('Generate pynsist configuration') + + installer_cfg_path = os.path.join(build_path, 'installer.cfg') + + certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'], + universal_newlines=True, cwd=repo_path).strip() + + with open(os.path.join(installer_cfg_path), 'w') as file_h: + file_h.write("""\ +[Application] +name=Certbot +version={certbot_version} +icon=certbot.ico +publisher=Electronic Frontier Foundation +target=$INSTDIR\\run.bat + +[Build] +directory=nsis +installer_name=certbot-{certbot_version}-installer-{installer_suffix}.exe + +[Python] +version={python_version} +bitness={python_bitness} + +[Include] +local_wheels=wheels\\*.whl +files=run.bat + +[Command certbot] +entry_point=certbot.main:main +""".format(certbot_version=certbot_version, + installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', + python_bitness=PYTHON_BITNESS, + python_version='.'.join([str(item) for item in PYTHON_VERSION]))) + + return installer_cfg_path + + +def _prepare_environment(): + print('Prepare environment') + try: + subprocess.check_output(['choco', '--version']) + except subprocess.CalledProcessError: + raise RuntimeError('Error: Chocolatey (https://chocolatey.org/) needs ' + 'to be installed to run this script.') + script_path = os.path.realpath(__file__) + repo_path = os.path.dirname(os.path.dirname(script_path)) + build_path = os.path.join(repo_path, 'windows-installer', 'build') + venv_path = os.path.join(build_path, 'venv-config') + venv_python = os.path.join(venv_path, 'Scripts', 'python.exe') + + return build_path, repo_path, venv_path, venv_python + + +if __name__ == '__main__': + if not os.name == 'nt': + raise RuntimeError('This script must be run under Windows.') + + if ctypes.windll.shell32.IsUserAnAdmin() == 0: + # Administrator privileges are required to properly install NSIS through Chocolatey + raise RuntimeError('This script must be run with administrator privileges.') + + if sys.version_info[:2] != PYTHON_VERSION[:2]: + raise RuntimeError('This script must be run with Python {0}' + .format('.'.join([str(item) for item in PYTHON_VERSION[0:2]]))) + + if struct.calcsize('P') * 8 != PYTHON_BITNESS: + raise RuntimeError('This script must be run with a {0} bit version of Python.' + .format(PYTHON_BITNESS)) + main() diff --git a/windows-installer/run.bat b/windows-installer/run.bat new file mode 100644 index 000000000..efba28800 --- /dev/null +++ b/windows-installer/run.bat @@ -0,0 +1,31 @@ +@echo off + +:: BatchGotAdmin +:------------------------------------- +REM --> Check for permissions + IF "%PROCESSOR_ARCHITECTURE%" EQU "amd64" ( +>nul 2>&1 "%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system" +) ELSE ( +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" +) + +REM --> If error flag set, we do not have admin. +if '%errorlevel%' NEQ '0' ( + echo Requesting administrative privileges... + goto UACPrompt +) else ( goto gotAdmin ) + +:UACPrompt + echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + set params= %* + echo UAC.ShellExecute "cmd.exe", "/c ""%~s0"" %params:"=""%", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + del "%temp%\getadmin.vbs" + exit /B + +:gotAdmin + pushd "%CD%" + CD /D "%~dp0" +:-------------------------------------- +cmd.exe /k echo You can run 'certbot' commands here. Type 'certbot --help' for more information. -- cgit v1.2.3 From e4af1f331979a178b0579bbb21cc24f063e2ab18 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Thu, 5 Sep 2019 16:51:56 -0400 Subject: Disable TLS session tickets in Nginx (#7355) * Find OpenSSL version * Create and update various config files * Update logic to use new version constraints * SSL_OPTIONS_HASHES_NEW and SSL_OPTIONS_HASHES_MEDIUM were just being used for testing, and maintaining them is becoming untenable, so remove them. * if we don't know the openssl version, we can't turn off session tickets * add unit test for _get_openssl_version * add unit tests * placate lint * Fix docs and tests and clean up code * use python correctly * update changelog * Lint * make comment a comment --- CHANGELOG.md | 2 +- certbot-nginx/certbot_nginx/configurator.py | 92 +++++++++++++++++++--- certbot-nginx/certbot_nginx/constants.py | 23 ++---- .../certbot_nginx/tests/configurator_test.py | 73 ++++++++++++++++- certbot-nginx/certbot_nginx/tests/util.py | 5 +- .../tls_configs/options-ssl-nginx-tls12-only.conf | 1 + .../options-ssl-nginx-tls13-session-tix-on.conf | 13 +++ .../tls_configs/options-ssl-nginx.conf | 1 + 8 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aff9670a..ce527dbc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ More details about these changes can be found on our GitHub repo. ### Added -* +* Disable session tickets for Nginx users when appropriate. ### Changed diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index d3de83593..95715916d 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -1,4 +1,6 @@ """Nginx Configuration""" +# https://github.com/PyCQA/pylint/issues/73 +from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error import logging import re import socket @@ -91,8 +93,12 @@ class NginxConfigurator(common.Installer): :param tup version: version of Nginx as a tuple (1, 4, 7) (used mostly for unittesting) + :param tup openssl_version: version of OpenSSL linked to Nginx as a tuple (1, 4, 7) + (used mostly for unittesting) + """ version = kwargs.pop("version", None) + openssl_version = kwargs.pop("openssl_version", None) super(NginxConfigurator, self).__init__(*args, **kwargs) # Verify that all directories and files exist with proper permissions @@ -115,6 +121,7 @@ class NginxConfigurator(common.Installer): # These will be set in the prepare function self.parser = None self.version = version + self.openssl_version = openssl_version self._enhance_func = {"redirect": self._enable_redirect, "ensure-http-header": self._set_http_header, "staple-ocsp": self._enable_ocsp_stapling} @@ -124,11 +131,33 @@ class NginxConfigurator(common.Installer): @property def mod_ssl_conf_src(self): """Full absolute path to SSL configuration file source.""" - config_filename = "options-ssl-nginx.conf" - if self.version < (1, 5, 9): - config_filename = "options-ssl-nginx-old.conf" - elif self.version < (1, 13, 0): - config_filename = "options-ssl-nginx-tls12-only.conf" + + # Why all this complexity? Well, we want to support Mozilla's intermediate + # recommendations. But TLS1.3 is only supported by newer versions of Nginx. + # And as for session tickets, our ideal is to turn them off across the board. + # But! Turning them off at all is only supported with new enough versions of + # Nginx. And older versions of OpenSSL have a bug that leads to browser errors + # given certain configurations. While we'd prefer to have forward secrecy, we'd + # rather fail open than error out. Unfortunately, Nginx can be compiled against + # many versions of OpenSSL. So we have to check both for the two different features, + # leading to four different combinations of options. + # For a complete history, check out https://github.com/certbot/certbot/issues/7322 + + use_tls13 = self.version >= (1, 13, 0) + session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ + LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') + + if use_tls13: + if session_tix_off: + config_filename = "options-ssl-nginx.conf" + else: + config_filename = "options-ssl-nginx-tls13-session-tix-on.conf" + else: + if session_tix_off: + config_filename = "options-ssl-nginx-tls12-only.conf" + else: + config_filename = "options-ssl-nginx-old.conf" + return pkg_resources.resource_filename( "certbot_nginx", os.path.join("tls_configs", config_filename)) @@ -169,6 +198,9 @@ class NginxConfigurator(common.Installer): if self.version is None: self.version = self.get_version() + if self.openssl_version is None: + self.openssl_version = self._get_openssl_version() + self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) self.install_ssl_dhparams() @@ -909,17 +941,14 @@ class NginxConfigurator(common.Installer): util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE) util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE) - def get_version(self): - """Return version of Nginx Server. + def _nginx_version(self): + """Return results of nginx -V - Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) - - :returns: version - :rtype: tuple + :returns: version text + :rtype: str :raises .PluginError: - Unable to find Nginx version or version is unsupported - + Unable to run Nginx version command """ try: proc = subprocess.Popen( @@ -932,6 +961,21 @@ class NginxConfigurator(common.Installer): logger.debug(str(error), exc_info=True) raise errors.PluginError( "Unable to run %s -V" % self.conf('ctl')) + return text + + def get_version(self): + """Return version of Nginx Server. + + Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) + + :returns: version + :rtype: tuple + + :raises .PluginError: + Unable to find Nginx version or version is unsupported + + """ + text = self._nginx_version() version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE) version_matches = version_regex.findall(text) @@ -964,6 +1008,28 @@ class NginxConfigurator(common.Installer): return nginx_version + def _get_openssl_version(self): + """Return version of OpenSSL linked to Nginx. + + Version is returned as string. If no version can be found, empty string is returned. + + :returns: openssl_version + :rtype: str + + :raises .PluginError: + Unable to run Nginx version command + """ + text = self._nginx_version() + + matches = re.findall(r"running with OpenSSL ([^ ]+) ", text) + if not matches: + matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) + if not matches: + logger.warning("NGINX configured with OpenSSL alternatives is not officially" + "supported by Certbot.") + return "" + return matches[0] + def more_info(self): """Human-readable string to help understand the module""" return ( diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index 2b22729a8..92dc9e79d 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -22,21 +22,6 @@ MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" -SSL_OPTIONS_HASHES_NEW = [ - '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', - 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', -] -"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0""" - -SSL_OPTIONS_HASHES_MEDIUM = [ - '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', - '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', - '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', - '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', -] -"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9 - and nginx < 1.13.0""" - ALL_SSL_OPTIONS_HASHES = [ '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', '9a7b32c49001fed4cff8ad24353329472a50e86ade1ef9b2b9e43566a619612e', @@ -46,7 +31,13 @@ ALL_SSL_OPTIONS_HASHES = [ '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', -] + SSL_OPTIONS_HASHES_MEDIUM + SSL_OPTIONS_HASHES_NEW + '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', + '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', + '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', + '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', + 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', +] """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" def os_constant(key): diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 8db202785..19624a7a2 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -394,6 +394,68 @@ class NginxConfiguratorTest(util.NginxTest): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) + @mock.patch("certbot_nginx.configurator.subprocess.Popen") + def test_get_openssl_version(self, mock_popen): + # pylint: disable=protected-access + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2g") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2-beta1 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 (running with OpenSSL 1.0.2a 1 Mar 2016) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2a") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with LibreSSL 2.2.2 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + @mock.patch("certbot_nginx.configurator.subprocess.Popen") def test_nginx_restart(self, mock_popen): mocked = mock_popen() @@ -920,13 +982,12 @@ class InstallSslOptionsConfTest(util.NginxTest): self._assert_current_file() def test_prev_file_updates_to_current_old_nginx(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES, SSL_OPTIONS_HASHES_NEW + from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES self.config.version = (1, 5, 8) with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() - self.assertTrue(self._current_ssl_options_hash() not in SSL_OPTIONS_HASHES_NEW) def test_manually_modified_current_file_does_not_update(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: @@ -987,11 +1048,13 @@ class InstallSslOptionsConfTest(util.NginxTest): def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) + self.config.openssl_version = "1.0.2g" # shouldn't matter self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-old.conf") self._call() self._assert_current_file() self.config.version = (1, 5, 9) + self.config.openssl_version = "1.0.2l" self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-tls12-only.conf") self._call() @@ -999,6 +1062,12 @@ class InstallSslOptionsConfTest(util.NginxTest): self.config.version = (1, 13, 0) self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) + self.config.openssl_version = "1.0.2k" + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls13-session-tix-on.conf") class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index c46ddabc9..c0a70368e 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -54,7 +54,7 @@ def get_data_filename(filename): def get_nginx_configurator( - config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2)): + config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2), openssl_version="1.0.2g"): """Create an Nginx Configurator with the specified options.""" backups = os.path.join(work_dir, "backups") @@ -79,7 +79,8 @@ def get_nginx_configurator( https_port=5001, ), name="nginx", - version=version) + version=version, + openssl_version=openssl_version) config.prepare() # Provide general config utility. diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf index a678b0507..1933cbc4f 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf @@ -6,6 +6,7 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; +ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf new file mode 100644 index 000000000..52fdfde24 --- /dev/null +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf index 52fdfde24..978e6e8ab 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf @@ -6,6 +6,7 @@ ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; +ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -- cgit v1.2.3 From ada2f5c767f11b60e246fa1a5130fa67d64f6a78 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 5 Sep 2019 23:59:24 -0700 Subject: Simplify testing of RHEL 8. (#7323) --- tests/letstest/scripts/test_leauto_upgrades.sh | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 1e1784883..541f54f6b 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -23,18 +23,8 @@ if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | se INITIAL_VERSION="0.20.0" RUN_RHEL6_TESTS=1 else - # 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. - INITIAL_VERSION="0.33.1" -fi - -# If we're on RHEL 8, the initial version of certbot-auto will fail until we do -# a release including https://github.com/certbot/certbot/pull/7240 and update -# INITIAL_VERSION above to use a version containing this fix. This works around -# the problem for now so we can successfully run tests on RHEL 8. -RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` -RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) | cut -d '.' -f1 || echo "0"` -if [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - sudo yum install python3-virtualenv -y + # 0.37.x is the oldest version of letsencrypt-auto that works on RHEL 8. + INITIAL_VERSION="0.37.1" fi git checkout -f "v$INITIAL_VERSION" letsencrypt-auto -- cgit v1.2.3 From ab76834100d75e5330f585b9619332e2e0c8a43e Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 6 Sep 2019 23:30:25 +0200 Subject: [Windows|Linux] Forbid os.stat and os.fstat (#7325) Fixes #7212 This PR forbid os.stat and os.fstat, and fix or provide alternatives to avoid its usage in certbot outside of certbot.compat.filesystem. * Reimplement private key mode propagation * Remove other os.stat * Remove last call of os.stat in certbot package * Forbid stat and fstat * Implement mode comparison checks * Add unit tests * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren * Handle case where multiple ace concerns a given SID in has_min_permissions * Add a new test scenario * Add a simple test for has_same_ownership * Fix name function * Add a comment explaining an ACE structure * Move a test in its dedicated class * Improve a message error * Calculate has_min_permission result using effective permission rights to be more generic. * Change an exception message * Add comments, avoid to skip a test. * Update certbot/compat/filesystem.py Co-Authored-By: Brad Warren --- .codecov.yml | 4 +- .../certbot_apache/tests/http_01_test.py | 10 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot/compat/filesystem.py | 122 ++++++++++++++++++++- certbot/compat/misc.py | 13 --- certbot/compat/os.py | 18 +++ certbot/lock.py | 8 +- certbot/plugins/dns_common.py | 5 +- certbot/plugins/dns_common_test.py | 21 ++-- certbot/plugins/webroot_test.py | 15 +-- certbot/storage.py | 5 +- certbot/tests/compat/filesystem_test.py | 55 +++++++++- certbot/tests/compat/os_test.py | 4 +- certbot/tests/lock_test.py | 9 +- tox.ini | 2 +- 44 files changed, 258 insertions(+), 93 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 8a1503da8..55af1a36c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,13 +6,13 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.5 + target: 97.4 threshold: 0.1 base: auto windows: flags: windows # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.6 + target: 97.7 threshold: 0.1 base: auto diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py index a6af68037..fade85b3a 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/certbot_apache/tests/http_01_test.py @@ -7,6 +7,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import achallenges from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util @@ -180,7 +181,7 @@ class ApacheHttp01Test(util.ApacheTest): self.assertEqual(self.http.perform(), expected_response) self.assertTrue(os.path.isdir(self.http.challenge_dir)) - self._has_min_permissions(self.http.challenge_dir, 0o755) + self.assertTrue(filesystem.has_min_permissions(self.http.challenge_dir, 0o755)) self._test_challenge_conf() for achall in achalls: @@ -218,15 +219,10 @@ class ApacheHttp01Test(util.ApacheTest): name = os.path.join(self.http.challenge_dir, achall.chall.encode("token")) validation = achall.validation(self.account_key) - self._has_min_permissions(name, 0o644) + self.assertTrue(filesystem.has_min_permissions(name, 0o644)) with open(name, 'rb') as f: self.assertEqual(f.read(), validation.encode()) - def _has_min_permissions(self, path, min_mode): - """Tests the given file has at least the permissions in mode.""" - st_mode = os.stat(path).st_mode - self.assertEqual(st_mode, st_mode | min_mode) - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index aafd37702..da509406e 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.37.0 +-e .[dev] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 21d11ea72..1393165ed 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.37.0', + 'certbot>=0.39.0.dev0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 0de6ac2fb..7676f595c 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 37b77c8de..2b93056cb 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 3b88276a2..8d17e9d61 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 860c4819e..1ca843189 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -9,7 +9,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 2b110d042..d7fc4d795 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f9a818fdf..faf986187 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -7,7 +7,7 @@ version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 83ec28253..c6fadad41 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', # 1.5 is the first version that supports oauth2client>=2.0 'google-api-python-client>=1.5', 'mock', diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index ff1651cf7..d48a789bb 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index c8d453e49..6a1421778 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -6,7 +6,7 @@ version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 0bccca2d4..0b8ce9671 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index cb4963c17..bb945a834 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 5472399aa..ed5aa6c87 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index c3f1ea636..a7fb6a5dc 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 1e480b046..d25ebb2a8 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -8,7 +8,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 0bc9ee027..da509406e 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 2f49e77f2..14af3d8c9 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -7,7 +7,7 @@ version = '0.39.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index c9999e87a..2b3ba9f32 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e .[dev] diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index a87fbb147..3fb1cb8ee 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -7,7 +7,7 @@ version = '0.39.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=0.39.0.dev0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 0649f9bad..6bcc9a693 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -20,7 +20,7 @@ except ImportError: else: POSIX_MODE = False -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List, Union, Tuple # pylint: disable=unused-import, no-name-in-module def chmod(file_path, mode): @@ -264,6 +264,7 @@ def replace(src, dst): def realpath(file_path): + # type: (str) -> str """ Find the real path for the given path. This method resolves symlinks, including recursive symlinks, and is protected against symlinks that creates an infinite loop. @@ -300,10 +301,11 @@ def realpath(file_path): # requires to be run under a privileged shell, so the user will always benefit # from the highest (privileged one) set of permissions on a given file. def is_executable(path): + # type: (str) -> bool """ Is path an executable file? :param str path: path to test - :returns: True if path is an executable file + :return: True if path is an executable file :rtype: bool """ if POSIX_MODE: @@ -312,6 +314,118 @@ def is_executable(path): return _win_is_executable(path) +def has_world_permissions(path): + # type: (str) -> bool + """ + Check if everybody/world has any right (read/write/execute) on a file given its path + :param str path: path to test + :return: True if everybody/world has any right to the file + :rtype: bool + """ + if POSIX_MODE: + return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO) + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + return bool(dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'), + })) + + +def compute_private_key_mode(old_key, base_mode): + # type: (str, int) -> int + """ + Calculate the POSIX mode to apply to a private key given the previous private key + :param str old_key: path to the previous private key + :param int base_mode: the minimum modes to apply to a private key + :return: the POSIX mode to apply + :rtype: int + """ + if POSIX_MODE: + # On Linux, we keep read/write/execute permissions + # for group and read permissions for everybody. + old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) & + (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH)) + return base_mode | old_mode + + # On Windows, the mode returned by os.stat is not reliable, + # so we do not keep any permission from the previous private key. + return base_mode + + +def has_same_ownership(path1, path2): + # type: (str, str) -> bool + """ + Return True if the ownership of two files given their respective path is the same. + On Windows, ownership is checked against owner only, since files do not have a group owner. + :param str path1: path to the first file + :param str path2: path to the second file + :return: True if both files have the same ownership, False otherwise + :rtype: bool + + """ + if POSIX_MODE: + stats1 = os.stat(path1) + stats2 = os.stat(path2) + return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid) + + security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION) + user1 = security1.GetSecurityDescriptorOwner() + + security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION) + user2 = security2.GetSecurityDescriptorOwner() + + return user1 == user2 + + +def has_min_permissions(path, min_mode): + # type: (str, int) -> bool + """ + Check if a file given its path has at least the permissions defined by the given minimal mode. + On Windows, group permissions are ignored since files do not have a group owner. + :param str path: path to the file to check + :param int min_mode: the minimal permissions expected + :return: True if the file matches the minimal permissions expectations, False otherwise + :rtype: bool + """ + if POSIX_MODE: + st_mode = os.stat(path).st_mode + return st_mode == st_mode | min_mode + + # Resolve symlinks, to get a consistent result with os.stat on Linux, + # that follows symlinks by default. + path = realpath(path) + + # Get owner sid of the file + security = win32security.GetFileSecurity( + path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + dacl = security.GetSecurityDescriptorDacl() + min_dacl = _generate_dacl(user, min_mode) + + for index in range(min_dacl.GetAceCount()): + min_ace = min_dacl.GetAce(index) + + # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID. + # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html + mask = min_ace[1] + user = min_ace[2] + + effective_mask = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': user, + }) + + if effective_mask != effective_mask | mask: + return False + + return True + + def _win_is_executable(path): if not os.path.isfile(path): return False @@ -472,8 +586,8 @@ def _compare_dacls(dacl1, dacl2): This method compare the two given DACLs to check if they are identical. Identical means here that they contains the same set of ACEs in the same order. """ - return ([dacl1.GetAce(index) for index in range(0, dacl1.GetAceCount())] == - [dacl2.GetAce(index) for index in range(0, dacl2.GetAceCount())]) + return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] == + [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())]) def _get_current_user(): diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 5151e7156..a8fbf2c96 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -5,7 +5,6 @@ particular category. from __future__ import absolute_import import select -import stat import sys try: @@ -18,18 +17,6 @@ from certbot import errors from certbot.compat import os -# MASK_FOR_PRIVATE_KEY_PERMISSIONS defines what are the permissions flags to keep -# when transferring the permissions from an old private key to a new one. -if POSIX_MODE: - # On Linux, we keep read/write/execute permissions - # for group and read permissions for everybody. - MASK_FOR_PRIVATE_KEY_PERMISSIONS = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH -else: - # On Windows, the mode returned by os.stat is not reliable, - # so we do not keep any permission from the previous private key. - MASK_FOR_PRIVATE_KEY_PERMISSIONS = 0 - - # For Linux: define OS specific standard binary directories STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 2857ea408..e5438f365 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -116,3 +116,21 @@ def access(*unused_args, **unused_kwargs): raise RuntimeError('Usage of os.access() is forbidden. ' 'Use certbot.compat.filesystem.check_mode() or ' 'certbot.compat.filesystem.is_executable() instead.') + + +# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or +# meaningless. We need to use specialized functions from the certbot.compat.filesystem module. +def stat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.stat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') + + +# Method os.fstat has the same problem than os.stat, since it is the same function, +# but accepting a file descriptor instead of a path. +def fstat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.fstat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') diff --git a/certbot/lock.py b/certbot/lock.py index cdb0fbb3c..eda2a72a1 100644 --- a/certbot/lock.py +++ b/certbot/lock.py @@ -167,14 +167,18 @@ class _UnixLockMechanism(_BaseLockMechanism): :returns: True if the lock was successfully acquired :rtype: bool """ + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since _lock_success is private and called only on Linux. + from os import stat, fstat # pylint: disable=os-module-forbidden try: - stat1 = os.stat(self._path) + stat1 = stat(self._path) except OSError as err: if err.errno == errno.ENOENT: return False raise - stat2 = os.fstat(fd) + stat2 = fstat(fd) # If our locked file descriptor and the file on disk refer to # the same device and inode, they're the same file. return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py index e7fbd3889..931778b07 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/plugins/dns_common.py @@ -2,7 +2,6 @@ import abc import logging -import stat from time import sleep import configobj @@ -12,6 +11,7 @@ from acme import challenges from certbot import errors from certbot import interfaces +from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util @@ -312,8 +312,7 @@ def validate_file_permissions(filename): validate_file(filename) - permissions = stat.S_IMODE(os.stat(filename).st_mode) - if permissions & stat.S_IRWXO: + if filesystem.has_world_permissions(filename): logger.warning('Unsafe permissions on credentials configuration file: %s', filename) diff --git a/certbot/plugins/dns_common_test.py b/certbot/plugins/dns_common_test.py index 6741ff8e5..eba3c89d6 100644 --- a/certbot/plugins/dns_common_test.py +++ b/certbot/plugins/dns_common_test.py @@ -7,14 +7,15 @@ import unittest import mock from certbot import errors +from certbot import util from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import dns_common from certbot.plugins import dns_test_common -from certbot.tests import util +from certbot.tests import util as test_util -class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): +class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): # pylint: disable=protected-access class _FakeDNSAuthenticator(dns_common.DNSAuthenticator): @@ -50,7 +51,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt(self, mock_get_utility): mock_display = mock_get_utility() mock_display.input.side_effect = ((display_util.OK, "",), @@ -59,14 +60,14 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._configure("other_key", "") self.assertEqual(self.auth.config.fake_other_key, "value") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.input.side_effect = ((display_util.CANCEL, "c",),) self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_file(self, mock_get_utility): path = os.path.join(self.tempdir, 'file.ini') open(path, "wb").close() @@ -80,7 +81,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._configure_file("file_path", "") self.assertEqual(self.auth.config.fake_file_path, path) - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_file_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),) @@ -96,7 +97,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.assertEqual(credentials.conf("test"), "value") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_credentials(self, mock_get_utility): bad_path = os.path.join(self.tempdir, 'bad-file.ini') dns_test_common.write({"fake_other": "other_value"}, bad_path) @@ -116,7 +117,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.assertEqual(credentials.conf("test"), "value") -class CredentialsConfigurationTest(util.TempDirTestCase): +class CredentialsConfigurationTest(test_util.TempDirTestCase): class _MockLoggingHandler(logging.Handler): messages = None @@ -150,14 +151,14 @@ class CredentialsConfigurationTest(util.TempDirTestCase): dns_common.logger.addHandler(log) path = os.path.join(self.tempdir, 'too-permissive-file.ini') - open(path, "wb").close() + util.safe_open(path, "wb", 0o744).close() dns_common.CredentialsConfiguration(path) self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")])) -class CredentialsConfigurationRequireTest(util.TempDirTestCase): +class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): def setUp(self): super(CredentialsConfigurationRequireTest, self).setUp() diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index a0b701cac..d1aea5817 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -34,7 +34,13 @@ class AuthenticatorTest(unittest.TestCase): def setUp(self): from certbot.plugins.webroot import Authenticator - self.path = tempfile.mkdtemp() + # On Linux directories created by tempfile.mkdtemp inherit their permissions from their + # parent directory. So the actual permissions are inconsistent over various tests env. + # To circumvent this, a dedicated sub-workspace is created under the workspace, using + # filesystem.mkdir to get consistent permissions. + self.workspace = tempfile.mkdtemp() + self.path = os.path.join(self.workspace, 'webroot') + filesystem.mkdir(self.path) self.partial_root_challenge_path = os.path.join( self.path, ".well-known") self.root_challenge_path = os.path.join( @@ -170,17 +176,12 @@ class AuthenticatorTest(unittest.TestCase): self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) # Check permissions of the directories - for dirpath, dirnames, _ in os.walk(self.path): for directory in dirnames: full_path = os.path.join(dirpath, directory) self.assertTrue(filesystem.check_mode(full_path, 0o755)) - parent_gid = os.stat(self.path).st_gid - parent_uid = os.stat(self.path).st_uid - - self.assertEqual(os.stat(self.validation_path).st_gid, parent_gid) - self.assertEqual(os.stat(self.validation_path).st_uid, parent_uid) + self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path)) def test_perform_cleanup(self): self.auth.prepare() diff --git a/certbot/storage.py b/certbot/storage.py index 518ba9464..9bf85fa0f 100644 --- a/certbot/storage.py +++ b/certbot/storage.py @@ -18,7 +18,6 @@ from certbot import crypto_util from certbot import error_handler from certbot import errors from certbot import util -from certbot.compat import misc from certbot.compat import os from certbot.compat import filesystem from certbot.plugins import common as plugins_common @@ -1107,9 +1106,7 @@ class RenewableCert(object): f.write(new_privkey) # Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS) # from previous privkey in this lineage. - old_mode = (stat.S_IMODE(os.stat(old_privkey).st_mode) & - misc.MASK_FOR_PRIVATE_KEY_PERMISSIONS) - mode = BASE_PRIVKEY_MODE | old_mode + mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) filesystem.copy_ownership_and_apply_mode( old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index c808a5238..ccb93efa8 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -150,6 +150,24 @@ class WindowsChmodTests(TempDirTestCase): self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2) +class ComputePrivateKeyModeTest(TempDirTestCase): + def setUp(self): + super(ComputePrivateKeyModeTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_compute_private_key_mode(self): + filesystem.chmod(self.probe_path, 0o777) + new_mode = filesystem.compute_private_key_mode(self.probe_path, 0o600) + + if POSIX_MODE: + # On Linux RWX permissions for group and R permission for world + # are persisted from the existing moe + self.assertEqual(new_mode, 0o674) + else: + # On Windows no permission is persisted + self.assertEqual(new_mode, 0o600) + + @unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') class WindowsOpenTest(TempDirTestCase): def test_new_file_correct_permissions(self): @@ -262,14 +280,14 @@ class WindowsMkdirTests(test_util.TempDirTestCase): self.assertEqual(original_mkdir, std_os.mkdir) -class CopyOwnershipTest(test_util.TempDirTestCase): - """Tests about replacement of chown: copy_ownership_and_apply_mode""" +class OwnershipTest(test_util.TempDirTestCase): + """Tests about copy_ownership_and_apply_mode and has_same_ownership""" def setUp(self): - super(CopyOwnershipTest, self).setUp() + super(OwnershipTest, self).setUp() self.probe_path = _create_probe(self.tempdir) @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') - def test_windows(self): + def test_copy_ownership_windows(self): system = win32security.ConvertStringSidToSid(SYSTEM_SID) security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR security.SetSecurityDescriptorOwner(system, False) @@ -295,7 +313,7 @@ class CopyOwnershipTest(test_util.TempDirTestCase): if dacl.GetAce(index)[2] == everybody]) @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') - def test_linux(self): + def test_copy_ownership_linux(self): with mock.patch('os.chown') as mock_chown: with mock.patch('os.chmod') as mock_chmod: with mock.patch('os.stat') as mock_stat: @@ -307,8 +325,18 @@ class CopyOwnershipTest(test_util.TempDirTestCase): mock_chown.assert_called_once_with(self.probe_path, 50, 51) mock_chmod.assert_called_once_with(self.probe_path, 0o700) + def test_has_same_ownership(self): + path1 = os.path.join(self.tempdir, 'test1') + path2 = os.path.join(self.tempdir, 'test2') + + util.safe_open(path1, 'w').close() + util.safe_open(path2, 'w').close() + + self.assertTrue(filesystem.has_same_ownership(path1, path2)) + class CheckPermissionsTest(test_util.TempDirTestCase): + """Tests relative to functions that check modes.""" def setUp(self): super(CheckPermissionsTest, self).setUp() self.probe_path = _create_probe(self.tempdir) @@ -353,6 +381,23 @@ class CheckPermissionsTest(test_util.TempDirTestCase): mock_owner.return_value = False self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + def test_check_min_permissions(self): + filesystem.chmod(self.probe_path, 0o744) + self.assertTrue(filesystem.has_min_permissions(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o741) + self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + + def test_is_world_reachable(self): + filesystem.chmod(self.probe_path, 0o744) + self.assertTrue(filesystem.has_world_permissions(self.probe_path)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.has_world_permissions(self.probe_path)) + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index e4928e4fb..2fe23f700 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -8,8 +8,8 @@ class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): # Checks for os module - for method in ['chmod', 'chown', 'open', 'mkdir', - 'makedirs', 'rename', 'replace', 'access']: + for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', + 'replace', 'access', 'stat', 'fstat']: self.assertRaises(RuntimeError, getattr(os, method)) # Checks for os.path module for method in ['realpath']: diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index fb3a8fedf..cd1f9ea86 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -82,7 +82,10 @@ class LockFileTest(test_util.TempDirTestCase): 'Race conditions on lock are specific to the non-blocking file access approach on Linux.') def test_race(self): should_delete = [True, False] - stat = os.stat + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since test_race is a test function called only on Linux. + from os import stat # pylint: disable=os-module-forbidden def delete_and_stat(path): """Wrap os.stat and maybe delete the file first.""" @@ -90,7 +93,7 @@ class LockFileTest(test_util.TempDirTestCase): os.remove(path) return stat(path) - with mock.patch('certbot.lock.os.stat') as mock_stat: + with mock.patch('certbot.lock.filesystem.os.stat') as mock_stat: mock_stat.side_effect = delete_and_stat self._call(self.lock_path) self.assertFalse(should_delete) @@ -117,7 +120,7 @@ class LockFileTest(test_util.TempDirTestCase): def test_unexpected_os_err(self): if POSIX_MODE: - mock_function = 'certbot.lock.os.stat' + mock_function = 'certbot.lock.filesystem.os.stat' else: mock_function = 'certbot.lock.msvcrt.locking' # The only expected errno are ENOENT and EACCES in lock module. diff --git a/tox.ini b/tox.ini index a4f4bd3e3..763f786fa 100644 --- a/tox.ini +++ b/tox.ini @@ -228,7 +228,7 @@ commands = --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config=certbot-ci/certbot_integration_tests/.coveragerc - coverage report --include 'certbot/*' --show-missing --fail-under=66 + coverage report --include 'certbot/*' --show-missing --fail-under=65 coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 passenv = DOCKER_* -- cgit v1.2.3 From fb6aad28bded071864d4827a3876a1f1bbb93ce2 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 16 Sep 2019 14:14:26 -0400 Subject: Get integration tests working on python 3.8 (#7372) * Get integration tests working on python 3.8 * Run unit tests on py38 * Update coveragercs to use coverage 4.5+ format * remove line added to tox.ini * update changelog * xenial is the new travis default; no need to specify in .travis.yml --- .coveragerc | 3 +++ .travis.yml | 16 ++++++++++++---- CHANGELOG.md | 2 +- certbot-ci/certbot_integration_tests/.coveragerc | 1 + tools/dev_constraints.txt | 4 ++-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.coveragerc b/.coveragerc index 1a87ab2da..5d2a93148 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,5 @@ +[run] +omit = */setup.py + [report] omit = */setup.py diff --git a/.travis.yml b/.travis.yml index ee3d99104..625da5c62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,9 +65,11 @@ matrix: env: TOXENV=py34 <<: *not-on-master - python: "3.7" - dist: xenial env: TOXENV=py37 <<: *not-on-master + - python: "3.8-dev" + env: TOXENV=py38 + <<: *not-on-master - sudo: required env: TOXENV=apache_compat services: docker @@ -157,9 +159,11 @@ matrix: env: TOXENV=py36 <<: *extended-test-suite - python: "3.7" - dist: xenial env: TOXENV=py37 <<: *extended-test-suite + - python: "3.8-dev" + env: TOXENV=py38 + <<: *extended-test-suite - python: "3.4" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required @@ -191,17 +195,21 @@ matrix: services: docker <<: *extended-test-suite - python: "3.7" - dist: xenial env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.7" - dist: xenial env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite + - python: "3.8-dev" + env: ACME_SERVER=boulder-v1 TOXENV=integration + <<: *extended-test-suite + - python: "3.8-dev" + env: ACME_SERVER=boulder-v2 TOXENV=integration + <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_jessie services: docker diff --git a/CHANGELOG.md b/CHANGELOG.md index ce527dbc3..075da8376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Run tests on Python3.8. ### Changed diff --git a/certbot-ci/certbot_integration_tests/.coveragerc b/certbot-ci/certbot_integration_tests/.coveragerc index 00929eda2..c83880b64 100644 --- a/certbot-ci/certbot_integration_tests/.coveragerc +++ b/certbot-ci/certbot_integration_tests/.coveragerc @@ -2,6 +2,7 @@ # Avoid false warnings because certbot packages are not installed in the thread that executes # the coverage: indeed, certbot is launched as a CLI from a subprocess. disable_warnings = module-not-imported,no-data-collected +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/parser_obj.py [report] # Exclude unit tests in coverage during integration tests. diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 0db06a1f1..e2bec5e20 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -15,7 +15,7 @@ botocore==1.12.36 cloudflare==1.5.1 codecov==2.0.15 configparser==3.7.4 -coverage==4.4.2 +coverage==4.5.4 decorator==4.1.2 dns-lexicon==3.2.1 dnspython==1.15.0 @@ -76,7 +76,7 @@ Sphinx==1.7.5 sphinx-rtd-theme==0.2.4 sphinxcontrib-websupport==1.0.1 tldextract==2.2.0 -tox==2.9.1 +tox==3.14.0 tqdm==4.19.4 traitlets==4.3.2 twine==1.11.0 -- cgit v1.2.3 From 2883ca839e000912a7e524c4087bb9f66f8affef Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 17 Sep 2019 15:24:53 -0700 Subject: Use xenial globally (#7380) As described at https://github.com/certbot/certbot/pull/7372#discussion_r323592366, Travis is transitioning people to Xenial, but it seems this transition still may not be complete as some of our jobs ran on Trusty with all references to `dist` removed as seen at https://travis-ci.com/certbot/certbot/builds/127960999. This PR sets `dist: xenial` globally and overrides it as needed for the oldest tests. * Set xenial globally. * Use trusty in all oldest tests. --- .travis.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.travis.yml b/.travis.yml index 625da5c62..4d4ea5f55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +dist: xenial cache: directories: @@ -131,21 +132,37 @@ matrix: <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v2 TOXENV=integration-certbot-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v1 TOXENV=integration-nginx-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v2 TOXENV=integration-nginx-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite -- cgit v1.2.3 From 754c34c12043bbbe3ceb12fb5660f841d09e3690 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 20 Sep 2019 08:21:07 -0700 Subject: Fix Windows sdist. (#7384) --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4bcdf78c3..b45ab711d 100644 --- a/setup.py +++ b/setup.py @@ -59,11 +59,17 @@ install_requires = [ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. +pywin32_req = 'pywin32>=224' if StrictVersion(setuptools_version) >= StrictVersion('36.2'): - install_requires.append("pywin32>=224 ; sys_platform == 'win32'") + install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' 'of setuptools. Version 36.2+ of setuptools is required.') +elif os.name == 'nt': + # This branch exists to improve this package's behavior on Windows. Without + # it, if the sdist is installed on Windows with an old version of + # setuptools, pywin32 will not be specified as a dependency. + install_requires.append(pywin32_req) dev_extras = [ 'astroid==1.6.5', -- cgit v1.2.3 From e402993c34ecf342db83bc3f0d8b34ae0d1d127a Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 23 Sep 2019 21:29:18 +0200 Subject: [Windows] Create a certbot renew scheduled task using the installer (#7371) This PR implements the item "register a scheduled task for certificate renewal" from the list of requirements described in #7365. This PR adds required instructions in the NSIS installer for Certbot to create a task, named "Certbot Renew Task" in the Windows Scheduler. This task is run twice a day, to execute the command certbot renew and keep the certificates up-to-date. Uninstalling Certbot will also remove this scheduled task. * Implementation * Corrections * Update template.nsi * Improve scripts * Add a random delay of 12 hours * Synchronize template against default one in pynsist 2.4 * Clean config of scheduled task * Install only in AllUsers mode * Add comments * Remove the logic of single user install --- tools/dev_constraints.txt | 3 + windows-installer/construct.py | 12 +- windows-installer/renew-down.ps1 | 6 + windows-installer/renew-up.ps1 | 15 +++ windows-installer/template.nsi | 257 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 windows-installer/renew-down.ps1 create mode 100644 windows-installer/renew-up.ps1 create mode 100644 windows-installer/template.nsi diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index e2bec5e20..c23cf9cce 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -53,6 +53,9 @@ pyasn1==0.1.9 pyasn1-modules==0.0.10 Pygments==2.2.0 pylint==1.9.4 +# If pynsist version is upgraded, our NSIS template windows-installer/template.nsi +# must be upgraded if necessary using the new built-in one from pynsist. +pynsist==2.4 pytest==3.2.5 pytest-cov==2.5.1 pytest-forked==0.2 diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 15296d559..2427c0128 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -19,7 +19,7 @@ def main(): installer_cfg_path = _generate_pynsist_config(repo_path, build_path) - _prepare_build_tools(venv_path, venv_python) + _prepare_build_tools(venv_path, venv_python, repo_path) _compile_wheels(repo_path, build_path, venv_python) _build_installer(installer_cfg_path, venv_path) @@ -47,12 +47,12 @@ def _compile_wheels(repo_path, build_path, venv_python): subprocess.check_call(command) -def _prepare_build_tools(venv_path, venv_python): +def _prepare_build_tools(venv_path, venv_python, repo_path): print('Prepare build tools') subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) subprocess.check_call(['choco', 'upgrade', '-y', 'nsis']) subprocess.check_call([venv_python, '-m', 'pip', 'install', '--upgrade', 'pip']) - subprocess.check_call([venv_python, '-m', 'pip', 'install', 'wheel', 'pynsist']) + subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'wheel', 'pynsist']) def _copy_assets(build_path, repo_path): @@ -62,6 +62,9 @@ def _copy_assets(build_path, repo_path): os.makedirs(build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'certbot.ico'), build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'run.bat'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'template.nsi'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-up.ps1'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-down.ps1'), build_path) def _generate_pynsist_config(repo_path, build_path): @@ -83,6 +86,7 @@ target=$INSTDIR\\run.bat [Build] directory=nsis +nsi_template=template.nsi installer_name=certbot-{certbot_version}-installer-{installer_suffix}.exe [Python] @@ -92,6 +96,8 @@ bitness={python_bitness} [Include] local_wheels=wheels\\*.whl files=run.bat + renew-up.ps1 + renew-down.ps1 [Command certbot] entry_point=certbot.main:main diff --git a/windows-installer/renew-down.ps1 b/windows-installer/renew-down.ps1 new file mode 100644 index 000000000..60dc4d9e6 --- /dev/null +++ b/windows-installer/renew-down.ps1 @@ -0,0 +1,6 @@ +$taskName = "Certbot Renew Task" + +$exists = Get-ScheduledTask | Where-Object {$_.TaskName -like $taskName} +if ($exists) { + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false +} diff --git a/windows-installer/renew-up.ps1 b/windows-installer/renew-up.ps1 new file mode 100644 index 000000000..c6a5fd9ea --- /dev/null +++ b/windows-installer/renew-up.ps1 @@ -0,0 +1,15 @@ +function Get-ScriptDirectory { Split-Path $MyInvocation.ScriptName } +$down = Join-Path (Get-ScriptDirectory) 'renew-down.ps1' +& $down + +$taskName = "Certbot Renew Task" + +$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -Command "certbot renew"' +$delay = New-TimeSpan -Hours 12 +$triggerAM = New-ScheduledTaskTrigger -Daily -At 12am -RandomDelay $delay +$triggerPM = New-ScheduledTaskTrigger -Daily -At 12pm -RandomDelay $delay +# NB: For now scheduled task is set up under SYSTEM account because Certbot Installer installs Certbot for all users. +# If in the future we allow the Installer to install Certbot for one specific user, the scheduled task will need to +# switch to this user, since Certbot will be available only for him. +$principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest +Register-ScheduledTask -Action $action -Trigger $triggerAM,$triggerPM -TaskName $taskName -Description "Execute twice a day the 'certbot renew' command, to renew managed certificates if needed." -Principal $principal diff --git a/windows-installer/template.nsi b/windows-installer/template.nsi new file mode 100644 index 000000000..0f366c22a --- /dev/null +++ b/windows-installer/template.nsi @@ -0,0 +1,257 @@ +; This NSIS template is based on the built-in one in pynsist 2.3. +; Added lines are enclosed within "CERTBOT CUSTOM BEGIN/END" comments. +; If pynsist is upgraded, this template must be updated if necessary using the new built-in one. +; Original file can be found here: https://github.com/takluyver/pynsist/blob/2.4/nsist/pyapp.nsi + +!define PRODUCT_NAME "[[ib.appname]]" +!define PRODUCT_VERSION "[[ib.version]]" +!define PY_VERSION "[[ib.py_version]]" +!define PY_MAJOR_VERSION "[[ib.py_major_version]]" +!define BITNESS "[[ib.py_bitness]]" +!define ARCH_TAG "[[arch_tag]]" +!define INSTALLER_NAME "[[ib.installer_name]]" +!define PRODUCT_ICON "[[icon]]" + +; Marker file to tell the uninstaller that it's a user installation +!define USER_INSTALL_MARKER _user_install_marker + +SetCompressor lzma + +; CERTBOT CUSTOM BEGIN +; Administrator privileges are required to insert a new task in Windows Scheduler. +; Also comment out some options to disable ability to choose AllUsers/CurrentUser install mode. +; As a result, installer run always with admin privileges (because of MULTIUSER_EXECUTIONLEVEL), +; using the AllUsers installation mode by default (because of MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER +; not set), and this default behavior cannot be overridden (because of MULTIUSER_MUI not set). +; See https://nsis.sourceforge.io/Docs/MultiUser/Readme.html +!define MULTIUSER_EXECUTIONLEVEL Admin +;!define MULTIUSER_EXECUTIONLEVEL Highest +;!define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER +;!define MULTIUSER_MUI +;!define MULTIUSER_INSTALLMODE_COMMANDLINE +; CERTBOT CUSTOM END +!define MULTIUSER_INSTALLMODE_INSTDIR "[[ib.appname]]" +[% if ib.py_bitness == 64 %] +!define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files +[% endif %] +!include MultiUser.nsh + +[% block modernui %] +; Modern UI installer stuff +!include "MUI2.nsh" +!define MUI_ABORTWARNING +!define MUI_ICON "[[icon]]" +!define MUI_UNICON "[[icon]]" + +; UI pages +[% block ui_pages %] +!insertmacro MUI_PAGE_WELCOME +[% if license_file %] +!insertmacro MUI_PAGE_LICENSE [[license_file]] +[% endif %] +; CERTBOT CUSTOM BEGIN +; Disable the installation mode page (AllUsers/CurrentUser) +;!insertmacro MULTIUSER_PAGE_INSTALLMODE +; CERTBOT CUSTOM END +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH +[% endblock ui_pages %] +!insertmacro MUI_LANGUAGE "English" +[% endblock modernui %] + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "${INSTALLER_NAME}" +ShowInstDetails show + +Section -SETTINGS + SetOutPath "$INSTDIR" + SetOverwrite ifnewer +SectionEnd + +[% block sections %] + +Section "!${PRODUCT_NAME}" sec_app + SetRegView [[ib.py_bitness]] + SectionIn RO + File ${PRODUCT_ICON} + SetOutPath "$INSTDIR\pkgs" + File /r "pkgs\*.*" + SetOutPath "$INSTDIR" + + ; Marker file for per-user install + StrCmp $MultiUser.InstallMode CurrentUser 0 +3 + FileOpen $0 "$INSTDIR\${USER_INSTALL_MARKER}" w + FileClose $0 + SetFileAttributes "$INSTDIR\${USER_INSTALL_MARKER}" HIDDEN + + [% block install_files %] + ; Install files + [% for destination, group in grouped_files %] + SetOutPath "[[destination]]" + [% for file in group %] + File "[[ file ]]" + [% endfor %] + [% endfor %] + + ; Install directories + [% for dir, destination in ib.install_dirs %] + SetOutPath "[[ pjoin(destination, dir) ]]" + File /r "[[dir]]\*.*" + [% endfor %] + [% endblock install_files %] + + [% block install_shortcuts %] + ; Install shortcuts + ; The output path becomes the working directory for shortcuts + SetOutPath "%HOMEDRIVE%\%HOMEPATH%" + [% if single_shortcut %] + [% for scname, sc in ib.shortcuts.items() %] + CreateShortCut "$SMPROGRAMS\[[scname]].lnk" "[[sc['target'] ]]" \ + '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]" + [% endfor %] + [% else %] + [# Multiple shortcuts: create a directory for them #] + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + [% for scname, sc in ib.shortcuts.items() %] + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\[[scname]].lnk" "[[sc['target'] ]]" \ + '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]" + [% endfor %] + [% endif %] + SetOutPath "$INSTDIR" + [% endblock install_shortcuts %] + + [% block install_commands %] + [% if has_commands %] + DetailPrint "Setting up command-line launchers..." + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" [[ python ]] "$INSTDIR\bin"' + + StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem + ; Add to PATH for current user + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add_user "$INSTDIR\bin"' + GoTo AddedSysPath + AddSysPathSystem: + ; Add to PATH for all users + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add "$INSTDIR\bin"' + AddedSysPath: + [% endif %] + [% endblock install_commands %] + + ; Byte-compile Python files. + DetailPrint "Byte-compiling Python modules..." + nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"' + WriteUninstaller $INSTDIR\uninstall.exe + ; Add ourselves to Add/remove programs + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayName" "${PRODUCT_NAME}" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "InstallLocation" "$INSTDIR" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayIcon" "$INSTDIR\${PRODUCT_ICON}" + [% if ib.publisher is not none %] + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "Publisher" "[[ib.publisher]]" + [% endif %] + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "NoModify" 1 + WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "NoRepair" 1 + + ; CERTBOT CUSTOM BEGIN + ; Execute ps script to create the certbot renew task + DetailPrint "Setting up certbot renew scheduled task" + nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-up.ps1"' + ; CERTBOT CUSTOM END + + ; Check if we need to reboot + IfRebootFlag 0 noreboot + MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \ + /SD IDNO IDNO noreboot + Reboot + noreboot: +SectionEnd + +Section "Uninstall" + ; CERTBOT CUSTOM BEGIN + ; Execute ps script to remove the certbot renew task + nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-down.ps1"' + ; CERTBOT CUSTOM END + + SetRegView [[ib.py_bitness]] + SetShellVarContext all + IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3 + SetShellVarContext current + Delete "$INSTDIR\${USER_INSTALL_MARKER}" + + Delete $INSTDIR\uninstall.exe + Delete "$INSTDIR\${PRODUCT_ICON}" + RMDir /r "$INSTDIR\pkgs" + + ; Remove ourselves from %PATH% + [% block uninstall_commands %] + [% if has_commands %] + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" remove "$INSTDIR\bin"' + [% endif %] + [% endblock uninstall_commands %] + + [% block uninstall_files %] + ; Uninstall files + [% for file, destination in ib.install_files %] + Delete "[[pjoin(destination, file)]]" + [% endfor %] + ; Uninstall directories + [% for dir, destination in ib.install_dirs %] + RMDir /r "[[pjoin(destination, dir)]]" + [% endfor %] + [% endblock uninstall_files %] + + [% block uninstall_shortcuts %] + ; Uninstall shortcuts + [% if single_shortcut %] + [% for scname in ib.shortcuts %] + Delete "$SMPROGRAMS\[[scname]].lnk" + [% endfor %] + [% else %] + RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}" + [% endif %] + [% endblock uninstall_shortcuts %] + RMDir $INSTDIR + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +SectionEnd + +[% endblock sections %] + +; Functions + +Function .onMouseOverSection + ; Find which section the mouse is over, and set the corresponding description. + FindWindow $R0 "#32770" "" $HWNDPARENT + GetDlgItem $R0 $R0 1043 ; description item (must be added to the UI) + + [% block mouseover_messages %] + StrCmp $0 ${sec_app} "" +2 + SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}" + + [% endblock mouseover_messages %] +FunctionEnd + +Function .onInit + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function un.onInit + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +[% if ib.py_bitness == 64 %] +Function correct_prog_files + ; The multiuser machinery doesn't know about the different Program files + ; folder for 64-bit applications. Override the install dir it set. + StrCmp $MultiUser.InstallMode AllUsers 0 +2 + StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}" +FunctionEnd +[% endif %] \ No newline at end of file -- cgit v1.2.3 From 18e6c6c2a883fcfae44a84524d0be57f99b2b357 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 23 Sep 2019 20:20:11 -0400 Subject: Don't send OCSP requests for expired certificates (#7387) Fixes #7152. * don't check ocsp if cert is expired when getting cert information * don't check ocsp if the cert is expired in ocsp_revoked * update tests * update changelog * move pytz import to the top of the test file --- CHANGELOG.md | 2 +- certbot/cert_manager.py | 2 +- certbot/ocsp.py | 20 ++++++++++++----- certbot/tests/ocsp_test.py | 55 +++++++++++++++++++++++++++++++--------------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 075da8376..f02e8fda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Don't send OCSP requests for expired certificates ### Fixed diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index 6d6d2e2e6..cd228ac12 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -262,7 +262,7 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False): reasons.append('TEST_CERT') if cert.target_expiry <= now: reasons.append('EXPIRED') - if checker.ocsp_revoked(cert.cert, cert.chain): + elif checker.ocsp_revoked(cert): reasons.append('REVOKED') if reasons: diff --git a/certbot/ocsp.py b/certbot/ocsp.py index 0e35f023f..1cc1e7529 100644 --- a/certbot/ocsp.py +++ b/certbot/ocsp.py @@ -16,11 +16,13 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import hashes # type: ignore from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +import pytz import requests from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module from certbot import crypto_util from certbot import errors +from certbot.storage import RenewableCert # pylint: disable=unused-import from certbot import util logger = logging.getLogger(__name__) @@ -48,21 +50,29 @@ class RevocationChecker(object): else: self.host_args = lambda host: ["Host", host] - def ocsp_revoked(self, cert_path, chain_path): - # type: (str, str) -> bool + def ocsp_revoked(self, cert): + # type: (RenewableCert) -> bool """Get revoked status for a particular cert version. .. todo:: Make this a non-blocking call - :param str cert_path: Path to certificate - :param str chain_path: Path to intermediate cert - :returns: True if revoked; False if valid or the check failed + :param `.storage.RenewableCert` cert: Certificate object + :returns: True if revoked; False if valid or the check failed or cert is expired. :rtype: bool """ + cert_path, chain_path = cert.cert, cert.chain + if self.broken: return False + # Let's Encrypt doesn't update OCSP for expired certificates, + # so don't check OCSP if the cert is expired. + # https://github.com/certbot/certbot/issues/7152 + now = pytz.UTC.fromutc(datetime.utcnow()) + if cert.target_expiry <= now: + return False + url, host = _determine_ocsp_server(cert_path) if not host or not url: return False diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index e8c1b9d03..680e2c2bb 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -16,6 +16,7 @@ try: except (ImportError, AttributeError): # pragma: no cover ocsp_lib = None # type: ignore import mock +import pytz from certbot import errors from certbot.tests import util as test_util @@ -72,21 +73,34 @@ class OCSPTestOpenSSL(unittest.TestCase): @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.util.run_script') def test_ocsp_revoked(self, mock_run, mock_determine): + now = pytz.UTC.fromutc(datetime.utcnow()) + cert_obj = mock.MagicMock() + cert_obj.cert = "x" + cert_obj.chain = "y" + cert_obj.target_expiry = now + timedelta(hours=2) + self.checker.broken = True mock_determine.return_value = ("", "") - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.checker.broken = False mock_run.return_value = tuple(openssl_happy[1:]) - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.assertEqual(mock_run.call_count, 0) mock_determine.return_value = ("http://x.co", "x.co") - self.assertEqual(self.checker.ocsp_revoked("blah.pem", "chain.pem"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) mock_run.side_effect = errors.SubprocessError("Unable to load certificate launcher") - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.assertEqual(mock_run.call_count, 2) + # cert expired + cert_obj.target_expiry = now + mock_determine.return_value = ("", "") + count_before = mock_determine.call_count + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) + self.assertEqual(mock_determine.call_count, count_before) + def test_determine_ocsp_server(self): cert_path = test_util.vector_path('ocsp_certificate.pem') @@ -131,18 +145,23 @@ class OSCPTestCryptography(unittest.TestCase): self.checker = ocsp.RevocationChecker() self.cert_path = test_util.vector_path('ocsp_certificate.pem') self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem') + self.cert_obj = mock.MagicMock() + self.cert_obj.cert = self.cert_path + self.cert_obj.chain = self.chain_path + now = pytz.UTC.fromutc(datetime.utcnow()) + self.cert_obj.target_expiry = now + timedelta(hours=2) @mock.patch('certbot.ocsp._determine_ocsp_server') @mock.patch('certbot.ocsp._check_ocsp_cryptography') def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): mock_determine.return_value = ('http://example.com', 'example.com') - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') def test_revoke(self): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertTrue(revoked) def test_responder_is_issuer(self): @@ -152,7 +171,7 @@ class OSCPTestCryptography(unittest.TestCase): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: mocks['mock_response'].return_value.responder_name = issuer.subject - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) # Here responder and issuer are the same. So only the signature of the OCSP # response is checked (using the issuer/responder public key). self.assertEqual(mocks['mock_check'].call_count, 1) @@ -167,7 +186,7 @@ class OSCPTestCryptography(unittest.TestCase): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) # Here responder and issuer are not the same. Two signatures will be checked then, # first to verify the responder cert (using the issuer public key), second to # to verify the OCSP response itself (using the responder public key). @@ -181,17 +200,17 @@ class OSCPTestCryptography(unittest.TestCase): # Server return an invalid HTTP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, http_status_code=400): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response in invalid with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response is valid, but certificate status is unknown with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # The OCSP response says that the certificate is revoked, but certificate @@ -200,32 +219,32 @@ class OSCPTestCryptography(unittest.TestCase): with mock.patch('cryptography.x509.Extensions.get_extension_for_class', side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response uses an unsupported signature. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=UnsupportedAlgorithm('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OSCP signature response is invalid. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=InvalidSignature('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # Assertion error on OCSP response validity with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=AssertionError('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # No responder cert in OCSP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: mocks['mock_response'].return_value.certificates = [] - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # Responder cert is not signed by certificate issuer @@ -234,7 +253,7 @@ class OSCPTestCryptography(unittest.TestCase): cert = mocks['mock_response'].return_value.certificates[0] mocks['mock_response'].return_value.certificates[0] = mock.Mock( issuer='fake', subject=cert.subject) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): @@ -245,7 +264,7 @@ class OSCPTestCryptography(unittest.TestCase): with mock.patch('cryptography.x509.Extensions.get_extension_for_class', side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) -- cgit v1.2.3 From 8cb57566c0be9fbed6d86316a0afb18e86d9f481 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 24 Sep 2019 11:38:38 -0700 Subject: List support for Python 3.8 (#7392) Fixes #7368. When updating the changelog, I replaced the line about running tests on Python 3.8 because I personally think that support for Python 3.8 is the most relevant information for our users/packagers about our changes in this area. * List support for Python 3.8. * Update changelog. --- CHANGELOG.md | 2 +- acme/setup.py | 1 + certbot-apache/setup.py | 1 + certbot-ci/setup.py | 1 + certbot-compatibility-test/setup.py | 1 + certbot-dns-cloudflare/setup.py | 1 + certbot-dns-cloudxns/setup.py | 1 + certbot-dns-digitalocean/setup.py | 1 + certbot-dns-dnsimple/setup.py | 1 + certbot-dns-dnsmadeeasy/setup.py | 1 + certbot-dns-gehirn/setup.py | 1 + certbot-dns-google/setup.py | 1 + certbot-dns-linode/setup.py | 1 + certbot-dns-luadns/setup.py | 1 + certbot-dns-nsone/setup.py | 1 + certbot-dns-ovh/setup.py | 1 + certbot-dns-rfc2136/setup.py | 1 + certbot-dns-route53/setup.py | 1 + certbot-dns-sakuracloud/setup.py | 1 + certbot-nginx/setup.py | 1 + letshelp-certbot/setup.py | 1 + setup.py | 1 + 22 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02e8fda3..5c0143d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* Run tests on Python3.8. +* Support for Python 3.8 was added to Certbot and all of its components. ### Changed diff --git a/acme/setup.py b/acme/setup.py index a02fd6199..517aef118 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -73,6 +73,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 1393165ed..784c1124f 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -62,6 +62,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 8ab9b9659..025bb3c81 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -51,6 +51,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 0de11b671..ae0f36938 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -47,6 +47,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 7676f595c..98e0af806 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 2b93056cb..05dae99d4 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 8d17e9d61..5c34157cd 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -44,6 +44,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1ca843189..45dfc2272 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -55,6 +55,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index d7fc4d795..a42206a81 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index faf986187..53f1db41f 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -42,6 +42,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index c6fadad41..833f04be0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -48,6 +48,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 6a1421778..143fec10c 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -41,6 +41,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 0b8ce9671..b2f2c7730 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index bb945a834..88183707d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a7fb6a5dc..d6e74350d 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d25ebb2a8..7bdd97c1e 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 14af3d8c9..8c63ac1ff 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -37,6 +37,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 3fb1cb8ee..675805c2c 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -42,6 +42,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 70ecab695..3a28a6c50 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -62,6 +62,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py index 3e9e31725..cb5171b72 100644 --- a/letshelp-certbot/setup.py +++ b/letshelp-certbot/setup.py @@ -36,6 +36,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', diff --git a/setup.py b/setup.py index b45ab711d..1f4838c90 100644 --- a/setup.py +++ b/setup.py @@ -138,6 +138,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', -- cgit v1.2.3 From 6c89aa52275bf0c4107d9e719031c8ca83c5804f Mon Sep 17 00:00:00 2001 From: Kenichi Maehashi Date: Fri, 27 Sep 2019 05:25:48 +0900 Subject: Fix to run with Apache on RHEL 6 (#7401) This PR fixes a regression in #7337 (0.38.0) that certbot cannot run with Apache on RHEL 6. In RHEL 6, `distro.linux_distribution()` returns `RedHatEnterpriseServer`. In RHEL 6: ```py >>> import distro >>> distro.linux_distribution() (u'RedHatEnterpriseServer', u'6.10', u'Santiago') >>> import platform >>> platform.linux_distribution() ('Red Hat Enterprise Linux Server', '6.10', 'Santiago') ``` In RHEL 7: ```py >>> import distro >>> distro.linux_distribution() ('Red Hat Enterprise Linux Server', '7.6', 'Maipo') >>> import platform >>> platform.linux_distribution() ('Red Hat Enterprise Linux Server', '7.6', 'Maipo') ``` * fix to run with Apache on RHEL 6 * fix docs --- AUTHORS.md | 1 + CHANGELOG.md | 2 +- certbot-apache/certbot_apache/entrypoint.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 051c59e61..182081e94 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -127,6 +127,7 @@ Authors * [Joubin Jabbari](https://github.com/joubin) * [Juho Juopperi](https://github.com/jkjuopperi) * [Kane York](https://github.com/riking) +* [Kenichi Maehashi](https://github.com/kmaehashi) * [Kenneth Skovhede](https://github.com/kenkendk) * [Kevin Burke](https://github.com/kevinburke) * [Kevin London](https://github.com/kevinlondon) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0143d01..ea0d316ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fixed OS detection in the Apache plugin on RHEL 6. More details about these changes can be found on our GitHub repo. diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py index 0b875add3..610191fea 100644 --- a/certbot-apache/certbot_apache/entrypoint.py +++ b/certbot-apache/certbot_apache/entrypoint.py @@ -24,6 +24,7 @@ OVERRIDE_CLASSES = { "fedora_old": override_centos.CentOSConfigurator, "fedora": override_fedora.FedoraConfigurator, "ol": override_centos.CentOSConfigurator, + "redhatenterpriseserver": override_centos.CentOSConfigurator, "red hat enterprise linux server": override_centos.CentOSConfigurator, "rhel": override_centos.CentOSConfigurator, "amazon": override_centos.CentOSConfigurator, -- cgit v1.2.3 From ca3077d0347aae12163a43bf74a0c8321284367e Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 27 Sep 2019 19:50:38 +0300 Subject: Try to use platform.linux_distribution() before distro equivalent (#7403) Try to primarily fall back to using `platform.linux_distribution()` if `/etc/os-release` isn't available. Only use `distro.linux_distribution()` on Python >= 3.8. * Try to use platform.linux_distribution() before distro equivalent * Fix tests for py38 * Added changelog entry --- CHANGELOG.md | 1 + certbot/tests/util_test.py | 17 ++++++++++------- certbot/util.py | 10 +++++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0d316ba..6f454e95c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Don't send OCSP requests for expired certificates +* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 ### Fixed diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 0ed6511f3..61b356779 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -520,13 +520,16 @@ class OsInfoTest(unittest.TestCase): with mock.patch('platform.system_alias', return_value=('linux', '', '')): - with mock.patch('distro.linux_distribution', - return_value=('', '', '')): - self.assertEqual(get_python_os_info(), ("linux", "")) - - with mock.patch('distro.linux_distribution', - return_value=('testdist', '42', '')): - self.assertEqual(get_python_os_info(), ("testdist", "42")) + with mock.patch('platform.linux_distribution', + side_effect=AttributeError, + create=True): + with mock.patch('distro.linux_distribution', + return_value=('', '', '')): + self.assertEqual(get_python_os_info(), ("linux", "")) + + with mock.patch('distro.linux_distribution', + return_value=('testdist', '42', '')): + self.assertEqual(get_python_os_info(), ("testdist", "42")) with mock.patch('platform.system_alias', return_value=('freebsd', '9.3-RC3-p1', '')): diff --git a/certbot/util.py b/certbot/util.py index 7d82eca8c..ec357573d 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -392,7 +392,7 @@ def get_python_os_info(): os_type, os_ver, _ = info os_type = os_type.lower() if os_type.startswith('linux'): - info = distro.linux_distribution() + info = _get_linux_distribution() # On arch, distro.linux_distribution() is reportedly ('','',''), # so handle it defensively if info[0]: @@ -424,6 +424,14 @@ def get_python_os_info(): os_ver = '' return os_type, os_ver +def _get_linux_distribution(): + """Gets the linux distribution name from the underlying OS""" + + try: + return platform.linux_distribution() + except AttributeError: + return distro.linux_distribution() + # Just make sure we don't get pwned... Make sure that it also doesn't # start with a period or have two consecutive periods <- this needs to # be done in addition to the regex -- cgit v1.2.3 From 8a4c2f505f9db7b2aee31748062f3f7752467816 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 27 Sep 2019 12:46:56 -0700 Subject: Remove listing for broken heroku plugin (#7409) The README for the [3rd party heroku plugin](https://github.com/gboudreau/certbot-heroku) says it has been deprecated. Because of this, let's remove it from the list of third party plugins. --- docs/using.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index 700fcf92a..f1e70285d 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -279,7 +279,6 @@ external_ Y N A plugin for convenient scripting (See also ticket icecast_ N Y Deploy certificates to Icecast 2 streaming media servers pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers proxmox_ N Y Install certificates in Proxmox Virtualization servers -heroku_ Y Y Integration with Heroku SSL dns-standalone_ Y N Obtain certificates via an integrated DNS server dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server ================== ==== ==== =============================================================== @@ -293,7 +292,6 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox .. _external: https://github.com/marcan/letsencrypt-external -.. _heroku: https://github.com/gboudreau/certbot-heroku .. _dns-standalone: https://github.com/siilike/certbot-dns-standalone .. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig -- cgit v1.2.3 From 6ac8633363961e7c1826609e9f40b1ac3f0ea527 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 27 Sep 2019 12:47:12 -0700 Subject: Remove listing for broken icecast plugin. (#7408) The repo description for the [3rd party Icecast plugin](https://github.com/e00E/lets-encrypt-icecast) says that the plugin isn't currently working and the repository hasn't been updated since 2017. Since it seems broken and unmaintained, let's remove it from the list of third party plugins. I would happily add it again to the list of third party plugins if people fix and maintain it. --- docs/using.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index f1e70285d..458ab9a01 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -276,7 +276,6 @@ s3front_ Y Y Integration with Amazon CloudFront distribution of gandi_ Y N Obtain certificates via the Gandi LiveDNS API varnish_ Y N Obtain certificates via a Varnish server external_ Y N A plugin for convenient scripting (See also ticket 2782_) -icecast_ N Y Deploy certificates to Icecast 2 streaming media servers pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers proxmox_ N Y Install certificates in Proxmox Virtualization servers dns-standalone_ Y N Obtain certificates via an integrated DNS server @@ -286,7 +285,6 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server .. _haproxy: https://github.com/greenhost/certbot-haproxy .. _s3front: https://github.com/dlapiduz/letsencrypt-s3front .. _gandi: https://github.com/obynio/certbot-plugin-gandi -.. _icecast: https://github.com/e00E/lets-encrypt-icecast .. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin .. _2782: https://github.com/certbot/certbot/issues/2782 .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl -- cgit v1.2.3 From c2480b29f7e289a0d3db3d8e7639b909b9069ace Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 30 Sep 2019 09:19:05 -0700 Subject: Add CentOS 8 support to certbot-auto. (#7406) Fixes #7396. --- CHANGELOG.md | 1 + letsencrypt-auto-source/letsencrypt-auto | 2 ++ letsencrypt-auto-source/letsencrypt-auto.template | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f454e95c..7155fa0d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added * Support for Python 3.8 was added to Certbot and all of its components. +* Support for CentOS 8 was added to certbot-auto. ### Changed diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ae7dc1d16..457701743 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -775,6 +775,8 @@ elif [ -f /etc/redhat-release ]; then RPM_USE_PYTHON_3=1 elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 else RPM_USE_PYTHON_3=0 fi diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index b38aa7017..31c5bb134 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -350,6 +350,8 @@ elif [ -f /etc/redhat-release ]; then RPM_USE_PYTHON_3=1 elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 else RPM_USE_PYTHON_3=0 fi -- cgit v1.2.3 From e3dbd9ce4a6336afd58c1917939172d7adc1d8bb Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 1 Oct 2019 10:34:11 -0700 Subject: Keep compatibility with IE11 in the Nginx plugin (#7414) As discussed at https://github.com/mozilla/server-side-tls/issues/263, Mozilla's current intermediate recommendations drop support for some non-EOL'd versions of IE. [Their TLS recommendations were updated to suggest a couple possible workarounds for people who need this support](https://github.com/mozilla/server-side-tls/pull/264) and [April suggested that we make this change in Certbot](https://github.com/mozilla/server-side-tls/issues/263#issuecomment-537085728). We know `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA` translates to `ECDHE-RSA-AES128-SHA` because [nginx uses the same cipher format as OpenSSL](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers) and the translation is shown in the table at https://github.com/mozilla/server-side-tls/blob/gh-pages/Cipher_Suites.mediawiki. The risk of regressions making this change is low as we always had this ciphersuite enabled just a few releases ago: https://github.com/certbot/certbot/tree/v0.36.0/certbot-nginx/certbot_nginx * Keep compatibility with IE11 * update changelog --- CHANGELOG.md | 1 + certbot-nginx/certbot_nginx/constants.py | 4 ++++ certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf | 2 +- .../certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf | 2 +- .../tls_configs/options-ssl-nginx-tls13-session-tix-on.conf | 2 +- certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7155fa0d7..fe806de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Don't send OCSP requests for expired certificates * Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 +* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11. ### Fixed diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py index 92dc9e79d..fbf6ed424 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/constants.py @@ -37,6 +37,10 @@ ALL_SSL_OPTIONS_HASHES = [ '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', + 'ef11e3fb17213e74d3e1816cde0ec37b8b95b4167cf21e7b8ff1eaa9c6f918ee', + 'af85f6193808a44789a1d293e6cffa249cad9a21135940800958b8e3c72dbc69', + 'a2a612fd21b02abaa32d9d11ac63d987d6e3054dbfa356de5800eea0d7ce17f3', + '2d9648302e3588a172c318e46bff88ade46fc7a16d6afc85322776a04800d473', ] """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf index a678b0507..731e38919 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf index 1933cbc4f..33771a189 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf index 52fdfde24..91197d2c8 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf index 978e6e8ab..98b1c4ab9 100644 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; -- cgit v1.2.3 From 9c18de993d00d367d614afd975dcadfc91e6335d Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 1 Oct 2019 12:48:40 -0700 Subject: Update changelog for 0.39.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe806de29..d2a3ea867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.39.0 - master +## 0.39.0 - 2019-10-01 ### Added -- cgit v1.2.3 From 0b605333d90b93b82a5529f5c524264582669fea Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 1 Oct 2019 13:04:08 -0700 Subject: Release 0.39.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 +-- certbot-auto | 28 +++++++++++---------- certbot-compatibility-test/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 4 +-- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 4 +-- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 4 +-- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 4 +-- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 4 +-- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 4 +-- certbot-dns-google/local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 4 +-- certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 4 +-- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 4 +-- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 4 +-- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 4 +-- certbot-dns-rfc2136/local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 4 +-- certbot-dns-route53/local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 4 +-- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 4 +-- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 28 +++++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 16 ++++++------ letsencrypt-auto-source/letsencrypt-auto | 26 +++++++++---------- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +++++++++--------- 41 files changed, 113 insertions(+), 109 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 517aef118..2c90264bd 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.39.0.dev0' +version = '0.39.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 784c1124f..0e14a8dfa 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index 122654d35..68ced3260 100755 --- a/certbot-auto +++ b/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.38.0" +LE_AUTO_VERSION="0.39.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -775,6 +775,8 @@ elif [ -f /etc/redhat-release ]; then RPM_USE_PYTHON_3=1 elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 else RPM_USE_PYTHON_3=0 fi @@ -1336,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.38.0 \ - --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ - --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b -acme==0.38.0 \ - --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ - --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 -certbot-apache==0.38.0 \ - --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ - --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 -certbot-nginx==0.38.0 \ - --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ - --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac +certbot==0.39.0 \ + --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ + --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 +acme==0.39.0 \ + --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ + --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 +certbot-apache==0.39.0 \ + --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ + --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a +certbot-nginx==0.39.0 \ + --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ + --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index ae0f36938..a720c65a9 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 98e0af806..51b930285 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 05dae99d4..01f4111eb 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 5c34157cd..53cc62101 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 45dfc2272..15cf5d17e 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,13 +3,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index a42206a81..8c4f8025d 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 53f1db41f..4b9552b1b 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 833f04be0..43e63a609 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', # 1.5 is the first version that supports oauth2client>=2.0 'google-api-python-client>=1.5', 'mock', diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index d48a789bb..838e70c69 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 143fec10c..67994526d 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,12 +1,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index b2f2c7730..d95ea8908 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 88183707d..e85c0ec86 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index ed5aa6c87..1116b6dfc 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index d6e74350d..fb5df9363 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 7bdd97c1e..32abe8272 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index da509406e..1ee716cd6 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 8c63ac1ff..ba54de99d 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,13 +1,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 2b3ba9f32..aefe03f90 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e .[dev] +certbot[dev]==0.39.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 675805c2c..18915f5d6 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0.dev0' +version = '0.39.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0.dev0', + 'certbot>=0.39.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 3a28a6c50..9c69a53e4 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.39.0.dev0' +version = '0.39.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 2021c56cc..a46a61a31 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.39.0.dev0' +__version__ = '0.39.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 1ec584e6b..134a6879a 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.38.0 + "". (default: CertbotACMEClient/0.39.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 122654d35..68ced3260 100755 --- a/letsencrypt-auto +++ b/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.38.0" +LE_AUTO_VERSION="0.39.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -775,6 +775,8 @@ elif [ -f /etc/redhat-release ]; then RPM_USE_PYTHON_3=1 elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 else RPM_USE_PYTHON_3=0 fi @@ -1336,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.38.0 \ - --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ - --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b -acme==0.38.0 \ - --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ - --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 -certbot-apache==0.38.0 \ - --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ - --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 -certbot-nginx==0.38.0 \ - --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ - --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac +certbot==0.39.0 \ + --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ + --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 +acme==0.39.0 \ + --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ + --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 +certbot-apache==0.39.0 \ + --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ + --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a +certbot-nginx==0.39.0 \ + --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ + --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 181452990..f25f27cdf 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl1uw5wACgkQTRfJlc2X -dfLRQggAium36If8RkfNxvNnKCpBteWx+wbPHhldn5gadRofFTyKXPaYpgtQ5e0P -2BIOZTwpXLBR3uAS3Rxfw4ZdoMYyuhD0Cz6SjBFHYA8ChjtCBKdeToA4e2QEV9Vi -42hBcacL7k3HhWQh+LZfu4D6pfr0ZZbZmkPWBjliEyN+g5Alfms3vzZ2aywcqoSv -iXWVwBfTk3NzVktsJVDIq2uZ1CItmYr3SyF/KRDNXTt/TL7689UF7xD7vm0RmlCZ -e6A5Si1q7RdS+OvPjyD4oKnJgJowWpFqIajOpgLVS4Z2pY3dEhe7eY7KVK5tDKhq -fTC7Elp3OKjzTXv98cEMhG6Oo67jKw== -=bbfh +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl2TsPMACgkQTRfJlc2X +dfJHUAf+NcnvHzowhLr1rkR11CSKMCMgwUee7Nm0QHnVPf09+Dd9mvuaRptuua1D +Qvtcb3F4OQ6/3khy3fzGXIcEe9kuI2+boe+ZA0dfmmzo4ELzpWUadXkuonYybZFE +JAaICgLLHOkiRL8J8ZTmXZI4tbFSsxTLMNOwoMZ6oGgp2plj2rm85L4Z+vUlfaTf +wcs/glbBtbYfW3WWapMsMWwgrE62Q/OOhBjbkPCywFRQDwwaXz6QPrvi+k6gLCqs +Okvg5bY2hP70tU1i9wxp2DAfF/P/5i2hVSWktRdMolUTTTeczLW81allmmDRJcAi +4xrj6wYhN7olMZrTpakXb7zRR9/MGQ== +=Ag2y -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 457701743..68ced3260 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.39.0.dev0" +LE_AUTO_VERSION="0.39.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1338,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.38.0 \ - --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ - --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b -acme==0.38.0 \ - --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ - --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 -certbot-apache==0.38.0 \ - --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ - --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 -certbot-nginx==0.38.0 \ - --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ - --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac +certbot==0.39.0 \ + --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ + --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 +acme==0.39.0 \ + --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ + --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 +certbot-apache==0.39.0 \ + --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ + --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a +certbot-nginx==0.39.0 \ + --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ + --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 7ea174475..d9147680b 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 791a8bd86..7d1c09069 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.38.0 \ - --hash=sha256:618abf3ae17c2fc3cb99baa4bf000dd5e2d7875b7811f5ef1edf6ebd7a33945f \ - --hash=sha256:c27712101794e3adf54f3a3067c63be5caa507a930a79865bc654b6864121c6b -acme==0.38.0 \ - --hash=sha256:6231571b4a94d6d621b28bef6f6d4846b3c2ebca840f9718d3212036c3bd2af8 \ - --hash=sha256:1c1e9c0826a8f72d670b0ca28b7e6392ce4781eb33222f35133705b6551885d8 -certbot-apache==0.38.0 \ - --hash=sha256:0b5a2c2bcc430470b5131941ebdfde0a13e28dec38918c1a4ebea5dd35ad38bc \ - --hash=sha256:2d335543e0ae9292303238736907ce6b321ac49eb49fe4e0b775abdc0ba57c62 -certbot-nginx==0.38.0 \ - --hash=sha256:af82944e171d2e93c81438b185f8051e742c6f47f7382cb1a647b1c7ca2b53f2 \ - --hash=sha256:cecd1fa3de6e19980fdb9c3b3269b15b7da71b5748ee7ae5caddcc18dbb208ac +certbot==0.39.0 \ + --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ + --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 +acme==0.39.0 \ + --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ + --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 +certbot-apache==0.39.0 \ + --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ + --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a +certbot-nginx==0.39.0 \ + --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ + --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 -- cgit v1.2.3 From 4599aff07f184d2932de5926291be0a46770c41e Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 1 Oct 2019 13:04:10 -0700 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a3ea867..de44cc583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.40.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.39.0 - 2019-10-01 ### Added -- cgit v1.2.3 From 6e38ad9cce810fd2371b871a18aa40bcf17ddbc8 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 1 Oct 2019 13:04:10 -0700 Subject: Bump version to 0.40.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 2c90264bd..f9306e350 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.39.0' +version = '0.40.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 0e14a8dfa..ad7b99862 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index a720c65a9..1d648db17 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 51b930285..5ad8f1568 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 01f4111eb..0dd5bc397 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 53cc62101..2d3139a2f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 15cf5d17e..6925946ec 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 8c4f8025d..d31e52686 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 4b9552b1b..f6b944625 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 43e63a609..9fd159c41 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 67994526d..ce4647514 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index d95ea8908..f260c68db 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index e85c0ec86..c6a5ca443 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index fb5df9363..48b8cee4e 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 32abe8272..6fc69ebc0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index ba54de99d..857e07965 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 18915f5d6..c153c681f 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.39.0' +version = '0.40.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 9c69a53e4..8fe300193 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.39.0' +version = '0.40.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index a46a61a31..27b8684e1 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.39.0' +__version__ = '0.40.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 68ced3260..ff1fbe8c3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.39.0" +LE_AUTO_VERSION="0.40.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 3608abb01a535c35740d82ce37b9ebdef3076886 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Wed, 2 Oct 2019 14:44:25 -0700 Subject: Remove unnecessary account ID match check. (#7416) * Remove unnecessary account ID match check. Right now the Account object calculates an ID using md5. This is unnecessary and causes problems on FIPS systems that forbid md5. It's just as good to pick a random series of bytes for the ID, since the ID gets read out of renewal/foo.conf. However, if we switched the algorithm right now, we could wind up breaking forward compatibility / downgradeability, since older versions would run into this check. Removing this check now lays the ground to change the ID-calculation algorithm in the future. Related to #1948 and https://github.com/certbot/certbot/pull/1013#issuecomment-149983479. * Remove test. * Remove unused import. --- certbot/account.py | 7 +------ certbot/tests/account_test.py | 8 -------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/certbot/account.py b/certbot/account.py index 7a1e2de7a..992d63d38 100644 --- a/certbot/account.py +++ b/certbot/account.py @@ -231,12 +231,7 @@ class AccountFileStorage(interfaces.AccountStorage): except IOError as error: raise errors.AccountStorageError(error) - acc = Account(regr, key, meta) - if acc.id != account_id: - raise errors.AccountStorageError( - "Account ids mismatch (expected: {0}, found: {1}".format( - account_id, acc.id)) - return acc + return Account(regr, key, meta) def load(self, account_id): return self._load_for_server_path(account_id, self.config.server_path) diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index c14500798..cad103052 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -1,7 +1,6 @@ """Tests for certbot.account.""" import datetime import json -import shutil import unittest import josepy as jose @@ -170,13 +169,6 @@ class AccountFileStorageTest(test_util.ConfigTestCase): def test_load_non_existent_raises_error(self): self.assertRaises(errors.AccountNotFound, self.storage.load, "missing") - def test_load_id_mismatch_raises_error(self): - self.storage.save(self.acc, self.mock_client) - shutil.move(os.path.join(self.config.accounts_dir, self.acc.id), - os.path.join(self.config.accounts_dir, "x" + self.acc.id)) - self.assertRaises(errors.AccountStorageError, self.storage.load, - "x" + self.acc.id) - def _set_server(self, server): self.config.server = server from certbot.account import AccountFileStorage -- cgit v1.2.3 From 0cfedbc5f5443e94c6e802d1f8fb46cbc44b776c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 3 Oct 2019 15:08:24 -0700 Subject: Add test farm tests for Debian 10 (#7421) Fixes #7225. I got the AMI ID from https://wiki.debian.org/Cloud/AmazonEC2Image/Buster. You can see all test farm tests including test_tests.sh passing with these changes at https://travis-ci.com/certbot/certbot/builds/130318446. --- tests/letstest/apache2_targets.yaml | 5 +++++ tests/letstest/targets.yaml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 490f0eca9..1450a8578 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -18,6 +18,11 @@ targets: user: ubuntu #----------------------------------------------------------------------------- # Debian + - ami: ami-01db78123b2b99496 + name: debian10 + type: ubuntu + virt: hvm + user: admin - ami: ami-003f19e0e687de1cd name: debian9 type: ubuntu diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 8821cbf3b..188be8e24 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -18,6 +18,11 @@ targets: user: ubuntu #----------------------------------------------------------------------------- # Debian + - ami: ami-01db78123b2b99496 + name: debian10 + type: ubuntu + virt: hvm + user: admin - ami: ami-003f19e0e687de1cd name: debian9 type: ubuntu -- cgit v1.2.3 From 9da07590bd189fa29fa817f48cdeb2216cf08f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20G=C3=B3rski?= Date: Tue, 8 Oct 2019 21:24:55 +0200 Subject: Remove --fast from the test farm tests (#7427) --- AUTHORS.md | 1 + CHANGELOG.md | 2 +- tests/letstest/multitester.py | 7 ++----- tox.ini | 8 ++++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 182081e94..8468cbc56 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -18,6 +18,7 @@ Authors * [Alex Zorin](https://github.com/alexzorin) * [Amjad Mashaal](https://github.com/TheNavigat) * [Andrew Murray](https://github.com/radarhere) +* [Andrzej Górski](https://github.com/andrzej3393) * [Anselm Levskaya](https://github.com/levskaya) * [Antoine Jacoutot](https://github.com/ajacoutot) * [asaph](https://github.com/asaph) diff --git a/CHANGELOG.md b/CHANGELOG.md index de44cc583..102eaf4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Removed `--fast` flag from the test farm tests ### Fixed diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index d63b7ab5a..cfa53df7e 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -84,9 +84,6 @@ parser.add_argument('--killboulder', parser.add_argument('--boulderonly', action='store_true', help="only make a boulder server") -parser.add_argument('--fast', - action='store_true', - help="use larger instance types to run faster (saves about a minute, probably not worth it)") cl_args = parser.parse_args() # Credential Variables @@ -310,10 +307,10 @@ def create_client_instance(ec2_client, target, security_group_id, subnet_id): if 'machine_type' in target: machine_type = target['machine_type'] elif target['virt'] == 'hvm': - machine_type = 't2.medium' if cl_args.fast else 't2.micro' + machine_type = 't2.medium' else: # 32 bit systems - machine_type = 'c1.medium' if cl_args.fast else 't1.micro' + machine_type = 'c1.medium' if 'userdata' in target.keys(): userdata = target['userdata'] else: diff --git a/tox.ini b/tox.ini index 763f786fa..04715cc2f 100644 --- a/tox.ini +++ b/tox.ini @@ -274,7 +274,7 @@ setenv = AWS_DEFAULT_REGION=us-east-1 changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py apache2_targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_apache2.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py apache2_targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_apache2.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -283,7 +283,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_leauto_upgrades.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_leauto_upgrades.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -292,7 +292,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_letsencrypt_auto_certonly_standalone.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_letsencrypt_auto_certonly_standalone.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -301,7 +301,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_sdists.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_sdists.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} -- cgit v1.2.3 From fcc398831b0e88106505a7879cac72afc47d1a93 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 8 Oct 2019 23:40:17 +0200 Subject: Create a new CI for Certbot on Windows using Azure Pipelines (#7377) This PR defines pipelines that can be run on Azure Pipelines. Currently there are two: * `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master, * `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master. These two pipelines covers all existing stuff done by AppVeyor currently, and so AppVeyor can be decommissioned once Azure Pipelines is operational. You can see working pipeline in my fork: * a PR for `master` (so using main pipeline): https://github.com/adferrand/certbot/pull/65 * a PR for `test-something` (so using advanced pipeline): https://github.com/adferrand/certbot/pull/66 * uploaded coverage from Azure Pipelines: https://codecov.io/gh/adferrand/certbot/commit/499aa2cbf25e1e0ab4c93ab64057db92dfec0fba/build Once this PR is merged, we need to enable Azure Pipelines for Certbot. Instructions are written in `azure-pipelines/INSTALL.md`. This document also references all access rights required to Azure Pipelines onto GitHub to make the CI process work. Future work for future PRs: * create a CD pipeline for the releases that will push the installer to GitHub releases * implement a solution to generate notification on IRC or Mattermost when a nightly build fails * Define pipelines * Update locations * Update nightly * Use x86 * Update nightly.yml for Azure Pipelines * Run script * Use script * Update install * Use local installation * Register warnings * Fix pywin32 loading * Clean context * Enable coverage publication * Consume codecov token * Document installation * Update tool to upload coverage * Prepare pipeline artifacts * Update artifact ignore * Protect against codecov failures * Add a comment about codecov * Add a comment on RW access asked by Azure * Add instructions * Rename pipeline file * Update instructions * Update .azure-pipelines/templates/tests-suite.yml Co-Authored-By: Brad Warren * Update .azure-pipelines/INSTALL.md Co-Authored-By: Brad Warren * Modified scheduled pipeline * Add comment * Remove dynamic version-based installer name --- .azure-pipelines/INSTALL.md | 117 +++++++++++++++++++++++++ .azure-pipelines/advanced.yml | 18 ++++ .azure-pipelines/main.yml | 11 +++ .azure-pipelines/templates/installer-tests.yml | 31 +++++++ .azure-pipelines/templates/tests-suite.yml | 36 ++++++++ windows-installer/construct.py | 35 +++++++- 6 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 .azure-pipelines/INSTALL.md create mode 100644 .azure-pipelines/advanced.yml create mode 100644 .azure-pipelines/main.yml create mode 100644 .azure-pipelines/templates/installer-tests.yml create mode 100644 .azure-pipelines/templates/tests-suite.yml diff --git a/.azure-pipelines/INSTALL.md b/.azure-pipelines/INSTALL.md new file mode 100644 index 000000000..b5f79e525 --- /dev/null +++ b/.azure-pipelines/INSTALL.md @@ -0,0 +1,117 @@ +# Configuring Azure Pipelines with Certbot + +Let's begin. All pipelines are defined in `.azure-pipelines`. Currently there are two: +* `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master, +* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master. + +Several templates are defined in `.azure-pipelines/templates`. These YAML files aggregate common jobs configuration that can be reused in several pipelines. + +Unlike Travis, where CodeCov is working without any action required, CodeCov supports Azure Pipelines +using the coverage-bash utility (not python-coverage for now) only if you provide the Codecov repo token +using the `CODECOV_TOKEN` environment variable. So `CODECOV_TOKEN` needs to be set as a secured +environment variable to allow the main pipeline to publish coverage reports to CodeCov. + +This INSTALL.md file explains how to configure Azure Pipelines with Certbot in order to execute the CI/CD logic defined in `.azure-pipelines` folder with it. +During this installation step, warnings describing user access and legal comitments will be displayed like this: +``` +!!! ACCESS REQUIRED !!! +``` + +This document suppose that the Azure DevOps organization is named _certbot_, and the Azure DevOps project is also _certbot_. + +## Useful links + +* https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema +* https://www.azuredevopslabs.com/labs/azuredevops/github-integration/ +* https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/python?view=azure-devops + +## Prerequisites + +### Having a GitHub account + +Use your GitHub user for a normal GitHub account, or a user that has administrative rights to the GitHub organization if relevant. + +### Having an Azure DevOps account +- Go to https://dev.azure.com/, click "Start free with GitHub" +- Login to GitHub + +``` +!!! ACCESS REQUIRED !!! +Personal user data (email + profile info, in read-only) +``` + +- Microsoft will create a Live account using the email referenced for the GitHub account. This account is also linked to GitHub account (meaning you can log it using GitHub authentication) +- Proceed with account registration (birth date, country), add details about name and email contact + +``` +!!! ACCESS REQUIRED !!! +Microsoft proposes to send commercial links to this mail +Azure DevOps terms of service need to be accepted +``` + +_Logged to Azure DevOps, account is ready._ + +### Installing Azure Pipelines to GitHub + +- On GitHub, go to Marketplace +- Select Azure Pipeline, and "Set up a plan" +- Select Free, then "Install it for free" +- Click "Complete order and begin installation" + +``` +!!! ACCESS !!! +Azure Pipeline needs RW on code, RO on metadata, RW on checks, commit statuses, deployments, issues, pull requests. +RW access here is required to allow update of the pipelines YAML files from Azure DevOps interface, and to +update the status of builds and PRs on GitHub side when Azure Pipelines are triggered. +Note however that no admin access is defined here: this means that Azure Pipelines cannot do anything with +protected branches, like master, and cannot modify the security context around this on GitHub. +Access can be defined for all or only selected repositories, which is nice. +``` + +- Redirected to Azure DevOps, select the account created in _Having an Azure DevOps account_ section. +- Select the organization, and click "Create a new project" (let's name it the same than the targetted github repo) +- The Visibility is public, to profit from 10 parallel jobs + +``` +!!! ACCESS !!! +Azure Pipelines needs access to the GitHub account (in term of beeing able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. +``` + +_Done. We can move to pipelines configuration._ + +## Import an existing pipelines from `.azure-pipelines` folder + +- On Azure DevOps, go to your organization (eg. _certbot_) then your project (eg. _certbot_) +- Click "Pipelines" tab +- Click "New pipeline" +- Where is your code?: select "__Use the classic editor__" + +__Warning: Do not choose the GitHub option in Where is your code? section. Indeed, this option will trigger an OAuth +grant permissions from Azure Pipelines to GitHub in order to setup a GitHub OAuth Application. The permissions asked +then are way too large (admin level on almost everything), while the classic approach does not add any more +permissions, and works perfectly well.__ + +- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, master in default branch. +- Click on YAML option for "Select a template" +- Choose a name for the pipeline (eg. test-pipeline), and browse to the actual pipeline YAML definition in the + "YAML file path" input (eg. `.azure-pipelines/test-pipeline.yml`) +- Click "Save & queue", choose the master branch to build the first pipeline, and click "Save and run" button. + +_Done. Pipeline is operational. Repeat to add more pipelines from existing YAML files in `.azure-pipelines`._ + +## Add a secret variable to a pipeline (like `CODECOV_TOKEN`) + +__NB: Following steps suppose that you already setup the YAML pipeline file to +consume the secret variable that these steps will create as an environment variable. +For a variable named `CODECOV_TOKEN` consuming the variable `codecov_token`, +in the YAML file this setup would take the form of the following: +``` +steps: + - script: ./do_something_that_consumes_CODECOV_TOKEN # Eg. `codecov -F windows` + env: + CODECOV_TOKEN: $(codecov_token) +``` + +- On Azure DevOps, go to you organization, project, pipeline tab +- Select the pipeline, click "Edit" button, then click "Variables" button +- Set name (eg `codecov_token`), value, tick "Keep this value secret" diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml new file mode 100644 index 000000000..69dbc5a30 --- /dev/null +++ b/.azure-pipelines/advanced.yml @@ -0,0 +1,18 @@ +# Advanced pipeline for isolated checks and release purpose +trigger: + - test-* + - '*.x' +pr: + - test-* + - '*.x' +# This pipeline is also nightly run on master +schedules: + - cron: "4 0 * * *" + displayName: Nightly build + branches: + include: + - master + +jobs: + - template: templates/tests-suite.yml + - template: templates/installer-tests.yml \ No newline at end of file diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml new file mode 100644 index 000000000..899a373be --- /dev/null +++ b/.azure-pipelines/main.yml @@ -0,0 +1,11 @@ +trigger: + # apache-parser-v2 is a temporary branch for doing work related to + # rewriting the parser in the Apache plugin. + - apache-parser-v2 + - master +pr: + - apache-parser-v2 + - master + +jobs: + - template: templates/tests-suite.yml \ No newline at end of file diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml new file mode 100644 index 000000000..853fda6fe --- /dev/null +++ b/.azure-pipelines/templates/installer-tests.yml @@ -0,0 +1,31 @@ +jobs: + - job: installer + pool: + vmImage: vs2017-win2016 + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: 3.7 + architecture: x86 + addToPath: true + - script: python windows-installer/construct.py + displayName: Build Certbot installer + - task: CopyFiles@2 + inputs: + sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis + contents: '*.exe' + targetFolder: $(Build.ArtifactStagingDirectory) + - task: PublishPipelineArtifact@1 + inputs: + path: $(Build.ArtifactStagingDirectory) + artifact: WindowsInstaller + - script: $(Build.ArtifactStagingDirectory)\certbot-installer-win32.exe /S + displayName: Install Certbot + - script: | + python -m venv venv + venv\Scripts\python tools\pip_install.py -e certbot-ci + displayName: Prepare Certbot-CI + - script: | + set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% + venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 + displayName: Run integration tests \ No newline at end of file diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml new file mode 100644 index 000000000..3fe0abf74 --- /dev/null +++ b/.azure-pipelines/templates/tests-suite.yml @@ -0,0 +1,36 @@ +jobs: + - job: test + pool: + vmImage: vs2017-win2016 + strategy: + matrix: + py35: + PYTHON_VERSION: 3.5 + TOXENV: py35 + py37-cover: + PYTHON_VERSION: 3.7 + TOXENV: py37-cover + integration-certbot: + PYTHON_VERSION: 3.7 + TOXENV: integration-certbot + PYTEST_ADDOPTS: --numprocesses 4 + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(PYTHON_VERSION) + addToPath: true + - script: python tools/pip_install.py -U tox coverage + displayName: Install dependencies + - script: python -m tox + displayName: Run tox + # We do not require codecov report upload to succeed. So to avoid to break the pipeline if + # something goes wrong, each command is suffixed with a command that hides any non zero exit + # codes and echoes an informative message instead. + - bash: | + curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash" + chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash" + ./codecov-bash -F windows || echo "Codecov did not collect coverage reports" + condition: eq(variables['TOXENV'], 'py37-cover') + env: + CODECOV_TOKEN: $(codecov_token) + displayName: Publish coverage diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 2427c0128..8de9da87c 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -70,13 +70,39 @@ def _copy_assets(build_path, repo_path): def _generate_pynsist_config(repo_path, build_path): print('Generate pynsist configuration') + pywin32_paths_file = os.path.join(build_path, 'pywin32_paths.py') + + # Pywin32 uses non-standard folders to hold its packages. We need to instruct pynsist bootstrap + # explicitly to add them into sys.path. This is done with a custom "pywin32_paths.py" that is + # referred in the pynsist configuration as an "extra_preamble". + # Reference example: https://github.com/takluyver/pynsist/tree/master/examples/pywebview + with open(pywin32_paths_file, 'w') as file_h: + file_h.write('''\ +pkgdir = os.path.join(os.path.dirname(installdir), 'pkgs') + +sys.path.extend([ + os.path.join(pkgdir, 'win32'), + os.path.join(pkgdir, 'win32', 'lib'), +]) + +# Preload pywintypes and pythoncom +pwt = os.path.join(pkgdir, 'pywin32_system32', 'pywintypes{0}{1}.dll') +pcom = os.path.join(pkgdir, 'pywin32_system32', 'pythoncom{0}{1}.dll') +import warnings +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import imp +imp.load_dynamic('pywintypes', pwt) +imp.load_dynamic('pythoncom', pcom) +'''.format(PYTHON_VERSION[0], PYTHON_VERSION[1])) + installer_cfg_path = os.path.join(build_path, 'installer.cfg') certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'], universal_newlines=True, cwd=repo_path).strip() - with open(os.path.join(installer_cfg_path), 'w') as file_h: - file_h.write("""\ + with open(installer_cfg_path, 'w') as file_h: + file_h.write('''\ [Application] name=Certbot version={certbot_version} @@ -87,7 +113,7 @@ target=$INSTDIR\\run.bat [Build] directory=nsis nsi_template=template.nsi -installer_name=certbot-{certbot_version}-installer-{installer_suffix}.exe +installer_name=certbot-installer-{installer_suffix}.exe [Python] version={python_version} @@ -101,7 +127,8 @@ files=run.bat [Command certbot] entry_point=certbot.main:main -""".format(certbot_version=certbot_version, +extra_preamble=pywin32_paths.py +'''.format(certbot_version=certbot_version, installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', python_bitness=PYTHON_BITNESS, python_version='.'.join([str(item) for item in PYTHON_VERSION]))) -- cgit v1.2.3 From c1f4b86d34c8637dc2a179aea0300f33a17b4602 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 8 Oct 2019 16:12:02 -0700 Subject: Use shared variable group (#7431) When setting up Azure Pipelines, I didn't like having to define codecov_token for each pipeline. This works around it by using a shared variable group. You can see this working successfully at https://dev.azure.com/certbot/certbot/_build/results?buildId=3. * Use certbot-common. * update instructions --- .azure-pipelines/INSTALL.md | 8 +++++--- .azure-pipelines/templates/tests-suite.yml | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/INSTALL.md b/.azure-pipelines/INSTALL.md index b5f79e525..9c1e4bff7 100644 --- a/.azure-pipelines/INSTALL.md +++ b/.azure-pipelines/INSTALL.md @@ -112,6 +112,8 @@ steps: CODECOV_TOKEN: $(codecov_token) ``` -- On Azure DevOps, go to you organization, project, pipeline tab -- Select the pipeline, click "Edit" button, then click "Variables" button -- Set name (eg `codecov_token`), value, tick "Keep this value secret" +To set up a variable that is shared between pipelines, follow the instructions +at +https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups. +When adding variables to a group, don't forget to tick "Keep this value secret" +if it shouldn't be shared publcily. diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml index 3fe0abf74..bb54c8eee 100644 --- a/.azure-pipelines/templates/tests-suite.yml +++ b/.azure-pipelines/templates/tests-suite.yml @@ -14,6 +14,8 @@ jobs: PYTHON_VERSION: 3.7 TOXENV: integration-certbot PYTEST_ADDOPTS: --numprocesses 4 + variables: + - group: certbot-common steps: - task: UsePythonVersion@0 inputs: -- cgit v1.2.3 From f755cfef4885e2175c9c2003c981919bd285c73f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 8 Oct 2019 16:16:04 -0700 Subject: Add final newlines to files. (#7432) More conventional and makes it nicer when doing things like running cat to quickly look at the file like I was doing when I noticed this. --- .azure-pipelines/advanced.yml | 2 +- .azure-pipelines/main.yml | 2 +- .azure-pipelines/templates/installer-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index 69dbc5a30..a072a8a85 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -15,4 +15,4 @@ schedules: jobs: - template: templates/tests-suite.yml - - template: templates/installer-tests.yml \ No newline at end of file + - template: templates/installer-tests.yml diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml index 899a373be..be9eaf0b0 100644 --- a/.azure-pipelines/main.yml +++ b/.azure-pipelines/main.yml @@ -8,4 +8,4 @@ pr: - master jobs: - - template: templates/tests-suite.yml \ No newline at end of file + - template: templates/tests-suite.yml diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index 853fda6fe..f0e151439 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -28,4 +28,4 @@ jobs: - script: | set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 - displayName: Run integration tests \ No newline at end of file + displayName: Run integration tests -- cgit v1.2.3 From ec3ec9068c59dcf94ceca3302ad6d08299cf9aba Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 9 Oct 2019 01:17:08 +0200 Subject: Upgrade to pywin32>=225 and fix unit tests (#7429) Fixes #7426 --- certbot/compat/filesystem.py | 8 +------- certbot/tests/compat/filesystem_test.py | 10 ++++------ setup.py | 2 +- tools/dev_constraints.txt | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 6bcc9a693..69a3a63c5 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -546,13 +546,7 @@ def _generate_windows_flags(rights_desc): if rights_desc['write']: flag = flag | (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - # Despite bit `512` being present in ntsecuritycon.FILE_ALL_ACCESS, it is - # not effectively applied to the file or the directory. - # As _generate_windows_flags is also used to compare two dacls, we remove - # it right now to have flags that contain only the bits effectively applied - # by Windows. - ^ 512) + ^ ntsecuritycon.FILE_GENERIC_EXECUTE) if rights_desc['execute']: flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index ccb93efa8..364993018 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -89,8 +89,8 @@ class WindowsChmodTests(TempDirTestCase): self.assertEqual(len(system_aces), 1) self.assertEqual(len(admin_aces), 1) - self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) - self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) + self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) + self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) def test_read_flag(self): self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ) @@ -101,12 +101,10 @@ class WindowsChmodTests(TempDirTestCase): def test_write_flag(self): self._test_flag(2, (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - ^ 512)) + ^ ntsecuritycon.FILE_GENERIC_EXECUTE)) def test_full_flag(self): - self._test_flag(7, (ntsecuritycon.FILE_ALL_ACCESS - ^ 512)) + self._test_flag(7, ntsecuritycon.FILE_ALL_ACCESS) def _test_flag(self, everyone_mode, windows_flag): # Note that flag is tested against `everyone`, not `user`, because practically these unit diff --git a/setup.py b/setup.py index 1f4838c90..d5469bb26 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ install_requires = [ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. -pywin32_req = 'pywin32>=224' +pywin32_req = 'pywin32>=225' if StrictVersion(setuptools_version) >= StrictVersion('36.2'): install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index c23cf9cce..419b65d6c 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -64,7 +64,7 @@ pytest-sugar==0.9.2 pytest-rerunfailures==4.2 python-dateutil==2.6.1 python-digitalocean==1.11 -pywin32==224 +pywin32==225 PyYAML==3.13 repoze.sphinx.autointerface==0.8 requests-file==1.4.2 -- cgit v1.2.3 From 717afebcff44c9abbb1338efc7831b0d3b3246f2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 9 Oct 2019 14:39:49 -0700 Subject: Upload coverage for integration tests (#7433) * Upload coverage for integration tests. * Use in not containsValue. --- .azure-pipelines/templates/tests-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml index bb54c8eee..119f755a6 100644 --- a/.azure-pipelines/templates/tests-suite.yml +++ b/.azure-pipelines/templates/tests-suite.yml @@ -32,7 +32,7 @@ jobs: curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash" chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash" ./codecov-bash -F windows || echo "Codecov did not collect coverage reports" - condition: eq(variables['TOXENV'], 'py37-cover') + condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot') env: CODECOV_TOKEN: $(codecov_token) displayName: Publish coverage -- cgit v1.2.3 From 118cb3c9b1e62599b57082b71ab7313b2cdc1632 Mon Sep 17 00:00:00 2001 From: alexzorin Date: Thu, 10 Oct 2019 09:09:25 +1100 Subject: cli: allow --dry-run to be combined with --server (#7436) The value of --server will now be respected, except when it is the default value, in which case it will be changed to the staging server, preserving Certbot's existing behavior. --- CHANGELOG.md | 2 ++ certbot/cli.py | 19 +++++++++++++------ certbot/tests/cli_test.py | 24 +++++++++++++++++------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 102eaf4bb..fa8ca2379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Removed `--fast` flag from the test farm tests +* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the + staging server instead of the live server when `--dry-run` is used. ### Fixed diff --git a/certbot/cli.py b/certbot/cli.py index d22a9a524..6715dfd9c 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -649,13 +649,20 @@ class HelpfulArgumentParser(object): def set_test_server(self, parsed_args): """We have --staging/--dry-run; perform sanity check and set config.server""" - if parsed_args.server not in (flag_default("server"), constants.STAGING_URI): - conflicts = ["--staging"] if parsed_args.staging else [] - conflicts += ["--dry-run"] if parsed_args.dry_run else [] - raise errors.Error("--server value conflicts with {0}".format( - " and ".join(conflicts))) + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server | Conflict error | Use | - parsed_args.server = constants.STAGING_URI + default_servers = (flag_default("server"), constants.STAGING_URI) + + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI if parsed_args.dry_run: if self.verb not in ["certonly", "renew"]: diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 87b074a81..166559040 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -333,16 +333,26 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self._assert_dry_run_flag_worked(self.parse(short_args + ['auth']), True) self._assert_dry_run_flag_worked(self.parse(short_args + ['renew']), True) + self._assert_dry_run_flag_worked(self.parse(short_args + ['certonly']), True) + short_args += ['certonly'] - self._assert_dry_run_flag_worked(self.parse(short_args), True) - short_args += '--server example.com'.split() - conflicts = ['--dry-run'] - self._check_server_conflict_message(short_args, '--dry-run') + # `--dry-run --server example.com` should emit example.com + self.assertEqual(self.parse(short_args + ['--server', 'example.com']).server, + 'example.com') + + # `--dry-run --server STAGING_URI` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', constants.STAGING_URI]).server, + constants.STAGING_URI) + + # `--dry-run --server LIVE` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', cli.flag_default("server")]).server, + constants.STAGING_URI) - short_args += ['--staging'] - conflicts += ['--staging'] - self._check_server_conflict_message(short_args, conflicts) + # `--dry-run --server example.com --staging` should emit an error + conflicts = ['--staging'] + self._check_server_conflict_message(short_args + ['--server', 'example.com', '--staging'], + conflicts) def test_option_was_set(self): key_size_option = 'rsa_key_size' -- cgit v1.2.3 From 032178bea05f2c17a734d64ed1f874e0f3eb9228 Mon Sep 17 00:00:00 2001 From: Victor Shih Date: Fri, 18 Oct 2019 13:36:45 -0700 Subject: Clarify possible existence of /etc/letsencrypt/cli.ini (#7449) --- docs/using.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/using.rst b/docs/using.rst index 458ab9a01..83d824058 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -917,8 +917,9 @@ Certbot accepts a global configuration file that applies its options to all invo of Certbot. Certificate specific configuration choices should be set in the ``.conf`` files that can be found in ``/etc/letsencrypt/renewal``. -By default no cli.ini file is created, after creating one -it is possible to specify the location of this configuration file with +By default no cli.ini file is created (though it may exist already if you installed Certbot +via a package manager, for instance). +After creating one it is possible to specify the location of this configuration file with ``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An example configuration file is shown below: -- cgit v1.2.3 From 37b3c22dee5576f4d799ae58e3e1d338ab8b9c7f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 18 Oct 2019 23:06:37 -0700 Subject: Run nightly on Azure even if no commits landed. (#7455) --- .azure-pipelines/advanced.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index a072a8a85..afcf16db4 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -12,6 +12,7 @@ schedules: branches: include: - master + always: true jobs: - template: templates/tests-suite.yml -- cgit v1.2.3 From f8e097a06176edfd69cd7306b6c83e3946b47f60 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 18 Oct 2019 23:09:08 -0700 Subject: Remove warning about rename. (#7453) --- certbot/cli.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/certbot/cli.py b/certbot/cli.py index 6715dfd9c..93cdc7408 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -163,24 +163,6 @@ def report_config_interaction(modified, modifiers): VAR_MODIFIERS.setdefault(var, set()).update(modifiers) -def possible_deprecation_warning(config): - "A deprecation warning for users with the old, not-self-upgrading letsencrypt-auto." - if cli_command != LEAUTO: - return - if config.no_self_upgrade: - # users setting --no-self-upgrade might be hanging on a client version like 0.3.0 - # or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't - # need warnings - return - 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. 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) - - class _Default(object): """A class to use as a default to detect if a value is set by a user""" @@ -642,8 +624,6 @@ class HelpfulArgumentParser(object): raise errors.Error( "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - possible_deprecation_warning(parsed_args) - return parsed_args def set_test_server(self, parsed_args): -- cgit v1.2.3 From 44cc8d7a3ca72ba5c19b2939d7bd226764507f85 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 21 Oct 2019 13:54:17 -0700 Subject: Require newer versions of oauth2client (#7458) Over the weekend, nightly tests on Windows failed for certbot-dns-google: https://dev.azure.com/certbot/web/build.aspx?pcguid=74ef9c03-9faf-405b-9d03-9acf8c43e8d6&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f72 The error occurred inside `oauth2client`'s locking code and the failure seems spurious as it did not reproduce this morning: https://dev.azure.com/certbot/certbot/_build/results?buildId=73 I could not find a relevant changelog entry in `oauth2client` saying they've fixed the problem, but the problematic code no longer exists in `oauth2client>=4.0`. This PR updates our minimum dependency required in an attempt to avoid spurious failures for us in the future. The only downside I am aware of is it'll make it harder for certbot-dns-google to be packaged in Debian Old Stable or Ubuntu 16.04, but I don't expect either of those things to happen anytime soon. * bump oauth2client dep * Update dev_constraints.txt. * Add changelog entry for packagers. --- CHANGELOG.md | 2 ++ certbot-dns-google/setup.py | 6 ++---- tools/dev_constraints.txt | 14 +++++++++++--- tools/oldest_constraints.txt | 6 ++++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8ca2379..ac1263e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Removed `--fast` flag from the test farm tests * `--server` may now be combined with `--dry-run`. Certbot will, as before, use the staging server instead of the live server when `--dry-run` is used. +* Updated certbot-dns-google to depend on newer versions of + google-api-python-client and oauth2client. ### Fixed diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 9fd159c41..6bf12ddbf 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -9,11 +9,9 @@ version = '0.40.0.dev0' install_requires = [ 'acme>=0.29.0', 'certbot>=0.39.0', - # 1.5 is the first version that supports oauth2client>=2.0 - 'google-api-python-client>=1.5', + 'google-api-python-client>=1.5.5', 'mock', - # for oauth2client.service_account.ServiceAccountCredentials - 'oauth2client>=2.0', + 'oauth2client>=4.0', 'setuptools', 'zope.interface', # already a dependency of google-api-python-client, but added for consistency diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 419b65d6c..d97f23a71 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -4,6 +4,7 @@ # files during tests (eg. letsencrypt-auto-source/pieces/dependency-requirements.txt). alabaster==0.7.10 apipkg==1.4 +appnope==0.1.0 asn1crypto==0.22.0 astroid==1.6.5 attrs==17.3.0 @@ -15,6 +16,7 @@ botocore==1.12.36 cloudflare==1.5.1 codecov==2.0.15 configparser==3.7.4 +contextlib2==0.6.0.post1 coverage==4.5.4 decorator==4.1.2 dns-lexicon==3.2.1 @@ -23,9 +25,11 @@ docutils==0.12 execnet==1.5.0 future==0.16.0 futures==3.1.1 -google-api-python-client==1.5 +filelock==3.0.12 +google-api-python-client==1.5.5 httplib2==0.10.3 imagesize==0.7.1 +importlib-metadata==0.23 ipdb==0.10.2 ipython==5.5.0 ipython-genutils==0.2.0 @@ -38,9 +42,11 @@ logger==1.4 logilab-common==1.4.1 MarkupSafe==1.0 mccabe==0.6.1 +more-itertools==5.0.0 mypy==0.600 ndg-httpsclient==0.3.2 -oauth2client==2.0.0 +oauth2client==4.0.0 +packaging==19.2 pathlib2==2.3.0 pexpect==4.7.0 pickleshare==0.7.4 @@ -79,13 +85,15 @@ Sphinx==1.7.5 sphinx-rtd-theme==0.2.4 sphinxcontrib-websupport==1.0.1 tldextract==2.2.0 +toml==0.10.0 tox==3.14.0 tqdm==4.19.4 traitlets==4.3.2 twine==1.11.0 typed-ast==1.1.0 typing==3.6.4 -uritemplate==0.6 +uritemplate==3.0.0 virtualenv==16.6.2 wcwidth==0.1.7 wrapt==1.11.1 +zipp==0.6.0 diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index 73465639f..c5a5c5aa0 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -16,6 +16,7 @@ pyOpenSSL==0.13.1 pyparsing==1.5.6 pyRFC3339==1.0 python-augeas==0.5.0 +oauth2client==4.0.0 six==1.9.0 # setuptools 0.9.8 is the actual version packaged, but some other dependencies # in this file require setuptools>=1.0 and there are no relevant changes for us @@ -35,11 +36,12 @@ idna==2.0 pbr==1.8.0 pytz==2012rc0 +# Debian Buster constraints +google-api-python-client==1.5.5 + # Our setup.py constraints cloudflare==1.5.1 cryptography==1.2.3 -google-api-python-client==1.5 -oauth2client==2.0 parsedatetime==1.3 pyparsing==1.5.5 python-digitalocean==1.11 -- cgit v1.2.3 From db46326e9597f2166483f5da459069fe8afd509c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 21 Oct 2019 14:42:51 -0700 Subject: Run at 4:00AM UTC not 0:04AM UTC. (#7460) Fixes [cron syntax](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml#supported-cron-syntax) to get the behavior I had in mind in https://github.com/certbot/certbot/pull/7377#discussion_r331295897. --- .azure-pipelines/advanced.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index afcf16db4..9832c0684 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -7,7 +7,7 @@ pr: - '*.x' # This pipeline is also nightly run on master schedules: - - cron: "4 0 * * *" + - cron: "0 4 * * *" displayName: Nightly build branches: include: -- cgit v1.2.3 From 3132c32c262e4d81b366a5df9aa72b4c6037bef4 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 23 Oct 2019 01:50:18 -0700 Subject: Update pluggy pinning. (#7459) --- tools/dev_constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d97f23a71..6854be466 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -51,7 +51,7 @@ pathlib2==2.3.0 pexpect==4.7.0 pickleshare==0.7.4 pkginfo==1.4.2 -pluggy==0.5.2 +pluggy==0.13.0 prompt-toolkit==1.0.15 ptyprocess==0.6.0 py==1.8.0 -- cgit v1.2.3 From 60673e8a81d1a0695921a30df808bfeed18fb884 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 24 Oct 2019 03:48:01 -0700 Subject: Remove AppVeyor. (#7440) --- appveyor.yml | 47 ----------------------------------------------- tox.cover.py | 6 +++--- 2 files changed, 3 insertions(+), 50 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 53f29a5e6..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,47 +0,0 @@ -image: Visual Studio 2015 - -environment: - matrix: - - TOXENV: py35 - - TOXENV: py37-cover - - TOXENV: integration-certbot - -branches: - only: - # apache-parser-v2 is a temporary branch for doing work related to - # rewriting the parser in the Apache plugin. - - apache-parser-v2 - - master - - /^\d+\.\d+\.x$/ # Version branches like X.X.X - - /^test-.*$/ - -init: - # Since master can receive only commits from PR that have already been tested, following - # condition avoid to launch all jobs except the coverage one for commits pushed to master. - - ps: | - if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master' ` - -And -Not ($Env:TOXENV -Like '*-cover')) - { $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild } - -install: - # Use Python 3.7 by default - - SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH% - # Using 4 processes is proven to be the most efficient integration tests config for AppVeyor - - IF %TOXENV%==integration-certbot SET PYTEST_ADDOPTS=--numprocesses=4 - # Check env - - python --version - # Upgrade pip to avoid warnings - - python -m pip install --upgrade pip - # Ready to install tox and coverage - # tools/pip_install.py is used to pin packages to a known working version. - - python tools\\pip_install.py tox codecov - -build: off - -test_script: - - set TOX_TESTENV_PASSENV=APPVEYOR - # Test env is set by TOXENV env variable - - tox - -on_success: - - if exist .coverage codecov -F windows diff --git a/tox.cover.py b/tox.cover.py index c313419ed..6981bbb41 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -56,9 +56,9 @@ def cover(package): def main(): description = """ -This script is used by tox.ini (and thus by Travis CI and AppVeyor) in order -to generate separate stats for each package. It should be removed once those -packages are moved to a separate repo. +This script is used by tox.ini (and thus by Travis CI and Azure Pipelines) in +order to generate separate stats for each package. It should be removed once +those packages are moved to a separate repo. Option -e makes sure we fail fast and don't submit to codecov.""" parser = argparse.ArgumentParser(description=description) -- cgit v1.2.3 From 0f31d9b7ac1bb01a3ff6ac396fe315cb657eef6f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 24 Oct 2019 05:46:55 -0700 Subject: Remove skip_unless cruft (#7410) * Remove skip_unless cruft. * remove unused import --- acme/acme/test_util.py | 21 --------------------- certbot/tests/util.py | 20 -------------------- 2 files changed, 41 deletions(-) diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py index f04829deb..6d9cbc8dc 100644 --- a/acme/acme/test_util.py +++ b/acme/acme/test_util.py @@ -4,7 +4,6 @@ """ import os -import unittest import pkg_resources from cryptography.hazmat.backends import default_backend @@ -73,23 +72,3 @@ def load_pyopenssl_private_key(*names): loader = _guess_loader( names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) return crypto.load_privatekey(loader, load_vector(*names)) - - -def skip_unless(condition, reason): # pragma: no cover - """Skip tests unless a condition holds. - - This implements the basic functionality of unittest.skipUnless - which is only available on Python 2.7+. - - :param bool condition: If ``False``, the test will be skipped - :param str reason: the reason for skipping the test - - :rtype: callable - :returns: decorator that hides tests unless condition is ``True`` - - """ - if hasattr(unittest, "skipUnless"): - return unittest.skipUnless(condition, reason) - elif condition: - return lambda cls: cls - return lambda cls: None diff --git a/certbot/tests/util.py b/certbot/tests/util.py index c46623e0a..dfd752511 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -94,26 +94,6 @@ def load_pyopenssl_private_key(*names): return OpenSSL.crypto.load_privatekey(loader, load_vector(*names)) -def skip_unless(condition, reason): # pragma: no cover - """Skip tests unless a condition holds. - - This implements the basic functionality of unittest.skipUnless - which is only available on Python 2.7+. - - :param bool condition: If ``False``, the test will be skipped - :param str reason: the reason for skipping the test - - :rtype: callable - :returns: decorator that hides tests unless condition is ``True`` - - """ - if hasattr(unittest, "skipUnless"): - return unittest.skipUnless(condition, reason) - elif condition: - return lambda cls: cls - return lambda cls: None - - def make_lineage(config_dir, testfile): """Creates a lineage defined by testfile. -- cgit v1.2.3 From 2dbe47f3a726f4c43b0d747193ed207999dc8ea8 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 30 Oct 2019 18:19:10 +0100 Subject: Create a release pipeline on Azure for Windows installer (#7441) This PR creates a pipeline triggered on tag push matching v0.* (eg. v0.40.0). Once triggered, this pipeline will build the windows installer, and run integration tests on it, like for the pipeline run nightly. I also add a simple script to extract from CHANGELOG.md file to extract the relevant part to put it in the body of the GitHub release. I believe it makes things nicer. * Create release pipeline * Relax condition on tags * Put beta keyword * Update job name * Fix release pipeline --- .azure-pipelines/advanced.yml | 2 ++ .azure-pipelines/release.yml | 13 ++++++++ .azure-pipelines/templates/changelog.yml | 14 +++++++++ .azure-pipelines/templates/installer-tests.yml | 5 ++-- tools/extract_changelog.py | 41 ++++++++++++++++++++++++++ windows-installer/construct.py | 2 +- windows-installer/template.nsi | 5 +++- 7 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 .azure-pipelines/release.yml create mode 100644 .azure-pipelines/templates/changelog.yml create mode 100755 tools/extract_changelog.py diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index 9832c0684..fe24a9ecb 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -15,5 +15,7 @@ schedules: always: true jobs: + # Any addition here should be reflected in the release pipeline. + # It is advised to declare all jobs here as templates to improve maintainability. - template: templates/tests-suite.yml - template: templates/installer-tests.yml diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml new file mode 100644 index 000000000..aeb5ee327 --- /dev/null +++ b/.azure-pipelines/release.yml @@ -0,0 +1,13 @@ +# Release pipeline to build and deploy Certbot for Windows for GitHub release tags +trigger: + tags: + include: + - v* +pr: none + +jobs: + # Any addition here should be reflected in the advanced pipeline. + # It is advised to declare all jobs here as templates to improve maintainability. + - template: templates/tests-suite.yml + - template: templates/installer-tests.yml + - template: templates/changelog.yml diff --git a/.azure-pipelines/templates/changelog.yml b/.azure-pipelines/templates/changelog.yml new file mode 100644 index 000000000..7619858a9 --- /dev/null +++ b/.azure-pipelines/templates/changelog.yml @@ -0,0 +1,14 @@ +jobs: + - job: changelog + pool: + vmImage: vs2017-win2016 + steps: + - bash: | + CERTBOT_VERSION="$(python -c "import certbot; print(certbot.__version__)")" + "${BUILD_REPOSITORY_LOCALPATH}\tools\extract_changelog.py" "${CERTBOT_VERSION}" >> "${BUILD_ARTIFACTSTAGINGDIRECTORY}/release_notes.md" + displayName: Prepare changelog + - task: PublishPipelineArtifact@1 + inputs: + path: $(Build.ArtifactStagingDirectory) + artifact: changelog + displayName: Publish changelog diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index f0e151439..b1c5ea6b1 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -18,8 +18,9 @@ jobs: - task: PublishPipelineArtifact@1 inputs: path: $(Build.ArtifactStagingDirectory) - artifact: WindowsInstaller - - script: $(Build.ArtifactStagingDirectory)\certbot-installer-win32.exe /S + artifact: windows-installer + displayName: Publish Windows installer + - script: $(Build.ArtifactStagingDirectory)\certbot-beta-installer-win32.exe /S displayName: Install Certbot - script: | python -m venv venv diff --git a/tools/extract_changelog.py b/tools/extract_changelog.py new file mode 100755 index 000000000..d3bd2aa44 --- /dev/null +++ b/tools/extract_changelog.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +from __future__ import print_function +import sys +import os +import re + + +CERTBOT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +NEW_SECTION_PATTERN = re.compile(r'^##\s*[\d.]+\s*-\s*[\d-]+$') + + +def main(): + version = sys.argv[1] + + section_pattern = re.compile(r'^##\s*{0}\s*-\s*[\d-]+$' + .format(version.replace('.', '\\.'))) + + with open(os.path.join(CERTBOT_ROOT, 'CHANGELOG.md')) as file_h: + lines = file_h.read().splitlines() + + changelog = [] + + i = 0 + while i < len(lines): + if section_pattern.match(lines[i]): + i = i + 1 + while i < len(lines): + if NEW_SECTION_PATTERN.match(lines[i]): + break + changelog.append(lines[i]) + i = i + 1 + i = i + 1 + + changelog = [entry for entry in changelog if entry] + + print('\n'.join(changelog)) + + +if __name__ == '__main__': + main() diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 8de9da87c..7fd3039f9 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -113,7 +113,7 @@ target=$INSTDIR\\run.bat [Build] directory=nsis nsi_template=template.nsi -installer_name=certbot-installer-{installer_suffix}.exe +installer_name=certbot-beta-installer-{installer_suffix}.exe [Python] version={python_version} diff --git a/windows-installer/template.nsi b/windows-installer/template.nsi index 0f366c22a..50a03865f 100644 --- a/windows-installer/template.nsi +++ b/windows-installer/template.nsi @@ -60,7 +60,10 @@ SetCompressor lzma !insertmacro MUI_LANGUAGE "English" [% endblock modernui %] -Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +; CERTBOT CUSTOM BEGIN +Name "${PRODUCT_NAME} (beta) ${PRODUCT_VERSION}" +;Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +; CERTBOT CUSTOM END OutFile "${INSTALLER_NAME}" ShowInstDetails show -- cgit v1.2.3 From e19b2e04c75b6df4e3f8a455700aa95fca79bcc3 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 30 Oct 2019 18:39:45 +0100 Subject: Migrate certbot-auto users on CentOS 6 to Python 3.6 (#7268) Fixes #7007 Python 3.4 is [EOL](https://www.python.org/dev/peps/pep-0429/), and only Python 3.x version available for CentOS 6 through EPEL is this version, and so is used by `certbot-auto`, the only official way to install Certbot on this platform. This unpleasant situation becomes a little more uncomfortable, considering that the newest `pip` version (19.2) [just dropped Python 3.4 support](https://github.com/pypa/pip/issues/6685) and will refuse to start on this Python version. We can expect a lot of dependencies to follow this path now. One direct result of this situation is that a fix to support correctly the ARM platforms requires to upgrade `pip` to 19.2 for `certbot-auto`. So this is not possible right now. Then, let's upgrade Certbot instances on CentOS 6 to a supported version of Python 3. This PR proposes a new bootstrap approach for CentOS 6 platform, `BootstrapRpmPython3Legacy`, that will install Python 3.6 from [SCL](https://www.softwarecollections.org) (the latest one available for now on CentOS 6). In term of Python 3 specific bootstrap methods, I take the occasion here to completely separate the bootstrap of CentOS 6 as a legacy system, from the RPM-based newest systems (like Fedora 29+) that are simply dropping support for Python 2.x. This is in prevision of future migration for all systems on Python 3.x, that is a different problematic than supporting old systems. * Add logic * Rebuilt letsencrypt-auto * Fix logic * Focus on specific packages * Maintain PATH for further invocations of letsencrypt-auto after bootstrap. * Various corrections * Fix farm test for RHEL6 * Working centos6 letsencrypt-auto self tests * Fix test_sdist for CentOS 6 * Corrections * Work in progress * Working configuration * Fix typo * Remove EPEL. Add a test. * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Improvements after review * Improvements * Add a comment * Add a test * Update a test * Corrections * Update function return * Work in progress * Correct behavior on oracle linux 6. * Corrections * Rebuild script * Add letsencrypt-auto tests for oraclelinux6 * Update tox.ini Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/tests/oraclelinux6_tests.sh Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/tests/oraclelinux6_tests.sh Co-Authored-By: Brad Warren * Remove specific code for scientific linux * Change some variables names * Update letsencrypt-auto-source/tests/oraclelinux6_tests.sh Co-Authored-By: Brad Warren * Various corrections * Fix tests * Add a comment * Update message * Fix test message * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto Co-Authored-By: Brad Warren * Update scripts * More focused assertion * Add back a test * Update script * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Check quiet mode * Add changelog * Update letsencrypt-auto-source/tests/oraclelinux6_tests.sh Co-Authored-By: Brad Warren --- .travis.yml | 3 + CHANGELOG.md | 1 + letsencrypt-auto-source/Dockerfile.centos6 | 37 ---- letsencrypt-auto-source/Dockerfile.redhat6 | 48 +++++ letsencrypt-auto-source/letsencrypt-auto | 212 +++++++++++++++------ letsencrypt-auto-source/letsencrypt-auto.template | 106 ++++++++--- .../pieces/bootstrappers/rpm_common_base.sh | 24 +-- .../pieces/bootstrappers/rpm_python3.sh | 7 - .../pieces/bootstrappers/rpm_python3_legacy.sh | 75 ++++++++ letsencrypt-auto-source/tests/centos6_tests.sh | 147 +++++++++----- .../tests/oraclelinux6_tests.sh | 85 +++++++++ tests/letstest/scripts/test_leauto_upgrades.sh | 2 + tests/letstest/scripts/test_sdists.sh | 13 ++ tox.ini | 12 +- 14 files changed, 572 insertions(+), 200 deletions(-) delete mode 100644 letsencrypt-auto-source/Dockerfile.centos6 create mode 100644 letsencrypt-auto-source/Dockerfile.redhat6 create mode 100644 letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh create mode 100644 letsencrypt-auto-source/tests/oraclelinux6_tests.sh diff --git a/.travis.yml b/.travis.yml index 4d4ea5f55..b3e26fc79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -234,6 +234,9 @@ matrix: - sudo: required env: TOXENV=le_auto_centos6 services: docker + - sudo: required + env: TOXENV=le_auto_oraclelinux6 + services: docker <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1263e85..ba301a553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). staging server instead of the live server when `--dry-run` is used. * Updated certbot-dns-google to depend on newer versions of google-api-python-client and oauth2client. +* Migrated CentOS 6 certbot-auto users from Python 3.4 to Python 3.6. ### Fixed diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 deleted file mode 100644 index 09aa52dcd..000000000 --- a/letsencrypt-auto-source/Dockerfile.centos6 +++ /dev/null @@ -1,37 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -FROM centos:6 - -RUN yum install -y epel-release - -# Install pip and sudo: -RUN yum install -y python-pip sudo -# Update to a stable and tested version of pip. -# We do not use pipstrap here because it no longer supports Python 2.6. -RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ - /etc/sudoers - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-trust - -# Copy code: -COPY . /home/lea/certbot/letsencrypt-auto-source - -USER lea -WORKDIR /home/lea - -RUN sudo chmod +x certbot/letsencrypt-auto-source/tests/centos6_tests.sh -CMD sudo certbot/letsencrypt-auto-source/tests/centos6_tests.sh diff --git a/letsencrypt-auto-source/Dockerfile.redhat6 b/letsencrypt-auto-source/Dockerfile.redhat6 new file mode 100644 index 000000000..d5cdc0458 --- /dev/null +++ b/letsencrypt-auto-source/Dockerfile.redhat6 @@ -0,0 +1,48 @@ +# For running tests, build a docker image with a passwordless sudo and a trust +# store we can manipulate. + +ARG REDHAT_DIST_FLAVOR +FROM ${REDHAT_DIST_FLAVOR}:6 + +ARG REDHAT_DIST_FLAVOR + +RUN curl -O https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm \ + && rpm -ivh epel-release-latest-6.noarch.rpm + +# Install pip and sudo: +RUN yum install -y python-pip sudo +# Update to a stable and tested version of pip. +# We do not use pipstrap here because it no longer supports Python 2.6. +RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 +# Pin pytest version for increased stability +RUN pip install pytest==3.2.5 six==1.10.0 + +# Add an unprivileged user: +RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea + +# Let that user sudo: +RUN sed -i.bkp -e \ + 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ + /etc/sudoers + +RUN mkdir -p /home/lea/certbot + +# Install fake testing CA: +COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ +RUN update-ca-trust + +# Copy current letsencrypt-auto: +COPY . /home/lea/certbot/letsencrypt-auto-source + +# Fetch previous letsencrypt-auto that was installing python 3.4 +RUN curl https://raw.githubusercontent.com/certbot/certbot/v0.38.0/letsencrypt-auto-source/letsencrypt-auto \ + -o /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 + +RUN cp /home/lea/certbot/letsencrypt-auto-source/tests/${REDHAT_DIST_FLAVOR}6_tests.sh /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh + +USER lea +WORKDIR /home/lea + +CMD ["sudo", "certbot/letsencrypt-auto-source/tests/redhat6_tests.sh"] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index ff1fbe8c3..c0f7466ae 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,88 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if ! scl --list 2>/dev/null | grep -q rh-python36; then + return 0 + fi + set +e + . scl_source enable rh-python36 + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +570,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -769,31 +828,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -1078,8 +1156,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1175,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 31c5bb134..bc4b92092 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -298,6 +306,7 @@ DeterminePythonVersion() { {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common_base.sh }} {{ bootstrappers/rpm_common.sh }} +{{ bootstrappers/rpm_python3_legacy.sh }} {{ bootstrappers/rpm_python3.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} @@ -344,31 +353,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -545,8 +573,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -557,12 +592,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh index 326ad8b3f..2b00b199b 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh @@ -3,7 +3,9 @@ # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -23,26 +25,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index f33b07ca9..ac0553db5 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -4,7 +4,6 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -15,12 +14,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh new file mode 100644 index 000000000..0935c1b94 --- /dev/null +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh @@ -0,0 +1,75 @@ +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if ! scl --list 2>/dev/null | grep -q rh-python36; then + return 0 + fi + set +e + . scl_source enable rh-python36 + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 2c6dcf734..c15cd80ec 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,81 +1,140 @@ #!/bin/bash # Start by making sure your system is up-to-date: -yum update -y > /dev/null -yum install -y centos-release-scl > /dev/null -yum install -y python27 > /dev/null 2> /dev/null +yum update -y >/dev/null +yum install -y centos-release-scl >/dev/null +yum install -y python27 >/dev/null 2>/dev/null +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" + # we're going to modify env variables, so do this in a subshell ( -source /opt/rh/python27/enable + . scl_source enable python27 -# ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "Python3 is already installed." - exit 1 -fi + # ensure python 3 isn't installed + python3 --version >/dev/null 2>/dev/null + RESULT=$? + if [ $RESULT -eq 0 ]; then + echo "ERROR: Python3 is already installed." + exit 1 + fi -# ensure python2.7 is available -python2.7 --version 2> /dev/null -RESULT=$? -if [ $RESULT -ne 0 ]; then - error "Python3 is not available." - exit 1 -fi + # ensure python2.7 is available + python2.7 --version >/dev/null 2>/dev/null + RESULT=$? + if [ $RESULT -ne 0 ]; then + echo "ERROR: Python2.7 is not available." + exit 1 + fi -# bootstrap, but don't install python 3. -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null + # bootstrap, but don't install python 3. + "$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null -# ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "letsencrypt-auto installed Python3 even though Python2.7 is present." - exit 1 -fi + # ensure python 3 isn't installed + python3 --version >/dev/null 2>/dev/null + RESULT=$? + if [ $RESULT -eq 0 ]; then + echo "ERROR: letsencrypt-auto installed Python3 even though Python2.7 is present." + exit 1 + fi -echo "" -echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." + echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." ) # ensure python2.7 isn't available -python2.7 --version 2> /dev/null +python2.7 --version >/dev/null 2>/dev/null RESULT=$? if [ $RESULT -eq 0 ]; then - error "Python2.7 is still available." + error "ERROR: Python2.7 is still available." exit 1 fi # Skip self upgrade due to Python 3 not being available. if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then - echo "Python upgrade failure warning not printed!" + echo "ERROR: Python upgrade failure warning not printed!" exit 1 fi -# bootstrap, this time installing python3 -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null +# bootstrap from the old letsencrypt-auto, this time installing python3.4 +"$LE_AUTO_PY_34" --no-self-upgrade -n >/dev/null 2>/dev/null -# ensure python 3 is installed -python3 --version > /dev/null +# ensure python 3.4 is installed +python3.4 --version >/dev/null 2>/dev/null RESULT=$? if [ $RESULT -ne 0 ]; then - error "letsencrypt-auto failed to install Python3 when only Python2.6 is present." + echo "ERROR: letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." + exit 1 +fi + +echo "PASSED: Successfully upgraded to Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." + +# As "certbot-auto" (so without implicit --non-interactive flag set), check that the script +# refuses to install SCL Python 3.6 when run in a non interactive shell (simulated here +# using | tee /dev/null) if --non-interactive flag is not provided. +cp "$LE_AUTO" /tmp/certbot-auto +# NB: Readline has an issue on all Python versions for CentOS 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$(/tmp/certbot-auto --version 2>/dev/null | tee /dev/null | tail -2 | head -1) + +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: certbot-auto upgraded certbot in a non-interactive shell with --non-interactive flag not set." exit 1 fi -echo "PASSED: Successfully upgraded to Python3 when only Python2.6 is present." -echo "" +echo "PASSED: certbot-auto did not upgrade certbot in a non-interactive shell with --non-interactive flag not set." -export VENV_PATH=$(mktemp -d) -"$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 -if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; then - echo "Python 3 wasn't used with --no-bootstrap!" +if [ -f /opt/rh/rh-python36/enable ]; then + echo "ERROR: certbot-auto installed Python3.6 in a non-interactive shell with --non-interactive flag not set." exit 1 fi -unset VENV_PATH + +echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell with --non-interactive flag not set." + +# now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL +"$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null + +# Following test is exectued in a subshell, to not leak any environment variable +( + # enable SCL rh-python36 + . scl_source enable rh-python36 + + # ensure python 3.6 is installed + python3.6 --version >/dev/null 2>/dev/null + RESULT=$? + if [ $RESULT -ne 0 ]; then + echo "ERROR: letsencrypt-auto failed to install Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." + exit 1 + fi + + echo "PASSED: Successfully upgraded to Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." +) + +# Following test is exectued in a subshell, to not leak any environment variable +( + export VENV_PATH=$(mktemp -d) + "$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 + if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Python 3.6 wasn't used with --no-bootstrap!" + exit 1 + fi +) + +# Following test is exectued in a subshell, to not leak any environment variable +( + # enable SCL rh-python36 + . scl_source enable rh-python36 + + # ensure everything works fine with certbot-auto bootstrap when python 3.6 is already enabled + export VENV_PATH=$(mktemp -d) + if ! "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null; then + echo "ERROR: Certbot-auto broke when Python 3.6 SCL is already enabled." + exit 1 + fi +) # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh new file mode 100644 index 000000000..f3fd952f3 --- /dev/null +++ b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -eo pipefail +# Start by making sure your system is up-to-date: +yum update -y >/dev/null + +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" +LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" + +# Apply installation instructions from official documentation: +# https://certbot.eff.org/lets-encrypt/centosrhel6-other +cp "$LE_AUTO" /usr/local/bin/certbot-auto +chown root /usr/local/bin/certbot-auto +chmod 0755 /usr/local/bin/certbot-auto +LE_AUTO=/usr/local/bin/certbot-auto + +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" + +# Check bootstrap from current certbot-auto will fail, because SCL is not enabled. +set +o pipefail +if ! "$LE_AUTO" -n 2>&1 | grep -q "Enable the SCL repository and try running Certbot again."; then + echo "ERROR: Bootstrap was not aborted although SCL was not installed!" + exit 1 +fi +set -o pipefail + +echo "PASSED: Bootstrap was aborted since SCL was not installed." + +# Bootstrap from the old letsencrypt-auto, Python 3.4 will be installed from EPEL. +"$LE_AUTO_PY_34" --no-self-upgrade -n --install-only >/dev/null 2>/dev/null + +# Ensure Python 3.4 is installed +if ! command -v python3.4 &>/dev/null; then + echo "ERROR: old letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." + exit 1 +fi + +echo "PASSED: Bootstrap from old letsencrypt-auto succeeded and installed Python 3.4" + +# Expect certbot-auto to skip rebootstrapping with a warning since SCL is not installed. +if ! "$LE_AUTO" --non-interactive --version 2>&1 | grep -q "This requires manual user intervention"; then + echo "FAILED: Script certbot-auto did not print a warning about needing manual intervention!" + exit 1 +fi + +echo "PASSED: Script certbot-auto did not rebootstrap." + +# NB: Readline has an issue on all Python versions for OL 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$($LE_AUTO --version 2>/dev/null | tail -2 | head -1) + +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: Script certbot-auto upgraded certbot in a non-interactive shell while SCL was not enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto did not upgrade certbot but started it successfully while SCL was not enabled." + +# Enable SCL +yum install -y oracle-softwarecollection-release-el6 >/dev/null + +# Expect certbot-auto to bootstrap successfully since SCL is available. +"$LE_AUTO" -n --version &>/dev/null + +if [ "$(/opt/eff.org/certbot/venv/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Script certbot-auto failed to bootstrap and install Python 3.6 while SCL is available." + exit 1 +fi + +if ! /opt/eff.org/certbot/venv/bin/certbot --version > /dev/null 2> /dev/null; then + echo "ERROR: Script certbot-auto did not install certbot correctly while SCL is enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto correctly bootstraped Certbot using rh-python36 when SCL is available." + +# Expect certbot-auto will be totally silent now that everything has been correctly boostraped. +OUTPUT_LEN=$("$LE_AUTO" --install-only --no-self-upgrade --quiet 2>&1 | wc -c) +if [ "$OUTPUT_LEN" != 0 ]; then + echo certbot-auto produced unexpected output! + exit 1 +fi + +echo "PASSED: Script certbot-auto did not print anything in quiet mode." diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 541f54f6b..fc7632793 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -117,6 +117,8 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then fi if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Add the SCL python release to PATH in order to resolve python3 command + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index 347589e04..c8ce9fe7f 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,8 +1,21 @@ #!/bin/sh -xe cd letsencrypt + +# If we're on a RHEL 6 based system, we can be confident Python is already +# installed because the package manager is written in Python. +if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then + # RHEL/CentOS 6 will need a special treatment, so we need to detect that environment + RUN_RHEL6_TESTS=1 +fi + letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug +if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Enable the SCL Python 3.6 installed by letsencrypt-auto bootstrap + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" +fi + PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) diff --git a/tox.ini b/tox.ini index 04715cc2f..952204a9e 100644 --- a/tox.ini +++ b/tox.ini @@ -206,7 +206,17 @@ passenv = DOCKER_* # At the moment, this tests under Python 2.6 only, as only that version is # readily available on the CentOS 6 Docker image. commands = - docker build -f letsencrypt-auto-source/Dockerfile.centos6 -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=centos -t lea letsencrypt-auto-source + docker run --rm -t -i lea +whitelist_externals = + docker +passenv = DOCKER_* + +[testenv:le_auto_oraclelinux6] +# At the moment, this tests under Python 2.6 only, as only that version is +# readily available on the Oracle Linux 6 Docker image. +commands = + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=oraclelinux -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker -- cgit v1.2.3 From 6fcdfb0e5006be85500fad67a5a67b47befedb2a Mon Sep 17 00:00:00 2001 From: sydneyli Date: Wed, 30 Oct 2019 10:57:46 -0700 Subject: Deprecation warnings for Python 3.4 (#7378) Fixes #7367 * Deprecation warnings for Python 3.4 users * CHANGELOG.md and AUTHORS.md * double equals typo --- AUTHORS.md | 1 + CHANGELOG.md | 2 ++ acme/acme/__init__.py | 7 +++++++ certbot/main.py | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 8468cbc56..1b521548e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -230,6 +230,7 @@ Authors * [Stavros Korokithakis](https://github.com/skorokithakis) * [Stefan Weil](https://github.com/stweil) * [Steve Desmond](https://github.com/stevedesmond-ca) +* [sydneyli](https://github.com/sydneyli) * [Tan Jay Jun](https://github.com/jayjun) * [Tapple Gao](https://github.com/tapple) * [Telepenin Nikolay](https://github.com/telepenin) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba301a553..398b2e07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* We deprecated support for Python 3.4 in Certbot and its ACME library. Support + for Python 3.4 will be removed in the next major release of Certbot. * Removed `--fast` flag from the test farm tests * `--server` may now be combined with `--dry-run`. Certbot will, as before, use the staging server instead of the live server when `--dry-run` is used. diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index 20c008d64..13bdd41aa 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -48,3 +48,10 @@ class _TLSSNI01DeprecationModule(object): def __dir__(self): # pragma: no cover return ['_module'] + dir(self._module) + +if sys.version_info[:2] == (3, 4): + warnings.warn( + "Python 3.4 support will be dropped in the next release of " + "acme. Please upgrade your Python version.", + PendingDeprecationWarning, + ) # pragma: no cover diff --git a/certbot/main.py b/certbot/main.py index fc91aca5f..30d0f1d94 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1368,6 +1368,10 @@ def main(cli_args=None): if config.func != plugins_cmd: raise + if sys.version_info[:2] == (3, 4): + logger.warning("Python 3.4 support will be dropped in the next release " + "of Certbot - please upgrade your Python version.") + set_displayer(config) # Reporter -- cgit v1.2.3 From 6f711d9ae8b266e418cd44882bc0d3a5c9d13437 Mon Sep 17 00:00:00 2001 From: James Renken Date: Wed, 30 Oct 2019 14:06:31 -0500 Subject: change random sleep to use fractional seconds (#7473) If we use fractional instead of whole seconds for the random sleep before renewing, it will reduce bunching of requests to Let's Encrypt's API. --- certbot/renewal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/renewal.py b/certbot/renewal.py index 4b4a5a082..b9b45ee40 100644 --- a/certbot/renewal.py +++ b/certbot/renewal.py @@ -434,7 +434,7 @@ def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many- 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) + sleep_time = random.uniform(1, 60 * 8) logger.info("Non-interactive renewal: random delay of %s seconds", sleep_time) time.sleep(sleep_time) -- cgit v1.2.3 From de6b56bec02881d5a63173aedb670b24d847f72d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 30 Oct 2019 15:19:38 -0700 Subject: Deprecate certbot.plugins.common.TLSSNI01 (#7477) While working on #7214, I noticed that certbot.plugins.common.TLSSNI01 wasn't printing a deprecation warning and it was still being used in our Apache plugin. This PR fixes that. --- CHANGELOG.md | 2 ++ certbot-apache/certbot_apache/http_01.py | 2 +- certbot/plugins/common.py | 32 ++++++++++++++++++++++++++++++++ certbot/plugins/common_test.py | 5 +++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398b2e07b..f6c5ce34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Updated certbot-dns-google to depend on newer versions of google-api-python-client and oauth2client. * Migrated CentOS 6 certbot-auto users from Python 3.4 to Python 3.6. +* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a + future release. ### Fixed diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py index 538806de0..de22edd85 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/http_01.py @@ -14,7 +14,7 @@ from certbot_apache.parser import get_aug_path logger = logging.getLogger(__name__) -class ApacheHttp01(common.TLSSNI01): +class ApacheHttp01(common.ChallengePerformer): """Class that performs HTTP-01 challenges within the Apache configurator.""" CONFIG_TEMPLATE22_PRE = """\ diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 5c292bfad..50b07ad99 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -2,6 +2,7 @@ import logging import re import shutil +import sys import tempfile import warnings @@ -503,3 +504,34 @@ def dir_setup(test_dir, pkg): # pragma: no cover test_configs, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if attr == 'TLSSNI01': + warnings.warn('TLSSNI01 is deprecated and will be removed soon.', + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 1684fb998..80df666a5 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -352,6 +352,11 @@ class TLSSNI01Test(unittest.TestCase): self.assertEqual(self.sni.get_z_domain(achall), achall.response(achall.account_key).z_domain.decode("utf-8")) + def test_warning(self): + with mock.patch('certbot.plugins.common.warnings.warn') as mock_warn: + from certbot.plugins.common import TLSSNI01 # pylint: disable=unused-variable + self.assertTrue(mock_warn.call_args[0][0].startswith('TLSSNI01')) + class InstallVersionControlledFileTest(test_util.TempDirTestCase): """Tests for certbot.plugins.common.install_version_controlled_file.""" -- cgit v1.2.3 From 9796128feeed8aebdbd3702335850840a69ae0bf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 30 Oct 2019 17:07:36 -0700 Subject: Polish changelog (#7476) I wanted to polish the changelog a bit. Changes made are: * We don't ship our test farm tests so including info about them in our changelog seems unnecessary. * I combined and expanded the info about the deprecation of Python 3.4. --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6c5ce34a..f2fe68e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,14 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * We deprecated support for Python 3.4 in Certbot and its ACME library. Support for Python 3.4 will be removed in the next major release of Certbot. -* Removed `--fast` flag from the test farm tests + certbot-auto users on RHEL 6 based systems will be asked to enable Software + Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can + enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based + systems will be asked to do this manually. * `--server` may now be combined with `--dry-run`. Certbot will, as before, use the staging server instead of the live server when `--dry-run` is used. * Updated certbot-dns-google to depend on newer versions of google-api-python-client and oauth2client. -* Migrated CentOS 6 certbot-auto users from Python 3.4 to Python 3.6. * certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a future release. -- cgit v1.2.3 From 63d673a3e04de4a64d18483a2f0df55c6a6c4198 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 31 Oct 2019 10:17:29 -0700 Subject: Remove references to TLS-SNI-01 outside of ACME (#7479) This is a big part of #7214. It removes all references to TLS-SNI-01 outside of acme (and pytest.ini). Those changes will come in a subsequent PR. I thought this one was getting big enough. * Remove references to TLS-SNI-01 in Apache plugin * Remove references to TLS-SNI-01 from certbot-nginx * Remove references to TLS-SNI from Certbot. * Remove TLS-SNI reference from docs * add certbot changelog * Clarify test behavior --- CHANGELOG.md | 3 + .../certbot_apache/tests/configurator_test.py | 4 +- certbot-apache/docs/api/tls_sni_01.rst | 5 -- certbot-nginx/certbot_nginx/configurator.py | 2 +- certbot-nginx/certbot_nginx/http_01.py | 8 +- .../certbot_nginx/tests/configurator_test.py | 2 +- certbot-nginx/certbot_nginx/tests/http_01_test.py | 6 +- certbot-nginx/docs/api/tls_sni_01.rst | 5 -- certbot/auth_handler.py | 3 - certbot/cli.py | 21 +---- certbot/tests/acme_util.py | 10 +-- certbot/tests/auth_handler_test.py | 90 +++++++++++----------- certbot/tests/cli_test.py | 7 -- certbot/tests/renewal_test.py | 9 +-- docs/contributing.rst | 2 +- 15 files changed, 64 insertions(+), 113 deletions(-) delete mode 100644 certbot-apache/docs/api/tls_sni_01.rst delete mode 100644 certbot-nginx/docs/api/tls_sni_01.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index f2fe68e11..c3f62851a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). google-api-python-client and oauth2client. * certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a future release. +* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. +* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no + longer accepted. ### Fixed diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 1eafae982..bf5281d29 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1296,13 +1296,13 @@ class MultipleVhostsTest(util.ApacheTest): account_key = self.rsa512jwk achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( - challenges.TLSSNI01( + challenges.HTTP01( token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"), "pending"), domain="encryption-example.demo", account_key=account_key) achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( - challenges.TLSSNI01( + challenges.HTTP01( token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"), "pending"), domain="certbot.demo", account_key=account_key) diff --git a/certbot-apache/docs/api/tls_sni_01.rst b/certbot-apache/docs/api/tls_sni_01.rst deleted file mode 100644 index 3ecd0a365..000000000 --- a/certbot-apache/docs/api/tls_sni_01.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.tls_sni_01` ------------------------------------- - -.. automodule:: certbot_apache.tls_sni_01 - :members: diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 95715916d..fe5c7da35 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -1107,7 +1107,7 @@ class NginxConfigurator(common.Installer): ########################################################################### def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" - return [challenges.HTTP01, challenges.TLSSNI01] + return [challenges.HTTP01] # Entry point in main.py for performing challenges def perform(self, achalls): diff --git a/certbot-nginx/certbot_nginx/http_01.py b/certbot-nginx/certbot_nginx/http_01.py index 70147a433..4dcc5324f 100644 --- a/certbot-nginx/certbot_nginx/http_01.py +++ b/certbot-nginx/certbot_nginx/http_01.py @@ -29,10 +29,10 @@ class NginxHttp01(common.ChallengePerformer): :param list indices: Meant to hold indices of challenges in a larger array. NginxHttp01 is capable of solving many challenges at once which causes an indexing issue within NginxConfigurator - who must return all responses in order. Imagine NginxConfigurator - maintaining state about where all of the http-01 Challenges, - TLS-SNI-01 Challenges belong in the response array. This is an - optional utility. + who must return all responses in order. Imagine + NginxConfigurator maintaining state about where all of the + challenges, possibly of different types, belong in the response + array. This is an optional utility. """ diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 19624a7a2..237e22d8f 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -97,7 +97,7 @@ class NginxConfiguratorTest(util.NginxTest): errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') def test_get_chall_pref(self): - self.assertEqual([challenges.HTTP01, challenges.TLSSNI01], + self.assertEqual([challenges.HTTP01], self.config.get_chall_pref('myhost')) def test_save(self): diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py index 41c4b95fc..c6d35b808 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/http_01_test.py @@ -73,11 +73,11 @@ class HttpPerformTest(util.NginxTest): self.http01.add_chall(achall) acme_responses.append(achall.response(self.account_key)) - sni_responses = self.http01.perform() + http_responses = self.http01.perform() - self.assertEqual(len(sni_responses), 4) + self.assertEqual(len(http_responses), 4) for i in six.moves.range(4): - self.assertEqual(sni_responses[i], acme_responses[i]) + self.assertEqual(http_responses[i], acme_responses[i]) def test_mod_config(self): self.http01.add_chall(self.achalls[0]) diff --git a/certbot-nginx/docs/api/tls_sni_01.rst b/certbot-nginx/docs/api/tls_sni_01.rst deleted file mode 100644 index 5074f63d9..000000000 --- a/certbot-nginx/docs/api/tls_sni_01.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.tls_sni_01` ------------------------------------ - -.. automodule:: certbot_nginx.tls_sni_01 - :members: diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py index 14207db4a..232b246d5 100644 --- a/certbot/auth_handler.py +++ b/certbot/auth_handler.py @@ -182,9 +182,6 @@ class AuthHandler(object): achalls.extend(self._challenge_factory(authzr, path)) - if any(isinstance(achall.chall, challenges.TLSSNI01) for achall in achalls): - logger.warning("TLS-SNI-01 is deprecated, and will stop working soon.") - return achalls def _get_chall_pref(self, domain): diff --git a/certbot/cli.py b/certbot/cli.py index 93cdc7408..c4ddcddbe 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1249,17 +1249,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis helpful.add_deprecated_argument("--agree-dev-preview", 0) helpful.add_deprecated_argument("--dialog", 0) - # Deprecation of tls-sni-01 related cli flags - # TODO: remove theses flags completely in few releases - class _DeprecatedTLSSNIAction(util._ShowWarning): # pylint: disable=protected-access - def __call__(self, parser, namespace, values, option_string=None): - super(_DeprecatedTLSSNIAction, self).__call__(parser, namespace, values, option_string) - namespace.https_port = values - helpful.add( - ["testing", "standalone", "apache", "nginx"], "--tls-sni-01-port", - type=int, action=_DeprecatedTLSSNIAction, help=argparse.SUPPRESS) - helpful.add_deprecated_argument("--tls-sni-01-address", 1) - # Populate the command line parameters for new style enhancements enhancements.populate_cli(helpful.add) @@ -1546,18 +1535,10 @@ def parse_preferred_challenges(pref_challs): :raises errors.Error: if pref_challs is invalid """ - aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"} + aliases = {"dns": "dns-01", "http": "http-01"} challs = [c.strip() for c in pref_challs] challs = [aliases.get(c, c) for c in challs] - # Ignore tls-sni-01 from the list, and generates a deprecation warning - # TODO: remove this option completely in few releases - if "tls-sni-01" in challs: - logger.warning('TLS-SNI-01 support is deprecated. This value is being dropped from the ' - 'setting of --preferred-challenges and future versions of Certbot will ' - 'error if it is included.') - challs = [chall for chall in challs if chall != "tls-sni-01"] - unrecognized = ", ".join(name for name in challs if name not in challenges.Challenge.TYPES) if unrecognized: diff --git a/certbot/tests/acme_util.py b/certbot/tests/acme_util.py index 4854626a1..58f3e29c4 100644 --- a/certbot/tests/acme_util.py +++ b/certbot/tests/acme_util.py @@ -18,12 +18,10 @@ KEY = util.load_rsa_private_key('rsa512_key.pem') # Challenges HTTP01 = challenges.HTTP01( token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") -TLSSNI01 = challenges.TLSSNI01( - token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA")) DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a") DNS01_2 = challenges.DNS01(token=b"cafecafecafecafecafecafe0feedbac") -CHALLENGES = [HTTP01, TLSSNI01, DNS01] +CHALLENGES = [HTTP01, DNS01] def gen_combos(challbs): @@ -47,21 +45,19 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name # Pending ChallengeBody objects -TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING) HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING) DNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING) -CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS01_P] +CHALLENGES_P = [HTTP01_P, DNS01_P] # AnnotatedChallenge objects HTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, "example.com") -TLSSNI01_A = auth_handler.challb_to_achall(TLSSNI01_P, JWK, "example.net") DNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, "example.org") DNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, "esimerkki.example.org") -ACHALLENGES = [HTTP01_A, TLSSNI01_A, DNS01_A] +ACHALLENGES = [HTTP01_A, DNS01_A] def gen_authzr(authz_status, domain, challs, statuses, combos=True): diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 9b2c84879..45eef8e2b 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -39,11 +39,11 @@ class ChallengeFactoryTest(unittest.TestCase): self.assertEqual( [achall.chall for achall in achalls], acme_util.CHALLENGES) - def test_one_tls_sni(self): - achalls = self.handler._challenge_factory(self.authzr, [1]) + def test_one_http(self): + achalls = self.handler._challenge_factory(self.authzr, [0]) self.assertEqual( - [achall.chall for achall in achalls], [acme_util.TLSSNI01]) + [achall.chall for achall in achalls], [acme_util.HTTP01]) def test_unrecognized(self): authzr = acme_util.gen_authzr( @@ -73,7 +73,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.mock_auth = mock.MagicMock(name="ApacheConfigurator") - self.mock_auth.get_chall_pref.return_value = [challenges.TLSSNI01] + self.mock_auth.get_chall_pref.return_value = [challenges.HTTP01] self.mock_auth.perform.side_effect = gen_auth_resp @@ -90,7 +90,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p def tearDown(self): logging.disable(logging.NOTSET) - def _test_name1_tls_sni_01_1_common(self, combos): + def _test_name1_http_01_1_common(self, combos): authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos) mock_order = mock.MagicMock(authorizations=[authzr]) @@ -109,44 +109,42 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertTrue(mock_time.sleep.call_args_list[1][0][0] > 3) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_acme_1(self): - self._test_name1_tls_sni_01_1_common(combos=True) + def test_name1_http_01_1_acme_1(self): + self._test_name1_http_01_1_common(combos=True) - def test_name1_tls_sni_01_1_acme_2(self): + def test_name1_http_01_1_acme_2(self): self.mock_net.acme_version = 2 - self._test_name1_tls_sni_01_1_common(combos=False) + self._test_name1_http_01_1_common(combos=False) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_1(self): + def test_name1_http_01_1_dns_1_acme_1(self): self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) mock_order = mock.MagicMock(authorizations=[authzr]) authzr = self.handler.handle_authorizations(mock_order) - self.assertEqual(self.mock_net.answer_challenge.call_count, 3) + self.assertEqual(self.mock_net.answer_challenge.call_count, 2) self.assertEqual(self.mock_net.poll.call_count, 1) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall for achall in self.mock_auth.cleanup.call_args[0][0]: - self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns-01"]) + self.assertTrue(achall.typ in ["http-01", "dns-01"]) # Length of authorizations list self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_2(self): + def test_name1_http_01_1_dns_1_acme_2(self): self.mock_net.acme_version = 2 self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) @@ -160,12 +158,12 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_auth.cleanup.call_count, 1) cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0] self.assertEqual(len(cleaned_up_achalls), 1) - self.assertEqual(cleaned_up_achalls[0].typ, "tls-sni-01") + self.assertEqual(cleaned_up_achalls[0].typ, "http-01") # Length of authorizations list self.assertEqual(len(authzr), 1) - def _test_name3_tls_sni_01_3_common(self, combos): + def _test_name3_http_01_3_common(self, combos): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.CHALLENGES, combos=combos) @@ -186,12 +184,12 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(len(authzr), 3) - def test_name3_tls_sni_01_3_common_acme_1(self): - self._test_name3_tls_sni_01_3_common(combos=True) + def test_name3_http_01_3_common_acme_1(self): + self._test_name3_http_01_3_common(combos=True) - def test_name3_tls_sni_01_3_common_acme_2(self): + def test_name3_http_01_3_common_acme_2(self): self.mock_net.acme_version = 2 - self._test_name3_tls_sni_01_3_common(combos=False) + self._test_name3_http_01_3_common(combos=False) def test_debug_challenges(self): zope.component.provideUtility( @@ -257,7 +255,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p def _test_preferred_challenges_not_supported_common(self, combos): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos)] mock_order = mock.MagicMock(authorizations=authzrs) - self.handler.pref_challs.append(challenges.HTTP01.typ) + self.handler.pref_challs.append(challenges.DNS01.typ) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) @@ -283,7 +281,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_answer_error(self): self.mock_net.answer_challenge.side_effect = errors.AuthorizationError @@ -295,7 +293,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p errors.AuthorizationError, self.handler.handle_authorizations, mock_order) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_incomplete_authzr_error(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -308,7 +306,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertTrue('Some challenges have failed.' in str(error.exception)) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_best_effort(self): def _conditional_mock_on_poll(authzr): @@ -344,28 +342,26 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertTrue('All challenges have failed.' in str(error.exception)) def test_validated_challenge_not_rerun(self): - # With pending challenge, we expect the challenge to be tried, and fail. + # With a pending challenge that is not supported by the plugin, we + # expect an exception to be raised. authzr = acme_util.gen_authzr( messages.STATUS_PENDING, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_PENDING], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) - # With validated challenge; we expect the challenge not be tried again, and succeed. + # With a validated challenge that is not supported by the plugin, we + # expect the challenge to not be solved again and + # handle_authorizations() to succeed. authzr = acme_util.gen_authzr( messages.STATUS_VALID, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_VALID], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.handler.handle_authorizations(mock_order) - @mock.patch("certbot.auth_handler.logger") - def test_tls_sni_logs(self, logger): - self._test_name1_tls_sni_01_1_common(combos=True) - self.assertTrue("deprecated" in logger.warning.call_args[0][0]) - def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): state = {'count': retry} @@ -417,9 +413,9 @@ class GenChallengePathTest(unittest.TestCase): return gen_challenge_path(challbs, preferences, combinations) def test_common_case(self): - """Given TLSSNI01 and HTTP01 with appropriate combos.""" - challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P) - prefs = [challenges.TLSSNI01, challenges.HTTP01] + """Given DNS01 and HTTP01 with appropriate combos.""" + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.DNS01, challenges.HTTP01] combos = ((0,), (1,)) # Smart then trivial dumb path test @@ -430,8 +426,8 @@ class GenChallengePathTest(unittest.TestCase): self.assertTrue(self._call(challbs[::-1], prefs, None)) def test_not_supported(self): - challbs = (acme_util.DNS01_P, acme_util.TLSSNI01_P) - prefs = [challenges.TLSSNI01] + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.HTTP01] combos = ((0, 1),) # smart path fails because no challs in perfs satisfies combos @@ -459,19 +455,19 @@ class ReportFailedAuthzrsTest(unittest.TestCase): http_01 = messages.ChallengeBody(**kwargs) - kwargs["chall"] = acme_util.TLSSNI01 - tls_sni_01 = messages.ChallengeBody(**kwargs) + kwargs["chall"] = acme_util.HTTP01 + http_01 = messages.ChallengeBody(**kwargs) self.authzr1 = mock.MagicMock() self.authzr1.body.identifier.value = 'example.com' - self.authzr1.body.challenges = [http_01, tls_sni_01] + self.authzr1.body.challenges = [http_01, http_01] kwargs["error"] = messages.Error(typ="dnssec", detail="detail") - tls_sni_01_diff = messages.ChallengeBody(**kwargs) + http_01_diff = messages.ChallengeBody(**kwargs) self.authzr2 = mock.MagicMock() self.authzr2.body.identifier.value = 'foo.bar' - self.authzr2.body.challenges = [tls_sni_01_diff] + self.authzr2.body.challenges = [http_01_diff] @test_util.patch_get_utility() def test_same_error_and_domain(self, mock_zope): diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 166559040..e8d8282df 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -249,13 +249,6 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(namespace.pref_challs, expected) - # TODO: to be removed once tls-sni deprecation logic is removed - with mock.patch('certbot.cli.logger.warning') as mock_warn: - self.assertEqual(self.parse(['--preferred-challenges', 'http, tls-sni']).pref_challs, - [challenges.HTTP01.typ]) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - short_args = ['--preferred-challenges', 'jumping-over-the-moon'] # argparse.ArgumentError makes argparse print more information # to stderr and call sys.exit() diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index e33869e13..dca16c16c 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -75,15 +75,10 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_list(self, mock_set_by_cli): mock_set_by_cli.return_value = False - # TODO: remove tls-sni and related assertions to logger.warning call once - # the deprecation logic has been removed - renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')} - with mock.patch('certbot.renewal.cli.logger.warning') as mock_warn: - self._call(self.config, renewalparams) + renewalparams = {'pref_challs': 'http-01, dns'.split(',')} + self._call(self.config, renewalparams) expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(self.config.pref_challs, expected) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) @mock.patch('certbot.renewal.cli.set_by_cli') def test_pref_challs_str(self, mock_set_by_cli): diff --git a/docs/contributing.rst b/docs/contributing.rst index 1051413ae..f01b1fd03 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -234,7 +234,7 @@ Authenticators Authenticators are plugins that prove control of a domain name by solving a 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`. +challenges: HTTP, TLS-ALPN, 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 -- cgit v1.2.3 From a45efcd40d5ac108a6fb15cdef4147fb76870b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Fri, 1 Nov 2019 17:27:18 +0100 Subject: Fix invalid escape sequence \. rebuild_dependencies.py (#7486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaël Schoentgen --- AUTHORS.md | 1 + CHANGELOG.md | 2 +- letsencrypt-auto-source/rebuild_dependencies.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 1b521548e..d24c5be1d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -167,6 +167,7 @@ Authors * [Michael Watters](https://github.com/blackknight36) * [Michal Moravec](https://github.com/https://github.com/Majkl578) * [Michal Papis](https://github.com/mpapis) +* [Mickaël Schoentgen](https://github.com/BoboTiG) * [Minn Soe](https://github.com/MinnSoe) * [Min RK](https://github.com/minrk) * [Miquel Ruiz](https://github.com/miquelruiz) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f62851a..ca169d57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fixed a DeprecationWarning: invalid escape sequence in rebuild_dependencies.py. More details about these changes can be found on our GitHub repo. diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index e5acf7db5..0db787a0b 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -62,7 +62,7 @@ CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) # without pinned dependencies, and respecting input authoritative requirements # - `certbot plugins` is called to check we have an healthy environment # - finally current set of dependencies is extracted out of the docker using pip freeze -SCRIPT = """\ +SCRIPT = r"""\ #!/bin/sh set -e -- cgit v1.2.3 From ef3f8888b5da268bf7513c0166bf66202fdb627f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 10:06:10 -0700 Subject: Don't use dev version of 3.8. (#7485) Now that Python 3.8 is out, we don't need to use the development version. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3e26fc79..a1172f086 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,7 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *not-on-master - - python: "3.8-dev" + - python: "3.8" env: TOXENV=py38 <<: *not-on-master - sudo: required -- cgit v1.2.3 From f8ff881d23e24299276a4ec7275b66763ee703df Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 10:06:32 -0700 Subject: Don't use acme.test_util outside of acme. (#7484) `certbot-compatibility-test` is using code in `acme` that I proposed making private and not trivially importable in https://github.com/certbot/certbot/issues/5775. To fix it, I switched to using Certbot's test utilities which I proposed keeping public to help with writing tests for plugins. When doing this I had to change the name of the key because `rsa1024_key.pem` doesn't exist in Certbot. I also deleted the keys in `certbot-compatibility-test`'s testdata because because they are unused. --- .../certbot_compatibility_test/testdata/rsa1024_key.pem | 15 --------------- .../certbot_compatibility_test/testdata/rsa1024_key2.pem | 15 --------------- .../certbot_compatibility_test/util.py | 4 ++-- 3 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem delete mode 100644 certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem diff --git a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem b/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem deleted file mode 100644 index 8f82146ba..000000000 --- a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCsREbM+UcfsgDy2w56AVGyxsO0HVsbEZHHoEzv7qksIwFgRYMp -rowwIxD450RQQqjvw9IoXlMVmr1t5szn5KXn9JRO9T5KNCCy3VPx75WBcp6kzd9Q -2HS1OEOtpilNnDkZ+TJfdgFWPUBYj2o4Md1hPmcvagiIJY5U6speka2bjwIDAQAB -AoGANCMZ9pF/mDUsmlP4Rq69hkkoFAxKdZ/UqkF256so4mXZ1cRUFTpxzWPfkCWW -hGAYdzCiG3uo08IYkPmojIqkN1dk5Hcq5eQAmshaPkQHQCHjmPjjcNvgjIXQoGUf -TpDU2hbY4UAlJlj4ZLh+jGP5Zq8/WrNi8RsI3v9Nagfp/FECQQDgi2q8p1gX0TNh -d1aEKmSXkR3bxkyFk6oS+pBrAG3+yX27ZayN6Rx6DOs/FcBsOu7fX3PYBziDeEWe -Lkf1P743AkEAxGYT/LY3puglSz4iJZZzWmRCrVOg41yhfQ+F1BRX43/2vtoU5GyM -2lUn1vQ2e/rfmnAvfJxc90GeZCIHB1ihaQJBALH8UMLxMtbOMJgVbDKfF9U8ZhqK -+KT5A1q/2jG2yXmoZU1hroFeQgBMtTvwFfK0VBwjIUQflSBA+Y4EyW0Q9ckCQGvd -jHitM1+N/H2YwHRYbz5j9mLvnVuCEod3MQ9LpQGj1Eb5y6OxIqL/RgQ+2HW7UXem -yc3sqvp5pZ5lOesE+JECQETPI64gqxlTIs3nErNMpMynUuTWpaElOcIJTT6icLzB -Xix67kKXjROO5D58GEYkM0Yi5k7YdUPoQBW7MoIrSIA= ------END RSA PRIVATE KEY----- diff --git a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem b/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem deleted file mode 100644 index 03f77d903..000000000 --- a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDCzejLjo8wsz0avrylt7HQyF0+vsKritF70EGmc64cV0XfkCTR -o+vMXBXMuUY6Kv3hTXV7klgkNYmL7gXAsFGQ4B9qeMnkYn0GcQdI51u076y/26Fu -37uJg45Q6eApKInJSsyLVMcAT4HUJ6fFnUodFAKR7vTzOQryNW7Et4gA4wIDAQAB -AoGAKiAU40/krwdTg2ETslJS5W8ums7tkeLnAfs69x+02vQUbA/jpmHoL70KCcdW -5GU/mWUCrsIqxUm+gL/sBosaV/TF256qUBt2qQCZTN8MbDaNSYiiMnucOfbWdIqx -Zgls6GUoXQvPic9RUoFSlgfSjo5ezz6el5ihvRMp+wbk24ECQQD3oz4hN029DSZo -Y3+flmBn77gA0BMUvLa6hmt9b3xT5U/ToCLfbmUvpx7zV1g5era2y9qt/o3UtAbW -1zCVETgzAkEAyWHv/+RnSXp8/D4YwTVWyeWi862uNBPkuLGP/0zASdwBfBK3uBls -+VumfSCtp0kt2AXXmScg1fkHdeAVT6AkkQJBAJb2XRnCrRFiwtdAULzo3zx9Vp6o -OfmaUYrEByMgo5pBYLiSFrA+jFDQgH238YCY3mnxPA517+CLHuA5rtQw+yECQCfm -gL/pyFE1tLfhsdPuNpDwL9YqLl7hJis1+zrxQRQhRCYKK16NoxrQ/u7B38ZKaIvp -tGsC5q2elszTJkXNjBECQCVE9QCVx056vHVdPWM8z3GAeV3sJQ01HLLjebTEEz6G -jH54gk+YYPp4kjCvVUykbnB58BY2n88GQt5Jj5eLuMo= ------END RSA PRIVATE KEY----- diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index c8de8ddac..a96ead21f 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -8,13 +8,13 @@ import tarfile import josepy as jose -from acme import test_util +from certbot.tests import util as test_util from certbot import constants from certbot_compatibility_test import errors -_KEY_BASE = "rsa1024_key.pem" +_KEY_BASE = "rsa2048_key.pem" KEY_PATH = test_util.vector_path(_KEY_BASE) KEY = test_util.load_pyopenssl_private_key(_KEY_BASE) JWK = jose.JWKRSA(key=test_util.load_rsa_private_key(_KEY_BASE)) -- cgit v1.2.3 From fb1aafb5d26aa25f0dcbc4884c51b5aa08b0b6bd Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 1 Nov 2019 19:51:21 +0200 Subject: Use distro library for all OS version detection (#7467) This pull request ensures that we use distro package in all the distribution version detection. It also replaces the custom systemd /etc/os-release parsing and adds a few version fingerprints to Apache override selection. Fixes: #7405 * Revert "Try to use platform.linux_distribution() before distro equivalent (#7403)" This reverts commit ca3077d0347aae12163a43bf74a0c8321284367e. * Use distro for all os detection code * Address review comments * Add changelog entry * Added tests * Fix tests to return a consistent os name * Do not crash on non-linux systems * Minor fixes to distro compatibility checks * Make the tests OS independent * Update certbot/util.py Co-Authored-By: Brad Warren * Skip linux specific tests on other platforms * Test fixes * Better test state handling * Lower the coverage target for Windows tests --- .codecov.yml | 2 +- CHANGELOG.md | 1 + certbot-apache/certbot_apache/entrypoint.py | 4 + certbot/tests/util_test.py | 115 ++++++++++++++++------------ certbot/util.py | 81 ++++++++------------ pytest.ini | 1 + 6 files changed, 105 insertions(+), 99 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 55af1a36c..0a97fffe3 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,6 +13,6 @@ coverage: flags: windows # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 97.7 + target: 97.4 threshold: 0.1 base: auto diff --git a/CHANGELOG.md b/CHANGELOG.md index ca169d57a..a2a57cb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). staging server instead of the live server when `--dry-run` is used. * Updated certbot-dns-google to depend on newer versions of google-api-python-client and oauth2client. +* The OS detection logic again uses distro library for Linux OSes * certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a future release. * CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py index 610191fea..9e04ff889 100644 --- a/certbot-apache/certbot_apache/entrypoint.py +++ b/certbot-apache/certbot_apache/entrypoint.py @@ -16,6 +16,7 @@ from certbot_apache import override_suse OVERRIDE_CLASSES = { "arch": override_arch.ArchConfigurator, + "cloudlinux": override_centos.CentOSConfigurator, "darwin": override_darwin.DarwinConfigurator, "debian": override_debian.DebianConfigurator, "ubuntu": override_debian.DebianConfigurator, @@ -23,7 +24,9 @@ OVERRIDE_CLASSES = { "centos linux": override_centos.CentOSConfigurator, "fedora_old": override_centos.CentOSConfigurator, "fedora": override_fedora.FedoraConfigurator, + "linuxmint": override_debian.DebianConfigurator, "ol": override_centos.CentOSConfigurator, + "oracle": override_centos.CentOSConfigurator, "redhatenterpriseserver": override_centos.CentOSConfigurator, "red hat enterprise linux server": override_centos.CentOSConfigurator, "rhel": override_centos.CentOSConfigurator, @@ -32,6 +35,7 @@ OVERRIDE_CLASSES = { "gentoo base system": override_gentoo.GentooConfigurator, "opensuse": override_suse.OpenSUSEConfigurator, "suse": override_suse.OpenSUSEConfigurator, + "sles": override_suse.OpenSUSEConfigurator, "scientific": override_centos.CentOSConfigurator, "scientific linux": override_centos.CentOSConfigurator, } diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 61b356779..65c51bfce 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -1,6 +1,7 @@ """Tests for certbot.util.""" import argparse import errno +import sys import unittest import mock @@ -473,74 +474,92 @@ class IsWildcardDomainTest(unittest.TestCase): class OsInfoTest(unittest.TestCase): """Test OS / distribution detection""" - def test_systemd_os_release(self): - from certbot.util import (get_os_info, get_systemd_os_info, - get_os_info_ua) - - with mock.patch('certbot.compat.os.path.isfile', return_value=True): - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[0], 'systemdos') - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[1], '42') - self.assertEqual(get_systemd_os_info(os.devnull), ("", "")) - self.assertEqual(get_os_info_ua( - test_util.vector_path("os-release")), "SystemdOS") - with mock.patch('certbot.compat.os.path.isfile', return_value=False): - self.assertEqual(get_systemd_os_info(), ("", "")) - - def test_systemd_os_release_like(self): - from certbot.util import get_systemd_os_like - - with mock.patch('certbot.compat.os.path.isfile', return_value=True): - id_likes = get_systemd_os_like(test_util.vector_path( - "os-release")) - self.assertEqual(len(id_likes), 3) - self.assertTrue("debian" in id_likes) + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_systemd_os_release_like(self, m_distro): + import certbot.util as cbutil + m_distro.like.return_value = "first debian third" + id_likes = cbutil.get_systemd_os_like() + self.assertEqual(len(id_likes), 3) + self.assertTrue("debian" in id_likes) + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info_ua(self, m_distro): + import certbot.util as cbutil + with mock.patch('platform.system_alias', + return_value=('linux', '42', '42')): + m_distro.name.return_value = "" + m_distro.linux_distribution.return_value = ("something", "1.0", "codename") + cbutil.get_python_os_info(pretty=True) + self.assertEqual(cbutil.get_os_info_ua(), + " ".join(cbutil.get_python_os_info(pretty=True))) + + m_distro.name.return_value = "whatever" + self.assertEqual(cbutil.get_os_info_ua(), "whatever") + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info(self, m_distro): + import certbot.util as cbutil + with mock.patch("platform.system") as mock_platform: + m_distro.linux_distribution.return_value = ("name", "version", 'x') + mock_platform.return_value = "linux" + self.assertEqual(cbutil.get_os_info(), ("name", "version")) + + m_distro.linux_distribution.return_value = ("something", "else") + self.assertEqual(cbutil.get_os_info(), ("something", "else")) + + @mock.patch("warnings.warn") + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_systemd_os_info_deprecation(self, _, mock_warn): + import certbot.util as cbutil + cbutil.get_systemd_os_info() + self.assertTrue(mock_warn.called) @mock.patch("certbot.util.subprocess.Popen") def test_non_systemd_os_info(self, popen_mock): - from certbot.util import (get_os_info, get_python_os_info, - get_os_info_ua) - with mock.patch('certbot.compat.os.path.isfile', return_value=False): + import certbot.util as cbutil + with mock.patch('certbot.util._USE_DISTRO', False): with mock.patch('platform.system_alias', return_value=('NonSystemD', '42', '42')): - self.assertEqual(get_os_info()[0], 'nonsystemd') - self.assertEqual(get_os_info_ua(), - " ".join(get_python_os_info())) + self.assertEqual(cbutil.get_python_os_info()[0], 'nonsystemd') with mock.patch('platform.system_alias', return_value=('darwin', '', '')): comm_mock = mock.Mock() comm_attrs = {'communicate.return_value': - ('42.42.42', 'error')} + ('42.42.42', 'error')} comm_mock.configure_mock(**comm_attrs) popen_mock.return_value = comm_mock - self.assertEqual(get_os_info()[0], 'darwin') - self.assertEqual(get_os_info()[1], '42.42.42') - - with mock.patch('platform.system_alias', - return_value=('linux', '', '')): - with mock.patch('platform.linux_distribution', - side_effect=AttributeError, - create=True): - with mock.patch('distro.linux_distribution', - return_value=('', '', '')): - self.assertEqual(get_python_os_info(), ("linux", "")) - - with mock.patch('distro.linux_distribution', - return_value=('testdist', '42', '')): - self.assertEqual(get_python_os_info(), ("testdist", "42")) + self.assertEqual(cbutil.get_python_os_info()[0], 'darwin') + self.assertEqual(cbutil.get_python_os_info()[1], '42.42.42') with mock.patch('platform.system_alias', return_value=('freebsd', '9.3-RC3-p1', '')): - self.assertEqual(get_python_os_info(), ("freebsd", "9")) + self.assertEqual(cbutil.get_python_os_info(), ("freebsd", "9")) with mock.patch('platform.system_alias', return_value=('windows', '', '')): with mock.patch('platform.win32_ver', return_value=('4242', '95', '2', '')): - self.assertEqual(get_python_os_info(), - ("windows", "95")) + self.assertEqual(cbutil.get_python_os_info(), + ("windows", "95")) + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_notfound(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('', '', '') + self.assertEqual(cbutil.get_python_os_info()[0], "linux") + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_custom(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('testdist', '42', '') + self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42")) class AtexitRegisterTest(unittest.TestCase): diff --git a/certbot/util.py b/certbot/util.py index ec357573d..ea680e050 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -12,9 +12,10 @@ import platform import re import socket import subprocess +import sys +import warnings import configargparse -import distro import six from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module @@ -25,6 +26,12 @@ from certbot import lock from certbot.compat import os from certbot.compat import filesystem +if sys.platform.startswith('linux'): + import distro + _USE_DISTRO = True +else: + _USE_DISTRO = False + logger = logging.getLogger(__name__) @@ -277,77 +284,59 @@ def get_filtered_names(all_names): logger.debug('Not suggesting name "%s"', name, exc_info=True) return filtered_names - -def get_os_info(filepath="/etc/os-release"): +def get_os_info(): """ Get OS name and version - :param str filepath: File path of os-release file :returns: (os_name, os_version) :rtype: `tuple` of `str` """ - if os.path.isfile(filepath): - # Systemd os-release parsing might be viable - os_name, os_version = get_systemd_os_info(filepath=filepath) - if os_name: - return os_name, os_version - - # Fallback to platform module - return get_python_os_info() - + return get_python_os_info(pretty=False) -def get_os_info_ua(filepath="/etc/os-release"): +def get_os_info_ua(): """ Get OS name and version string for User Agent - :param str filepath: File path of os-release file :returns: os_ua :rtype: `str` """ + if _USE_DISTRO: + os_info = distro.name(pretty=True) - if os.path.isfile(filepath): - os_ua = get_var_from_file("PRETTY_NAME", filepath=filepath) - if not os_ua: - os_ua = get_var_from_file("NAME", filepath=filepath) - if os_ua: - return os_ua - - # Fallback - return " ".join(get_python_os_info()) - + if not _USE_DISTRO or not os_info: + return " ".join(get_python_os_info(pretty=True)) + return os_info -def get_systemd_os_info(filepath="/etc/os-release"): +def get_systemd_os_info(): """ Parse systemd /etc/os-release for distribution information - :param str filepath: File path of os-release file :returns: (os_name, os_version) :rtype: `tuple` of `str` """ - os_name = get_var_from_file("ID", filepath=filepath) - os_version = get_var_from_file("VERSION_ID", filepath=filepath) - - return (os_name, os_version) + warnings.warn( + "The get_sytemd_os_like() function is deprecated and will be removed in " + "a future release.", DeprecationWarning, stacklevel=2) + return get_os_info()[:2] - -def get_systemd_os_like(filepath="/etc/os-release"): +def get_systemd_os_like(): """ Get a list of strings that indicate the distribution likeness to other distributions. - :param str filepath: File path of os-release file :returns: List of distribution acronyms :rtype: `list` of `str` """ - return get_var_from_file("ID_LIKE", filepath).split(" ") - + if _USE_DISTRO: + return distro.like().split(" ") + return [] def get_var_from_file(varname, filepath="/etc/os-release"): """ - Get single value from systemd /etc/os-release + Get single value from a file formatted like systemd /etc/os-release :param str varname: Name of variable to fetch :param str filepath: File path of os-release file @@ -367,7 +356,6 @@ def get_var_from_file(varname, filepath="/etc/os-release"): return _normalize_string(line.strip()[len(var_string):]) return "" - def _normalize_string(orig): """ Helper function for get_var_from_file() to remove quotes @@ -375,12 +363,13 @@ def _normalize_string(orig): """ return orig.replace('"', '').replace("'", "").strip() - -def get_python_os_info(): +def get_python_os_info(pretty=False): """ Get Operating System type/distribution and major version using python platform module + :param bool pretty: If the returned OS name should be in longer (pretty) form + :returns: (os_name, os_version) :rtype: `tuple` of `str` """ @@ -391,8 +380,8 @@ def get_python_os_info(): ) os_type, os_ver, _ = info os_type = os_type.lower() - if os_type.startswith('linux'): - info = _get_linux_distribution() + if os_type.startswith('linux') and _USE_DISTRO: + info = distro.linux_distribution(pretty) # On arch, distro.linux_distribution() is reportedly ('','',''), # so handle it defensively if info[0]: @@ -424,14 +413,6 @@ def get_python_os_info(): os_ver = '' return os_type, os_ver -def _get_linux_distribution(): - """Gets the linux distribution name from the underlying OS""" - - try: - return platform.linux_distribution() - except AttributeError: - return distro.linux_distribution() - # Just make sure we don't get pwned... Make sure that it also doesn't # start with a period or have two consecutive periods <- this needs to # be done in addition to the regex diff --git a/pytest.ini b/pytest.ini index 27a0f2b74..12b081b0e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -18,3 +18,4 @@ filterwarnings = ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* ignore:.*view_config_changes:DeprecationWarning + ignore:.*get_systemd_os_info:DeprecationWarning -- cgit v1.2.3 From 3e848b8fcef90ccbef6dd64024208f620de9ca88 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 13:59:17 -0700 Subject: Remove changelog entry about unpackaged scripts. (#7490) We don't package rebuild_dependencies.py so I don't think we need to mention changes to it in our changelog which is primarily read by users and packagers. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a57cb21..2334aa666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* Fixed a DeprecationWarning: invalid escape sequence in rebuild_dependencies.py. +* More details about these changes can be found on our GitHub repo. -- cgit v1.2.3 From 9bc4286a27a904706533a7b73e2312f2da15ac63 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 15:00:22 -0700 Subject: Deprecate more code related to TLS-SNI-01 (#7483) I tried to finish up #7214 by removing the code in acme but we can't really do that until #7478 is resolved which we cannot do until we release 0.40.0. Since we have to wait, this PR adds deprecation warnings for code that uses the TLS-SNI-01 code or was only used by the long deprecated TLS-SNI-01 code. I'd like this PR to land before our next release. * Deprecate more code related to TLS-SNI-01. * Assert about warning message. --- CHANGELOG.md | 3 +++ acme/acme/__init__.py | 2 +- acme/acme/standalone.py | 8 ++++---- acme/acme/standalone_test.py | 27 +++++++++++++++++++++++---- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2334aa666..8477525b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. * The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no longer accepted. +* acme.standalone.BaseRequestHandlerWithLogging and + acme.standalone.simple_tls_sni_01_server have been deprecated and will be + removed in a future release of the library. ### Fixed diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index 13bdd41aa..e68ebd765 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -35,7 +35,7 @@ class _TLSSNI01DeprecationModule(object): self.__dict__['_module'] = module def __getattr__(self, attr): - if 'TLSSNI01' in attr: + if 'TLSSNI01' in attr or attr == 'BaseRequestHandlerWithLogging': warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr), DeprecationWarning, stacklevel=2) return getattr(self._module, attr) diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 844960b2f..69c35fb6f 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -7,6 +7,7 @@ import os import socket import sys import threading +import warnings from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error from six.moves import http_client # pylint: disable=import-error @@ -267,6 +268,9 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def simple_tls_sni_01_server(cli_args, forever=True): """Run simple standalone TLSSNI01 server.""" + warnings.warn( + 'simple_tls_sni_01_server is deprecated and will be removed soon.', + DeprecationWarning, stacklevel=2) logging.basicConfig(level=logging.DEBUG) parser = argparse.ArgumentParser() @@ -299,7 +303,3 @@ def simple_tls_sni_01_server(cli_args, forever=True): # Patching ourselves to warn about TLS-SNI challenge deprecation and removal. sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) - - -if __name__ == "__main__": - sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 86ceaa316..c14f82d39 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -6,6 +6,7 @@ import socket import threading import tempfile import unittest +import warnings import time from contextlib import closing @@ -67,6 +68,18 @@ class TLSSNI01ServerTest(unittest.TestCase): jose.ComparableX509(self.certs[b'localhost'][1])) +class BaseRequestHandlerWithLoggingTest(unittest.TestCase): + """Test for acme.standalone.BaseRequestHandlerWithLogging.""" + + def test_it(self): + with mock.patch('acme.standalone.warnings.warn') as mock_warn: + # pylint: disable=unused-variable + from acme.standalone import BaseRequestHandlerWithLogging + self.assertTrue(mock_warn.called) + msg = mock_warn.call_args[0][0] + self.assertTrue(msg.startswith('BaseRequestHandlerWithLogging')) + + class HTTP01ServerTest(unittest.TestCase): """Tests for acme.standalone.HTTP01Server.""" @@ -266,8 +279,7 @@ class TestSimpleTLSSNI01Server(unittest.TestCase): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.port = sock.getsockname()[1] - from acme.standalone import simple_tls_sni_01_server - self.process = multiprocessing.Process(target=simple_tls_sni_01_server, + self.process = multiprocessing.Process(target=_simple_tls_sni_01_server_no_warnings, args=(['path', '-p', str(self.port)],)) self.old_cwd = os.getcwd() os.chdir(self.test_cwd) @@ -284,8 +296,8 @@ class TestSimpleTLSSNI01Server(unittest.TestCase): @mock.patch('acme.standalone.TLSSNI01Server.handle_request') def test_mock(self, handle): - from acme.standalone import simple_tls_sni_01_server - simple_tls_sni_01_server(cli_args=['path', '-p', str(self.port)], forever=False) + _simple_tls_sni_01_server_no_warnings(cli_args=['path', '-p', str(self.port)], + forever=False) self.assertEqual(handle.call_count, 1) def test_live(self): @@ -302,5 +314,12 @@ class TestSimpleTLSSNI01Server(unittest.TestCase): test_util.load_comparable_cert('rsa2048_cert.pem')) +def _simple_tls_sni_01_server_no_warnings(*args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', 'simple_tls.*') + from acme.standalone import simple_tls_sni_01_server + return simple_tls_sni_01_server(*args, **kwargs) + + if __name__ == "__main__": unittest.main() # pragma: no cover -- cgit v1.2.3 From 8956de6beec12d46ea5acc456f10d8199ab34878 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 15:05:41 -0700 Subject: Describe distributed Certbot components. (#7493) --- docs/contributing.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index f01b1fd03..cc0a74b43 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -197,15 +197,20 @@ using an HTTP-01 challenge on a machine with Python 3: Code components and layout ========================== +The following components of the Certbot repository are distributed to users: + acme contains all protocol specific code certbot main client code certbot-apache and certbot-nginx client code to configure specific web servers -certbot.egg-info - configuration for packaging Certbot - +certbot-dns-* + client code to configure DNS providers +certbot-auto and letsencrypt-auto + shell scripts to install Certbot and its dependencies on UNIX systems +windows installer + Installs Certbot on Windows and is built using the files in windows-installer/ Plugin-architecture ------------------- -- cgit v1.2.3 From fffa74edb223d120b9855ed17f2f50745f282ba0 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 1 Nov 2019 16:40:31 -0700 Subject: Clarify when the changelog should be modified (#7491) --- pull_request_template.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pull_request_template.md b/pull_request_template.md index 77d65499f..9bf8f3f2d 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,7 +1,9 @@ ## Pull Request Checklist -- [ ] Edit the `master` section of `CHANGELOG.md` to include a description of - the change being made. +- [ ] If the change being made is to a [distributed + component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), + edit the `master` section of `CHANGELOG.md` to include a description of the + change being made. - [ ] Add [mypy type annotations](https://certbot.eff.org/docs/contributing.html#mypy-type-annotations) for any functions that were added or modified. -- cgit v1.2.3 From 1c05b9bd07191512b33dabc50c0b617d58c4be7d Mon Sep 17 00:00:00 2001 From: Brandon Moore Date: Mon, 4 Nov 2019 12:50:57 -0500 Subject: Dropped deprecated flags from commands (#7482) This pull request addresses #7451 by removing the deprecated flags. * Dropped deprecated flags from commands * Updated changelog for dropped flags and deleted outdated tests * removed init-script part of apache test --- CHANGELOG.md | 1 + certbot-apache/certbot_apache/configurator.py | 2 -- certbot-apache/certbot_apache/tests/configurator_test.py | 4 ---- certbot/cli.py | 3 --- certbot/tests/cli_test.py | 10 ---------- certbot/tests/main_test.py | 5 ----- 6 files changed, 1 insertion(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8477525b1..13f425209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. * The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no longer accepted. +* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script` * acme.standalone.BaseRequestHandlerWithLogging and acme.standalone.simple_tls_sni_01_server have been deprecated and will be removed in a future release of the library. diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index f7c27bf76..3868451f5 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -174,8 +174,6 @@ class ApacheConfigurator(common.Installer): "(Only Ubuntu/Debian currently)") add("ctl", default=DEFAULTS["ctl"], help="Full path to Apache control script") - util.add_deprecated_argument( - add, argument_name="init-script", nargs=1) def __init__(self, *args, **kwargs): """Initialize an Apache Configurator. diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index bf5281d29..52c5b437c 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -108,13 +108,9 @@ class MultipleVhostsTest(util.ApacheTest): exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] # Special cases exp["vhost-root"] = None - exp["init-script"] = None found = set() for call in mock_add.call_args_list: - # init-script is a special case: deprecated argument - if call[0][0] != "init-script": - self.assertEqual(exp[call[0][0]], call[1]['default']) found.add(call[0][0]) # Make sure that all (and only) the expected values exist diff --git a/certbot/cli.py b/certbot/cli.py index c4ddcddbe..97a0abbfb 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1246,9 +1246,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis default=flag_default("autorenew"), dest="autorenew", help="Disable auto renewal of certificates.") - helpful.add_deprecated_argument("--agree-dev-preview", 0) - helpful.add_deprecated_argument("--dialog", 0) - # Populate the command line parameters for new style enhancements enhancements.populate_cli(helpful.add) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index e8d8282df..7f3830d72 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -265,16 +265,6 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertTrue(namespace.must_staple) self.assertTrue(namespace.staple) - def test_no_gui(self): - args = ['renew', '--dialog'] - with mock.patch("certbot.util.logger.warning") as mock_warn: - namespace = self.parse(args) - - self.assertTrue(namespace.noninteractive_mode) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) - self.assertEqual("--dialog", mock_warn.call_args[0][1]) - def _check_server_conflict_message(self, parser_args, conflicting_args): try: self.parse(parser_args) diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index b7477e355..d8c7f4ab8 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -1381,11 +1381,6 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met jose.ComparableX509(cert), mock.ANY) - def test_agree_dev_preview_config(self): - with mock.patch('certbot.main.run') as mocked_run: - self._call(['-c', test_util.vector_path('cli.ini')]) - self.assertTrue(mocked_run.called) - @mock.patch('certbot.log.post_arg_parse_setup') def test_register(self, _): with mock.patch('certbot.main.client') as mocked_client: -- cgit v1.2.3 From 08d91b456be1759a1c61602c590766d39b61105a Mon Sep 17 00:00:00 2001 From: alexzorin Date: Tue, 5 Nov 2019 07:23:25 +1100 Subject: Use fresh authorizations in dry runs (#7442) * acme: re-populate uri in deactivate_authorization * Use fresh authorizations in dry runs --dry-run now deactivates 'valid' authorizations if it encounters them when creating a new order. Resolves #5116. * remove unused code * typo in local-oldest-requirements * better error handling * certbot-ci: AUTHREUSE to 100 + unskip dry-run test * improve test coverage for error cases * restore newline to local-oldest-requirements.txt --- CHANGELOG.md | 2 ++ acme/acme/client.py | 3 +- .../certbot_tests/test_main.py | 18 ++++++++++ .../certbot_integration_tests/utils/acme_server.py | 1 + certbot/auth_handler.py | 28 ++++++++++++++- certbot/client.py | 14 +++++++- certbot/tests/auth_handler_test.py | 34 ++++++++++++++++++ certbot/tests/client_test.py | 42 ++++++++++++++++++++++ local-oldest-requirements.txt | 2 +- setup.py | 2 +- 10 files changed, 141 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f425209..4a71d24ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). systems will be asked to do this manually. * `--server` may now be combined with `--dry-run`. Certbot will, as before, use the staging server instead of the live server when `--dry-run` is used. +* `--dry-run` now requests fresh authorizations every time, fixing the issue + where it was prone to falsely reporting success. * Updated certbot-dns-google to depend on newer versions of google-api-python-client and oauth2client. * The OS detection logic again uses distro library for Linux OSes diff --git a/acme/acme/client.py b/acme/acme/client.py index 9698c7609..d2002576e 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -136,7 +136,8 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes """ body = messages.UpdateAuthorization(status='deactivated') response = self._post(authzr.uri, body) - return self._authzr_from_response(response) + return self._authzr_from_response(response, + authzr.body.identifier, authzr.uri) def _authzr_from_response(self, response, identifier=None, uri=None): authzr = messages.AuthorizationResource( diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 80fa15584..50c685a57 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -594,3 +594,21 @@ def test_ocsp_status_live(context): assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert) assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert) + + +def test_dry_run_deactivate_authzs(context): + """Test that Certbot deactivates authorizations when performing a dry run""" + + name = context.get_domain('dry-run-authz-deactivation') + args = ['certonly', '--cert-name', name, '-d', name, '--dry-run'] + log_line = 'Recreating order after authz deactivation' + + # First order will not need deactivation + context.certbot(args) + with open(join(context.workspace, 'logs', 'letsencrypt.log'), 'r') as f: + assert log_line not in f.read(), 'First order should not have had any authz reuse' + + # Second order will require deactivation + context.certbot(args) + with open(join(context.workspace, 'logs', 'letsencrypt.log'), 'r') as f: + assert log_line in f.read(), 'Second order should have been recreated due to authz reuse' diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index f13855d1e..9e7ead916 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -125,6 +125,7 @@ class ACMEServer(object): environ = os.environ.copy() environ['PEBBLE_VA_NOSLEEP'] = '1' environ['PEBBLE_WFE_NONCEREJECT'] = '0' + environ['PEBBLE_AUTHZREUSE'] = '100' self._launch_process( [pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'], diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py index 232b246d5..a8c8f6e9b 100644 --- a/certbot/auth_handler.py +++ b/certbot/auth_handler.py @@ -7,8 +7,9 @@ import zope.component from acme import challenges from acme import messages +from acme import errors as acme_errors # pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List +from acme.magic_typing import Dict, List, Tuple # pylint: enable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors @@ -97,6 +98,31 @@ class AuthHandler(object): return authzrs_validated + def deactivate_valid_authorizations(self, orderr): + # type: (messages.OrderResource) -> Tuple[List, List] + """ + Deactivate all `valid` authorizations in the order, so that they cannot be re-used + in subsequent orders. + :param messages.OrderResource orderr: must have authorizations filled in + :returns: tuple of list of successfully deactivated authorizations, and + list of unsuccessfully deactivated authorizations. + :rtype: tuple + """ + to_deactivate = [authzr for authzr in orderr.authorizations + if authzr.body.status == messages.STATUS_VALID] + deactivated = [] + failed = [] + + for authzr in to_deactivate: + try: + authzr = self.acme.deactivate_authorization(authzr) + deactivated.append(authzr) + except acme_errors.Error as e: + failed.append(authzr) + logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) + + return (deactivated, failed) + def _poll_authorizations(self, authzrs, max_retries, best_effort): """ Poll the ACME CA server, to wait for confirmation that authorizations have their challenges diff --git a/certbot/client.py b/certbot/client.py index c1199daac..1800a301c 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -15,7 +15,7 @@ from acme import client as acme_client from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Optional # pylint: disable=unused-import,no-name-in-module +from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module import certbot from certbot import account @@ -366,6 +366,7 @@ class Client(object): return cert, chain, key, csr def _get_order_and_authorizations(self, csr_pem, best_effort): + # type: (str, bool) -> List[messages.OrderResource] """Request a new order and complete its authorizations. :param str csr_pem: A CSR in PEM format. @@ -381,6 +382,17 @@ class Client(object): except acme_errors.WildcardUnsupportedError: raise errors.Error("The currently selected ACME CA endpoint does" " not support issuing wildcard certificates.") + + # For a dry run, ensure we have an order with fresh authorizations + if orderr and self.config.dry_run: + deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) + if deactivated: + logger.debug("Recreating order after authz deactivations") + orderr = self.acme.new_order(csr_pem) + if failed: + logger.warning("Certbot was unable to obtain fresh authorizations for every domain" + ". The dry run will continue, but results may not be accurate.") + authzr = self.auth_handler.handle_authorizations(orderr, best_effort) return orderr.update(authorizations=authzr) diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 45eef8e2b..9f58b95d3 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -9,6 +9,7 @@ import zope.component from acme import challenges from acme import client as acme_client from acme import messages +from acme import errors as acme_errors from certbot import achallenges from certbot import errors @@ -362,6 +363,39 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p mock_order = mock.MagicMock(authorizations=[authzr]) self.handler.handle_authorizations(mock_order) + def test_valid_authzrs_deactivated(self): + """When we deactivate valid authzrs in an orderr, we expect them to become deactivated + and to receive a list of deactivated authzrs in return.""" + def _mock_deactivate(authzr): + if authzr.body.status == messages.STATUS_VALID: + if authzr.body.identifier.value == "is_valid_but_will_fail": + raise acme_errors.Error("Mock deactivation ACME error") + authzb = authzr.body.update(status=messages.STATUS_DEACTIVATED) + authzr = messages.AuthorizationResource(body=authzb) + else: # pragma: no cover + raise errors.Error("Can't deactivate non-valid authz") + return authzr + + to_deactivate = [("is_valid", messages.STATUS_VALID), + ("is_pending", messages.STATUS_PENDING), + ("is_valid_but_will_fail", messages.STATUS_VALID)] + + to_deactivate = [acme_util.gen_authzr(a[1], a[0], [acme_util.HTTP01], + [a[1], False]) for a in to_deactivate] + orderr = mock.MagicMock(authorizations=to_deactivate) + + self.mock_net.deactivate_authorization.side_effect = _mock_deactivate + + authzrs, failed = self.handler.deactivate_valid_authorizations(orderr) + + self.assertEqual(self.mock_net.deactivate_authorization.call_count, 2) + self.assertEqual(len(authzrs), 1) + self.assertEqual(len(failed), 1) + self.assertEqual(authzrs[0].body.identifier.value, "is_valid") + self.assertEqual(authzrs[0].body.status, messages.STATUS_DEACTIVATED) + self.assertEqual(failed[0].body.identifier.value, "is_valid_but_will_fail") + self.assertEqual(failed[0].body.status, messages.STATUS_VALID) + def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): state = {'count': retry} diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 8d2cd76b9..f624c5320 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -256,6 +256,7 @@ class ClientTest(ClientTestCommon): def _mock_obtain_certificate(self): self.client.auth_handler = mock.MagicMock() self.client.auth_handler.handle_authorizations.return_value = [None] + self.client.auth_handler.deactivate_valid_authorizations.return_value = ([], []) self.acme.finalize_order.return_value = self.eg_order self.acme.new_order.return_value = self.eg_order self.eg_order.update.return_value = self.eg_order @@ -360,6 +361,47 @@ class ClientTest(ClientTestCommon): mock_crypto.init_save_csr.assert_not_called() self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1) + @mock.patch("certbot.client.logger") + @mock.patch("certbot.client.crypto_util") + @mock.patch("certbot.client.acme_crypto_util") + def test_obtain_certificate_dry_run_authz_deactivations_failed(self, mock_acme_crypto, + mock_crypto, mock_log): + from acme import messages + csr = util.CSR(form="pem", file=None, data=CSR_SAN) + mock_acme_crypto.make_csr.return_value = CSR_SAN + mock_crypto.make_key.return_value = mock.sentinel.key_pem + key = util.Key(file=None, pem=mock.sentinel.key_pem) + self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain) + + self._mock_obtain_certificate() + self.client.config.dry_run = True + + # Two authzs that are already valid and should get deactivated (dry run) + authzrs = self._authzr_from_domains(["example.com", "www.example.com"]) + for authzr in authzrs: + authzr.body.status = messages.STATUS_VALID + + # One deactivation succeeds, one fails + auth_handler = self.client.auth_handler + auth_handler.deactivate_valid_authorizations.return_value = ([authzrs[0]], [authzrs[1]]) + + # Certificate should get issued despite one failed deactivation + self.eg_order.authorizations = authzrs + self.client.auth_handler.handle_authorizations.return_value = authzrs + with test_util.patch_get_utility(): + result = self.client.obtain_certificate(self.eg_domains) + self.assertEqual(result, (mock.sentinel.cert, mock.sentinel.chain, key, csr)) + self._check_obtain_certificate(1) + + # Deactivation success/failure should have been handled properly + self.assertEqual(auth_handler.deactivate_valid_authorizations.call_count, 1, + "Deactivate authorizations should be called") + self.assertEqual(self.acme.new_order.call_count, 2, + "Order should be recreated due to successfully deactivated authorizations") + mock_log.warning.assert_called_with("Certbot was unable to obtain fresh authorizations for" + " every domain. The dry run will continue, but results" + " may not be accurate.") + def _set_mock_from_fullchain(self, mock_from_fullchain): mock_cert = mock.Mock() mock_cert.encode.return_value = mock.sentinel.cert diff --git a/local-oldest-requirements.txt b/local-oldest-requirements.txt index e2a804e1b..0acc68652 100644 --- a/local-oldest-requirements.txt +++ b/local-oldest-requirements.txt @@ -1,2 +1,2 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.29.0 +-e acme[dev] diff --git a/setup.py b/setup.py index d5469bb26..7088f3b11 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 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.29.0', + 'acme>=0.40.0.dev0', # 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. -- cgit v1.2.3 From 3c24ff88cc0106ac39e5b0f5bd6bf0f29572201e Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 4 Nov 2019 23:20:42 +0100 Subject: Build Windows installers with pinned dependencies (#7498) * Consume constraints file * Independent pywin32 dependency definition in setup.py and construct.py --- setup.py | 2 +- windows-installer/construct.py | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 7088f3b11..160726f72 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ install_requires = [ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. -pywin32_req = 'pywin32>=225' +pywin32_req = 'pywin32>=225' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py if StrictVersion(setuptools_version) >= StrictVersion('36.2'): install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 7fd3039f9..94ce1fa4d 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 +import contextlib import ctypes import struct import subprocess import os import sys import shutil +import tempfile import time - PYTHON_VERSION = (3, 7, 4) PYTHON_BITNESS = 32 +PYWIN32_VERSION = 225 # do not forget to edit pywin32 dependency accordingly in setup.py def main(): @@ -42,9 +44,10 @@ def _compile_wheels(repo_path, build_path, venv_python): # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')]) wheels_project = [os.path.join(repo_path, package) for package in certbot_packages] - command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path] - command.extend(wheels_project) - subprocess.check_call(command) + with _prepare_constraints(repo_path) as constraints_file_path: + command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path, '--constraint', constraints_file_path] + command.extend(wheels_project) + subprocess.check_call(command) def _prepare_build_tools(venv_path, venv_python, repo_path): @@ -55,6 +58,23 @@ def _prepare_build_tools(venv_path, venv_python, repo_path): subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'wheel', 'pynsist']) +@contextlib.contextmanager +def _prepare_constraints(repo_path): + requirements = os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'dependency-requirements.txt') + constraints = subprocess.check_output( + [sys.executable, os.path.join(repo_path, 'tools', 'strip_hashes.py'), requirements], + universal_newlines=True) + workdir = tempfile.mkdtemp() + try: + constraints_file_path = os.path.join(workdir, 'constraints.txt') + with open(constraints_file_path, 'a') as file_h: + file_h.write(constraints) + file_h.write('pywin32=={0}'.format(PYWIN32_VERSION)) + yield constraints_file_path + finally: + shutil.rmtree(workdir) + + def _copy_assets(build_path, repo_path): print('Copy assets') if os.path.exists(build_path): -- cgit v1.2.3 From 78deca4f60afaf694134cb8ae4812e90b035ef52 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 5 Nov 2019 08:34:46 -0800 Subject: Don't use --agree-dev-preview in tests. (#7501) --- tests/letstest/scripts/test_apache2.sh | 2 +- tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/letstest/scripts/test_apache2.sh b/tests/letstest/scripts/test_apache2.sh index 007ab720e..7ebaaa5fd 100755 --- a/tests/letstest/scripts/test_apache2.sh +++ b/tests/letstest/scripts/test_apache2.sh @@ -51,7 +51,7 @@ fi . tests/letstest/scripts/set_python_envvars.sh "$VENV_SCRIPT" -e acme[dev] -e .[dev,docs] -e certbot-apache -sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-dev-preview --agree-tos \ +sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-tos \ --renew-by-default --redirect --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL if [ $? -ne 0 ] ; then diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index eb63b9ca7..c028031c7 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -32,7 +32,7 @@ mkdir -p "$OLD_VENV_BIN" touch "$OLD_VENV_BIN/letsencrypt" letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \ - --text --agree-dev-preview --agree-tos \ + --text --agree-tos \ --renew-by-default --redirect \ --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL -- cgit v1.2.3 From 3d9d2120402894233e99813a8767839c5d95c153 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 12:35:33 -0800 Subject: Update changelog for 0.40.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a71d24ad..a0b6eaaa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.40.0 - master +## 0.40.0 - 2019-11-05 ### Added -- cgit v1.2.3 From 73cd5aa81c56a1dfc87f27dd9124dab525c1e9df Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 12:52:26 -0800 Subject: Release 0.40.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 238 ++++++++++++++------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 238 ++++++++++++++------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- local-oldest-requirements.txt | 2 +- setup.py | 2 +- 28 files changed, 387 insertions(+), 199 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index f9306e350..ee2d225e8 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.40.0.dev0' +version = '0.40.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index ad7b99862..9d22bacff 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 68ced3260..5df7f5f30 100755 --- a/certbot-auto +++ b/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.39.0" +LE_AUTO_VERSION="0.40.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,88 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if ! scl --list 2>/dev/null | grep -q rh-python36; then + return 0 + fi + set +e + . scl_source enable rh-python36 + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +570,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -769,31 +828,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -1078,8 +1156,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1175,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1338,18 +1432,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.39.0 \ - --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ - --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 -acme==0.39.0 \ - --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ - --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 -certbot-apache==0.39.0 \ - --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ - --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a -certbot-nginx==0.39.0 \ - --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ - --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 +certbot==0.40.0 \ + --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ + --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 +acme==0.40.0 \ + --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ + --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 +certbot-apache==0.40.0 \ + --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ + --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e +certbot-nginx==0.40.0 \ + --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ + --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 1d648db17..e7f4880f1 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 5ad8f1568..1855428d2 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 0dd5bc397..e642d406a 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 2d3139a2f..c6fbc9a0c 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 6925946ec..a62afa912 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index d31e52686..cf35a0427 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f6b944625..06399cace 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 6bf12ddbf..37903034d 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index ce4647514..11c0d577f 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index f260c68db..2bf62d4a0 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index c6a5ca443..7ed20ad8a 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 48b8cee4e..849cbc548 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 6fc69ebc0..d03c7cc0c 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 857e07965..0f097f977 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index c153c681f..9d2b0f901 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0.dev0' +version = '0.40.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 8fe300193..b53e16659 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0.dev0' +version = '0.40.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index 27b8684e1..ca79e552f 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.40.0.dev0' +__version__ = '0.40.0' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 134a6879a..37539a24b 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.39.0 + "". (default: CertbotACMEClient/0.40.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 68ced3260..5df7f5f30 100755 --- a/letsencrypt-auto +++ b/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.39.0" +LE_AUTO_VERSION="0.40.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,88 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if ! scl --list 2>/dev/null | grep -q rh-python36; then + return 0 + fi + set +e + . scl_source enable rh-python36 + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +570,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -769,31 +828,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -1078,8 +1156,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1175,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1338,18 +1432,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.39.0 \ - --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ - --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 -acme==0.39.0 \ - --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ - --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 -certbot-apache==0.39.0 \ - --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ - --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a -certbot-nginx==0.39.0 \ - --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ - --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 +certbot==0.40.0 \ + --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ + --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 +acme==0.40.0 \ + --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ + --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 +certbot-apache==0.40.0 \ + --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ + --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e +certbot-nginx==0.40.0 \ + --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ + --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index f25f27cdf..3bdb8a93c 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl2TsPMACgkQTRfJlc2X -dfJHUAf+NcnvHzowhLr1rkR11CSKMCMgwUee7Nm0QHnVPf09+Dd9mvuaRptuua1D -Qvtcb3F4OQ6/3khy3fzGXIcEe9kuI2+boe+ZA0dfmmzo4ELzpWUadXkuonYybZFE -JAaICgLLHOkiRL8J8ZTmXZI4tbFSsxTLMNOwoMZ6oGgp2plj2rm85L4Z+vUlfaTf -wcs/glbBtbYfW3WWapMsMWwgrE62Q/OOhBjbkPCywFRQDwwaXz6QPrvi+k6gLCqs -Okvg5bY2hP70tU1i9wxp2DAfF/P/5i2hVSWktRdMolUTTTeczLW81allmmDRJcAi -4xrj6wYhN7olMZrTpakXb7zRR9/MGQ== -=Ag2y +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3B4KcACgkQTRfJlc2X +dfJKHAf+PUViUdwbaXUMNfDRo7g6v44RA0RIj+SG3cjLsX2E/A2G70KndfUC/9KS +cgYpFZ3h/2y3fLLsYgDIOPRhAKLgrk+LFKrtDsUbOLF7K3eS70KQmDxYFXNzw0jc +34zhc9BKsKrqX6a80LprkVtbEuRlE58JaXyqjMW8NvGvLXNV8qCZK8xG8SrCkVnU +KFlXgHAl3UFibm3yJOlIjHikuOaU0jlDbO/S2WfkkgV3BWQkngUKu+9gr+ItV3We +GMidJljIoho8CqYQnLWtsjhOmjLQogsUKZJSg/riAxrDW3cCEmF4EaV/S8lNnSiL +f49WauHsGdfIaFabl8HVG7h+R3Uh8Q== +=JXq9 -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index c0f7466ae..5df7f5f30 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.40.0.dev0" +LE_AUTO_VERSION="0.40.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1432,18 +1432,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.39.0 \ - --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ - --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 -acme==0.39.0 \ - --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ - --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 -certbot-apache==0.39.0 \ - --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ - --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a -certbot-nginx==0.39.0 \ - --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ - --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 +certbot==0.40.0 \ + --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ + --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 +acme==0.40.0 \ + --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ + --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 +certbot-apache==0.40.0 \ + --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ + --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e +certbot-nginx==0.40.0 \ + --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ + --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index d9147680b..b961bbc91 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 7d1c09069..415663f96 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.39.0 \ - --hash=sha256:f1a70651a6c5137a448f4a8db17b09af619f80a077326caae6b74278bf1db488 \ - --hash=sha256:885cee1c4d05888af86b626cbbfc29d3c6c842ef4fe8f4a486994cef9daddfe0 -acme==0.39.0 \ - --hash=sha256:4f8be913df289b981852042719469cc367a7e436256f232c799d0bd1521db710 \ - --hash=sha256:a2fcb75d16de6804f4b4d773a457ee2f6434ebaf8fd1aa60862a91d4e8f73608 -certbot-apache==0.39.0 \ - --hash=sha256:c7a8630a85b753a52ca0b8c19e24b8f85ac4ba028292a95745e250c2e72faab9 \ - --hash=sha256:4651a0212c9ebc3087281dad92ad3cb355bb2730f432d0180a8d23325d11825a -certbot-nginx==0.39.0 \ - --hash=sha256:76e5862ad5cc0fbc099df3502987c101c60dee1c188a579eac990edee7a910df \ - --hash=sha256:ceac88df52d3b27d14c3052b9e90ada327d7e14ecd6e4af7519918182d6138b4 +certbot==0.40.0 \ + --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ + --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 +acme==0.40.0 \ + --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ + --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 +certbot-apache==0.40.0 \ + --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ + --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e +certbot-nginx==0.40.0 \ + --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ + --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 diff --git a/local-oldest-requirements.txt b/local-oldest-requirements.txt index 0acc68652..f6d158890 100644 --- a/local-oldest-requirements.txt +++ b/local-oldest-requirements.txt @@ -1,2 +1,2 @@ # Remember to update setup.py to match the package versions below. --e acme[dev] +acme[dev]==0.40.0 diff --git a/setup.py b/setup.py index 160726f72..8b45fd80c 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 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.40.0.dev0', + 'acme>=0.40.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. -- cgit v1.2.3 From fcecdfbcc5cfd5612041f5c40f203178bb388196 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 12:53:16 -0800 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b6eaaa9..bd709ae75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.41.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.40.0 - 2019-11-05 ### Added -- cgit v1.2.3 From 0de2645a8fa1020ac415f89cb251b1034facbd9a Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 12:53:16 -0800 Subject: Bump version to 0.41.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index ee2d225e8..8026f7f81 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.41.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 9d22bacff..9cf242a34 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index e7f4880f1..7aec1fe43 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 1855428d2..236edcb5d 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e642d406a..ba115a140 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index c6fbc9a0c..a6e81d060 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index a62afa912..a6410f234 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index cf35a0427..f3ec4de88 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 06399cace..95710290f 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 37903034d..db1953ac7 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 11c0d577f..3f7a48e98 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 2bf62d4a0..01e25e3f2 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 7ed20ad8a..475e943dc 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 849cbc548..5dab97c86 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d03c7cc0c..84d24257e 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 0f097f977..57e3930a5 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 9d2b0f901..6e4559639 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.41.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index b53e16659..a7b3b4ea7 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.41.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index ca79e552f..a3899a31b 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.40.0' +__version__ = '0.41.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 5df7f5f30..2c4d5d171 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.40.0" +LE_AUTO_VERSION="0.41.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 9b848b1d65783000a13ef3f94ac5fe0e8c3879e7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 5 Nov 2019 16:45:08 -0800 Subject: Add back Python 3.4 support (#7510) * Revert "Deprecation warnings for Python 3.4 (#7378)" This reverts commit 6fcdfb0e5006be85500fad67a5a67b47befedb2a. * Revert "Migrate certbot-auto users on CentOS 6 to Python 3.6 (#7268)" This reverts commit e19b2e04c75b6df4e3f8a455700aa95fca79bcc3. * add changelog entry * keep mona in authors --- .travis.yml | 3 - CHANGELOG.md | 3 +- acme/acme/__init__.py | 7 - certbot/main.py | 4 - letsencrypt-auto-source/Dockerfile.centos6 | 37 ++++ letsencrypt-auto-source/Dockerfile.redhat6 | 48 ----- letsencrypt-auto-source/letsencrypt-auto | 212 ++++++--------------- letsencrypt-auto-source/letsencrypt-auto.template | 106 +++-------- .../pieces/bootstrappers/rpm_common_base.sh | 24 ++- .../pieces/bootstrappers/rpm_python3.sh | 7 + .../pieces/bootstrappers/rpm_python3_legacy.sh | 75 -------- letsencrypt-auto-source/tests/centos6_tests.sh | 147 +++++--------- .../tests/oraclelinux6_tests.sh | 85 --------- tests/letstest/scripts/test_leauto_upgrades.sh | 2 - tests/letstest/scripts/test_sdists.sh | 13 -- tox.ini | 12 +- 16 files changed, 202 insertions(+), 583 deletions(-) create mode 100644 letsencrypt-auto-source/Dockerfile.centos6 delete mode 100644 letsencrypt-auto-source/Dockerfile.redhat6 delete mode 100644 letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh delete mode 100644 letsencrypt-auto-source/tests/oraclelinux6_tests.sh diff --git a/.travis.yml b/.travis.yml index a1172f086..22391c84f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -234,9 +234,6 @@ matrix: - sudo: required env: TOXENV=le_auto_centos6 services: docker - - sudo: required - env: TOXENV=le_auto_oraclelinux6 - services: docker <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev diff --git a/CHANGELOG.md b/CHANGELOG.md index bd709ae75..806905564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Added back support for Python 3.4 to Certbot components and certbot-auto due + to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. ### Fixed diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index e68ebd765..7439712b0 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -48,10 +48,3 @@ class _TLSSNI01DeprecationModule(object): def __dir__(self): # pragma: no cover return ['_module'] + dir(self._module) - -if sys.version_info[:2] == (3, 4): - warnings.warn( - "Python 3.4 support will be dropped in the next release of " - "acme. Please upgrade your Python version.", - PendingDeprecationWarning, - ) # pragma: no cover diff --git a/certbot/main.py b/certbot/main.py index 30d0f1d94..fc91aca5f 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1368,10 +1368,6 @@ def main(cli_args=None): if config.func != plugins_cmd: raise - if sys.version_info[:2] == (3, 4): - logger.warning("Python 3.4 support will be dropped in the next release " - "of Certbot - please upgrade your Python version.") - set_displayer(config) # Reporter diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 new file mode 100644 index 000000000..09aa52dcd --- /dev/null +++ b/letsencrypt-auto-source/Dockerfile.centos6 @@ -0,0 +1,37 @@ +# For running tests, build a docker image with a passwordless sudo and a trust +# store we can manipulate. + +FROM centos:6 + +RUN yum install -y epel-release + +# Install pip and sudo: +RUN yum install -y python-pip sudo +# Update to a stable and tested version of pip. +# We do not use pipstrap here because it no longer supports Python 2.6. +RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 +# Pin pytest version for increased stability +RUN pip install pytest==3.2.5 six==1.10.0 + +# Add an unprivileged user: +RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea + +# Let that user sudo: +RUN sed -i.bkp -e \ + 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ + /etc/sudoers + +RUN mkdir -p /home/lea/certbot + +# Install fake testing CA: +COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ +RUN update-ca-trust + +# Copy code: +COPY . /home/lea/certbot/letsencrypt-auto-source + +USER lea +WORKDIR /home/lea + +RUN sudo chmod +x certbot/letsencrypt-auto-source/tests/centos6_tests.sh +CMD sudo certbot/letsencrypt-auto-source/tests/centos6_tests.sh diff --git a/letsencrypt-auto-source/Dockerfile.redhat6 b/letsencrypt-auto-source/Dockerfile.redhat6 deleted file mode 100644 index d5cdc0458..000000000 --- a/letsencrypt-auto-source/Dockerfile.redhat6 +++ /dev/null @@ -1,48 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -ARG REDHAT_DIST_FLAVOR -FROM ${REDHAT_DIST_FLAVOR}:6 - -ARG REDHAT_DIST_FLAVOR - -RUN curl -O https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm \ - && rpm -ivh epel-release-latest-6.noarch.rpm - -# Install pip and sudo: -RUN yum install -y python-pip sudo -# Update to a stable and tested version of pip. -# We do not use pipstrap here because it no longer supports Python 2.6. -RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ - /etc/sudoers - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-trust - -# Copy current letsencrypt-auto: -COPY . /home/lea/certbot/letsencrypt-auto-source - -# Fetch previous letsencrypt-auto that was installing python 3.4 -RUN curl https://raw.githubusercontent.com/certbot/certbot/v0.38.0/letsencrypt-auto-source/letsencrypt-auto \ - -o /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 \ - && chmod +x /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 - -RUN cp /home/lea/certbot/letsencrypt-auto-source/tests/${REDHAT_DIST_FLAVOR}6_tests.sh /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh \ - && chmod +x /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh - -USER lea -WORKDIR /home/lea - -CMD ["sudo", "certbot/letsencrypt-auto-source/tests/redhat6_tests.sh"] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2c4d5d171..f476b8cb1 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -376,9 +368,7 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -398,6 +388,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { @@ -478,88 +488,13 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} - # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -570,6 +505,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -828,50 +769,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -1156,15 +1078,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1175,21 +1090,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index bc4b92092..31c5bb134 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -306,7 +298,6 @@ DeterminePythonVersion() { {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common_base.sh }} {{ bootstrappers/rpm_common.sh }} -{{ bootstrappers/rpm_python3_legacy.sh }} {{ bootstrappers/rpm_python3.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} @@ -353,50 +344,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -573,15 +545,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -592,21 +557,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh index 2b00b199b..326ad8b3f 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh @@ -3,9 +3,7 @@ # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -25,6 +23,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index ac0553db5..f33b07ca9 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -4,6 +4,7 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -14,6 +15,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh deleted file mode 100644 index 0935c1b94..000000000 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh +++ /dev/null @@ -1,75 +0,0 @@ -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index c15cd80ec..2c6dcf734 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,140 +1,81 @@ #!/bin/bash # Start by making sure your system is up-to-date: -yum update -y >/dev/null -yum install -y centos-release-scl >/dev/null -yum install -y python27 >/dev/null 2>/dev/null +yum update -y > /dev/null +yum install -y centos-release-scl > /dev/null +yum install -y python27 > /dev/null 2> /dev/null -LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" -# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users -INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" - # we're going to modify env variables, so do this in a subshell ( - . scl_source enable python27 +source /opt/rh/python27/enable - # ensure python 3 isn't installed - python3 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -eq 0 ]; then - echo "ERROR: Python3 is already installed." - exit 1 - fi +# ensure python 3 isn't installed +python3 --version 2> /dev/null +RESULT=$? +if [ $RESULT -eq 0 ]; then + error "Python3 is already installed." + exit 1 +fi - # ensure python2.7 is available - python2.7 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -ne 0 ]; then - echo "ERROR: Python2.7 is not available." - exit 1 - fi +# ensure python2.7 is available +python2.7 --version 2> /dev/null +RESULT=$? +if [ $RESULT -ne 0 ]; then + error "Python3 is not available." + exit 1 +fi - # bootstrap, but don't install python 3. - "$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null +# bootstrap, but don't install python 3. +"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null - # ensure python 3 isn't installed - python3 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -eq 0 ]; then - echo "ERROR: letsencrypt-auto installed Python3 even though Python2.7 is present." - exit 1 - fi +# ensure python 3 isn't installed +python3 --version 2> /dev/null +RESULT=$? +if [ $RESULT -eq 0 ]; then + error "letsencrypt-auto installed Python3 even though Python2.7 is present." + exit 1 +fi - echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." +echo "" +echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." ) # ensure python2.7 isn't available -python2.7 --version >/dev/null 2>/dev/null +python2.7 --version 2> /dev/null RESULT=$? if [ $RESULT -eq 0 ]; then - error "ERROR: Python2.7 is still available." + error "Python2.7 is still available." exit 1 fi # Skip self upgrade due to Python 3 not being available. if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then - echo "ERROR: Python upgrade failure warning not printed!" + echo "Python upgrade failure warning not printed!" exit 1 fi -# bootstrap from the old letsencrypt-auto, this time installing python3.4 -"$LE_AUTO_PY_34" --no-self-upgrade -n >/dev/null 2>/dev/null +# bootstrap, this time installing python3 +"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null -# ensure python 3.4 is installed -python3.4 --version >/dev/null 2>/dev/null +# ensure python 3 is installed +python3 --version > /dev/null RESULT=$? if [ $RESULT -ne 0 ]; then - echo "ERROR: letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - exit 1 -fi - -echo "PASSED: Successfully upgraded to Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - -# As "certbot-auto" (so without implicit --non-interactive flag set), check that the script -# refuses to install SCL Python 3.6 when run in a non interactive shell (simulated here -# using | tee /dev/null) if --non-interactive flag is not provided. -cp "$LE_AUTO" /tmp/certbot-auto -# NB: Readline has an issue on all Python versions for CentOS 6, making `certbot --version` -# output an unprintable ASCII character on a new line at the end. -# So we take the second last line of the output. -version=$(/tmp/certbot-auto --version 2>/dev/null | tee /dev/null | tail -2 | head -1) - -if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then - echo "ERROR: certbot-auto upgraded certbot in a non-interactive shell with --non-interactive flag not set." + error "letsencrypt-auto failed to install Python3 when only Python2.6 is present." exit 1 fi -echo "PASSED: certbot-auto did not upgrade certbot in a non-interactive shell with --non-interactive flag not set." +echo "PASSED: Successfully upgraded to Python3 when only Python2.6 is present." +echo "" -if [ -f /opt/rh/rh-python36/enable ]; then - echo "ERROR: certbot-auto installed Python3.6 in a non-interactive shell with --non-interactive flag not set." +export VENV_PATH=$(mktemp -d) +"$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 +if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; then + echo "Python 3 wasn't used with --no-bootstrap!" exit 1 fi - -echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell with --non-interactive flag not set." - -# now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL -"$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null - -# Following test is exectued in a subshell, to not leak any environment variable -( - # enable SCL rh-python36 - . scl_source enable rh-python36 - - # ensure python 3.6 is installed - python3.6 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -ne 0 ]; then - echo "ERROR: letsencrypt-auto failed to install Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." - exit 1 - fi - - echo "PASSED: Successfully upgraded to Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." -) - -# Following test is exectued in a subshell, to not leak any environment variable -( - export VENV_PATH=$(mktemp -d) - "$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 - if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then - echo "ERROR: Python 3.6 wasn't used with --no-bootstrap!" - exit 1 - fi -) - -# Following test is exectued in a subshell, to not leak any environment variable -( - # enable SCL rh-python36 - . scl_source enable rh-python36 - - # ensure everything works fine with certbot-auto bootstrap when python 3.6 is already enabled - export VENV_PATH=$(mktemp -d) - if ! "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null; then - echo "ERROR: Certbot-auto broke when Python 3.6 SCL is already enabled." - exit 1 - fi -) +unset VENV_PATH # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh deleted file mode 100644 index f3fd952f3..000000000 --- a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash -set -eo pipefail -# Start by making sure your system is up-to-date: -yum update -y >/dev/null - -LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" -LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" - -# Apply installation instructions from official documentation: -# https://certbot.eff.org/lets-encrypt/centosrhel6-other -cp "$LE_AUTO" /usr/local/bin/certbot-auto -chown root /usr/local/bin/certbot-auto -chmod 0755 /usr/local/bin/certbot-auto -LE_AUTO=/usr/local/bin/certbot-auto - -# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users -INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" - -# Check bootstrap from current certbot-auto will fail, because SCL is not enabled. -set +o pipefail -if ! "$LE_AUTO" -n 2>&1 | grep -q "Enable the SCL repository and try running Certbot again."; then - echo "ERROR: Bootstrap was not aborted although SCL was not installed!" - exit 1 -fi -set -o pipefail - -echo "PASSED: Bootstrap was aborted since SCL was not installed." - -# Bootstrap from the old letsencrypt-auto, Python 3.4 will be installed from EPEL. -"$LE_AUTO_PY_34" --no-self-upgrade -n --install-only >/dev/null 2>/dev/null - -# Ensure Python 3.4 is installed -if ! command -v python3.4 &>/dev/null; then - echo "ERROR: old letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - exit 1 -fi - -echo "PASSED: Bootstrap from old letsencrypt-auto succeeded and installed Python 3.4" - -# Expect certbot-auto to skip rebootstrapping with a warning since SCL is not installed. -if ! "$LE_AUTO" --non-interactive --version 2>&1 | grep -q "This requires manual user intervention"; then - echo "FAILED: Script certbot-auto did not print a warning about needing manual intervention!" - exit 1 -fi - -echo "PASSED: Script certbot-auto did not rebootstrap." - -# NB: Readline has an issue on all Python versions for OL 6, making `certbot --version` -# output an unprintable ASCII character on a new line at the end. -# So we take the second last line of the output. -version=$($LE_AUTO --version 2>/dev/null | tail -2 | head -1) - -if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then - echo "ERROR: Script certbot-auto upgraded certbot in a non-interactive shell while SCL was not enabled." - exit 1 -fi - -echo "PASSED: Script certbot-auto did not upgrade certbot but started it successfully while SCL was not enabled." - -# Enable SCL -yum install -y oracle-softwarecollection-release-el6 >/dev/null - -# Expect certbot-auto to bootstrap successfully since SCL is available. -"$LE_AUTO" -n --version &>/dev/null - -if [ "$(/opt/eff.org/certbot/venv/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then - echo "ERROR: Script certbot-auto failed to bootstrap and install Python 3.6 while SCL is available." - exit 1 -fi - -if ! /opt/eff.org/certbot/venv/bin/certbot --version > /dev/null 2> /dev/null; then - echo "ERROR: Script certbot-auto did not install certbot correctly while SCL is enabled." - exit 1 -fi - -echo "PASSED: Script certbot-auto correctly bootstraped Certbot using rh-python36 when SCL is available." - -# Expect certbot-auto will be totally silent now that everything has been correctly boostraped. -OUTPUT_LEN=$("$LE_AUTO" --install-only --no-self-upgrade --quiet 2>&1 | wc -c) -if [ "$OUTPUT_LEN" != 0 ]; then - echo certbot-auto produced unexpected output! - exit 1 -fi - -echo "PASSED: Script certbot-auto did not print anything in quiet mode." diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index fc7632793..541f54f6b 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -117,8 +117,6 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then fi if [ "$RUN_RHEL6_TESTS" = 1 ]; then - # Add the SCL python release to PATH in order to resolve python3 command - PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index c8ce9fe7f..347589e04 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,21 +1,8 @@ #!/bin/sh -xe cd letsencrypt - -# If we're on a RHEL 6 based system, we can be confident Python is already -# installed because the package manager is written in Python. -if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then - # RHEL/CentOS 6 will need a special treatment, so we need to detect that environment - RUN_RHEL6_TESTS=1 -fi - letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug -if [ "$RUN_RHEL6_TESTS" = 1 ]; then - # Enable the SCL Python 3.6 installed by letsencrypt-auto bootstrap - PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" -fi - PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) diff --git a/tox.ini b/tox.ini index 952204a9e..04715cc2f 100644 --- a/tox.ini +++ b/tox.ini @@ -206,17 +206,7 @@ passenv = DOCKER_* # At the moment, this tests under Python 2.6 only, as only that version is # readily available on the CentOS 6 Docker image. commands = - docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=centos -t lea letsencrypt-auto-source - docker run --rm -t -i lea -whitelist_externals = - docker -passenv = DOCKER_* - -[testenv:le_auto_oraclelinux6] -# At the moment, this tests under Python 2.6 only, as only that version is -# readily available on the Oracle Linux 6 Docker image. -commands = - docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=oraclelinux -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.centos6 -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker -- cgit v1.2.3 From d87c905c0601918093ffa50d1536cb5704a95190 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 5 Nov 2019 17:11:23 -0800 Subject: Add back Python 3.4 support (#7510) (#7511) * Revert "Deprecation warnings for Python 3.4 (#7378)" This reverts commit 6fcdfb0e5006be85500fad67a5a67b47befedb2a. * Revert "Migrate certbot-auto users on CentOS 6 to Python 3.6 (#7268)" This reverts commit e19b2e04c75b6df4e3f8a455700aa95fca79bcc3. * add changelog entry * keep mona in authors (cherry picked from commit 9b848b1d65783000a13ef3f94ac5fe0e8c3879e7) --- .travis.yml | 3 - CHANGELOG.md | 9 + acme/acme/__init__.py | 7 - certbot/main.py | 4 - letsencrypt-auto-source/Dockerfile.centos6 | 37 ++++ letsencrypt-auto-source/Dockerfile.redhat6 | 48 ----- letsencrypt-auto-source/letsencrypt-auto | 212 ++++++--------------- letsencrypt-auto-source/letsencrypt-auto.template | 106 +++-------- .../pieces/bootstrappers/rpm_common_base.sh | 24 ++- .../pieces/bootstrappers/rpm_python3.sh | 7 + .../pieces/bootstrappers/rpm_python3_legacy.sh | 75 -------- letsencrypt-auto-source/tests/centos6_tests.sh | 147 +++++--------- .../tests/oraclelinux6_tests.sh | 85 --------- tests/letstest/scripts/test_leauto_upgrades.sh | 2 - tests/letstest/scripts/test_sdists.sh | 13 -- tox.ini | 12 +- 16 files changed, 209 insertions(+), 582 deletions(-) create mode 100644 letsencrypt-auto-source/Dockerfile.centos6 delete mode 100644 letsencrypt-auto-source/Dockerfile.redhat6 delete mode 100644 letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh delete mode 100644 letsencrypt-auto-source/tests/oraclelinux6_tests.sh diff --git a/.travis.yml b/.travis.yml index a1172f086..22391c84f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -234,9 +234,6 @@ matrix: - sudo: required env: TOXENV=le_auto_centos6 services: docker - - sudo: required - env: TOXENV=le_auto_oraclelinux6 - services: docker <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b6eaaa9..6c18de0d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 0.40.1 - master + +### Changed + +* Added back support for Python 3.4 to Certbot components and certbot-auto due + to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. + +More details about these changes can be found on our GitHub repo. + ## 0.40.0 - 2019-11-05 ### Added diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index e68ebd765..7439712b0 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -48,10 +48,3 @@ class _TLSSNI01DeprecationModule(object): def __dir__(self): # pragma: no cover return ['_module'] + dir(self._module) - -if sys.version_info[:2] == (3, 4): - warnings.warn( - "Python 3.4 support will be dropped in the next release of " - "acme. Please upgrade your Python version.", - PendingDeprecationWarning, - ) # pragma: no cover diff --git a/certbot/main.py b/certbot/main.py index 30d0f1d94..fc91aca5f 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1368,10 +1368,6 @@ def main(cli_args=None): if config.func != plugins_cmd: raise - if sys.version_info[:2] == (3, 4): - logger.warning("Python 3.4 support will be dropped in the next release " - "of Certbot - please upgrade your Python version.") - set_displayer(config) # Reporter diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 new file mode 100644 index 000000000..09aa52dcd --- /dev/null +++ b/letsencrypt-auto-source/Dockerfile.centos6 @@ -0,0 +1,37 @@ +# For running tests, build a docker image with a passwordless sudo and a trust +# store we can manipulate. + +FROM centos:6 + +RUN yum install -y epel-release + +# Install pip and sudo: +RUN yum install -y python-pip sudo +# Update to a stable and tested version of pip. +# We do not use pipstrap here because it no longer supports Python 2.6. +RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 +# Pin pytest version for increased stability +RUN pip install pytest==3.2.5 six==1.10.0 + +# Add an unprivileged user: +RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea + +# Let that user sudo: +RUN sed -i.bkp -e \ + 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ + /etc/sudoers + +RUN mkdir -p /home/lea/certbot + +# Install fake testing CA: +COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ +RUN update-ca-trust + +# Copy code: +COPY . /home/lea/certbot/letsencrypt-auto-source + +USER lea +WORKDIR /home/lea + +RUN sudo chmod +x certbot/letsencrypt-auto-source/tests/centos6_tests.sh +CMD sudo certbot/letsencrypt-auto-source/tests/centos6_tests.sh diff --git a/letsencrypt-auto-source/Dockerfile.redhat6 b/letsencrypt-auto-source/Dockerfile.redhat6 deleted file mode 100644 index d5cdc0458..000000000 --- a/letsencrypt-auto-source/Dockerfile.redhat6 +++ /dev/null @@ -1,48 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -ARG REDHAT_DIST_FLAVOR -FROM ${REDHAT_DIST_FLAVOR}:6 - -ARG REDHAT_DIST_FLAVOR - -RUN curl -O https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm \ - && rpm -ivh epel-release-latest-6.noarch.rpm - -# Install pip and sudo: -RUN yum install -y python-pip sudo -# Update to a stable and tested version of pip. -# We do not use pipstrap here because it no longer supports Python 2.6. -RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ - /etc/sudoers - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-trust - -# Copy current letsencrypt-auto: -COPY . /home/lea/certbot/letsencrypt-auto-source - -# Fetch previous letsencrypt-auto that was installing python 3.4 -RUN curl https://raw.githubusercontent.com/certbot/certbot/v0.38.0/letsencrypt-auto-source/letsencrypt-auto \ - -o /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 \ - && chmod +x /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 - -RUN cp /home/lea/certbot/letsencrypt-auto-source/tests/${REDHAT_DIST_FLAVOR}6_tests.sh /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh \ - && chmod +x /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh - -USER lea -WORKDIR /home/lea - -CMD ["sudo", "certbot/letsencrypt-auto-source/tests/redhat6_tests.sh"] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 5df7f5f30..b2ab0e5dd 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -376,9 +368,7 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -398,6 +388,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { @@ -478,88 +488,13 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} - # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -570,6 +505,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -828,50 +769,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -1156,15 +1078,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1175,21 +1090,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index bc4b92092..31c5bb134 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -306,7 +298,6 @@ DeterminePythonVersion() { {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common_base.sh }} {{ bootstrappers/rpm_common.sh }} -{{ bootstrappers/rpm_python3_legacy.sh }} {{ bootstrappers/rpm_python3.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} @@ -353,50 +344,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -573,15 +545,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -592,21 +557,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh index 2b00b199b..326ad8b3f 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh @@ -3,9 +3,7 @@ # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -25,6 +23,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index ac0553db5..f33b07ca9 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -4,6 +4,7 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -14,6 +15,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh deleted file mode 100644 index 0935c1b94..000000000 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh +++ /dev/null @@ -1,75 +0,0 @@ -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index c15cd80ec..2c6dcf734 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,140 +1,81 @@ #!/bin/bash # Start by making sure your system is up-to-date: -yum update -y >/dev/null -yum install -y centos-release-scl >/dev/null -yum install -y python27 >/dev/null 2>/dev/null +yum update -y > /dev/null +yum install -y centos-release-scl > /dev/null +yum install -y python27 > /dev/null 2> /dev/null -LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" -# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users -INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" - # we're going to modify env variables, so do this in a subshell ( - . scl_source enable python27 +source /opt/rh/python27/enable - # ensure python 3 isn't installed - python3 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -eq 0 ]; then - echo "ERROR: Python3 is already installed." - exit 1 - fi +# ensure python 3 isn't installed +python3 --version 2> /dev/null +RESULT=$? +if [ $RESULT -eq 0 ]; then + error "Python3 is already installed." + exit 1 +fi - # ensure python2.7 is available - python2.7 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -ne 0 ]; then - echo "ERROR: Python2.7 is not available." - exit 1 - fi +# ensure python2.7 is available +python2.7 --version 2> /dev/null +RESULT=$? +if [ $RESULT -ne 0 ]; then + error "Python3 is not available." + exit 1 +fi - # bootstrap, but don't install python 3. - "$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null +# bootstrap, but don't install python 3. +"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null - # ensure python 3 isn't installed - python3 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -eq 0 ]; then - echo "ERROR: letsencrypt-auto installed Python3 even though Python2.7 is present." - exit 1 - fi +# ensure python 3 isn't installed +python3 --version 2> /dev/null +RESULT=$? +if [ $RESULT -eq 0 ]; then + error "letsencrypt-auto installed Python3 even though Python2.7 is present." + exit 1 +fi - echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." +echo "" +echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." ) # ensure python2.7 isn't available -python2.7 --version >/dev/null 2>/dev/null +python2.7 --version 2> /dev/null RESULT=$? if [ $RESULT -eq 0 ]; then - error "ERROR: Python2.7 is still available." + error "Python2.7 is still available." exit 1 fi # Skip self upgrade due to Python 3 not being available. if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then - echo "ERROR: Python upgrade failure warning not printed!" + echo "Python upgrade failure warning not printed!" exit 1 fi -# bootstrap from the old letsencrypt-auto, this time installing python3.4 -"$LE_AUTO_PY_34" --no-self-upgrade -n >/dev/null 2>/dev/null +# bootstrap, this time installing python3 +"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null -# ensure python 3.4 is installed -python3.4 --version >/dev/null 2>/dev/null +# ensure python 3 is installed +python3 --version > /dev/null RESULT=$? if [ $RESULT -ne 0 ]; then - echo "ERROR: letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - exit 1 -fi - -echo "PASSED: Successfully upgraded to Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - -# As "certbot-auto" (so without implicit --non-interactive flag set), check that the script -# refuses to install SCL Python 3.6 when run in a non interactive shell (simulated here -# using | tee /dev/null) if --non-interactive flag is not provided. -cp "$LE_AUTO" /tmp/certbot-auto -# NB: Readline has an issue on all Python versions for CentOS 6, making `certbot --version` -# output an unprintable ASCII character on a new line at the end. -# So we take the second last line of the output. -version=$(/tmp/certbot-auto --version 2>/dev/null | tee /dev/null | tail -2 | head -1) - -if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then - echo "ERROR: certbot-auto upgraded certbot in a non-interactive shell with --non-interactive flag not set." + error "letsencrypt-auto failed to install Python3 when only Python2.6 is present." exit 1 fi -echo "PASSED: certbot-auto did not upgrade certbot in a non-interactive shell with --non-interactive flag not set." +echo "PASSED: Successfully upgraded to Python3 when only Python2.6 is present." +echo "" -if [ -f /opt/rh/rh-python36/enable ]; then - echo "ERROR: certbot-auto installed Python3.6 in a non-interactive shell with --non-interactive flag not set." +export VENV_PATH=$(mktemp -d) +"$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 +if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; then + echo "Python 3 wasn't used with --no-bootstrap!" exit 1 fi - -echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell with --non-interactive flag not set." - -# now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL -"$LE_AUTO" --no-self-upgrade -n >/dev/null 2>/dev/null - -# Following test is exectued in a subshell, to not leak any environment variable -( - # enable SCL rh-python36 - . scl_source enable rh-python36 - - # ensure python 3.6 is installed - python3.6 --version >/dev/null 2>/dev/null - RESULT=$? - if [ $RESULT -ne 0 ]; then - echo "ERROR: letsencrypt-auto failed to install Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." - exit 1 - fi - - echo "PASSED: Successfully upgraded to Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." -) - -# Following test is exectued in a subshell, to not leak any environment variable -( - export VENV_PATH=$(mktemp -d) - "$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 - if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then - echo "ERROR: Python 3.6 wasn't used with --no-bootstrap!" - exit 1 - fi -) - -# Following test is exectued in a subshell, to not leak any environment variable -( - # enable SCL rh-python36 - . scl_source enable rh-python36 - - # ensure everything works fine with certbot-auto bootstrap when python 3.6 is already enabled - export VENV_PATH=$(mktemp -d) - if ! "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null; then - echo "ERROR: Certbot-auto broke when Python 3.6 SCL is already enabled." - exit 1 - fi -) +unset VENV_PATH # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh deleted file mode 100644 index f3fd952f3..000000000 --- a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash -set -eo pipefail -# Start by making sure your system is up-to-date: -yum update -y >/dev/null - -LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" -LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" - -# Apply installation instructions from official documentation: -# https://certbot.eff.org/lets-encrypt/centosrhel6-other -cp "$LE_AUTO" /usr/local/bin/certbot-auto -chown root /usr/local/bin/certbot-auto -chmod 0755 /usr/local/bin/certbot-auto -LE_AUTO=/usr/local/bin/certbot-auto - -# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users -INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" - -# Check bootstrap from current certbot-auto will fail, because SCL is not enabled. -set +o pipefail -if ! "$LE_AUTO" -n 2>&1 | grep -q "Enable the SCL repository and try running Certbot again."; then - echo "ERROR: Bootstrap was not aborted although SCL was not installed!" - exit 1 -fi -set -o pipefail - -echo "PASSED: Bootstrap was aborted since SCL was not installed." - -# Bootstrap from the old letsencrypt-auto, Python 3.4 will be installed from EPEL. -"$LE_AUTO_PY_34" --no-self-upgrade -n --install-only >/dev/null 2>/dev/null - -# Ensure Python 3.4 is installed -if ! command -v python3.4 &>/dev/null; then - echo "ERROR: old letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." - exit 1 -fi - -echo "PASSED: Bootstrap from old letsencrypt-auto succeeded and installed Python 3.4" - -# Expect certbot-auto to skip rebootstrapping with a warning since SCL is not installed. -if ! "$LE_AUTO" --non-interactive --version 2>&1 | grep -q "This requires manual user intervention"; then - echo "FAILED: Script certbot-auto did not print a warning about needing manual intervention!" - exit 1 -fi - -echo "PASSED: Script certbot-auto did not rebootstrap." - -# NB: Readline has an issue on all Python versions for OL 6, making `certbot --version` -# output an unprintable ASCII character on a new line at the end. -# So we take the second last line of the output. -version=$($LE_AUTO --version 2>/dev/null | tail -2 | head -1) - -if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then - echo "ERROR: Script certbot-auto upgraded certbot in a non-interactive shell while SCL was not enabled." - exit 1 -fi - -echo "PASSED: Script certbot-auto did not upgrade certbot but started it successfully while SCL was not enabled." - -# Enable SCL -yum install -y oracle-softwarecollection-release-el6 >/dev/null - -# Expect certbot-auto to bootstrap successfully since SCL is available. -"$LE_AUTO" -n --version &>/dev/null - -if [ "$(/opt/eff.org/certbot/venv/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then - echo "ERROR: Script certbot-auto failed to bootstrap and install Python 3.6 while SCL is available." - exit 1 -fi - -if ! /opt/eff.org/certbot/venv/bin/certbot --version > /dev/null 2> /dev/null; then - echo "ERROR: Script certbot-auto did not install certbot correctly while SCL is enabled." - exit 1 -fi - -echo "PASSED: Script certbot-auto correctly bootstraped Certbot using rh-python36 when SCL is available." - -# Expect certbot-auto will be totally silent now that everything has been correctly boostraped. -OUTPUT_LEN=$("$LE_AUTO" --install-only --no-self-upgrade --quiet 2>&1 | wc -c) -if [ "$OUTPUT_LEN" != 0 ]; then - echo certbot-auto produced unexpected output! - exit 1 -fi - -echo "PASSED: Script certbot-auto did not print anything in quiet mode." diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index fc7632793..541f54f6b 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -117,8 +117,6 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then fi if [ "$RUN_RHEL6_TESTS" = 1 ]; then - # Add the SCL python release to PATH in order to resolve python3 command - PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index c8ce9fe7f..347589e04 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,21 +1,8 @@ #!/bin/sh -xe cd letsencrypt - -# If we're on a RHEL 6 based system, we can be confident Python is already -# installed because the package manager is written in Python. -if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then - # RHEL/CentOS 6 will need a special treatment, so we need to detect that environment - RUN_RHEL6_TESTS=1 -fi - letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug -if [ "$RUN_RHEL6_TESTS" = 1 ]; then - # Enable the SCL Python 3.6 installed by letsencrypt-auto bootstrap - PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" -fi - PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) diff --git a/tox.ini b/tox.ini index 952204a9e..04715cc2f 100644 --- a/tox.ini +++ b/tox.ini @@ -206,17 +206,7 @@ passenv = DOCKER_* # At the moment, this tests under Python 2.6 only, as only that version is # readily available on the CentOS 6 Docker image. commands = - docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=centos -t lea letsencrypt-auto-source - docker run --rm -t -i lea -whitelist_externals = - docker -passenv = DOCKER_* - -[testenv:le_auto_oraclelinux6] -# At the moment, this tests under Python 2.6 only, as only that version is -# readily available on the Oracle Linux 6 Docker image. -commands = - docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=oraclelinux -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.centos6 -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker -- cgit v1.2.3 From 5f6ab47a7bbb3d3c1add2e1afb701d87f2355850 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 18:24:52 -0800 Subject: Update changelog for 0.40.1 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c18de0d7..2cbe49f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.40.1 - master +## 0.40.1 - 2019-11-05 ### Changed -- cgit v1.2.3 From b79bcd0bf233d6886ba14c7bc1e20b6e2b9d0221 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 18:32:20 -0800 Subject: Release 0.40.1 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 238 +++++++-------------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- docs/cli-help.txt | 2 +- letsencrypt-auto | 238 +++++++-------------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 26 files changed, 197 insertions(+), 385 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index ee2d225e8..3ac2271a8 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.40.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 9d22bacff..e4fa92a3c 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 5df7f5f30..9f3d2af08 100755 --- a/certbot-auto +++ b/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.40.0" +LE_AUTO_VERSION="0.40.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -376,9 +368,7 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -398,6 +388,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { @@ -478,88 +488,13 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} - # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -570,6 +505,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -828,50 +769,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -1156,15 +1078,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1175,21 +1090,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1432,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.0 \ - --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ - --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 -acme==0.40.0 \ - --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ - --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 -certbot-apache==0.40.0 \ - --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ - --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e -certbot-nginx==0.40.0 \ - --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ - --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 +certbot==0.40.1 \ + --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ + --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 +acme==0.40.1 \ + --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ + --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc +certbot-apache==0.40.1 \ + --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ + --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c +certbot-nginx==0.40.1 \ + --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ + --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index e7f4880f1..063f9d1aa 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 1855428d2..f56169196 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index e642d406a..3e677bfce 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index c6fbc9a0c..561e8b72b 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index a62afa912..88bc0f243 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index cf35a0427..84577d85e 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 06399cace..b7fa4bfb6 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 37903034d..a2f636d77 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 11c0d577f..b9e025a6c 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 2bf62d4a0..692fae164 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 7ed20ad8a..4ec01e8d6 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 849cbc548..a18ee280e 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d03c7cc0c..0daf434f0 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 0f097f977..c1974c470 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 9d2b0f901..fe999b996 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.0' +version = '0.40.1' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index b53e16659..cc2b4317d 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.0' +version = '0.40.1' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index ca79e552f..d3ca008a0 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.40.0' +__version__ = '0.40.1' diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 37539a24b..6a608ba09 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,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.40.0 + "". (default: CertbotACMEClient/0.40.1 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the diff --git a/letsencrypt-auto b/letsencrypt-auto index 5df7f5f30..9f3d2af08 100755 --- a/letsencrypt-auto +++ b/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.40.0" +LE_AUTO_VERSION="0.40.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,28 +256,20 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_2_VERSION="2.7" -MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') -MIN_PYTHON_3_VERSION="3.5" -MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +MIN_PYTHON_VERSION="2.7" +MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version. -# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their -# values depend on if we try to use Python 3 or Python 2. +# digits of the python version DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then - MIN_PYVER=$MIN_PYVER3 - MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else - MIN_PYVER=$MIN_PYVER2 - MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -293,7 +285,7 @@ DeterminePythonVersion() { fi fi - PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -376,9 +368,7 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Note: this function is called both while selecting the bootstrap scripts and -# during the actual bootstrap. Some things like prompting to user can be done in the latter -# case, but not in the former one. +# Enables EPEL if applicable and possible. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -398,6 +388,26 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi + + if ! $TOOL list *virtualenv >/dev/null 2>&1; then + echo "To use Certbot, packages from the EPEL repository need to be installed." + if ! $TOOL list epel-release >/dev/null 2>&1; then + error "Enable the EPEL repository and try running Certbot again." + exit 1 + fi + if [ "$ASSUME_YES" = 1 ]; then + /bin/echo -n "Enabling the EPEL repository in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." + sleep 1s + fi + if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then + error "Could not enable EPEL. Aborting bootstrap!" + exit 1 + fi + fi } BootstrapRpmCommonBase() { @@ -478,88 +488,13 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } -# If new packages are installed by BootstrapRpmPython3 below, this version -# number must be increased. -BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 - -# Checks if rh-python36 can be installed. -Python36SclIsAvailable() { - InitializeRPMCommonBase >/dev/null 2>&1; - - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - return 0 - fi - if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Try to enable rh-python36 from SCL if it is necessary and possible. -EnablePython36SCL() { - if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then - return 0 - fi - if ! scl --list 2>/dev/null | grep -q rh-python36; then - return 0 - fi - set +e - . scl_source enable rh-python36 - set -e -} - -# This bootstrap concerns old RedHat-based distributions that do not ship by default -# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing -# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. -BootstrapRpmPython3Legacy() { - # Tested with: - # - CentOS 6 - - InitializeRPMCommonBase - - if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then - echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." - if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then - error "Enable the SCL repository and try running Certbot again." - exit 1 - fi - if [ "${ASSUME_YES}" = 1 ]; then - /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" - sleep 1s - /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" - sleep 1s - fi - if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then - error "Could not enable SCL. Aborting bootstrap!" - exit 1 - fi - fi - - # CentOS 6 must use rh-python36 from SCL - if "${TOOL}" list rh-python36 >/dev/null 2>&1; then - python_pkgs="rh-python36-python - rh-python36-python-virtualenv - rh-python36-python-devel - " - else - error "No supported Python package available to install. Aborting bootstrap!" - exit 1 - fi - - BootstrapRpmCommonBase "${python_pkgs}" - - # Enable SCL rh-python36 after bootstrapping. - EnablePython36SCL -} - # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: + # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -570,6 +505,12 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " + # EPEL uses python34 + elif $TOOL list python34 >/dev/null 2>&1; then + python_pkgs="python34 + python34-devel + python34-tools + " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -828,50 +769,31 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Handle legacy RPM distributions - if [ "$PYVER" -eq 26 ]; then - # Check if an automated bootstrap can be achieved on this system. - if ! Python36SclIsAvailable; then - INTERACTIVE_BOOTSTRAP=1 - fi + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "Legacy RedHat-based OSes that will use Python3" - BootstrapRpmPython3Legacy + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" - - # Try now to enable SCL rh-python36 for systems already bootstrapped - # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto - EnablePython36SCL + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" else - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi - - if [ "$RPM_USE_PYTHON_3" = 1 ]; then - Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 - } - USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" - else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" - fi + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi LE_PYTHON="$prev_le_python" @@ -1156,15 +1078,8 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # Check if we can rebootstrap without manual user intervention: this requires that - # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to - # require a manual user intervention. - if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then - CAN_REBOOTSTRAP=1 - fi - # Check if rebootstrap can be done non-interactively and current shell is non-interactive - # (true if stdin and stdout are not attached to a terminal). - if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # if non-interactive mode or stdin and stdout are connected to a terminal + if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1175,21 +1090,12 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" - # Otherwise bootstrap needs to be done manually by the user. else - # If it is because bootstrapping is interactive, --non-interactive will be of no use. - if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then - error "Skipping upgrade because new OS dependencies may need to be installed." - error "This requires manual user intervention: please run this script again manually." - # If this is because of the environment (eg. non interactive shell without - # --non-interactive flag set), help the user in that direction. - else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." - fi + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1432,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.0 \ - --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ - --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 -acme==0.40.0 \ - --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ - --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 -certbot-apache==0.40.0 \ - --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ - --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e -certbot-nginx==0.40.0 \ - --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ - --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 +certbot==0.40.1 \ + --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ + --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 +acme==0.40.1 \ + --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ + --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc +certbot-apache==0.40.1 \ + --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ + --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c +certbot-nginx==0.40.1 \ + --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ + --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 3bdb8a93c..2c7986ea9 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3B4KcACgkQTRfJlc2X -dfJKHAf+PUViUdwbaXUMNfDRo7g6v44RA0RIj+SG3cjLsX2E/A2G70KndfUC/9KS -cgYpFZ3h/2y3fLLsYgDIOPRhAKLgrk+LFKrtDsUbOLF7K3eS70KQmDxYFXNzw0jc -34zhc9BKsKrqX6a80LprkVtbEuRlE58JaXyqjMW8NvGvLXNV8qCZK8xG8SrCkVnU -KFlXgHAl3UFibm3yJOlIjHikuOaU0jlDbO/S2WfkkgV3BWQkngUKu+9gr+ItV3We -GMidJljIoho8CqYQnLWtsjhOmjLQogsUKZJSg/riAxrDW3cCEmF4EaV/S8lNnSiL -f49WauHsGdfIaFabl8HVG7h+R3Uh8Q== -=JXq9 +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3CMH0ACgkQTRfJlc2X +dfJ4DwgAljOxkQ4uhiF1R8Mw3r5+lwSezh7seA01QVUcuLArZM8B+IJM0FbqrSca +ToYCrUXXTVL6aPn/1yxuNMMmVlJbIl5tMc3tfm//lbXTpLkiVASZl5+3HKtR5o6w +GP2v6apjvkM1oaT2jL0VKcMGheVYaLfUQ3MGhMHbgOZKALNYwMGP9WOwgn0GGa1l +3SIYJOqgEjDQTSr1tK+Ki+UkuoMsL4HamfvgNWeXbTpChdjth46UTj+N60rafIA2 +HAkXoP4eav6XbDlzDulG2cTBtnxvr6yiK76YF68P7BHNNYWbybFW8k6TVwexGgDF +2di01Av0nXsa4O8QkYdtVQcCgniaUw== +=E9YT -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index b2ab0e5dd..9f3d2af08 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.40.0" +LE_AUTO_VERSION="0.40.1" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1338,18 +1338,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.0 \ - --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ - --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 -acme==0.40.0 \ - --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ - --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 -certbot-apache==0.40.0 \ - --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ - --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e -certbot-nginx==0.40.0 \ - --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ - --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 +certbot==0.40.1 \ + --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ + --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 +acme==0.40.1 \ + --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ + --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc +certbot-apache==0.40.1 \ + --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ + --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c +certbot-nginx==0.40.1 \ + --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ + --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index b961bbc91..ce698e84d 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 415663f96..5029feb9d 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.40.0 \ - --hash=sha256:b9ff74c4f3d3e06d9c467465f97bcbb07b0f4d778d3c4232ab91583d933dba61 \ - --hash=sha256:cff166597b3c714c3e7e60b2bcd6089135b375cadca04cf36abd15bfdb22be40 -acme==0.40.0 \ - --hash=sha256:1b026b07a2099e50dac11cbdb834925f1d9b5691e349b52e9d397a12f3dc4eac \ - --hash=sha256:f29c1185d1e33919bad6c1f3fece168ee191d96d47f5997117561dc74a454221 -certbot-apache==0.40.0 \ - --hash=sha256:f1c034a05fbd6cc6fde9494f493a8a6ed0e02e7652e51af16342082bc17387e4 \ - --hash=sha256:43c3d7628ca6630467c4f57dd30423f031c1c7cbca46f7500293172d0fe3581e -certbot-nginx==0.40.0 \ - --hash=sha256:55cd3c90e2851069b536859050374fe2fcfa22c3e862cc0e1811fbce9e52dccc \ - --hash=sha256:3df8cec22910f2d41ccb4494661ff65f98c52dd441864a53a318b32979256881 +certbot==0.40.1 \ + --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ + --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 +acme==0.40.1 \ + --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ + --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc +certbot-apache==0.40.1 \ + --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ + --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c +certbot-nginx==0.40.1 \ + --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ + --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f -- cgit v1.2.3 From 1b76faada67d178e363c01a0bb17e75935a8a93d Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 18:32:22 -0800 Subject: Add contents to CHANGELOG.md for next version --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cbe49f90..331f1727f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.0.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 0.40.1 - 2019-11-05 ### Changed -- cgit v1.2.3 From 61f77c35c06fc27b0df3497576b1d2d9b630d6a5 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 5 Nov 2019 18:32:22 -0800 Subject: Bump version to 1.0.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 3ac2271a8..6d3dcdf84 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.40.1' +version = '1.0.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index e4fa92a3c..5895ec810 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 063f9d1aa..40cde4352 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index f56169196..6b72d0a76 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 3e677bfce..4c58d56d0 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 561e8b72b..4dabc207f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 88bc0f243..20ed5457d 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -3,7 +3,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 84577d85e..85dcaafa3 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index b7fa4bfb6..f020354dc 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a2f636d77..61d365189 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index b9e025a6c..807e9b889 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 692fae164..3ee8fb2fe 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 4ec01e8d6..2cc44406f 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index a18ee280e..b3abce754 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0daf434f0..0da7cc98a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index c1974c470..548a567b4 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index fe999b996..7c5bfe7f3 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.40.1' +version = '1.0.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index cc2b4317d..f3f0b6640 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '0.40.1' +version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index d3ca008a0..30b52be1a 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.40.1' +__version__ = '1.0.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 9f3d2af08..386d19531 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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.40.1" +LE_AUTO_VERSION="1.0.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From baf43a2dbc6ab105ba69165a9e66ac8175ca41cd Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 6 Nov 2019 19:17:53 +0100 Subject: Pin all build dependencies for the Windows installer (#7504) This PR uses pipstrap to bootstrap the venv used to build Windows installers. This effectively pin all build dependencies, since pynsist is already installed through pip_install.py script. * Use pipstrap * Pin also NSIS version --- windows-installer/construct.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 94ce1fa4d..cdf309f13 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -12,6 +12,7 @@ import time PYTHON_VERSION = (3, 7, 4) PYTHON_BITNESS = 32 PYWIN32_VERSION = 225 # do not forget to edit pywin32 dependency accordingly in setup.py +NSIS_VERSION = '3.04' def main(): @@ -53,9 +54,9 @@ def _compile_wheels(repo_path, build_path, venv_python): def _prepare_build_tools(venv_path, venv_python, repo_path): print('Prepare build tools') subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) - subprocess.check_call(['choco', 'upgrade', '-y', 'nsis']) - subprocess.check_call([venv_python, '-m', 'pip', 'install', '--upgrade', 'pip']) - subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'wheel', 'pynsist']) + subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')]) + subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist']) + subprocess.check_call(['choco', 'upgrade', '-y', 'nsis', '--version', NSIS_VERSION]) @contextlib.contextmanager -- cgit v1.2.3 From 2b4c2a7f5560bfef6b331f72a26e3e7482c29cde Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Nov 2019 10:45:17 -0800 Subject: Match our Travis logic in Azure. (#7514) In Travis, the full test suite doesn't run on PRs for point release branches, just on commits for them. I think this behavior makes sense because what we actually want to test before a point release is the exact commit we want to release after any squashing/merging has been done. This PR modifies Azure to match this behavior. After this PR lands, I need to update the tests required to pass on GitHub. --- .azure-pipelines/advanced.yml | 1 - .azure-pipelines/main.yml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index fe24a9ecb..44cdf5d54 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -4,7 +4,6 @@ trigger: - '*.x' pr: - test-* - - '*.x' # This pipeline is also nightly run on master schedules: - cron: "0 4 * * *" diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml index be9eaf0b0..d9609037e 100644 --- a/.azure-pipelines/main.yml +++ b/.azure-pipelines/main.yml @@ -6,6 +6,7 @@ trigger: pr: - apache-parser-v2 - master + - '*.x' jobs: - template: templates/tests-suite.yml -- cgit v1.2.3 From 1dff022d05fc05f2e1f6e0fb3d8782e8deb4bd81 Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Wed, 6 Nov 2019 21:29:07 +0200 Subject: Deprecate config_changes (#7469) Closes #7454 * Deprecate config_changes * Error on config_changes * Fix tests for main.py * Fix CHANGELOG entry * Remove remnants of config_changes * Fix CHANGELOG and add removed functions --- CHANGELOG.md | 5 ++++ certbot/cli.py | 8 +----- certbot/client.py | 15 ----------- certbot/main.py | 20 -------------- certbot/plugins/common.py | 20 -------------- certbot/plugins/common_test.py | 17 ------------ certbot/reverter.py | 61 ------------------------------------------ certbot/tests/cli_test.py | 2 +- certbot/tests/main_test.py | 11 -------- certbot/tests/reverter_test.py | 25 ----------------- docs/cli-help.txt | 3 --- pytest.ini | 2 -- 12 files changed, 7 insertions(+), 182 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 806905564..cef8789a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Added back support for Python 3.4 to Certbot components and certbot-auto due to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. +* Certbot's `config_changes` subcommand has been removed +* The functions `certbot.client.view_config_changes`, + `certbot.main.config_changes`, + `certbot.plugins.common.Installer.view_config_changes`, and + `certbot.reverter.Reverter.view_config_changes` have been removed ### Fixed diff --git a/certbot/cli.py b/certbot/cli.py index 97a0abbfb..9b116fe3f 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -115,7 +115,7 @@ More detailed help: all, automation, commands, paths, security, testing, or any of the subcommands or plugins (certonly, renew, install, register, nginx, apache, standalone, webroot, etc.) - -h all print a detailed help page including all topics + -h all print a detailed help page including all topics --version print the version number """ @@ -398,11 +398,6 @@ VERB_HELP = [ "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " " --key-path /path/to/private-key [options]\n\n" }), - ("config_changes", { - "short": "Show changes that Certbot has made to server configurations", - "opts": "Options for viewing configuration changes", - "usage": "\n\n certbot config_changes [options]\n\n" - }), ("rollback", { "short": "Roll back server conf changes made during certificate installation", "opts": "Options for rolling back server configuration changes", @@ -447,7 +442,6 @@ class HelpfulArgumentParser(object): self.VERBS = { "auth": main.certonly, "certonly": main.certonly, - "config_changes": main.config_changes, "run": main.run, "install": main.install, "plugins": main.plugins_cmd, diff --git a/certbot/client.py b/certbot/client.py index 1800a301c..91b54b205 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -27,7 +27,6 @@ from certbot import eff from certbot import error_handler from certbot import errors from certbot import interfaces -from certbot import reverter from certbot import storage from certbot import util from certbot.compat import os @@ -710,20 +709,6 @@ def rollback(default_installer, checkpoints, config, plugins): installer.rollback_checkpoints(checkpoints) installer.restart() - -def view_config_changes(config): - """View checkpoints and associated configuration changes. - - .. note:: This assumes that the installation is using a Reverter object. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - """ - rev = reverter.Reverter(config) - rev.recovery_routine() - rev.view_config_changes() - def _open_pem_file(cli_arg_path, pem_path): """Open a pem file. diff --git a/certbot/main.py b/certbot/main.py index fc91aca5f..620e33be9 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -961,26 +961,6 @@ def rollback(config, plugins): """ client.rollback(config.installer, config.checkpoints, config, plugins) - -def config_changes(config, unused_plugins): - """Show changes made to server config during installation - - View checkpoints and associated configuration changes. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - logger.warning("The config_changes subcommand has been deprecated" - " and will be removed in a future release.") - client.view_config_changes(config) - def update_symlinks(config, unused_plugins): """Update the certificate file family symlinks diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 50b07ad99..8123f0027 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -192,26 +192,6 @@ class Installer(Plugin): except errors.ReverterError as err: raise errors.PluginError(str(err)) - def view_config_changes(self): - """Show all of the configuration changes that have taken place. - - :raises .errors.PluginError: If there is a problem while processing - the checkpoints directories. - - """ - warnings.warn( - "The view_config_changes method is no longer part of Certbot's" - " plugin interface, has been deprecated, and will be removed in a" - " future release.", DeprecationWarning, stacklevel=2) - with warnings.catch_warnings(): - # Don't let the reverter code warn about this function. Calling - # this function in the first place is the real problem. - warnings.filterwarnings("ignore", ".*view_config_changes", DeprecationWarning) - try: - self.reverter.view_config_changes() - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - @property def ssl_dhparams(self): """Full absolute path to ssl_dhparams file.""" diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 80df666a5..94f7dd0be 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -3,7 +3,6 @@ import functools import shutil import tempfile import unittest -import warnings import OpenSSL import josepy as jose @@ -140,22 +139,6 @@ class InstallerTest(test_util.ConfigTestCase): def test_rollback_checkpoints(self): self._test_wrapped_method("rollback_checkpoints", 42) - def test_view_config_changes(self): - self._test_wrapped_method("view_config_changes") - - def test_view_config_changes_warning_supression(self): - with warnings.catch_warnings(): - # Without the catch_warnings() code in - # common.Installer.view_config_changes, this would raise an - # exception. The module parameter here is ".*common$" because the - # stacklevel=2 parameter of warnings.warn causes the warning to - # refer to the code in the caller rather than the call to - # warnings.warn. This means the warning in common.Installer refers - # to this module and the warning in the reverter refers to the - # plugins.common module. - warnings.filterwarnings("error", ".*view_config_changes", module=".*common$") - self.installer.view_config_changes() - def _test_wrapped_method(self, name, *args, **kwargs): """Test a wrapped reverter method. diff --git a/certbot/reverter.py b/certbot/reverter.py index f4f1c8c67..ac2804164 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -6,14 +6,11 @@ import shutil import sys import time import traceback -import warnings import six -import zope.component from certbot import constants from certbot import errors -from certbot import interfaces from certbot import util from certbot.compat import os from certbot.compat import filesystem @@ -131,64 +128,6 @@ class Reverter(object): "Unable to load checkpoint during rollback") rollback -= 1 - def view_config_changes(self): - """Displays all saved checkpoints. - - All checkpoints are printed by - :meth:`certbot.interfaces.IDisplay.notification`. - - .. todo:: Decide on a policy for error handling, OSError IOError... - - :raises .errors.ReverterError: If invalid directory structure. - - """ - warnings.warn( - "The view_config_changes method has been deprecated and will be" - " removed in a future release. If you were using this method to" - " implement the view_config_changes method of IInstaller, know that" - " that method has been removed from the plugin interface and is no" - " longer used by Certbot.", DeprecationWarning, stacklevel=2) - backups = os.listdir(self.config.backup_dir) - backups.sort(reverse=True) - if not backups: - logger.info("Certbot has not saved backups of your configuration") - - return None - # Make sure there isn't anything unexpected in the backup folder - # There should only be timestamped (float) directories - try: - for bkup in backups: - float(bkup) - except ValueError: - raise errors.ReverterError( - "Invalid directories in {0}".format(self.config.backup_dir)) - - output = [] - for bkup in backups: - output.append(time.ctime(float(bkup))) - cur_dir = os.path.join(self.config.backup_dir, bkup) - with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd: - output.append(changes_fd.read()) - - output.append("Affected files:") - with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd: - filepaths = paths_fd.read().splitlines() - for path in filepaths: - output.append(" {0}".format(path)) - - if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")): - with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd: - output.append("New Configuration Files:") - filepaths = new_fd.read().splitlines() - for path in filepaths: - output.append(" {0}".format(path)) - - output.append('\n') - - zope.component.getUtility(interfaces.IDisplay).notification( - '\n'.join(output), force_interactive=True, pause=False) - return None - def add_to_temp_checkpoint(self, save_files, save_notes): """Add files to temporary checkpoint. diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 7f3830d72..7a2a7fabf 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -186,7 +186,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertTrue("--delete-after-revoke" in out) self.assertTrue("--no-delete-after-revoke" in out) - out = self._help_output(['-h', 'config_changes']) + out = self._help_output(['-h', 'register']) self.assertTrue("--cert-path" not in out) self.assertTrue("--key-path" not in out) diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index d8c7f4ab8..05266835f 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -753,17 +753,6 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met client.rollback.assert_called_once_with( mock.ANY, 123, mock.ANY, mock.ANY) - def test_config_changes(self): - _, _, _, client = self._call(['config_changes']) - self.assertEqual(1, client.view_config_changes.call_count) - - @mock.patch('certbot.main.logger.warning') - def test_config_changes_deprecation(self, mock_warning): - self._call(['config_changes']) - self.assertTrue(mock_warning.called) - msg = mock_warning.call_args[0][0] - self.assertIn("config_changes subcommand has been deprecated", msg) - @mock.patch('certbot.cert_manager.update_live_symlinks') def test_update_symlinks(self, mock_cert_manager): self._call_no_clientmock(['update_symlinks']) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 31b0ea754..3ba628475 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -10,7 +10,6 @@ import six from certbot import errors from certbot.compat import os -from certbot.compat import filesystem from certbot.tests import util as test_util @@ -377,30 +376,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertEqual(read_in(self.config2), "directive-dir2") self.assertFalse(os.path.isfile(config3)) - @test_util.patch_get_utility() - def test_view_config_changes(self, mock_output): - """This is not strict as this is subject to change.""" - self._setup_three_checkpoints() - - # Make sure it doesn't throw any errors - self.reverter.view_config_changes() - - # Make sure notification is output - self.assertEqual(mock_output().notification.call_count, 1) - - @mock.patch("certbot.reverter.logger") - def test_view_config_changes_no_backups(self, mock_logger): - self.reverter.view_config_changes() - self.assertTrue(mock_logger.info.call_count > 0) - - def test_view_config_changes_bad_backups_dir(self): - # There shouldn't be any "in progress directories when this is called - # It must just be clean checkpoints - filesystem.makedirs(os.path.join(self.config.backup_dir, "in_progress")) - - self.assertRaises( - errors.ReverterError, self.reverter.view_config_changes) - def _setup_three_checkpoints(self): """Generate some finalized checkpoints.""" # Checkpoint1 - config1 diff --git a/docs/cli-help.txt b/docs/cli-help.txt index 37539a24b..dfcd84ccd 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -392,9 +392,6 @@ unregister: install: Options for modifying how a certificate is deployed -config_changes: - Options for viewing configuration changes - rollback: Options for rolling back server configuration changes diff --git a/pytest.ini b/pytest.ini index 12b081b0e..54ae4e9d6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,12 +10,10 @@ # but it should be corrected to allow Certbot compatiblity with Python >= 3.8 # 4- ipdb uses deprecated functionality of IPython. See # https://github.com/gotcha/ipdb/issues/144. -# 5- ignore our own warnings about deprecated view_config_changes methods filterwarnings = error ignore:decodestring:DeprecationWarning ignore:(TLSSNI01|TLS-SNI-01):DeprecationWarning ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* - ignore:.*view_config_changes:DeprecationWarning ignore:.*get_systemd_os_info:DeprecationWarning -- cgit v1.2.3 From f4f16605eda7274b1390e01eba66c2266085f415 Mon Sep 17 00:00:00 2001 From: Shell Chen Date: Fri, 8 Nov 2019 01:37:12 +0800 Subject: dns-rfc2136: use TCP to query SOA records (#7503) * Use tcp query on dns-rfc2136 plugin To improve network robust; fixes #7502. * Update CHANGELOG.md * Fix dns-rfc2136 test cases * Add UDP fallback to dns-rfc2136 --- CHANGELOG.md | 1 + .../certbot_dns_rfc2136/dns_rfc2136.py | 6 +++++- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 20 +++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d30694873..b317b4069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ More details about these changes can be found on our GitHub repo. * acme.standalone.BaseRequestHandlerWithLogging and acme.standalone.simple_tls_sni_01_server have been deprecated and will be removed in a future release of the library. +* certbot-dns-rfc2136 now use TCP to query SOA records. ### Fixed diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py index 2061374e0..ee71c9681 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py @@ -206,7 +206,11 @@ class _RFC2136Client(object): request.flags ^= dns.flags.RD try: - response = dns.query.udp(request, self.server, port=self.port) + try: + response = dns.query.tcp(request, self.server, port=self.port) + except OSError as e: + logger.debug('TCP query failed, fallback to UDP: %s', e) + response = dns.query.udp(request, self.server, port=self.port) rcode = response.rcode() # Authoritative Answer bit should be set diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index d800f1ec7..1950ee62e 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -162,7 +162,7 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._find_domain, 'foo.bar.'+DOMAIN) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_found(self, query_mock): query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) query_mock.return_value.rcode.return_value = dns.rcode.NOERROR @@ -173,7 +173,7 @@ class RFC2136ClientTest(unittest.TestCase): query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) self.assertTrue(result) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_not_found(self, query_mock): query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN @@ -183,7 +183,7 @@ class RFC2136ClientTest(unittest.TestCase): query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) self.assertFalse(result) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_wraps_errors(self, query_mock): query_mock.side_effect = Exception @@ -193,6 +193,20 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._query_soa, DOMAIN) + @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") + def test_query_soa_fallback_to_udp(self, tcp_mock, udp_mock): + tcp_mock.side_effect = OSError + udp_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) + udp_mock.return_value.rcode.return_value = dns.rcode.NOERROR + + # _query_soa | pylint: disable=protected-access + result = self.rfc2136_client._query_soa(DOMAIN) + + tcp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + udp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue(result) + if __name__ == "__main__": unittest.main() # pragma: no cover -- cgit v1.2.3 From 4b488614cf7749c8139c11f0983fe4b71e29827f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 Nov 2019 06:11:09 -0800 Subject: Remove tls sni common (#7527) * fixes #7478 * add changelog entry --- CHANGELOG.md | 1 + certbot/plugins/common.py | 59 --------------------------------- certbot/plugins/common_test.py | 75 +++--------------------------------------- 3 files changed, 6 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b317b4069..03b4ec995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Certbot's `config_changes` subcommand has been removed +* `certbot.plugins.common.TLSSNI01` has been removed. * The functions `certbot.client.view_config_changes`, `certbot.main.config_changes`, `certbot.plugins.common.Installer.view_config_changes`, and diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index 8123f0027..fc56972b5 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -6,7 +6,6 @@ import sys import tempfile import warnings -import OpenSSL import pkg_resources import zope.interface @@ -20,7 +19,6 @@ from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import reverter -from certbot import util from certbot.compat import os from certbot.compat import filesystem from certbot.plugins.storage import PluginStorage @@ -349,63 +347,6 @@ class ChallengePerformer(object): raise NotImplementedError() -class TLSSNI01(ChallengePerformer): - # pylint: disable=abstract-method - """Abstract base for TLS-SNI-01 challenge performers""" - - def __init__(self, configurator): - super(TLSSNI01, self).__init__(configurator) - self.challenge_conf = os.path.join( - configurator.config.config_dir, "le_tls_sni_01_cert_challenge.conf") - # self.completed = 0 - - def get_cert_path(self, achall): - """Returns standardized name for challenge certificate. - - :param .KeyAuthorizationAnnotatedChallenge achall: Annotated - tls-sni-01 challenge. - - :returns: certificate file name - :rtype: str - - """ - return os.path.join(self.configurator.config.work_dir, - achall.chall.encode("token") + ".crt") - - def get_key_path(self, achall): - """Get standardized path to challenge key.""" - return os.path.join(self.configurator.config.work_dir, - achall.chall.encode("token") + '.pem') - - def get_z_domain(self, achall): - """Returns z_domain (SNI) name for the challenge.""" - return achall.response(achall.account_key).z_domain.decode("utf-8") - - def _setup_challenge_cert(self, achall, cert_key=None): - - """Generate and write out challenge certificate.""" - cert_path = self.get_cert_path(achall) - key_path = self.get_key_path(achall) - # Register the path before you write out the file - self.configurator.reverter.register_file_creation(True, key_path) - self.configurator.reverter.register_file_creation(True, cert_path) - - response, (cert, key) = achall.response_and_validation( - cert_key=cert_key) - cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - key_pem = OpenSSL.crypto.dump_privatekey( - OpenSSL.crypto.FILETYPE_PEM, key) - - # Write out challenge cert and key - with open(cert_path, "wb") as cert_chall_fd: - cert_chall_fd.write(cert_pem) - with util.safe_open(key_path, 'wb', chmod=0o400) as key_file: - key_file.write(key_pem) - - return response - - def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes): """Copy a file into an active location (likely the system's config dir) if required. diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 94f7dd0be..cdcde2c76 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -1,10 +1,8 @@ """Tests for certbot.plugins.common.""" import functools import shutil -import tempfile import unittest -import OpenSSL import josepy as jose import mock @@ -19,16 +17,10 @@ from certbot.tests import acme_util from certbot.tests import util as test_util AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) -ACHALLS = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01(token=b'token1'), "pending"), - domain="encryption-example.demo", account_key=AUTH_KEY), - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01(token=b'token2'), "pending"), - domain="certbot.demo", account_key=AUTH_KEY), -] +ACHALL = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb(challenges.HTTP01(token=b'token1'), + "pending"), + domain="encryption-example.demo", account_key=AUTH_KEY) class NamespaceFunctionsTest(unittest.TestCase): """Tests for certbot.plugins.common.*_namespace functions.""" @@ -276,7 +268,7 @@ class ChallengePerformerTest(unittest.TestCase): self.performer = ChallengePerformer(configurator) def test_add_chall(self): - self.performer.add_chall(ACHALLS[0], 0) + self.performer.add_chall(ACHALL, 0) self.assertEqual(1, len(self.performer.achalls)) self.assertEqual([0], self.performer.indices) @@ -284,63 +276,6 @@ class ChallengePerformerTest(unittest.TestCase): self.assertRaises(NotImplementedError, self.performer.perform) -class TLSSNI01Test(unittest.TestCase): - """Tests for certbot.plugins.common.TLSSNI01.""" - - def setUp(self): - self.tempdir = tempfile.mkdtemp() - configurator = mock.MagicMock() - configurator.config.config_dir = os.path.join(self.tempdir, "config") - configurator.config.work_dir = os.path.join(self.tempdir, "work") - - from certbot.plugins.common import TLSSNI01 - self.sni = TLSSNI01(configurator=configurator) - - def tearDown(self): - shutil.rmtree(self.tempdir) - - def test_setup_challenge_cert(self): - # This is a helper function that can be used for handling - # open context managers more elegantly. It avoids dealing with - # __enter__ and __exit__ calls. - # http://www.voidspace.org.uk/python/mock/helpers.html#mock.mock_open - mock_open, mock_safe_open = mock.mock_open(), mock.mock_open() - - response = challenges.TLSSNI01Response() - achall = mock.MagicMock() - achall.chall.encode.return_value = "token" - key = test_util.load_pyopenssl_private_key("rsa512_key.pem") - achall.response_and_validation.return_value = ( - response, (test_util.load_cert("cert_512.pem"), key)) - - with mock.patch("certbot.plugins.common.open", - mock_open, create=True): - with mock.patch("certbot.plugins.common.util.safe_open", - mock_safe_open): - # pylint: disable=protected-access - self.assertEqual(response, self.sni._setup_challenge_cert( - achall, "randomS1")) - - # pylint: disable=no-member - mock_open.assert_called_once_with(self.sni.get_cert_path(achall), "wb") - mock_open.return_value.write.assert_called_once_with( - test_util.load_vector("cert_512.pem")) - mock_safe_open.assert_called_once_with( - self.sni.get_key_path(achall), "wb", chmod=0o400) - mock_safe_open.return_value.write.assert_called_once_with( - OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)) - - def test_get_z_domain(self): - achall = ACHALLS[0] - self.assertEqual(self.sni.get_z_domain(achall), - achall.response(achall.account_key).z_domain.decode("utf-8")) - - def test_warning(self): - with mock.patch('certbot.plugins.common.warnings.warn') as mock_warn: - from certbot.plugins.common import TLSSNI01 # pylint: disable=unused-variable - self.assertTrue(mock_warn.call_args[0][0].startswith('TLSSNI01')) - - class InstallVersionControlledFileTest(test_util.TempDirTestCase): """Tests for certbot.plugins.common.install_version_controlled_file.""" -- cgit v1.2.3 From 0a48d7bf7ee9f48dc9eef3dd162424ed8b69851c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 Nov 2019 11:11:03 -0800 Subject: remove get_systemd_os_info (#7526) Fixes #7500. --- CHANGELOG.md | 5 +++-- certbot/tests/util_test.py | 8 -------- certbot/util.py | 14 -------------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b4ec995..826a59dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * `certbot.plugins.common.TLSSNI01` has been removed. * The functions `certbot.client.view_config_changes`, `certbot.main.config_changes`, - `certbot.plugins.common.Installer.view_config_changes`, and - `certbot.reverter.Reverter.view_config_changes` have been removed + `certbot.plugins.common.Installer.view_config_changes`, + `certbot.reverter.Reverter.view_config_changes`, and + `certbot.util.get_systemd_os_info` have been removed ### Fixed diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 65c51bfce..5ced9f78e 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -510,14 +510,6 @@ class OsInfoTest(unittest.TestCase): m_distro.linux_distribution.return_value = ("something", "else") self.assertEqual(cbutil.get_os_info(), ("something", "else")) - @mock.patch("warnings.warn") - @mock.patch("certbot.util.distro") - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_get_systemd_os_info_deprecation(self, _, mock_warn): - import certbot.util as cbutil - cbutil.get_systemd_os_info() - self.assertTrue(mock_warn.called) - @mock.patch("certbot.util.subprocess.Popen") def test_non_systemd_os_info(self, popen_mock): import certbot.util as cbutil diff --git a/certbot/util.py b/certbot/util.py index ea680e050..f435753ef 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -13,7 +13,6 @@ import re import socket import subprocess import sys -import warnings import configargparse import six @@ -308,19 +307,6 @@ def get_os_info_ua(): return " ".join(get_python_os_info(pretty=True)) return os_info -def get_systemd_os_info(): - """ - Parse systemd /etc/os-release for distribution information - - :returns: (os_name, os_version) - :rtype: `tuple` of `str` - """ - - warnings.warn( - "The get_sytemd_os_like() function is deprecated and will be removed in " - "a future release.", DeprecationWarning, stacklevel=2) - return get_os_info()[:2] - def get_systemd_os_like(): """ Get a list of strings that indicate the distribution likeness to -- cgit v1.2.3 From 96e02d614b5db0ecd30481323ee522e049ddb05c Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 8 Nov 2019 16:19:21 -0800 Subject: Make uncomplicated modules private (#7528) * Create _internal package for Certbot's non-public modules * Move account.py to _internal * Move auth_handler.py to _internal * Move cert_manager.py to _internal * Move client.py to _internal * Move error_handler.py to _internal * Move lock.py to _internal * Move main.py to _internal * Move notify.py to _internal * Move ocsp.py to _internal * Move renewal.py to _internal * Move reporter.py to _internal * Move storage.py to _internal * Move updater.py to _internal * update apache and nginx oldest requirements * Keep the lock file as certbot.lock * nginx oldest tests still need to rely on newer certbot * python doesn't have good dependency resolution, so specify the transitive dependency * update required minimum versions in nginx setup.py --- certbot-apache/certbot_apache/configurator.py | 4 +- certbot-nginx/local-oldest-requirements.txt | 4 +- certbot-nginx/setup.py | 4 +- certbot/_internal/__init__.py | 6 + certbot/_internal/account.py | 344 +++++++ certbot/_internal/auth_handler.py | 470 +++++++++ certbot/_internal/cert_manager.py | 388 +++++++ certbot/_internal/client.py | 742 ++++++++++++++ certbot/_internal/error_handler.py | 172 ++++ certbot/_internal/lock.py | 261 +++++ certbot/_internal/main.py | 1365 +++++++++++++++++++++++++ certbot/_internal/notify.py | 34 + certbot/_internal/ocsp.py | 292 ++++++ certbot/_internal/renewal.py | 476 +++++++++ certbot/_internal/reporter.py | 100 ++ certbot/_internal/storage.py | 1130 ++++++++++++++++++++ certbot/_internal/updater.py | 122 +++ certbot/account.py | 344 ------- certbot/auth_handler.py | 470 --------- certbot/cert_manager.py | 388 ------- certbot/cli.py | 4 +- certbot/client.py | 742 -------------- certbot/display/ops.py | 2 +- certbot/error_handler.py | 172 ---- certbot/lock.py | 261 ----- certbot/main.py | 1365 ------------------------- certbot/notify.py | 34 - certbot/ocsp.py | 292 ------ certbot/plugins/common_test.py | 2 +- certbot/plugins/enhancements.py | 8 +- certbot/renewal.py | 476 --------- certbot/reporter.py | 100 -- certbot/storage.py | 1130 -------------------- certbot/tests/account_test.py | 32 +- certbot/tests/acme_util.py | 2 +- certbot/tests/auth_handler_test.py | 24 +- certbot/tests/cert_manager_test.py | 163 +-- certbot/tests/cli_test.py | 6 +- certbot/tests/client_test.py | 113 +- certbot/tests/compat/filesystem_test.py | 2 +- certbot/tests/display/ops_test.py | 2 +- certbot/tests/error_handler_test.py | 10 +- certbot/tests/lock_test.py | 20 +- certbot/tests/log_test.py | 2 +- certbot/tests/main_test.py | 412 ++++---- certbot/tests/notify_test.py | 18 +- certbot/tests/ocsp_test.py | 33 +- certbot/tests/renewal_test.py | 28 +- certbot/tests/renewupdater_test.py | 12 +- certbot/tests/reporter_test.py | 6 +- certbot/tests/storage_test.py | 78 +- certbot/tests/util.py | 4 +- certbot/updater.py | 122 --- certbot/util.py | 2 +- docs/api/account.rst | 5 - docs/api/auth_handler.rst | 5 - docs/api/cert_manager.rst | 5 - docs/api/client.rst | 5 - docs/api/error_handler.rst | 5 - docs/api/lock.rst | 5 - docs/api/main.rst | 5 - docs/api/notify.rst | 5 - docs/api/ocsp.rst | 5 - docs/api/renewal.rst | 5 - docs/api/reporter.rst | 5 - docs/api/storage.rst | 5 - setup.py | 2 +- tests/lock_test.py | 2 +- windows-installer/construct.py | 2 +- 69 files changed, 6406 insertions(+), 6455 deletions(-) create mode 100644 certbot/_internal/__init__.py create mode 100644 certbot/_internal/account.py create mode 100644 certbot/_internal/auth_handler.py create mode 100644 certbot/_internal/cert_manager.py create mode 100644 certbot/_internal/client.py create mode 100644 certbot/_internal/error_handler.py create mode 100644 certbot/_internal/lock.py create mode 100644 certbot/_internal/main.py create mode 100644 certbot/_internal/notify.py create mode 100644 certbot/_internal/ocsp.py create mode 100644 certbot/_internal/renewal.py create mode 100644 certbot/_internal/reporter.py create mode 100644 certbot/_internal/storage.py create mode 100644 certbot/_internal/updater.py delete mode 100644 certbot/account.py delete mode 100644 certbot/auth_handler.py delete mode 100644 certbot/cert_manager.py delete mode 100644 certbot/client.py delete mode 100644 certbot/error_handler.py delete mode 100644 certbot/lock.py delete mode 100644 certbot/main.py delete mode 100644 certbot/notify.py delete mode 100644 certbot/ocsp.py delete mode 100644 certbot/renewal.py delete mode 100644 certbot/reporter.py delete mode 100644 certbot/storage.py delete mode 100644 certbot/updater.py delete mode 100644 docs/api/account.rst delete mode 100644 docs/api/auth_handler.rst delete mode 100644 docs/api/cert_manager.rst delete mode 100644 docs/api/client.rst delete mode 100644 docs/api/error_handler.rst delete mode 100644 docs/api/lock.rst delete mode 100644 docs/api/main.rst delete mode 100644 docs/api/notify.rst delete mode 100644 docs/api/ocsp.rst delete mode 100644 docs/api/renewal.rst delete mode 100644 docs/api/reporter.rst delete mode 100644 docs/api/storage.rst diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 3868451f5..6ff7d328e 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -2345,7 +2345,7 @@ class ApacheConfigurator(common.Installer): Enable the AutoHSTS enhancement for defined domains :param _unused_lineage: Certificate lineage object, unused - :type _unused_lineage: certbot.storage.RenewableCert + :type _unused_lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str @@ -2470,7 +2470,7 @@ class ApacheConfigurator(common.Installer): and changes the HSTS max-age to a high value. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert """ self._autohsts_fetch_state() if not self._autohsts: diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index a959ad44f..3192f8360 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.29.0 -certbot[dev]==0.36.0 +-e acme[dev] +-e .[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index f3f0b6640..50febc33a 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -9,8 +9,8 @@ version = '1.0.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.29.0', - 'certbot>=0.35.0', + 'acme>=1.0.0.dev0', + 'certbot>=1.0.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/_internal/__init__.py b/certbot/_internal/__init__.py new file mode 100644 index 000000000..45ec6ac9c --- /dev/null +++ b/certbot/_internal/__init__.py @@ -0,0 +1,6 @@ +""" +Modules internal to Certbot. + +This package contains modules that are not considered part of Certbot's public +API. They may be changed without updating Certbot's major version. +""" diff --git a/certbot/_internal/account.py b/certbot/_internal/account.py new file mode 100644 index 000000000..992d63d38 --- /dev/null +++ b/certbot/_internal/account.py @@ -0,0 +1,344 @@ +"""Creates ACME accounts for server.""" +import datetime +import functools +import hashlib +import logging +import shutil +import socket + +import josepy as jose +import pyrfc3339 +import pytz +import six +import zope.component +from cryptography.hazmat.primitives import serialization + +from acme import fields as acme_fields +from acme import messages + +from certbot import constants +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +class Account(object): # pylint: disable=too-few-public-methods + """ACME protocol registration. + + :ivar .RegistrationResource regr: Registration Resource + :ivar .JWK key: Authorized Account Key + :ivar .Meta: Account metadata + :ivar str id: Globally unique account identifier. + + """ + + class Meta(jose.JSONObjectWithFields): + """Account metadata + + :ivar datetime.datetime creation_dt: Creation date and time (UTC). + :ivar str creation_host: FQDN of host, where account has been created. + + .. note:: ``creation_dt`` and ``creation_host`` are useful in + cross-machine migration scenarios. + + """ + creation_dt = acme_fields.RFC3339Field("creation_dt") + creation_host = jose.Field("creation_host") + + def __init__(self, regr, key, meta=None): + self.key = key + self.regr = regr + self.meta = self.Meta( + # pyrfc3339 drops microseconds, make sure __eq__ is sane + creation_dt=datetime.datetime.now( + tz=pytz.UTC).replace(microsecond=0), + creation_host=socket.getfqdn()) if meta is None else meta + + self.id = hashlib.md5( + self.key.key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + ).hexdigest() + # Implementation note: Email? Multiple accounts can have the + # same email address. Registration URI? Assigned by the + # server, not guaranteed to be stable over time, nor + # canonical URI can be generated. ACME protocol doesn't allow + # account key (and thus its fingerprint) to be updated... + + @property + def slug(self): + """Short account identification string, useful for UI.""" + return "{1}@{0} ({2})".format(pyrfc3339.generate( + self.meta.creation_dt), self.meta.creation_host, self.id[:4]) + + def __repr__(self): + return "<{0}({1}, {2}, {3})>".format( + self.__class__.__name__, self.regr, self.id, self.meta) + + def __eq__(self, other): + return (isinstance(other, self.__class__) and + self.key == other.key and self.regr == other.regr and + self.meta == other.meta) + + +def report_new_account(config): + """Informs the user about their new ACME account.""" + reporter = zope.component.queryUtility(interfaces.IReporter) + if reporter is None: + return + reporter.add_message( + "Your account credentials have been saved in your Certbot " + "configuration directory at {0}. You should make a secure backup " + "of this folder now. This configuration directory will also " + "contain certificates and private keys obtained by Certbot " + "so making regular backups of this folder is ideal.".format( + config.config_dir), + reporter.MEDIUM_PRIORITY) + + +class AccountMemoryStorage(interfaces.AccountStorage): + """In-memory account storage.""" + + def __init__(self, initial_accounts=None): + self.accounts = initial_accounts if initial_accounts is not None else {} + + def find_all(self): + return list(six.itervalues(self.accounts)) + + def save(self, account, client): + if account.id in self.accounts: + logger.debug("Overwriting account: %s", account.id) + self.accounts[account.id] = account + + def load(self, account_id): + try: + return self.accounts[account_id] + except KeyError: + raise errors.AccountNotFound(account_id) + +class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): + """A backwards-compatible RegistrationResource with a new-authz URI. + + Hack: Certbot versions pre-0.11.1 expect to load + new_authzr_uri as part of the account. Because people + sometimes switch between old and new versions, we will + continue to write out this field for some time so older + clients don't crash in that scenario. + """ + new_authzr_uri = jose.Field('new_authzr_uri') + +class AccountFileStorage(interfaces.AccountStorage): + """Accounts file storage. + + :ivar .IConfig config: Client configuration + + """ + def __init__(self, config): + self.config = config + util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) + + def _account_dir_path(self, account_id): + return self._account_dir_path_for_server_path(account_id, self.config.server_path) + + def _account_dir_path_for_server_path(self, account_id, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + return os.path.join(accounts_dir, account_id) + + @classmethod + def _regr_path(cls, account_dir_path): + return os.path.join(account_dir_path, "regr.json") + + @classmethod + def _key_path(cls, account_dir_path): + return os.path.join(account_dir_path, "private_key.json") + + @classmethod + def _metadata_path(cls, account_dir_path): + return os.path.join(account_dir_path, "meta.json") + + def _find_all_for_server_path(self, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + try: + candidates = os.listdir(accounts_dir) + except OSError: + return [] + + accounts = [] + for account_id in candidates: + try: + accounts.append(self._load_for_server_path(account_id, server_path)) + except errors.AccountStorageError: + logger.debug("Account loading problem", exc_info=True) + + if not accounts and server_path in constants.LE_REUSE_SERVERS: + # find all for the next link down + prev_server_path = constants.LE_REUSE_SERVERS[server_path] + prev_accounts = self._find_all_for_server_path(prev_server_path) + # if we found something, link to that + if prev_accounts: + try: + self._symlink_to_accounts_dir(prev_server_path, server_path) + except OSError: + return [] + accounts = prev_accounts + return accounts + + def find_all(self): + return self._find_all_for_server_path(self.config.server_path) + + def _symlink_to_account_dir(self, prev_server_path, server_path, account_id): + prev_account_dir = self._account_dir_path_for_server_path(account_id, prev_server_path) + new_account_dir = self._account_dir_path_for_server_path(account_id, server_path) + os.symlink(prev_account_dir, new_account_dir) + + def _symlink_to_accounts_dir(self, prev_server_path, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + if os.path.islink(accounts_dir): + os.unlink(accounts_dir) + else: + os.rmdir(accounts_dir) + prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path) + os.symlink(prev_account_dir, accounts_dir) + + def _load_for_server_path(self, account_id, server_path): + account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) + if not os.path.isdir(account_dir_path): # isdir is also true for symlinks + if server_path in constants.LE_REUSE_SERVERS: + prev_server_path = constants.LE_REUSE_SERVERS[server_path] + prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) + # we didn't error so we found something, so create a symlink to that + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + # If accounts_dir isn't empty, make an account specific symlink + if os.listdir(accounts_dir): + self._symlink_to_account_dir(prev_server_path, server_path, account_id) + else: + self._symlink_to_accounts_dir(prev_server_path, server_path) + return prev_loaded_account + else: + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) + + try: + with open(self._regr_path(account_dir_path)) as regr_file: + regr = messages.RegistrationResource.json_loads(regr_file.read()) + with open(self._key_path(account_dir_path)) as key_file: + key = jose.JWK.json_loads(key_file.read()) + with open(self._metadata_path(account_dir_path)) as metadata_file: + meta = Account.Meta.json_loads(metadata_file.read()) + except IOError as error: + raise errors.AccountStorageError(error) + + return Account(regr, key, meta) + + def load(self, account_id): + return self._load_for_server_path(account_id, self.config.server_path) + + def save(self, account, client): + self._save(account, client, regr_only=False) + + def save_regr(self, account, acme): + """Save the registration resource. + + :param Account account: account whose regr should be saved + + """ + self._save(account, acme, regr_only=True) + + def delete(self, account_id): + """Delete registration info from disk + + :param account_id: id of account which should be deleted + + """ + account_dir_path = self._account_dir_path(account_id) + if not os.path.isdir(account_dir_path): + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) + # Step 1: Delete account specific links and the directory + self._delete_account_dir_for_server_path(account_id, self.config.server_path) + + # Step 2: Remove any accounts links and directories that are now empty + if not os.listdir(self.config.accounts_dir): + self._delete_accounts_dir_for_server_path(self.config.server_path) + + def _delete_account_dir_for_server_path(self, account_id, server_path): + link_func = functools.partial(self._account_dir_path_for_server_path, account_id) + nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) + shutil.rmtree(nonsymlinked_dir) + + def _delete_accounts_dir_for_server_path(self, server_path): + link_func = self.config.accounts_dir_for_server_path + nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) + os.rmdir(nonsymlinked_dir) + + def _delete_links_and_find_target_dir(self, server_path, link_func): + """Delete symlinks and return the nonsymlinked directory path. + + :param str server_path: file path based on server + :param callable link_func: callable that returns possible links + given a server_path + + :returns: the final, non-symlinked target + :rtype: str + + """ + dir_path = link_func(server_path) + + # does an appropriate directory link to me? if so, make sure that's gone + reused_servers = {} + for k in constants.LE_REUSE_SERVERS: + reused_servers[constants.LE_REUSE_SERVERS[k]] = k + + # is there a next one up? + possible_next_link = True + while possible_next_link: + possible_next_link = False + if server_path in reused_servers: + next_server_path = reused_servers[server_path] + next_dir_path = link_func(next_server_path) + if os.path.islink(next_dir_path) and os.readlink(next_dir_path) == dir_path: + possible_next_link = True + server_path = next_server_path + dir_path = next_dir_path + + # if there's not a next one up to delete, then delete me + # and whatever I link to + while os.path.islink(dir_path): + target = os.readlink(dir_path) + os.unlink(dir_path) + dir_path = target + + return dir_path + + def _save(self, account, acme, regr_only): + account_dir_path = self._account_dir_path(account.id) + util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) + try: + with open(self._regr_path(account_dir_path), "w") as regr_file: + regr = account.regr + # If we have a value for new-authz, save it for forwards + # compatibility with older versions of Certbot. If we don't + # have a value for new-authz, this is an ACMEv2 directory where + # an older version of Certbot won't work anyway. + if hasattr(acme.directory, "new-authz"): + regr = RegistrationResourceWithNewAuthzrURI( + new_authzr_uri=acme.directory.new_authz, + body={}, + uri=regr.uri) + else: + regr = messages.RegistrationResource( + body={}, + uri=regr.uri) + regr_file.write(regr.json_dumps()) + if not regr_only: + with util.safe_open(self._key_path(account_dir_path), + "w", chmod=0o400) as key_file: + key_file.write(account.key.json_dumps()) + with open(self._metadata_path( + account_dir_path), "w") as metadata_file: + metadata_file.write(account.meta.json_dumps()) + except IOError as error: + raise errors.AccountStorageError(error) diff --git a/certbot/_internal/auth_handler.py b/certbot/_internal/auth_handler.py new file mode 100644 index 000000000..5c037e8dc --- /dev/null +++ b/certbot/_internal/auth_handler.py @@ -0,0 +1,470 @@ +"""ACME AuthHandler.""" +import logging +import time +import datetime + +import zope.component + +from acme import challenges +from acme import messages +from acme import errors as acme_errors +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict, List, Tuple +# pylint: enable=unused-import, no-name-in-module +from certbot import achallenges +from certbot import errors +from certbot._internal import error_handler +from certbot import interfaces + +logger = logging.getLogger(__name__) + + +class AuthHandler(object): + """ACME Authorization Handler for a client. + + :ivar auth: Authenticator capable of solving + :class:`~acme.challenges.Challenge` types + :type auth: :class:`certbot.interfaces.IAuthenticator` + + :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API. + + :ivar account: Client's Account + :type account: :class:`certbot._internal.account.Account` + + :ivar list pref_challs: sorted user specified preferred challenges + type strings with the most preferred challenge listed first + + """ + def __init__(self, auth, acme_client, account, pref_challs): + self.auth = auth + self.acme = acme_client + + self.account = account + self.pref_challs = pref_challs + + def handle_authorizations(self, orderr, best_effort=False, max_retries=30): + """ + Retrieve all authorizations, perform all challenges required to validate + these authorizations, then poll and wait for the authorization to be checked. + :param acme.messages.OrderResource orderr: must have authorizations filled in + :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) + :param int max_retries: maximum number of retries to poll authorizations + :returns: list of all validated authorizations + :rtype: List + + :raises .AuthorizationError: If unable to retrieve all authorizations + """ + authzrs = orderr.authorizations[:] + if not authzrs: + raise errors.AuthorizationError('No authorization to handle.') + + # Retrieve challenges that need to be performed to validate authorizations. + achalls = self._choose_challenges(authzrs) + if not achalls: + return authzrs + + # Starting now, challenges will be cleaned at the end no matter what. + with error_handler.ExitHandler(self._cleanup_challenges, achalls): + # To begin, let's ask the authenticator plugin to perform all challenges. + try: + resps = self.auth.perform(achalls) + + # If debug is on, wait for user input before starting the verification process. + logger.info('Waiting for verification...') + config = zope.component.getUtility(interfaces.IConfig) + if config.debug_challenges: + notify = zope.component.getUtility(interfaces.IDisplay).notification + notify('Challenges loaded. Press continue to submit to CA. ' + 'Pass "-v" for more info about challenges.', pause=True) + except errors.AuthorizationError as error: + logger.critical('Failure in setting up challenges.') + logger.info('Attempting to clean up outstanding challenges...') + raise error + # All challenges should have been processed by the authenticator. + assert len(resps) == len(achalls), 'Some challenges have not been performed.' + + # Inform the ACME CA server that challenges are available for validation. + for achall, resp in zip(achalls, resps): + self.acme.answer_challenge(achall.challb, resp) + + # Wait for authorizations to be checked. + self._poll_authorizations(authzrs, max_retries, best_effort) + + # Keep validated authorizations only. If there is none, no certificate can be issued. + authzrs_validated = [authzr for authzr in authzrs + if authzr.body.status == messages.STATUS_VALID] + if not authzrs_validated: + raise errors.AuthorizationError('All challenges have failed.') + + return authzrs_validated + + def deactivate_valid_authorizations(self, orderr): + # type: (messages.OrderResource) -> Tuple[List, List] + """ + Deactivate all `valid` authorizations in the order, so that they cannot be re-used + in subsequent orders. + :param messages.OrderResource orderr: must have authorizations filled in + :returns: tuple of list of successfully deactivated authorizations, and + list of unsuccessfully deactivated authorizations. + :rtype: tuple + """ + to_deactivate = [authzr for authzr in orderr.authorizations + if authzr.body.status == messages.STATUS_VALID] + deactivated = [] + failed = [] + + for authzr in to_deactivate: + try: + authzr = self.acme.deactivate_authorization(authzr) + deactivated.append(authzr) + except acme_errors.Error as e: + failed.append(authzr) + logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) + + return (deactivated, failed) + + def _poll_authorizations(self, authzrs, max_retries, best_effort): + """ + Poll the ACME CA server, to wait for confirmation that authorizations have their challenges + all verified. The poll may occur several times, until all authorizations are checked + (valid or invalid), or after a maximum of retries. + """ + authzrs_to_check = {index: (authzr, None) + for index, authzr in enumerate(authzrs)} + authzrs_failed_to_report = [] + # Give an initial second to the ACME CA server to check the authorizations + sleep_seconds = 1 + for _ in range(max_retries): + # Wait for appropriate time (from Retry-After, initial wait, or no wait) + if sleep_seconds > 0: + time.sleep(sleep_seconds) + # Poll all updated authorizations. + authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _) + in authzrs_to_check.items()} + # Update the original list of authzr with the updated authzrs from server. + for index, (authzr, _) in authzrs_to_check.items(): + authzrs[index] = authzr + + # Gather failed authorizations + authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values() + if authzr.body.status == messages.STATUS_INVALID] + for authzr_failed in authzrs_failed: + logger.warning('Challenge failed for domain %s', + authzr_failed.body.identifier.value) + # Accumulating all failed authzrs to build a consolidated report + # on them at the end of the polling. + authzrs_failed_to_report.extend(authzrs_failed) + + # Extract out the authorization already checked for next poll iteration. + # Poll may stop here because there is no pending authorizations anymore. + authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp) + in authzrs_to_check.items() + if authzr.body.status == messages.STATUS_PENDING} + if not authzrs_to_check: + # Polling process is finished, we can leave the loop + break + + # Be merciful with the ACME server CA, check the Retry-After header returned, + # and wait this time before polling again in next loop iteration. + # From all the pending authorizations, we take the greatest Retry-After value + # to avoid polling an authorization before its relevant Retry-After value. + retry_after = max(self.acme.retry_after(resp, 3) + for _, resp in authzrs_to_check.values()) + sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() + + # In case of failed authzrs, create a report to the user. + if authzrs_failed_to_report: + _report_failed_authzrs(authzrs_failed_to_report, self.account.key) + if not best_effort: + # Without best effort, having failed authzrs is critical and fail the process. + raise errors.AuthorizationError('Some challenges have failed.') + + if authzrs_to_check: + # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. + raise errors.AuthorizationError('All authorizations were not finalized by the CA.') + + def _choose_challenges(self, authzrs): + """ + Retrieve necessary and pending challenges to satisfy server. + NB: Necessary and already validated challenges are not retrieved, + as they can be reused for a certificate issuance. + """ + pending_authzrs = [authzr for authzr in authzrs + if authzr.body.status != messages.STATUS_VALID] + achalls = [] # type: List[achallenges.AnnotatedChallenge] + if pending_authzrs: + logger.info("Performing the following challenges:") + for authzr in pending_authzrs: + authzr_challenges = authzr.body.challenges + if self.acme.acme_version == 1: + combinations = authzr.body.combinations + else: + combinations = tuple((i,) for i in range(len(authzr_challenges))) + + path = gen_challenge_path( + authzr_challenges, + self._get_chall_pref(authzr.body.identifier.value), + combinations) + + achalls.extend(self._challenge_factory(authzr, path)) + + return achalls + + def _get_chall_pref(self, domain): + """Return list of challenge preferences. + + :param str domain: domain for which you are requesting preferences + + """ + chall_prefs = [] + # Make sure to make a copy... + plugin_pref = self.auth.get_chall_pref(domain) + if self.pref_challs: + plugin_pref_types = set(chall.typ for chall in plugin_pref) + for typ in self.pref_challs: + if typ in plugin_pref_types: + chall_prefs.append(challenges.Challenge.TYPES[typ]) + if chall_prefs: + return chall_prefs + raise errors.AuthorizationError( + "None of the preferred challenges " + "are supported by the selected plugin") + chall_prefs.extend(plugin_pref) + return chall_prefs + + def _cleanup_challenges(self, achalls): + """Cleanup challenges. + + :param achalls: annotated challenges to cleanup + :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge` + + """ + logger.info("Cleaning up challenges") + self.auth.cleanup(achalls) + + def _challenge_factory(self, authzr, path): + """Construct Namedtuple Challenges + + :param messages.AuthorizationResource authzr: authorization + + :param list path: List of indices from `challenges`. + + :returns: achalls, list of challenge type + :class:`certbot.achallenges.Indexed` + :rtype: list + + :raises .errors.Error: if challenge type is not recognized + + """ + achalls = [] + + for index in path: + challb = authzr.body.challenges[index] + achalls.append(challb_to_achall( + challb, self.account.key, authzr.body.identifier.value)) + + return achalls + + +def challb_to_achall(challb, account_key, domain): + """Converts a ChallengeBody object to an AnnotatedChallenge. + + :param .ChallengeBody challb: ChallengeBody + :param .JWK account_key: Authorized Account Key + :param str domain: Domain of the challb + + :returns: Appropriate AnnotatedChallenge + :rtype: :class:`certbot.achallenges.AnnotatedChallenge` + + """ + chall = challb.chall + logger.info("%s challenge for %s", chall.typ, domain) + + if isinstance(chall, challenges.KeyAuthorizationChallenge): + return achallenges.KeyAuthorizationAnnotatedChallenge( + challb=challb, domain=domain, account_key=account_key) + elif isinstance(chall, challenges.DNS): + return achallenges.DNS(challb=challb, domain=domain) + else: + raise errors.Error( + "Received unsupported challenge of type: {0}".format(chall.typ)) + + +def gen_challenge_path(challbs, preferences, combinations): + """Generate a plan to get authority over the identity. + + .. todo:: This can be possibly be rewritten to use resolved_combinations. + + :param tuple challbs: A tuple of challenges + (:class:`acme.messages.Challenge`) from + :class:`acme.messages.AuthorizationResource` to be + fulfilled by the client in order to prove possession of the + identifier. + + :param list preferences: List of challenge preferences for domain + (:class:`acme.challenges.Challenge` subclasses) + + :param tuple combinations: A collection of sets of challenges from + :class:`acme.messages.Challenge`, each of which would + be sufficient to prove possession of the identifier. + + :returns: tuple of indices from ``challenges``. + :rtype: tuple + + :raises certbot.errors.AuthorizationError: If a + path cannot be created that satisfies the CA given the preferences and + combinations. + + """ + if combinations: + return _find_smart_path(challbs, preferences, combinations) + return _find_dumb_path(challbs, preferences) + + +def _find_smart_path(challbs, preferences, combinations): + """Find challenge path with server hints. + + Can be called if combinations is included. Function uses a simple + ranking system to choose the combo with the lowest cost. + + """ + chall_cost = {} + max_cost = 1 + for i, chall_cls in enumerate(preferences): + chall_cost[chall_cls] = i + max_cost += i + + # max_cost is now equal to sum(indices) + 1 + + best_combo = None + # Set above completing all of the available challenges + best_combo_cost = max_cost + + combo_total = 0 + for combo in combinations: + for challenge_index in combo: + combo_total += chall_cost.get(challbs[ + challenge_index].chall.__class__, max_cost) + + if combo_total < best_combo_cost: + best_combo = combo + best_combo_cost = combo_total + + combo_total = 0 + + if not best_combo: + _report_no_chall_path(challbs) + + return best_combo + + +def _find_dumb_path(challbs, preferences): + """Find challenge path without server hints. + + Should be called if the combinations hint is not included by the + server. This function either returns a path containing all + challenges provided by the CA or raises an exception. + + """ + path = [] + for i, challb in enumerate(challbs): + # supported is set to True if the challenge type is supported + supported = next((True for pref_c in preferences + if isinstance(challb.chall, pref_c)), False) + if supported: + path.append(i) + else: + _report_no_chall_path(challbs) + + return path + + +def _report_no_chall_path(challbs): + """Logs and raises an error that no satisfiable chall path exists. + + :param challbs: challenges from the authorization that can't be satisfied + + """ + msg = ("Client with the currently selected authenticator does not support " + "any combination of challenges that will satisfy the CA.") + if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): + msg += ( + " You may need to use an authenticator " + "plugin that can do challenges over DNS.") + logger.critical(msg) + raise errors.AuthorizationError(msg) + + +_ERROR_HELP_COMMON = ( + "To fix these errors, please make sure that your domain name was entered " + "correctly and the DNS A/AAAA record(s) for that domain contain(s) the " + "right IP address.") + + +_ERROR_HELP = { + "connection": + _ERROR_HELP_COMMON + " Additionally, please check that your computer " + "has a publicly routable IP address and that no firewalls are preventing " + "the server from communicating with the client. If you're using the " + "webroot plugin, you should also verify that you are serving files " + "from the webroot path you provided.", + "dnssec": + _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for " + "your domain, please ensure that the signature is valid.", + "malformed": + "To fix these errors, please make sure that you did not provide any " + "invalid information to the client, and try running Certbot " + "again.", + "serverInternal": + "Unfortunately, an error on the ACME server prevented you from completing " + "authorization. Please try again later.", + "tls": + _ERROR_HELP_COMMON + " Additionally, please check that you have an " + "up-to-date TLS configuration that allows the server to communicate " + "with the Certbot client.", + "unauthorized": _ERROR_HELP_COMMON, + "unknownHost": _ERROR_HELP_COMMON, +} + + +def _report_failed_authzrs(failed_authzrs, account_key): + """Notifies the user about failed authorizations.""" + problems = {} # type: Dict[str, List[achallenges.AnnotatedChallenge]] + failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value) + for authzr in failed_authzrs for challb in authzr.body.challenges + if challb.error] + + for achall in failed_achalls: + problems.setdefault(achall.error.typ, []).append(achall) + + reporter = zope.component.getUtility(interfaces.IReporter) + for achalls in problems.values(): + reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) + + +def _generate_failed_chall_msg(failed_achalls): + """Creates a user friendly error message about failed challenges. + + :param list failed_achalls: A list of failed + :class:`certbot.achallenges.AnnotatedChallenge` with the same error + type. + + :returns: A formatted error message for the client. + :rtype: str + + """ + error = failed_achalls[0].error + typ = error.typ + if messages.is_acme_error(error): + typ = error.code + msg = ["The following errors were reported by the server:"] + + for achall in failed_achalls: + msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % ( + achall.domain, typ, achall.error.detail)) + + if typ in _ERROR_HELP: + msg.append("\n\n") + msg.append(_ERROR_HELP[typ]) + + return "".join(msg) diff --git a/certbot/_internal/cert_manager.py b/certbot/_internal/cert_manager.py new file mode 100644 index 000000000..ed72b12e5 --- /dev/null +++ b/certbot/_internal/cert_manager.py @@ -0,0 +1,388 @@ +"""Tools for managing certificates.""" +import datetime +import logging +import re +import traceback + +import pytz +import zope.component + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot._internal import ocsp +from certbot._internal import storage +from certbot import util +from certbot.compat import os +from certbot.display import util as display_util + +logger = logging.getLogger(__name__) + +################### +# Commands +################### + +def update_live_symlinks(config): + """Update the certificate file family symlinks to use archive_dir. + + Use the information in the config file to make symlinks point to + the correct archive directory. + + .. note:: This assumes that the installation is using a Reverter object. + + :param config: Configuration. + :type config: :class:`certbot.configuration.NamespaceConfig` + + """ + for renewal_file in storage.renewal_conf_files(config): + storage.RenewableCert(renewal_file, config, update_symlinks=True) + +def rename_lineage(config): + """Rename the specified lineage to the new name. + + :param config: Configuration. + :type config: :class:`certbot.configuration.NamespaceConfig` + + """ + disp = zope.component.getUtility(interfaces.IDisplay) + + certname = get_certnames(config, "rename")[0] + + new_certname = config.new_certname + if not new_certname: + code, new_certname = disp.input( + "Enter the new name for certificate {0}".format(certname), + flag="--updated-cert-name", force_interactive=True) + if code != display_util.OK or not new_certname: + raise errors.Error("User ended interaction.") + + lineage = lineage_for_certname(config, certname) + if not lineage: + raise errors.ConfigurationError("No existing certificate with name " + "{0} found.".format(certname)) + storage.rename_renewal_config(certname, new_certname, config) + disp.notification("Successfully renamed {0} to {1}." + .format(certname, new_certname), pause=False) + +def certificates(config): + """Display information about certs configured with Certbot + + :param config: Configuration. + :type config: :class:`certbot.configuration.NamespaceConfig` + """ + parsed_certs = [] + parse_failures = [] + for renewal_file in storage.renewal_conf_files(config): + try: + renewal_candidate = storage.RenewableCert(renewal_file, config) + crypto_util.verify_renewable_cert(renewal_candidate) + parsed_certs.append(renewal_candidate) + except Exception as e: # pylint: disable=broad-except + logger.warning("Renewal configuration file %s produced an " + "unexpected error: %s. Skipping.", renewal_file, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + parse_failures.append(renewal_file) + + # Describe all the certs + _describe_certs(config, parsed_certs, parse_failures) + +def delete(config): + """Delete Certbot files associated with a certificate lineage.""" + certnames = get_certnames(config, "delete", allow_multiple=True) + for certname in certnames: + storage.delete_files(config, certname) + disp = zope.component.getUtility(interfaces.IDisplay) + disp.notification("Deleted all files relating to certificate {0}." + .format(certname), pause=False) + +################### +# Public Helpers +################### + +def lineage_for_certname(cli_config, certname): + """Find a lineage object with name certname.""" + configs_dir = cli_config.renewal_configs_dir + # Verify the directory is there + util.make_or_verify_dir(configs_dir, mode=0o755) + try: + renewal_file = storage.renewal_file_for_certname(cli_config, certname) + except errors.CertStorageError: + return None + try: + return storage.RenewableCert(renewal_file, cli_config) + except (errors.CertStorageError, IOError): + logger.debug("Renewal conf file %s is broken.", renewal_file) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + +def domains_for_certname(config, certname): + """Find the domains in the cert with name certname.""" + lineage = lineage_for_certname(config, certname) + return lineage.names() if lineage else None + +def find_duplicative_certs(config, domains): + """Find existing certs that match the given domain names. + + This function searches for certificates whose domains are equal to + the `domains` parameter and certificates whose domains are a subset + of the domains in the `domains` parameter. If multiple certificates + are found whose names are a subset of `domains`, the one whose names + are the largest subset of `domains` is returned. + + If multiple certificates' domains are an exact match or equally + sized subsets, which matching certificates are returned is + undefined. + + :param config: Configuration. + :type config: :class:`certbot.configuration.NamespaceConfig` + :param domains: List of domain names + :type domains: `list` of `str` + + :returns: lineages representing the identically matching cert and the + largest subset if they exist + :rtype: `tuple` of `storage.RenewableCert` or `None` + + """ + def update_certs_for_domain_matches(candidate_lineage, rv): + """Return cert as identical_names_cert if it matches, + or subset_names_cert if it matches as subset + """ + # TODO: Handle these differently depending on whether they are + # expired or still valid? + identical_names_cert, subset_names_cert = rv + candidate_names = set(candidate_lineage.names()) + if candidate_names == set(domains): + identical_names_cert = candidate_lineage + elif candidate_names.issubset(set(domains)): + # This logic finds and returns the largest subset-names cert + # in the case where there are several available. + if subset_names_cert is None: + subset_names_cert = candidate_lineage + elif len(candidate_names) > len(subset_names_cert.names()): + subset_names_cert = candidate_lineage + return (identical_names_cert, subset_names_cert) + + return _search_lineages(config, update_certs_for_domain_matches, (None, None)) + +def _archive_files(candidate_lineage, filetype): + """ In order to match things like: + /etc/letsencrypt/archive/example.com/chain1.pem. + + Anonymous functions which call this function are eventually passed (in a list) to + `match_and_check_overlaps` to help specify the acceptable_matches. + + :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to + be searched. + :param str filetype: main file name prefix e.g. "fullchain" or "chain". + + :returns: Files in candidate_lineage's archive dir that match the provided filetype. + :rtype: list of str or None + """ + archive_dir = candidate_lineage.archive_dir + pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) + if re.match("{0}[0-9]*.pem".format(filetype), f)] + if pattern: + return pattern + return None + +def _acceptable_matches(): + """ Generates the list that's passed to match_and_check_overlaps. Is its own function to + make unit testing easier. + + :returns: list of functions + :rtype: list + """ + return [lambda x: x.fullchain_path, lambda x: x.cert_path, + lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")] + +def cert_path_to_lineage(cli_config): + """ If config.cert_path is defined, try to find an appropriate value for config.certname. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + + :returns: a lineage name + :rtype: str + + :raises `errors.Error`: If the specified cert path can't be matched to a lineage name. + :raises `errors.OverlappingMatchFound`: If the matched lineage's archive is shared. + """ + acceptable_matches = _acceptable_matches() + match = match_and_check_overlaps(cli_config, acceptable_matches, + lambda x: cli_config.cert_path[0], lambda x: x.lineagename) + return match[0] + +def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func): + """ Searches through all lineages for a match, and checks for duplicates. + If a duplicate is found, an error is raised, as performing operations on lineages + that have their properties incorrectly duplicated elsewhere is probably a bad idea. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + :param list acceptable_matches: a list of functions that specify acceptable matches + :param function match_func: specifies what to match + :param function rv_func: specifies what to return + + """ + def find_matches(candidate_lineage, return_value, acceptable_matches): + """Returns a list of matches using _search_lineages.""" + acceptable_matches = [func(candidate_lineage) for func in acceptable_matches] + acceptable_matches_rv = [] # type: List[str] + for item in acceptable_matches: + if isinstance(item, list): + acceptable_matches_rv += item + else: + acceptable_matches_rv.append(item) + match = match_func(candidate_lineage) + if match in acceptable_matches_rv: + return_value.append(rv_func(candidate_lineage)) + return return_value + + matched = _search_lineages(cli_config, find_matches, [], acceptable_matches) + if not matched: + raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) + elif len(matched) > 1: + raise errors.OverlappingMatchFound() + else: + return matched + +def human_readable_cert_info(config, cert, skip_filter_checks=False): + """ Returns a human readable description of info about a RenewableCert object""" + certinfo = [] + checker = ocsp.RevocationChecker() + + if config.certname and cert.lineagename != config.certname and not skip_filter_checks: + return "" + if config.domains and not set(config.domains).issubset(cert.names()): + return "" + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + + reasons = [] + if cert.is_test_cert: + reasons.append('TEST_CERT') + if cert.target_expiry <= now: + reasons.append('EXPIRED') + elif checker.ocsp_revoked(cert): + reasons.append('REVOKED') + + if reasons: + status = "INVALID: " + ", ".join(reasons) + else: + diff = cert.target_expiry - now + if diff.days == 1: + status = "VALID: 1 day" + elif diff.days < 1: + status = "VALID: {0} hour(s)".format(diff.seconds // 3600) + else: + status = "VALID: {0} days".format(diff.days) + + valid_string = "{0} ({1})".format(cert.target_expiry, status) + certinfo.append(" Certificate Name: {0}\n" + " Domains: {1}\n" + " Expiry Date: {2}\n" + " Certificate Path: {3}\n" + " Private Key Path: {4}".format( + cert.lineagename, + " ".join(cert.names()), + valid_string, + cert.fullchain, + cert.privkey)) + return "".join(certinfo) + +def get_certnames(config, verb, allow_multiple=False, custom_prompt=None): + """Get certname from flag, interactively, or error out. + """ + certname = config.certname + if certname: + certnames = [certname] + else: + disp = zope.component.getUtility(interfaces.IDisplay) + filenames = storage.renewal_conf_files(config) + choices = [storage.lineagename_for_filename(name) for name in filenames] + if not choices: + raise errors.Error("No existing certificates found.") + if allow_multiple: + if not custom_prompt: + prompt = "Which certificate(s) would you like to {0}?".format(verb) + else: + prompt = custom_prompt + code, certnames = disp.checklist( + prompt, choices, cli_flag="--cert-name", force_interactive=True) + if code != display_util.OK: + raise errors.Error("User ended interaction.") + else: + if not custom_prompt: + prompt = "Which certificate would you like to {0}?".format(verb) + else: + prompt = custom_prompt + + code, index = disp.menu( + prompt, choices, cli_flag="--cert-name", force_interactive=True) + + if code != display_util.OK or index not in range(0, len(choices)): + raise errors.Error("User ended interaction.") + certnames = [choices[index]] + return certnames + +################### +# Private Helpers +################### + +def _report_lines(msgs): + """Format a results report for a category of single-line renewal outcomes""" + return " " + "\n ".join(str(msg) for msg in msgs) + +def _report_human_readable(config, parsed_certs): + """Format a results report for a parsed cert""" + certinfo = [] + for cert in parsed_certs: + certinfo.append(human_readable_cert_info(config, cert)) + return "\n".join(certinfo) + +def _describe_certs(config, parsed_certs, parse_failures): + """Print information about the certs we know about""" + out = [] # type: List[str] + + notify = out.append + + if not parsed_certs and not parse_failures: + notify("No certs found.") + else: + if parsed_certs: + match = "matching " if config.certname or config.domains else "" + notify("Found the following {0}certs:".format(match)) + notify(_report_human_readable(config, parsed_certs)) + if parse_failures: + notify("\nThe following renewal configurations " + "were invalid:") + notify(_report_lines(parse_failures)) + + disp = zope.component.getUtility(interfaces.IDisplay) + disp.notification("\n".join(out), pause=False, wrap=False) + +def _search_lineages(cli_config, func, initial_rv, *args): + """Iterate func over unbroken lineages, allowing custom return conditions. + + Allows flexible customization of return values, including multiple + return values and complex checks. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + :param function func: function used while searching over lineages + :param initial_rv: initial return value of the function (any type) + + :returns: Whatever was specified by `func` if a match is found. + """ + configs_dir = cli_config.renewal_configs_dir + # Verify the directory is there + util.make_or_verify_dir(configs_dir, mode=0o755) + + rv = initial_rv + for renewal_file in storage.renewal_conf_files(cli_config): + try: + candidate_lineage = storage.RenewableCert(renewal_file, cli_config) + except (errors.CertStorageError, IOError): + logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + continue + rv = func(candidate_lineage, rv, *args) + return rv diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py new file mode 100644 index 000000000..7a0daf77b --- /dev/null +++ b/certbot/_internal/client.py @@ -0,0 +1,742 @@ +"""Certbot client API.""" +import datetime +import logging +import platform + +import OpenSSL +import josepy as jose +import zope.component +from cryptography.hazmat.backends import default_backend +# https://github.com/python/typeshed/blob/master/third_party/ +# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi +from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore + +from acme import client as acme_client +from acme import crypto_util as acme_crypto_util +from acme import errors as acme_errors +from acme import messages +from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module + +import certbot +from certbot._internal import account +from certbot._internal import auth_handler +from certbot import cli +from certbot import constants +from certbot import crypto_util +from certbot import eff +from certbot._internal import error_handler +from certbot import errors +from certbot import interfaces +from certbot._internal import storage +from certbot import util +from certbot.compat import os +from certbot.display import enhancements +from certbot.display import ops as display_ops +from certbot.plugins import selection as plugin_selection + +logger = logging.getLogger(__name__) + + +def acme_from_config_key(config, key, regr=None): + "Wrangle ACME client construction" + # TODO: Allow for other alg types besides RS256 + net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl), + user_agent=determine_user_agent(config)) + return acme_client.BackwardsCompatibleClientV2(net, key, config.server) + + +def determine_user_agent(config): + """ + Set a user_agent string in the config based on the choice of plugins. + (this wasn't knowable at construction time) + + :returns: the client's User-Agent string + :rtype: `str` + """ + + # WARNING: To ensure changes are in line with Certbot's privacy + # policy, talk to a core Certbot team member before making any + # changes here. + if config.user_agent is None: + ua = ("CertbotACMEClient/{0} ({1}; {2}{8}) Authenticator/{3} Installer/{4} " + "({5}; flags: {6}) Py/{7}") + if os.environ.get("CERTBOT_DOCS") == "1": + cli_command = "certbot(-auto)" + os_info = "OS_NAME OS_VERSION" + python_version = "major.minor.patchlevel" + else: + cli_command = cli.cli_command + os_info = util.get_os_info_ua() + python_version = platform.python_version() + ua = ua.format(certbot.__version__, cli_command, os_info, + config.authenticator, config.installer, config.verb, + ua_flags(config), python_version, + "; " + config.user_agent_comment if config.user_agent_comment else "") + else: + ua = config.user_agent + return ua + +def ua_flags(config): + "Turn some very important CLI flags into clues in the user agent." + if isinstance(config, DummyConfig): + return "FLAGS" + flags = [] + if config.duplicate: + flags.append("dup") + if config.renew_by_default: + flags.append("frn") + if config.allow_subset_of_names: + flags.append("asn") + if config.noninteractive_mode: + flags.append("n") + hook_names = ("pre", "post", "renew", "manual_auth", "manual_cleanup") + hooks = [getattr(config, h + "_hook") for h in hook_names] + if any(hooks): + flags.append("hook") + return " ".join(flags) + +class DummyConfig(object): + "Shim for computing a sample user agent." + def __init__(self): + self.authenticator = "XXX" + self.installer = "YYY" + self.user_agent = None + self.verb = "SUBCOMMAND" + + def __getattr__(self, name): + "Any config properties we might have are None." + return None + +def sample_user_agent(): + "Document what this Certbot's user agent string will be like." + + return determine_user_agent(DummyConfig()) + + +def register(config, account_storage, tos_cb=None): + """Register new account with an ACME CA. + + This function takes care of generating fresh private key, + registering the account, optionally accepting CA Terms of Service + and finally saving the account. It should be called prior to + initialization of `Client`, unless account has already been created. + + :param .IConfig config: Client configuration. + + :param .AccountStorage account_storage: Account storage where newly + registered account will be saved to. Save happens only after TOS + acceptance step, so any account private keys or + `.RegistrationResource` will not be persisted if `tos_cb` + returns ``False``. + + :param tos_cb: If ACME CA requires the user to accept a Terms of + Service before registering account, client action is + necessary. For example, a CLI tool would prompt the user + acceptance. `tos_cb` must be a callable that should accept + `.RegistrationResource` and return a `bool`: ``True`` iff the + Terms of Service present in the contained + `.Registration.terms_of_service` is accepted by the client, and + ``False`` otherwise. ``tos_cb`` will be called only if the + client action is necessary, i.e. when ``terms_of_service is not + None``. This argument is optional, if not supplied it will + default to automatic acceptance! + + :raises certbot.errors.Error: In case of any client problems, in + particular registration failure, or unaccepted Terms of Service. + :raises acme.errors.Error: In case of any protocol problems. + + :returns: Newly registered and saved account, as well as protocol + API handle (should be used in `Client` initialization). + :rtype: `tuple` of `.Account` and `acme.client.Client` + + """ + # Log non-standard actions, potentially wrong API calls + if account_storage.find_all(): + logger.info("There are already existing accounts for %s", config.server) + if config.email is None: + if not config.register_unsafely_without_email: + msg = ("No email was provided and " + "--register-unsafely-without-email was not present.") + logger.warning(msg) + raise errors.Error(msg) + if not config.dry_run: + logger.info("Registering without email!") + + # If --dry-run is used, and there is no staging account, create one with no email. + if config.dry_run: + config.email = None + + # Each new registration shall use a fresh new key + rsa_key = generate_private_key( + public_exponent=65537, + key_size=config.rsa_key_size, + backend=default_backend()) + key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key)) + acme = acme_from_config_key(config, key) + # TODO: add phone? + regr = perform_registration(acme, config, tos_cb) + + acc = account.Account(regr, key) + account.report_new_account(config) + account_storage.save(acc, acme) + + eff.handle_subscription(config) + + return acc, acme + + +def perform_registration(acme, config, tos_cb): + """ + Actually register new account, trying repeatedly if there are email + problems + + :param acme.client.Client client: ACME client object. + :param .IConfig config: Client configuration. + :param Callable tos_cb: a callback to handle Term of Service agreement. + + :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: + 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: + msg = ("The ACME server believes %s is an invalid email address. " + "Please ensure it is a valid email and attempt " + "registration again." % config.email) + raise errors.Error(msg) + else: + config.email = display_ops.get_email(invalid=True) + return perform_registration(acme, config, tos_cb) + else: + raise + + +class Client(object): + """Certbot's client. + + :ivar .IConfig config: Client configuration. + :ivar .Account account: Account registered with `register`. + :ivar .AuthHandler auth_handler: Authorizations handler that will + dispatch DV challenges to appropriate authenticators + (providing `.IAuthenticator` interface). + :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) + authenticator that can solve ACME challenges. + :ivar .IInstaller installer: Installer. + :ivar acme.client.BackwardsCompatibleClientV2 acme: Optional ACME + client API handle. You might already have one from `register`. + + """ + + def __init__(self, config, account_, auth, installer, acme=None): + """Initialize a client.""" + self.config = config + self.account = account_ + self.auth = auth + self.installer = installer + + # Initialize ACME if account is provided + if acme is None and self.account is not None: + acme = acme_from_config_key(config, self.account.key, self.account.regr) + self.acme = acme + + if auth is not None: + self.auth_handler = auth_handler.AuthHandler( + auth, self.acme, self.account, self.config.pref_challs) + else: + self.auth_handler = None + + def obtain_certificate_from_csr(self, csr, orderr=None): + """Obtain certificate. + + :param .util.CSR csr: PEM-encoded Certificate Signing + Request. The key used to generate this CSR can be different + than `authkey`. + :param acme.messages.OrderResource orderr: contains authzrs + + :returns: certificate and chain as PEM byte strings + :rtype: tuple + + """ + if self.auth_handler is None: + msg = ("Unable to obtain certificate because authenticator is " + "not set.") + logger.warning(msg) + raise errors.Error(msg) + if self.account.regr is None: + raise errors.Error("Please register with the ACME server first.") + + logger.debug("CSR: %s", csr) + + if orderr is None: + orderr = self._get_order_and_authorizations(csr.data, best_effort=False) + + deadline = datetime.datetime.now() + datetime.timedelta(seconds=90) + orderr = self.acme.finalize_order(orderr, deadline) + cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem) + return cert.encode(), chain.encode() + + def obtain_certificate(self, domains, old_keypath=None): + """Obtains a certificate from the ACME server. + + `.register` must be called before `.obtain_certificate` + + :param list domains: domains to get a certificate + + :returns: certificate as PEM string, chain as PEM string, + newly generated private key (`.util.Key`), and DER-encoded + Certificate Signing Request (`.util.CSR`). + :rtype: tuple + + """ + + # We need to determine the key path, key PEM data, CSR path, + # and CSR PEM data. For a dry run, the paths are None because + # they aren't permanently saved to disk. For a lineage with + # --reuse-key, the key path and PEM data are derived from an + # existing file. + + if old_keypath is not None: + # We've been asked to reuse a specific existing private key. + # Therefore, we'll read it now and not generate a new one in + # either case below. + # + # We read in bytes here because the type of `key.pem` + # created below is also bytes. + with open(old_keypath, "rb") as f: + keypath = old_keypath + keypem = f.read() + key = util.Key(file=keypath, pem=keypem) # type: Optional[util.Key] + logger.info("Reusing existing private key from %s.", old_keypath) + else: + # The key is set to None here but will be created below. + key = None + + # Create CSR from names + if self.config.dry_run: + key = key or util.Key(file=None, + pem=crypto_util.make_key(self.config.rsa_key_size)) + csr = util.CSR(file=None, form="pem", + data=acme_crypto_util.make_csr( + key.pem, domains, self.config.must_staple)) + else: + key = key or crypto_util.init_save_key(self.config.rsa_key_size, + self.config.key_dir) + csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir) + + orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) + authzr = orderr.authorizations + auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable + successful_domains = [d for d in domains if d in auth_domains] + + # allow_subset_of_names is currently disabled for wildcard + # certificates. The reason for this and checking allow_subset_of_names + # below is because successful_domains == domains is never true if + # domains contains a wildcard because the ACME spec forbids identifiers + # in authzs from containing a wildcard character. + if self.config.allow_subset_of_names and successful_domains != domains: + if not self.config.dry_run: + os.remove(key.file) + os.remove(csr.file) + return self.obtain_certificate(successful_domains) + else: + cert, chain = self.obtain_certificate_from_csr(csr, orderr) + + return cert, chain, key, csr + + def _get_order_and_authorizations(self, csr_pem, best_effort): + # type: (str, bool) -> List[messages.OrderResource] + """Request a new order and complete its authorizations. + + :param str csr_pem: A CSR in PEM format. + :param bool best_effort: True if failing to complete all + authorizations should not raise an exception + + :returns: order resource containing its completed authorizations + :rtype: acme.messages.OrderResource + + """ + try: + orderr = self.acme.new_order(csr_pem) + except acme_errors.WildcardUnsupportedError: + raise errors.Error("The currently selected ACME CA endpoint does" + " not support issuing wildcard certificates.") + + # For a dry run, ensure we have an order with fresh authorizations + if orderr and self.config.dry_run: + deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) + if deactivated: + logger.debug("Recreating order after authz deactivations") + orderr = self.acme.new_order(csr_pem) + if failed: + logger.warning("Certbot was unable to obtain fresh authorizations for every domain" + ". The dry run will continue, but results may not be accurate.") + + authzr = self.auth_handler.handle_authorizations(orderr, best_effort) + return orderr.update(authorizations=authzr) + + # pylint: disable=no-member + def obtain_and_enroll_certificate(self, domains, certname): + """Obtain and enroll certificate. + + Get a new certificate for the specified domains using the specified + authenticator and installer, and then create a new renewable lineage + containing it. + + :param domains: domains to request a certificate for + :type domains: `list` of `str` + :param certname: requested name of lineage + :type certname: `str` or `None` + + :returns: A new :class:`certbot._internal.storage.RenewableCert` instance + referred to the enrolled cert lineage, False if the cert could not + be obtained, or None if doing a successful dry run. + + """ + cert, chain, key, _ = self.obtain_certificate(domains) + + if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or + self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): + logger.info( + "Non-standard path(s), might not work with crontab installed " + "by your operating system package manager") + + new_name = self._choose_lineagename(domains, certname) + + if self.config.dry_run: + logger.debug("Dry run: Skipping creating new lineage for %s", + new_name) + return None + return storage.RenewableCert.new_lineage( + new_name, cert, + key.pem, chain, + self.config) + + def _choose_lineagename(self, domains, certname): + """Chooses a name for the new lineage. + + :param domains: domains in certificate request + :type domains: `list` of `str` + :param certname: requested name of lineage + :type certname: `str` or `None` + + :returns: lineage name that should be used + :rtype: str + + """ + if certname: + return certname + elif util.is_wildcard_domain(domains[0]): + # Don't make files and directories starting with *. + return domains[0][2:] + return domains[0] + + def save_certificate(self, cert_pem, chain_pem, + cert_path, chain_path, fullchain_path): + """Saves the certificate received from the ACME server. + + :param str cert_pem: + :param str chain_pem: + :param str cert_path: Candidate path to a certificate. + :param str chain_path: Candidate path to a certificate chain. + :param str fullchain_path: Candidate path to a full cert chain. + + :returns: cert_path, chain_path, and fullchain_path as absolute + paths to the actual files + :rtype: `tuple` of `str` + + :raises IOError: If unable to find room to write the cert files + + """ + for path in cert_path, chain_path, fullchain_path: + util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) + + + cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) + + try: + cert_file.write(cert_pem) + finally: + cert_file.close() + logger.info("Server issued certificate; certificate written to %s", + abs_cert_path) + + chain_file, abs_chain_path =\ + _open_pem_file('chain_path', chain_path) + fullchain_file, abs_fullchain_path =\ + _open_pem_file('fullchain_path', fullchain_path) + + _save_chain(chain_pem, chain_file) + _save_chain(cert_pem + chain_pem, fullchain_file) + + return abs_cert_path, abs_chain_path, abs_fullchain_path + + def deploy_certificate(self, domains, privkey_path, + cert_path, chain_path, fullchain_path): + """Install certificate + + :param list domains: list of domains to install the certificate + :param str privkey_path: path to certificate private key + :param str cert_path: certificate file path (optional) + :param str chain_path: chain file path + + """ + if self.installer is None: + logger.warning("No installer specified, client is unable to deploy" + "the certificate") + raise errors.Error("No installer available") + + chain_path = None if chain_path is None else os.path.abspath(chain_path) + + msg = ("Unable to install the certificate") + with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): + for dom in domains: + self.installer.deploy_cert( + domain=dom, cert_path=os.path.abspath(cert_path), + key_path=os.path.abspath(privkey_path), + chain_path=chain_path, + fullchain_path=fullchain_path) + self.installer.save() # needed by the Apache plugin + + self.installer.save("Deployed ACME Certificate") + + msg = ("We were unable to install your certificate, " + "however, we successfully restored your " + "server to its prior configuration.") + with error_handler.ErrorHandler(self._rollback_and_restart, msg): + # sites may have been enabled / final cleanup + self.installer.restart() + + def enhance_config(self, domains, chain_path, ask_redirect=True): + """Enhance the configuration. + + :param list domains: list of domains to configure + :param chain_path: chain file path + :type chain_path: `str` or `None` + + :raises .errors.Error: if no installer is specified in the + client. + + """ + if self.installer is None: + logger.warning("No installer is specified, there isn't any " + "configuration to enhance.") + raise errors.Error("No installer available") + + enhanced = False + enhancement_info = ( + ("hsts", "ensure-http-header", "Strict-Transport-Security"), + ("redirect", "redirect", None), + ("staple", "staple-ocsp", chain_path), + ("uir", "ensure-http-header", "Upgrade-Insecure-Requests"),) + supported = self.installer.supported_enhancements() + + for config_name, enhancement_name, option in enhancement_info: + config_value = getattr(self.config, config_name) + if enhancement_name in supported: + if ask_redirect: + if config_name == "redirect" and config_value is None: + config_value = enhancements.ask(enhancement_name) + if not config_value: + logger.warning("Future versions of Certbot will automatically " + "configure the webserver so that all requests redirect to secure " + "HTTPS access. You can control this behavior and disable this " + "warning with the --redirect and --no-redirect flags.") + if config_value: + self.apply_enhancement(domains, enhancement_name, option) + enhanced = True + elif config_value: + logger.warning( + "Option %s is not supported by the selected installer. " + "Skipping enhancement.", config_name) + + msg = ("We were unable to restart web server") + if enhanced: + with error_handler.ErrorHandler(self._rollback_and_restart, msg): + self.installer.restart() + + def apply_enhancement(self, domains, enhancement, options=None): + """Applies an enhancement on all domains. + + :param list domains: list of ssl_vhosts (as strings) + :param str enhancement: name of enhancement, e.g. ensure-http-header + :param str options: options to enhancement, e.g. Strict-Transport-Security + + .. note:: When more `options` are needed, make options a list. + + :raises .errors.PluginError: If Enhancement is not supported, or if + there is any other problem with the enhancement. + + + """ + msg = ("We were unable to set up enhancement %s for your server, " + "however, we successfully installed your certificate." + % (enhancement)) + with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): + for dom in domains: + try: + self.installer.enhance(dom, enhancement, options) + except errors.PluginEnhancementAlreadyPresent: + if enhancement == "ensure-http-header": + logger.warning("Enhancement %s was already set.", + options) + else: + logger.warning("Enhancement %s was already set.", + enhancement) + except errors.PluginError: + logger.warning("Unable to set enhancement %s for %s", + enhancement, dom) + raise + + self.installer.save("Add enhancement %s" % (enhancement)) + + def _recovery_routine_with_msg(self, success_msg): + """Calls the installer's recovery routine and prints success_msg + + :param str success_msg: message to show on successful recovery + + """ + self.installer.recovery_routine() + reporter = zope.component.getUtility(interfaces.IReporter) + reporter.add_message(success_msg, reporter.HIGH_PRIORITY) + + def _rollback_and_restart(self, success_msg): + """Rollback the most recent checkpoint and restart the webserver + + :param str success_msg: message to show on successful rollback + + """ + logger.critical("Rolling back to previous server configuration...") + reporter = zope.component.getUtility(interfaces.IReporter) + try: + self.installer.rollback_checkpoints() + self.installer.restart() + except: + reporter.add_message( + "An error occurred and we failed to restore your config and " + "restart your server. Please post to " + "https://community.letsencrypt.org/c/help " + "with details about your configuration and this error you received.", + reporter.HIGH_PRIORITY) + raise + reporter.add_message(success_msg, reporter.HIGH_PRIORITY) + + +def validate_key_csr(privkey, csr=None): + """Validate Key and CSR files. + + Verifies that the client key and csr arguments are valid and correspond to + one another. This does not currently check the names in the CSR due to + the inability to read SANs from CSRs in python crypto libraries. + + If csr is left as None, only the key will be validated. + + :param privkey: Key associated with CSR + :type privkey: :class:`certbot.util.Key` + + :param .util.CSR csr: CSR + + :raises .errors.Error: when validation fails + + """ + # TODO: Handle all of these problems appropriately + # The client can eventually do things like prompt the user + # and allow the user to take more appropriate actions + + # Key must be readable and valid. + if privkey.pem and not crypto_util.valid_privkey(privkey.pem): + raise errors.Error("The provided key is not a valid key") + + if csr: + if csr.form == "der": + csr_obj = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_ASN1, csr.data) + cert_buffer = OpenSSL.crypto.dump_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_obj + ) + csr = util.CSR(csr.file, cert_buffer, "pem") + + # If CSR is provided, it must be readable and valid. + if csr.data and not crypto_util.valid_csr(csr.data): + raise errors.Error("The provided CSR is not a valid CSR") + + # If both CSR and key are provided, the key must be the same key used + # in the CSR. + if csr.data and privkey.pem: + if not crypto_util.csr_matches_pubkey( + csr.data, privkey.pem): + raise errors.Error("The key and CSR do not match") + + +def rollback(default_installer, checkpoints, config, plugins): + """Revert configuration the specified number of checkpoints. + + :param int checkpoints: Number of checkpoints to revert. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + + """ + # Misconfigurations are only a slight problems... allow the user to rollback + installer = plugin_selection.pick_installer( + config, default_installer, plugins, question="Which installer " + "should be used for rollback?") + + # No Errors occurred during init... proceed normally + # If installer is None... couldn't find an installer... there shouldn't be + # anything to rollback + if installer is not None: + installer.rollback_checkpoints(checkpoints) + installer.restart() + +def _open_pem_file(cli_arg_path, pem_path): + """Open a pem file. + + If cli_arg_path was set by the client, open that. + Otherwise, uniquify the file path. + + :param str cli_arg_path: the cli arg name, e.g. cert_path + :param str pem_path: the pem file path to open + + :returns: a tuple of file object and its absolute file path + + """ + if cli.set_by_cli(cli_arg_path): + return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ + os.path.abspath(pem_path) + uniq = util.unique_file(pem_path, 0o644, "wb") + return uniq[0], os.path.abspath(uniq[1]) + +def _save_chain(chain_pem, chain_file): + """Saves chain_pem at a unique path based on chain_path. + + :param str chain_pem: certificate chain in PEM format + :param str chain_file: chain file object + + """ + try: + chain_file.write(chain_pem) + finally: + chain_file.close() + + logger.info("Cert chain written to %s", chain_file.name) diff --git a/certbot/_internal/error_handler.py b/certbot/_internal/error_handler.py new file mode 100644 index 000000000..1a570e48e --- /dev/null +++ b/certbot/_internal/error_handler.py @@ -0,0 +1,172 @@ +"""Registers functions to be called if an exception or signal occurs.""" +import functools +import logging +import signal +import traceback + +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Callable, Dict, List, Union +# pylint: enable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +# _SIGNALS stores the signals that will be handled by the ErrorHandler. These +# signals were chosen as their default handler terminates the process and could +# potentially occur from inside Python. Signals such as SIGILL were not +# included as they could be a sign of something devious and we should terminate +# immediately. +if os.name != "nt": + _SIGNALS = [signal.SIGTERM] + for signal_code in [signal.SIGHUP, signal.SIGQUIT, + signal.SIGXCPU, signal.SIGXFSZ]: + # Adding only those signals that their default action is not Ignore. + # This is platform-dependent, so we check it dynamically. + if signal.getsignal(signal_code) != signal.SIG_IGN: + _SIGNALS.append(signal_code) +else: + # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. + # As consumed by CPython, most of handlers on theses signals are useless, in particular + # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops + # immediately the process without calling the attached handler. Besides, non-POSIX signals + # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the + # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable + # behavior in fact, and maps to the handler to SIGINT. However in this case, a + # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager + # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. + # + # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, + # https://docs.python.org/2/library/os.html#os.kill, + # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question + _SIGNALS = [] + + +class ErrorHandler(object): + """Context manager for running code that must be cleaned up on failure. + + The context manager allows you to register functions that will be called + when an exception (excluding SystemExit) or signal is encountered. + Usage:: + + handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs) + handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs) + + with handler: + do_something() + + Or for one cleanup function:: + + with ErrorHandler(func, args, kwargs): + do_something() + + If an exception is raised out of do_something, the cleanup functions will + be called in last in first out order. Then the exception is raised. + Similarly, if a signal is encountered, the cleanup functions are called + followed by the previously received signal handler. + + Each registered cleanup function is called exactly once. If a registered + function raises an exception, it is logged and the next function is called. + Signals received while the registered functions are executing are + deferred until they finish. + + """ + def __init__(self, func, *args, **kwargs): + self.call_on_regular_exit = False + self.body_executed = False + self.funcs = [] # type: List[Callable[[], Any]] + self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]] + self.received_signals = [] # type: List[int] + if func is not None: + self.register(func, *args, **kwargs) + + def __enter__(self): + self.body_executed = False + self._set_signal_handlers() + + def __exit__(self, exec_type, exec_value, trace): + self.body_executed = True + retval = False + # SystemExit is ignored to properly handle forks that don't exec + if exec_type is SystemExit: + return retval + elif exec_type is None: + if not self.call_on_regular_exit: + return retval + elif exec_type is errors.SignalExit: + logger.debug("Encountered signals: %s", self.received_signals) + retval = True + else: + logger.debug("Encountered exception:\n%s", "".join( + traceback.format_exception(exec_type, exec_value, trace))) + + self._call_registered() + self._reset_signal_handlers() + self._call_signals() + return retval + + def register(self, func, *args, **kwargs): + # type: (Callable, *Any, **Any) -> None + """Sets func to be run with the given arguments during cleanup. + + :param function func: function to be called in case of an error + + """ + self.funcs.append(functools.partial(func, *args, **kwargs)) + + def _call_registered(self): + """Calls all registered functions""" + logger.debug("Calling registered functions") + while self.funcs: + try: + self.funcs[-1]() + except Exception: # pylint: disable=broad-except + logger.error("Encountered exception during recovery: ", exc_info=True) + self.funcs.pop() + + def _set_signal_handlers(self): + """Sets signal handlers for signals in _SIGNALS.""" + for signum in _SIGNALS: + prev_handler = signal.getsignal(signum) + # If prev_handler is None, the handler was set outside of Python + if prev_handler is not None: + self.prev_handlers[signum] = prev_handler + signal.signal(signum, self._signal_handler) + + def _reset_signal_handlers(self): + """Resets signal handlers for signals in _SIGNALS.""" + for signum in self.prev_handlers: + signal.signal(signum, self.prev_handlers[signum]) + self.prev_handlers.clear() + + def _signal_handler(self, signum, unused_frame): + """Replacement function for handling received signals. + + Store the received signal. If we are executing the code block in + the body of the context manager, stop by raising signal exit. + + :param int signum: number of current signal + + """ + self.received_signals.append(signum) + if not self.body_executed: + raise errors.SignalExit + + def _call_signals(self): + """Finally call the deferred signals.""" + for signum in self.received_signals: + logger.debug("Calling signal %s", signum) + os.kill(os.getpid(), signum) + +class ExitHandler(ErrorHandler): + """Context manager for running code that must be cleaned up. + + Subclass of ErrorHandler, with the same usage and parameters. + In addition to cleaning up on all signals, also cleans up on + regular exit. + """ + def __init__(self, func, *args, **kwargs): + ErrorHandler.__init__(self, func, *args, **kwargs) + self.call_on_regular_exit = True diff --git a/certbot/_internal/lock.py b/certbot/_internal/lock.py new file mode 100644 index 000000000..eda2a72a1 --- /dev/null +++ b/certbot/_internal/lock.py @@ -0,0 +1,261 @@ +"""Implements file locks compatible with Linux and Windows for locking files and directories.""" +import errno +import logging +try: + import fcntl # pylint: disable=import-error +except ImportError: + import msvcrt # pylint: disable=import-error + POSIX_MODE = False +else: + POSIX_MODE = True + +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem + +logger = logging.getLogger(__name__) + + +def lock_dir(dir_path): + # type: (str) -> LockFile + """Place a lock file on the directory at dir_path. + + The lock file is placed in the root of dir_path with the name + .certbot.lock. + + :param str dir_path: path to directory + + :returns: the locked LockFile object + :rtype: LockFile + + :raises errors.LockError: if unable to acquire the lock + + """ + return LockFile(os.path.join(dir_path, '.certbot.lock')) + + +class LockFile(object): + """ + Platform independent file lock system. + LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, + instance is created, the associated file is 'locked from the point of view of the OS, + meaning that if another instance of Certbot try at the same time to acquire the same lock, + it will raise an Exception. Calling release method will release the lock, and make it + available to every other instance. + Upon exit, Certbot will also release all the locks. + This allows us to protect a file or directory from being concurrently accessed + or modified by two Certbot instances. + LockFile is platform independent: it will proceed to the appropriate OS lock mechanism + depending on Linux or Windows. + """ + def __init__(self, path): + # type: (str) -> None + """ + Create a LockFile instance on the given file path, and acquire lock. + :param str path: the path to the file that will hold a lock + """ + self._path = path + mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism + self._lock_mechanism = mechanism(path) + + self.acquire() + + def __repr__(self): + # type: () -> str + repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) + if self.is_locked(): + repr_str += 'acquired>' + else: + repr_str += 'released>' + return repr_str + + def acquire(self): + # type: () -> None + """ + Acquire the lock on the file, forbidding any other Certbot instance to acquire it. + :raises errors.LockError: if unable to acquire the lock + """ + self._lock_mechanism.acquire() + + def release(self): + # type: () -> None + """ + Release the lock on the file, allowing any other Certbot instance to acquire it. + """ + self._lock_mechanism.release() + + def is_locked(self): + # type: () -> bool + """ + Check if the file is currently locked. + :return: True if the file is locked, False otherwise + """ + return self._lock_mechanism.is_locked() + + +class _BaseLockMechanism(object): + def __init__(self, path): + # type: (str) -> None + """ + Create a lock file mechanism for Unix. + :param str path: the path to the lock file + """ + self._path = path + self._fd = None # type: Optional[int] + + def is_locked(self): + # type: () -> bool + """Check if lock file is currently locked. + :return: True if the lock file is locked + :rtype: bool + """ + return self._fd is not None + + def acquire(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + def release(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + +class _UnixLockMechanism(_BaseLockMechanism): + """ + A UNIX lock file mechanism. + This lock file is released when the locked file is closed or the + process exits. It cannot be used to provide synchronization between + threads. It is based on the lock_file package by Martin Horcicka. + """ + def acquire(self): + # type: () -> None + """Acquire the lock.""" + while self._fd is None: + # Open the file + fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) + try: + self._try_lock(fd) + if self._lock_success(fd): + self._fd = fd + finally: + # Close the file if it is not the required one + if self._fd is None: + os.close(fd) + + def _try_lock(self, fd): + # type: (int) -> None + """ + Try to acquire the lock file without blocking. + :param int fd: file descriptor of the opened file to lock + """ + try: + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError as err: + if err.errno in (errno.EACCES, errno.EAGAIN): + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + raise + + def _lock_success(self, fd): + # type: (int) -> bool + """ + Did we successfully grab the lock? + Because this class deletes the locked file when the lock is + released, it is possible another process removed and recreated + the file between us opening the file and acquiring the lock. + :param int fd: file descriptor of the opened file to lock + :returns: True if the lock was successfully acquired + :rtype: bool + """ + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since _lock_success is private and called only on Linux. + from os import stat, fstat # pylint: disable=os-module-forbidden + try: + stat1 = stat(self._path) + except OSError as err: + if err.errno == errno.ENOENT: + return False + raise + + stat2 = fstat(fd) + # If our locked file descriptor and the file on disk refer to + # the same device and inode, they're the same file. + return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino + + def release(self): + # type: () -> None + """Remove, close, and release the lock file.""" + # It is important the lock file is removed before it's released, + # otherwise: + # + # process A: open lock file + # process B: release lock file + # process A: lock file + # process A: check device and inode + # process B: delete file + # process C: open and lock a different file at the same path + try: + os.remove(self._path) + finally: + # Following check is done to make mypy happy: it ensure that self._fd, marked + # as Optional[int] is effectively int to make it compatible with os.close signature. + if self._fd is None: # pragma: no cover + raise TypeError('Error, self._fd is None.') + try: + os.close(self._fd) + finally: + self._fd = None + + +class _WindowsLockMechanism(_BaseLockMechanism): + """ + A Windows lock file mechanism. + By default on Windows, acquiring a file handler gives exclusive access to the process + and results in an effective lock. However, it is possible to explicitly acquire the + file handler in shared access in terms of read and write, and this is done by os.open + and io.open in Python. So an explicit lock needs to be done through the call of + msvcrt.locking, that will lock the first byte of the file. In theory, it is also + possible to access a file in shared delete access, allowing other processes to delete an + opened file. But this needs also to be done explicitly by all processes using the Windows + low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers + state that deleting a file opened by a process from another process is not possible with + os.open and io.open. + Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race + condition encountered on Linux is not possible on Windows, leading to a simpler workflow. + """ + def acquire(self): + """Acquire the lock""" + open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC + + fd = None + try: + # Under Windows, filesystem.open will raise directly an EACCES error + # if the lock file is already locked. + fd = filesystem.open(self._path, open_mode, 0o600) + msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) + except (IOError, OSError) as err: + if fd: + os.close(fd) + # Anything except EACCES is unexpected. Raise directly the error in that case. + if err.errno != errno.EACCES: + raise + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + + self._fd = fd + + def release(self): + """Release the lock.""" + try: + msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) + os.close(self._fd) + + try: + os.remove(self._path) + except OSError as e: + # If the lock file cannot be removed, it is not a big deal. + # Likely another instance is acquiring the lock we just released. + logger.debug(str(e)) + finally: + self._fd = None diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py new file mode 100644 index 000000000..1552131e8 --- /dev/null +++ b/certbot/_internal/main.py @@ -0,0 +1,1365 @@ +"""Certbot main entry point.""" +# pylint: disable=too-many-lines +from __future__ import print_function + +import functools +import logging.handlers +import sys + +import configobj +import josepy as jose +import zope.component + +from acme import errors as acme_errors +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module + +import certbot +from certbot._internal import account +from certbot._internal import cert_manager +from certbot import cli +from certbot._internal import client +from certbot import configuration +from certbot import constants +from certbot import crypto_util +from certbot import eff +from certbot import errors +from certbot import hooks +from certbot import interfaces +from certbot import log +from certbot._internal import renewal +from certbot._internal import reporter +from certbot._internal import storage +from certbot._internal import updater +from certbot import util +from certbot.compat import filesystem +from certbot.compat import misc +from certbot.compat import os +from certbot.display import util as display_util, ops as display_ops +from certbot.plugins import disco as plugins_disco +from certbot.plugins import enhancements +from certbot.plugins import selection as plug_sel + +USER_CANCELLED = ("User chose to cancel the operation and may " + "reinvoke the client.") + + +logger = logging.getLogger(__name__) + + +def _suggest_donation_if_appropriate(config): + """Potentially suggest a donation to support Certbot. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + assert config.verb != "renew" + if config.staging: + # --dry-run implies --staging + return + reporter_util = zope.component.getUtility(interfaces.IReporter) + msg = ("If you like Certbot, please consider supporting our work by:\n\n" + "Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n" + "Donating to EFF: https://eff.org/donate-le\n\n") + reporter_util.add_message(msg, reporter_util.LOW_PRIORITY) + +def _report_successful_dry_run(config): + """Reports on successful dry run + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + reporter_util = zope.component.getUtility(interfaces.IReporter) + assert config.verb != "renew" + reporter_util.add_message("The dry run was successful.", + reporter_util.HIGH_PRIORITY, on_crash=False) + + +def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None): + """Authenticate and enroll certificate. + + This method finds the relevant lineage, figures out what to do with it, + then performs that action. Includes calls to hooks, various reports, + checks, and requests for user input. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names to get a certificate. Defaults to `None` + :type domains: `list` of `str` + + :param certname: Name of new certificate. Defaults to `None` + :type certname: str + + :param lineage: Certificate lineage object. Defaults to `None` + :type lineage: storage.RenewableCert + + :returns: the issued certificate or `None` if doing a dry run + :rtype: storage.RenewableCert or None + + :raises errors.Error: if certificate could not be obtained + + """ + hooks.pre_hook(config) + try: + if lineage is not None: + # Renewal, where we already know the specific lineage we're + # interested in + logger.info("Renewing an existing certificate") + renewal.renew_cert(config, domains, le_client, lineage) + else: + # TREAT AS NEW REQUEST + assert domains is not None + logger.info("Obtaining a new certificate") + lineage = le_client.obtain_and_enroll_certificate(domains, certname) + if lineage is False: + raise errors.Error("Certificate could not be obtained") + elif lineage is not None: + hooks.deploy_hook(config, lineage.names(), lineage.live_dir) + finally: + hooks.post_hook(config) + + return lineage + + +def _handle_subset_cert_request(config, domains, cert): + """Figure out what to do if a previous cert had a subset of the names now requested + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param cert: Certificate object + :type cert: storage.RenewableCert + + :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname + action can be: "newcert" | "renew" | "reinstall" + :rtype: `tuple` of `str` + + """ + existing = ", ".join(cert.names()) + question = ( + "You have an existing certificate that contains a portion of " + "the domains you requested (ref: {0}){br}{br}It contains these " + "names: {1}{br}{br}You requested these names for the new " + "certificate: {2}.{br}{br}Do you want to expand and replace this existing " + "certificate with the new certificate?" + ).format(cert.configfile.filename, + existing, + ", ".join(domains), + br=os.linesep) + if config.expand or config.renew_by_default or zope.component.getUtility( + interfaces.IDisplay).yesno(question, "Expand", "Cancel", + cli_flag="--expand", + force_interactive=True): + return "renew", cert + else: + reporter_util = zope.component.getUtility(interfaces.IReporter) + reporter_util.add_message( + "To obtain a new certificate that contains these names without " + "replacing your existing certificate for {0}, you must use the " + "--duplicate option.{br}{br}" + "For example:{br}{br}{1} --duplicate {2}".format( + existing, + sys.argv[0], " ".join(sys.argv[1:]), + br=os.linesep + ), + reporter_util.HIGH_PRIORITY) + raise errors.Error(USER_CANCELLED) + + +def _handle_identical_cert_request(config, lineage): + """Figure out what to do if a lineage has the same names as a previously obtained one + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname + action can be: "newcert" | "renew" | "reinstall" + :rtype: `tuple` of `str` + + """ + if not lineage.ensure_deployed(): + return "reinstall", lineage + if renewal.should_renew(config, lineage): + return "renew", lineage + if config.reinstall: + # Set with --reinstall, force an identical certificate to be + # reinstalled without further prompting. + return "reinstall", lineage + question = ( + "You have an existing certificate that has exactly the same " + "domains or certificate name you requested and isn't close to expiry." + "{br}(ref: {0}){br}{br}What would you like to do?" + ).format(lineage.configfile.filename, br=os.linesep) + + if config.verb == "run": + keep_opt = "Attempt to reinstall this existing certificate" + elif config.verb == "certonly": + keep_opt = "Keep the existing certificate for now" + choices = [keep_opt, + "Renew & replace the cert (limit ~5 per 7 days)"] + + display = zope.component.getUtility(interfaces.IDisplay) + response = display.menu(question, choices, + default=0, force_interactive=True) + if response[0] == display_util.CANCEL: + # TODO: Add notification related to command-line options for + # skipping the menu for this case. + raise errors.Error( + "Operation canceled. You may re-run the client.") + elif response[1] == 0: + return "reinstall", lineage + elif response[1] == 1: + return "renew", lineage + raise AssertionError('This is impossible') + + +def _find_lineage_for_domains(config, domains): + """Determine whether there are duplicated names and how to handle + them (renew, reinstall, newcert, or raising an error to stop + the client run if the user chooses to cancel the operation when + prompted). + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :returns: Two-element tuple containing desired new-certificate behavior as + a string token ("reinstall", "renew", or "newcert"), plus either + a RenewableCert instance or `None` if renewal shouldn't occur. + :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` + + :raises errors.Error: If the user would like to rerun the client again. + + """ + # Considering the possibility that the requested certificate is + # related to an existing certificate. (config.duplicate, which + # is set with --duplicate, skips all of this logic and forces any + # kind of certificate to be obtained with renewal = False.) + if config.duplicate: + return "newcert", None + # TODO: Also address superset case + ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, domains) + # XXX ^ schoen is not sure whether that correctly reads the systemwide + # configuration file. + if ident_names_cert is None and subset_names_cert is None: + return "newcert", None + + if ident_names_cert is not None: + return _handle_identical_cert_request(config, ident_names_cert) + elif subset_names_cert is not None: + return _handle_subset_cert_request(config, domains, subset_names_cert) + return None, None + +def _find_cert(config, domains, certname): + """Finds an existing certificate object given domains and/or a certificate name. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :returns: Two-element tuple of a boolean that indicates if this function should be + followed by a call to fetch a certificate from the server, and either a + RenewableCert instance or None. + :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None` + + """ + action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname) + if action == "reinstall": + logger.info("Keeping the existing certificate") + return (action != "reinstall"), lineage + +def _find_lineage_for_domains_and_certname(config, domains, certname): + """Find appropriate lineage based on given domains and/or certname. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :returns: Two-element tuple containing desired new-certificate behavior as + a string token ("reinstall", "renew", or "newcert"), plus either + a RenewableCert instance or None if renewal should not occur. + + :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` + + :raises errors.Error: If the user would like to rerun the client again. + + """ + if not certname: + return _find_lineage_for_domains(config, domains) + else: + lineage = cert_manager.lineage_for_certname(config, certname) + if lineage: + if domains: + if set(cert_manager.domains_for_certname(config, certname)) != set(domains): + _ask_user_to_confirm_new_names(config, domains, certname, + lineage.names()) # raises if no + return "renew", lineage + # unnecessarily specified domains or no domains specified + return _handle_identical_cert_request(config, lineage) + else: + if domains: + return "newcert", None + else: + raise errors.ConfigurationError("No certificate with name {0} found. " + "Use -d to specify domains, or run certbot certificates to see " + "possible certificate names.".format(certname)) + +def _get_added_removed(after, before): + """Get lists of items removed from `before` + and a lists of items added to `after` + """ + added = list(set(after) - set(before)) + removed = list(set(before) - set(after)) + added.sort() + removed.sort() + return added, removed + +def _format_list(character, strings): + """Format list with given character + """ + if not strings: + formatted = "{br}(None)" + else: + formatted = "{br}{ch} " + "{br}{ch} ".join(strings) + return formatted.format( + ch=character, + br=os.linesep + ) + +def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains): + """Ask user to confirm update cert certname to contain new_domains. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param new_domains: List of new domain names + :type new_domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :param old_domains: List of old domain names + :type old_domains: `list` of `str` + + :returns: None + :rtype: None + + :raises errors.ConfigurationError: if cert name and domains mismatch + + """ + if config.renew_with_new_domains: + return + + added, removed = _get_added_removed(new_domains, old_domains) + + msg = ("You are updating certificate {0} to include new domain(s): {1}{br}{br}" + "You are also removing previously included domain(s): {2}{br}{br}" + "Did you intend to make this change?".format( + certname, + _format_list("+", added), + _format_list("-", removed), + br=os.linesep)) + obj = zope.component.getUtility(interfaces.IDisplay) + if not obj.yesno(msg, "Update cert", "Cancel", default=True): + raise errors.ConfigurationError("Specified mismatched cert name and domains.") + +def _find_domains_or_certname(config, installer, question=None): + """Retrieve domains and certname from config or user input. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param `str` question: Overriding dialog question to ask the user if asked + to choose from domain names. + + :returns: Two-part tuple of domains and certname + :rtype: `tuple` of list of `str` and `str` + + :raises errors.Error: Usage message, if parameters are not used correctly + + """ + domains = None + certname = config.certname + # first, try to get domains from the config + if config.domains: + domains = config.domains + # if we can't do that but we have a certname, get the domains + # with that certname + elif certname: + domains = cert_manager.domains_for_certname(config, certname) + + # that certname might not have existed, or there was a problem. + # try to get domains from the user. + if not domains: + domains = display_ops.choose_names(installer, question) + + if not domains and not certname: + raise errors.Error("Please specify --domains, or --installer that " + "will help in domain names autodiscovery, or " + "--cert-name for an existing certificate name.") + + return domains, certname + + +def _report_new_cert(config, cert_path, fullchain_path, key_path=None): + """Reports the creation of a new certificate to the user. + + :param cert_path: path to certificate + :type cert_path: str + + :param fullchain_path: path to full chain + :type fullchain_path: str + + :param key_path: path to private key, if available + :type key_path: str + + :returns: `None` + :rtype: None + + """ + if config.dry_run: + _report_successful_dry_run(config) + return + + assert cert_path and fullchain_path, "No certificates saved to report." + + expiry = crypto_util.notAfter(cert_path).date() + reporter_util = zope.component.getUtility(interfaces.IReporter) + # Print the path to fullchain.pem because that's what modern webservers + # (Nginx and Apache2.4) will want. + + verbswitch = ' with the "certonly" option' if config.verb == "run" else "" + privkey_statement = 'Your key file has been saved at:{br}{0}{br}'.format( + key_path, br=os.linesep) if key_path else "" + # XXX Perhaps one day we could detect the presence of known old webservers + # and say something more informative here. + msg = ('Congratulations! Your certificate and chain have been saved at:{br}' + '{0}{br}{1}' + 'Your cert will expire on {2}. To obtain a new or tweaked version of this ' + 'certificate in the future, simply run {3} again{4}. ' + 'To non-interactively renew *all* of your certificates, run "{3} renew"' + .format(fullchain_path, privkey_statement, expiry, cli.cli_command, verbswitch, + br=os.linesep)) + reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY) + + +def _determine_account(config): + """Determine which account to use. + + If ``config.account`` is ``None``, it will be updated based on the + user input. Same for ``config.email``. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: Account and optionally ACME client API (biproduct of new + registration). + :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client` + + :raises errors.Error: If unable to register an account with ACME server + + """ + def _tos_cb(terms_of_service): + if config.tos: + return True + msg = ("Please read the Terms of Service at {0}. You " + "must agree in order to register with the ACME " + "server at {1}".format( + terms_of_service, config.server)) + obj = zope.component.getUtility(interfaces.IDisplay) + result = obj.yesno(msg, "Agree", "Cancel", + cli_flag="--agree-tos", force_interactive=True) + if not result: + raise errors.Error( + "Registration cannot proceed without accepting " + "Terms of Service.") + return None + + account_storage = account.AccountFileStorage(config) + acme = None + + if config.account is not None: + acc = account_storage.load(config.account) + else: + accounts = account_storage.find_all() + if len(accounts) > 1: + acc = display_ops.choose_account(accounts) + elif len(accounts) == 1: + acc = accounts[0] + else: # no account registered yet + if config.email is None and not config.register_unsafely_without_email: + config.email = display_ops.get_email() + try: + acc, acme = client.register( + config, account_storage, tos_cb=_tos_cb) + except errors.MissingCommandlineFlag: + raise + except errors.Error: + logger.debug("", exc_info=True) + raise errors.Error( + "Unable to register an account with ACME server") + + config.account = acc.id + return acc, acme + + +def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-branches + """Does the user want to delete their now-revoked certs? If run in non-interactive mode, + deleting happens automatically. + + :param config: parsed command line arguments + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping + archive dir is found for the specified lineage, etc ... + """ + display = zope.component.getUtility(interfaces.IDisplay) + reporter_util = zope.component.getUtility(interfaces.IReporter) + + attempt_deletion = config.delete_after_revoke + if attempt_deletion is None: + msg = ("Would you like to delete the cert(s) you just revoked, along with all earlier and " + "later versions of the cert?") + attempt_deletion = display.yesno(msg, yes_label="Yes (recommended)", no_label="No", + force_interactive=True, default=True) + + if not attempt_deletion: + reporter_util.add_message("Not deleting revoked certs.", reporter_util.LOW_PRIORITY) + return + + # config.cert_path must have been set + # config.certname may have been set + assert config.cert_path + + if not config.certname: + config.certname = cert_manager.cert_path_to_lineage(config) + + # don't delete if the archive_dir is used by some other lineage + archive_dir = storage.full_archive_path( + configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)), + config, config.certname) + try: + cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir], + lambda x: x.archive_dir, lambda x: x) + except errors.OverlappingMatchFound: + msg = ('Not deleting revoked certs due to overlapping archive dirs. More than ' + 'one lineage is using {0}'.format(archive_dir)) + reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY) + return + except Exception as e: + msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},' + 'original exception: {3}') + msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e) + raise errors.Error(msg) + + cert_manager.delete(config) + + +def _init_le_client(config, authenticator, installer): + """Initialize Let's Encrypt Client + + :param config: Configuration object + :type config: interfaces.IConfig + + :param authenticator: Acme authentication handler + :type authenticator: interfaces.IAuthenticator + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: client: Client object + :rtype: client.Client + + """ + if authenticator is not None: + # if authenticator was given, then we will need account... + acc, acme = _determine_account(config) + logger.debug("Picked account: %r", acc) + # XXX + #crypto_util.validate_key_csr(acc.key) + else: + acc, acme = None, None + + return client.Client(config, acc, authenticator, installer, acme=acme) + + +def unregister(config, unused_plugins): + """Deactivate account on server + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + account_storage = account.AccountFileStorage(config) + accounts = account_storage.find_all() + reporter_util = zope.component.getUtility(interfaces.IReporter) + + if not accounts: + return "Could not find existing account to deactivate." + yesno = zope.component.getUtility(interfaces.IDisplay).yesno + prompt = ("Are you sure you would like to irrevocably deactivate " + "your account?") + wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort', + default=True) + + if not wants_deactivate: + return "Deactivation aborted." + + acc, acme = _determine_account(config) + cb_client = client.Client(config, acc, None, None, acme=acme) + + # delete on boulder + cb_client.acme.deactivate_registration(acc.regr) + account_files = account.AccountFileStorage(config) + # delete local account files + account_files.delete(config.account) + + reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY) + return None + + +def register(config, unused_plugins): + """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 'certbot 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 accounts: + # 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 None + + +def update_account(config, unused_plugins): + """Modify 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 + + """ + # Portion of _determine_account logic to see whether accounts already + # exist or not. + account_storage = account.AccountFileStorage(config) + accounts = account_storage.find_all() + reporter_util = zope.component.getUtility(interfaces.IReporter) + add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY) + + if not accounts: + return "Could not find an existing account to update." + if config.email is None: + if config.register_unsafely_without_email: + return ("--register-unsafely-without-email provided, however, a " + "new e-mail address must\ncurrently be provided when " + "updating a registration.") + config.email = display_ops.get_email(optional=False) + + acc, acme = _determine_account(config) + cb_client = client.Client(config, acc, None, None, acme=acme) + # We rely on an exception to interrupt this process if it didn't work. + acc_contacts = ['mailto:' + email for email in config.email.split(',')] + prev_regr_uri = acc.regr.uri + acc.regr = cb_client.acme.update_registration(acc.regr.update( + body=acc.regr.body.update(contact=acc_contacts))) + # A v1 account being used as a v2 account will result in changing the uri to + # the v2 uri. Since it's the same object on disk, put it back to the v1 uri + # so that we can also continue to use the account object with acmev1. + acc.regr = acc.regr.update(uri=prev_regr_uri) + account_storage.save_regr(acc, cb_client.acme) + eff.handle_subscription(config) + add_msg("Your e-mail address was updated to {0}.".format(config.email)) + return None + +def _install_cert(config, le_client, domains, lineage=None): + """Install a cert + + :param config: Configuration object + :type config: interfaces.IConfig + + :param le_client: Client object + :type le_client: client.Client + + :param domains: List of domains + :type domains: `list` of `str` + + :param lineage: Certificate lineage object. Defaults to `None` + :type lineage: storage.RenewableCert + + :returns: `None` + :rtype: None + + """ + path_provider = lineage if lineage else config + assert path_provider.cert_path is not None + + le_client.deploy_certificate(domains, path_provider.key_path, + path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path) + le_client.enhance_config(domains, path_provider.chain_path) + + +def install(config, plugins): + """Install a previously obtained cert in a server. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + # XXX: Update for renewer/RenewableCert + # FIXME: be consistent about whether errors are raised or returned from + # this function ... + + try: + installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "install") + except errors.PluginSelectionError as e: + return str(e) + + custom_cert = (config.key_path and config.cert_path) + if not config.certname and not custom_cert: + certname_question = "Which certificate would you like to install?" + config.certname = cert_manager.get_certnames( + config, "install", allow_multiple=False, + custom_prompt=certname_question)[0] + + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + # If cert-path is defined, populate missing (ie. not overridden) values. + # Unfortunately this can't be done in argument parser, as certificate + # manager needs the access to renewal directory paths + if config.certname: + config = _populate_from_certname(config) + elif enhancements.are_requested(config): + # Preflight config check + raise errors.ConfigurationError("One or more of the requested enhancements " + "require --cert-name to be provided") + + if config.key_path and config.cert_path: + _check_certificate_and_key(config) + domains, _ = _find_domains_or_certname(config, installer) + le_client = _init_le_client(config, authenticator=None, installer=installer) + _install_cert(config, le_client, domains) + else: + raise errors.ConfigurationError("Path to certificate or key was not defined. " + "If your certificate is managed by Certbot, please use --cert-name " + "to define which certificate you would like to install.") + + if enhancements.are_requested(config): + # In the case where we don't have certname, we have errored out already + lineage = cert_manager.lineage_for_certname(config, config.certname) + enhancements.enable(lineage, domains, installer, config) + + return None + +def _populate_from_certname(config): + """Helper function for install to populate missing config values from lineage + defined by --cert-name.""" + + lineage = cert_manager.lineage_for_certname(config, config.certname) + if not lineage: + return config + if not config.key_path: + config.namespace.key_path = lineage.key_path + if not config.cert_path: + config.namespace.cert_path = lineage.cert_path + if not config.chain_path: + config.namespace.chain_path = lineage.chain_path + if not config.fullchain_path: + config.namespace.fullchain_path = lineage.fullchain_path + return config + +def _check_certificate_and_key(config): + if not os.path.isfile(filesystem.realpath(config.cert_path)): + raise errors.ConfigurationError("Error while reading certificate from path " + "{0}".format(config.cert_path)) + if not os.path.isfile(filesystem.realpath(config.key_path)): + raise errors.ConfigurationError("Error while reading private key from path " + "{0}".format(config.key_path)) +def plugins_cmd(config, plugins): + """List server software plugins. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + logger.debug("Expected interfaces: %s", config.ifaces) + + ifaces = [] if config.ifaces is None else config.ifaces + filtered = plugins.visible().ifaces(ifaces) + logger.debug("Filtered plugins: %r", filtered) + + notify = functools.partial(zope.component.getUtility( + interfaces.IDisplay).notification, pause=False) + if not config.init and not config.prepare: + notify(str(filtered)) + return + + filtered.init(config) + verified = filtered.verify(ifaces) + logger.debug("Verified plugins: %r", verified) + + if not config.prepare: + notify(str(verified)) + return + + verified.prepare() + available = verified.available() + logger.debug("Prepared plugins: %s", available) + notify(str(available)) + + +def enhance(config, plugins): + """Add security enhancements to existing configuration + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + supported_enhancements = ["hsts", "redirect", "uir", "staple"] + # Check that at least one enhancement was requested on command line + oldstyle_enh = any([getattr(config, enh) for enh in supported_enhancements]) + if not enhancements.are_requested(config) and not oldstyle_enh: + msg = ("Please specify one or more enhancement types to configure. To list " + "the available enhancement types, run:\n\n%s --help enhance\n") + logger.warning(msg, sys.argv[0]) + raise errors.MisconfigurationError("No enhancements requested, exiting.") + + try: + installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance") + except errors.PluginSelectionError as e: + return str(e) + + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + + certname_question = ("Which certificate would you like to use to enhance " + "your configuration?") + config.certname = cert_manager.get_certnames( + config, "enhance", allow_multiple=False, + custom_prompt=certname_question)[0] + cert_domains = cert_manager.domains_for_certname(config, config.certname) + if config.noninteractive_mode: + domains = cert_domains + else: + domain_question = ("Which domain names would you like to enable the " + "selected enhancements for?") + domains = display_ops.choose_values(cert_domains, domain_question) + if not domains: + raise errors.Error("User cancelled the domain selection. No domains " + "defined, exiting.") + + lineage = cert_manager.lineage_for_certname(config, config.certname) + if not config.chain_path: + config.chain_path = lineage.chain_path + if oldstyle_enh: + le_client = _init_le_client(config, authenticator=None, installer=installer) + le_client.enhance_config(domains, config.chain_path, ask_redirect=False) + if enhancements.are_requested(config): + enhancements.enable(lineage, domains, installer, config) + + return None + + +def rollback(config, plugins): + """Rollback server configuration changes made during install. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + client.rollback(config.installer, config.checkpoints, config, plugins) + +def update_symlinks(config, unused_plugins): + """Update the certificate file family symlinks + + Use the information in the config file to make symlinks point to + the correct archive directory. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.update_live_symlinks(config) + +def rename(config, unused_plugins): + """Rename a certificate + + Use the information in the config file to rename an existing + lineage. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.rename_lineage(config) + +def delete(config, unused_plugins): + """Delete a certificate + + Use the information in the config file to delete an existing + lineage. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.delete(config) + +def certificates(config, unused_plugins): + """Display information about certs configured with Certbot + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.certificates(config) + +# TODO: coop with renewal config +def revoke(config, unused_plugins): + """Revoke a previously obtained certificate. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` or string indicating error in case of error + :rtype: None or str + + """ + # For user-agent construction + config.installer = config.authenticator = None + + if config.cert_path is None and config.certname: + config.cert_path = storage.cert_path_for_cert_name(config, config.certname) + elif not config.cert_path or (config.cert_path and config.certname): + # intentionally not supporting --cert-path & --cert-name together, + # to avoid dealing with mismatched values + raise errors.Error("Error! Exactly one of --cert-path or --cert-name must be specified!") + + if config.key_path is not None: # revocation by cert key + logger.debug("Revoking %s using cert key %s", + config.cert_path[0], config.key_path[0]) + crypto_util.verify_cert_matches_priv_key(config.cert_path[0], config.key_path[0]) + key = jose.JWK.load(config.key_path[1]) + acme = client.acme_from_config_key(config, key) + else: # revocation by account key + logger.debug("Revoking %s using Account Key", config.cert_path[0]) + acc, _ = _determine_account(config) + acme = client.acme_from_config_key(config, acc.key, acc.regr) + cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0] + logger.debug("Reason code for revocation: %s", config.reason) + try: + acme.revoke(jose.ComparableX509(cert), config.reason) + _delete_if_appropriate(config) + except acme_errors.ClientError as e: + return str(e) + + display_ops.success_revocation(config.cert_path[0]) + return None + + +def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals + """Obtain a certificate and install. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + # TODO: Make run as close to auth + install as possible + # Possible difficulties: config.csr was hacked into auth + try: + installer, authenticator = plug_sel.choose_configurator_plugins(config, plugins, "run") + except errors.PluginSelectionError as e: + return str(e) + + # Preflight check for enhancement support by the selected installer + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + + # TODO: Handle errors from _init_le_client? + le_client = _init_le_client(config, authenticator, installer) + + domains, certname = _find_domains_or_certname(config, installer) + should_get_cert, lineage = _find_cert(config, domains, certname) + + new_lineage = lineage + if should_get_cert: + new_lineage = _get_and_save_cert(le_client, config, domains, + certname, lineage) + + cert_path = new_lineage.cert_path if new_lineage else None + fullchain_path = new_lineage.fullchain_path if new_lineage else None + key_path = new_lineage.key_path if new_lineage else None + _report_new_cert(config, cert_path, fullchain_path, key_path) + + _install_cert(config, le_client, domains, new_lineage) + + if enhancements.are_requested(config) and new_lineage: + enhancements.enable(new_lineage, domains, installer, config) + + if lineage is None or not should_get_cert: + display_ops.success_installation(domains) + else: + display_ops.success_renewal(domains) + + _suggest_donation_if_appropriate(config) + return None + + +def _csr_get_and_save_cert(config, le_client): + """Obtain a cert using a user-supplied CSR + + This works differently in the CSR case (for now) because we don't + have the privkey, and therefore can't construct the files for a lineage. + So we just save the cert & chain to disk :/ + + :param config: Configuration object + :type config: interfaces.IConfig + + :param client: Client object + :type client: client.Client + + :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files + :rtype: `tuple` of `str` + + """ + csr, _ = config.actual_csr + cert, chain = le_client.obtain_certificate_from_csr(csr) + if config.dry_run: + logger.debug( + "Dry run: skipping saving certificate to %s", config.cert_path) + return None, None + cert_path, _, fullchain_path = le_client.save_certificate( + cert, chain, os.path.normpath(config.cert_path), + os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path)) + return cert_path, fullchain_path + +def renew_cert(config, plugins, lineage): + """Renew & save an existing cert. Do not install it. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :returns: `None` + :rtype: None + + :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass + + """ + try: + # installers are used in auth mode to determine domain names + installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") + except errors.PluginSelectionError as e: + logger.info("Could not choose appropriate plugin: %s", e) + raise + le_client = _init_le_client(config, auth, installer) + + renewed_lineage = _get_and_save_cert(le_client, config, lineage=lineage) + + notify = zope.component.getUtility(interfaces.IDisplay).notification + if installer is None: + notify("new certificate deployed without reload, fullchain is {0}".format( + lineage.fullchain), pause=False) + else: + # In case of a renewal, reload server to pick up new certificate. + # In principle we could have a configuration option to inhibit this + # from happening. + # Run deployer + updater.run_renewal_deployer(config, renewed_lineage, installer) + installer.restart() + notify("new certificate deployed with reload of {0} server; fullchain is {1}".format( + config.installer, lineage.fullchain), pause=False) + +def certonly(config, plugins): + """Authenticate & obtain cert, but do not install it. + + This implements the 'certonly' subcommand. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + :raises errors.Error: If specified plugin could not be used + + """ + # SETUP: Select plugins and construct a client instance + try: + # installers are used in auth mode to determine domain names + installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") + except errors.PluginSelectionError as e: + logger.info("Could not choose appropriate plugin: %s", e) + raise + + le_client = _init_le_client(config, auth, installer) + + if config.csr: + cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client) + _report_new_cert(config, cert_path, fullchain_path) + _suggest_donation_if_appropriate(config) + return + + domains, certname = _find_domains_or_certname(config, installer) + should_get_cert, lineage = _find_cert(config, domains, certname) + + if not should_get_cert: + notify = zope.component.getUtility(interfaces.IDisplay).notification + notify("Certificate not yet due for renewal; no action taken.", pause=False) + return + + lineage = _get_and_save_cert(le_client, config, domains, certname, lineage) + + cert_path = lineage.cert_path if lineage else None + fullchain_path = lineage.fullchain_path if lineage else None + key_path = lineage.key_path if lineage else None + _report_new_cert(config, cert_path, fullchain_path, key_path) + _suggest_donation_if_appropriate(config) + +def renew(config, unused_plugins): + """Renew previously-obtained certificates. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + try: + renewal.handle_renewal_request(config) + finally: + hooks.run_saved_post_hooks() + + +def make_or_verify_needed_dirs(config): + """Create or verify existence of config, work, and hook directories. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + + hook_dirs = (config.renewal_pre_hooks_dir, + config.renewal_deploy_hooks_dir, + config.renewal_post_hooks_dir,) + for hook_dir in hook_dirs: + util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) + + +def set_displayer(config): + """Set the displayer + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + if config.quiet: + config.noninteractive_mode = True + displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) \ + # type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay] + elif config.noninteractive_mode: + displayer = display_util.NoninteractiveDisplay(sys.stdout) + else: + displayer = display_util.FileDisplay(sys.stdout, + config.force_interactive) + zope.component.provideUtility(displayer) + + +def main(cli_args=None): + """Command line argument parsing and main script execution. + + :returns: result of requested command + + :raises errors.Error: OS errors triggered by wrong permissions + :raises errors.Error: error if plugin command is not supported + + """ + if not cli_args: + cli_args = sys.argv[1:] + + log.pre_arg_parse_setup() + + plugins = plugins_disco.PluginsRegistry.find_all() + logger.debug("certbot version: %s", certbot.__version__) + # do not log `config`, as it contains sensitive data (e.g. revoke --key)! + logger.debug("Arguments: %r", cli_args) + logger.debug("Discovered plugins: %r", plugins) + + # note: arg parser internally handles --help (and exits afterwards) + args = cli.prepare_and_parse_args(plugins, cli_args) + config = configuration.NamespaceConfig(args) + zope.component.provideUtility(config) + + # On windows, shell without administrative right cannot create symlinks required by certbot. + # So we check the rights before continuing. + misc.raise_for_non_administrative_windows_rights() + + try: + log.post_arg_parse_setup(config) + make_or_verify_needed_dirs(config) + except errors.Error: + # Let plugins_cmd be run as un-privileged user. + if config.func != plugins_cmd: + raise + + set_displayer(config) + + # Reporter + report = reporter.Reporter(config) + zope.component.provideUtility(report) + util.atexit_register(report.print_messages) + + return config.func(config, plugins) + + +if __name__ == "__main__": + err_string = main() + if err_string: + logger.warning("Exiting with message %s", err_string) + sys.exit(err_string) # pragma: no cover diff --git a/certbot/_internal/notify.py b/certbot/_internal/notify.py new file mode 100644 index 000000000..dda0a85af --- /dev/null +++ b/certbot/_internal/notify.py @@ -0,0 +1,34 @@ +"""Send e-mail notification to system administrators.""" + +import email +import smtplib +import socket +import subprocess + + +def notify(subject, whom, what): + """Send email notification. + + Try to notify the addressee (``whom``) by e-mail, with Subject: + defined by ``subject`` and message body by ``what``. + + """ + msg = email.message_from_string(what) + msg.add_header("From", "Certbot renewal agent ") + msg.add_header("To", whom) + msg.add_header("Subject", subject) + msg = msg.as_string() + try: + lmtp = smtplib.LMTP() + lmtp.connect() + lmtp.sendmail("root", [whom], msg) + except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused, + smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error): + # We should try using /usr/sbin/sendmail in this case + try: + proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"], + stdin=subprocess.PIPE) + proc.communicate(msg) + except OSError: + return False + return True diff --git a/certbot/_internal/ocsp.py b/certbot/_internal/ocsp.py new file mode 100644 index 000000000..2a63412a0 --- /dev/null +++ b/certbot/_internal/ocsp.py @@ -0,0 +1,292 @@ +"""Tools for checking certificate revocation.""" +import logging +import re +from datetime import datetime, timedelta +from subprocess import Popen, PIPE + +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp # pylint: disable=import-error + getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp = None # type: ignore +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +import pytz +import requests + +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot import crypto_util +from certbot import errors +from certbot._internal.storage import RenewableCert # pylint: disable=unused-import +from certbot import util + +logger = logging.getLogger(__name__) + + +class RevocationChecker(object): + """This class figures out OCSP checking on this system, and performs it.""" + + def __init__(self, enforce_openssl_binary_usage=False): + self.broken = False + self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp + + if self.use_openssl_binary: + if not util.exe_exists("openssl"): + logger.info("openssl not installed, can't check revocation") + self.broken = True + return + + # New versions of openssl want -header var=val, old ones want -header var val + test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], + stdout=PIPE, stderr=PIPE, universal_newlines=True) + _out, err = test_host_format.communicate() + if "Missing =" in err: + self.host_args = lambda host: ["Host=" + host] + else: + self.host_args = lambda host: ["Host", host] + + def ocsp_revoked(self, cert): + # type: (RenewableCert) -> bool + """Get revoked status for a particular cert version. + + .. todo:: Make this a non-blocking call + + :param `.storage.RenewableCert` cert: Certificate object + :returns: True if revoked; False if valid or the check failed or cert is expired. + :rtype: bool + + """ + cert_path, chain_path = cert.cert, cert.chain + + if self.broken: + return False + + # Let's Encrypt doesn't update OCSP for expired certificates, + # so don't check OCSP if the cert is expired. + # https://github.com/certbot/certbot/issues/7152 + now = pytz.UTC.fromutc(datetime.utcnow()) + if cert.target_expiry <= now: + return False + + url, host = _determine_ocsp_server(cert_path) + if not host or not url: + return False + + if self.use_openssl_binary: + return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) + return _check_ocsp_cryptography(cert_path, chain_path, url) + + def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): + # type: (str, str, str, str) -> bool + # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! + cmd = ["openssl", "ocsp", + "-no_nonce", + "-issuer", chain_path, + "-cert", cert_path, + "-url", url, + "-CAfile", chain_path, + "-verify_other", chain_path, + "-trust_other", + "-header"] + self.host_args(host) + logger.debug("Querying OCSP for %s", cert_path) + logger.debug(" ".join(cmd)) + try: + output, err = util.run_script(cmd, log=logger.debug) + except errors.SubprocessError: + logger.info("OCSP check failed for %s (are we offline?)", cert_path) + return False + return _translate_ocsp_query(cert_path, output, err) + + +def _determine_ocsp_server(cert_path): + # type: (str) -> Tuple[Optional[str], Optional[str]] + """Extract the OCSP server host from a certificate. + + :param str cert_path: Path to the cert we're checking OCSP for + :rtype tuple: + :returns: (OCSP server URL or None, OCSP server host or None) + + """ + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + try: + extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + ocsp_oid = x509.AuthorityInformationAccessOID.OCSP + descriptions = [description for description in extension.value + if description.access_method == ocsp_oid] + + url = descriptions[0].access_location.value + except (x509.ExtensionNotFound, IndexError): + logger.info("Cannot extract OCSP URI from %s", cert_path) + return None, None + + url = url.rstrip() + host = url.partition("://")[2].rstrip("/") + + if host: + return url, host + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None + + +def _check_ocsp_cryptography(cert_path, chain_path, url): + # type: (str, str, str) -> bool + # Retrieve OCSP response + with open(chain_path, 'rb') as file_handler: + issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + request_binary = request.public_bytes(serialization.Encoding.DER) + try: + response = requests.post(url, data=request_binary, + headers={'Content-Type': 'application/ocsp-request'}) + except requests.exceptions.RequestException: + logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True) + return False + if response.status_code != 200: + logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code) + return False + + response_ocsp = ocsp.load_der_ocsp_response(response.content) + + # Check OCSP response validity + if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: + logger.error("Invalid OCSP response status for %s: %s", + cert_path, response_ocsp.response_status) + return False + + # Check OCSP signature + try: + _check_ocsp_response(response_ocsp, request, issuer, cert_path) + except UnsupportedAlgorithm as e: + logger.error(str(e)) + except errors.Error as e: + logger.error(str(e)) + except InvalidSignature: + logger.error('Invalid signature on OCSP response for %s', cert_path) + except AssertionError as error: + logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) + else: + # Check OCSP certificate status + logger.debug("OCSP certificate status for %s is: %s", + cert_path, response_ocsp.certificate_status) + return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED + + return False + + +def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): + """Verify that the OCSP is valid for serveral criterias""" + # Assert OCSP response corresponds to the certificate we are talking about + if response_ocsp.serial_number != request_ocsp.serial_number: + raise AssertionError('the certificate in response does not correspond ' + 'to the certificate in request') + + # Assert signature is valid + _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path) + + # Assert issuer in response is the expected one + if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) + or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash + or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): + raise AssertionError('the issuer does not correspond to issuer of the certificate.') + + # In following checks, two situations can occur: + # * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate + # * nextUpdate is not set, and requirement is thisUpdate < now + # NB1: We add a validity period tolerance to handle clock time inconsistencies, + # value is 5 min like for OpenSSL. + # NB2: Another check is to verify that thisUpdate is not too old, it is optional + # for OpenSSL, so we do not do it here. + # See OpenSSL implementation as a reference: + # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 + now = datetime.utcnow() # thisUpdate/nextUpdate are expressed in UTC/GMT time zone + if not response_ocsp.this_update: + raise AssertionError('param thisUpdate is not set.') + if response_ocsp.this_update > now + timedelta(minutes=5): + raise AssertionError('param thisUpdate is in the future.') + if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5): + raise AssertionError('param nextUpdate is in the past.') + + +def _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path): + """Verify an OCSP response signature against certificate issuer or responder""" + if response_ocsp.responder_name == issuer_cert.subject: + # Case where the OCSP responder is also the certificate issuer + logger.debug('OCSP response for certificate %s is signed by the certificate\'s issuer.', + cert_path) + responder_cert = issuer_cert + else: + # Case where the OCSP responder is not the certificate issuer + logger.debug('OCSP response for certificate %s is delegated to an external responder.', + cert_path) + + responder_certs = [cert for cert in response_ocsp.certificates + if cert.subject == response_ocsp.responder_name] + if not responder_certs: + raise AssertionError('no matching responder certificate could be found') + + # We suppose here that the ACME server support only one certificate in the OCSP status + # request. This is currently the case for LetsEncrypt servers. + # See https://github.com/letsencrypt/boulder/issues/2331 + responder_cert = responder_certs[0] + + if responder_cert.issuer != issuer_cert.subject: + raise AssertionError('responder certificate is not signed ' + 'by the certificate\'s issuer') + + try: + extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value + except (x509.ExtensionNotFound, IndexError): + delegate_authorized = False + if not delegate_authorized: + raise AssertionError('responder is not authorized by issuer to sign OCSP responses') + + # Following line may raise UnsupportedAlgorithm + chosen_hash = responder_cert.signature_hash_algorithm + # For a delegate OCSP responder, we need first check that its certificate is effectively + # signed by the certificate issuer. + crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, + responder_cert.tbs_certificate_bytes, chosen_hash) + + # Following line may raise UnsupportedAlgorithm + chosen_hash = response_ocsp.signature_hash_algorithm + # We check that the OSCP response is effectively signed by the responder + # (an authorized delegate one or the certificate issuer itself). + crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature, + response_ocsp.tbs_response_bytes, chosen_hash) + + +def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): + """Parse openssl's weird output to work out what it means.""" + + states = ("good", "revoked", "unknown") + patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states] + good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns) + + warning = good.group(1) if good else None + + if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: + logger.info("Revocation status for %s is unknown", cert_path) + logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) + return False + elif good and not warning: + return False + elif revoked: + warning = revoked.group(1) + if warning: + logger.info("OCSP revocation warning: %s", warning) + return True + else: + logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", + ocsp_output, ocsp_errors) + return False diff --git a/certbot/_internal/renewal.py b/certbot/_internal/renewal.py new file mode 100644 index 000000000..c11e675c1 --- /dev/null +++ b/certbot/_internal/renewal.py @@ -0,0 +1,476 @@ +"""Functionality for autorenewal and associated juggling of configurations""" +from __future__ import print_function + +import copy +import itertools +import logging +import random +import sys +import time +import traceback + +import OpenSSL +import six +import zope.component + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import cli +from certbot import crypto_util +from certbot import errors +from certbot import hooks +from certbot import interfaces +from certbot._internal import storage +from certbot._internal import updater +from certbot import util +from certbot.compat import os +from certbot.plugins import disco as plugins_disco + +logger = logging.getLogger(__name__) + +# These are the items which get pulled out of a renewal configuration +# file's renewalparams and actually used in the client configuration +# during the renewal process. We have to record their types here because +# the renewal configuration process loses this information. +STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent", + "server", "account", "authenticator", "installer", + "renew_hook", "pre_hook", "post_hook", "http01_address"] +INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"] +BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key", + "autorenew"] + +CONFIG_ITEMS = set(itertools.chain( + BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',))) + + +def _reconstitute(config, full_path): + """Try to instantiate a RenewableCert, updating config with relevant items. + + This is specifically for use in renewal and enforces several checks + and policies to ensure that we can try to proceed with the renewal + request. The config argument is modified by including relevant options + read from the renewal configuration file. + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param str full_path: Absolute path to the configuration file that + defines this lineage + + :returns: the RenewableCert object or None if a fatal error occurred + :rtype: `storage.RenewableCert` or NoneType + + """ + try: + renewal_candidate = storage.RenewableCert(full_path, config) + except (errors.CertStorageError, IOError): + logger.warning("", exc_info=True) + logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + if "renewalparams" not in renewal_candidate.configuration: + logger.warning("Renewal configuration file %s lacks " + "renewalparams. Skipping.", full_path) + return None + renewalparams = renewal_candidate.configuration["renewalparams"] + if "authenticator" not in renewalparams: + logger.warning("Renewal configuration file %s does not specify " + "an authenticator. Skipping.", full_path) + return None + # Now restore specific values along with their data types, if + # those elements are present. + try: + restore_required_config_elements(config, renewalparams) + _restore_plugin_configs(config, renewalparams) + except (ValueError, errors.Error) as error: + logger.warning( + "An error occurred while parsing %s. The error was %s. " + "Skipping the file.", full_path, str(error)) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + + try: + config.domains = [util.enforce_domain_sanity(d) + for d in renewal_candidate.names()] + except errors.ConfigurationError as error: + logger.warning("Renewal configuration file %s references a cert " + "that contains an invalid domain name. The problem " + "was: %s. Skipping.", full_path, error) + return None + + return renewal_candidate + + +def _restore_webroot_config(config, renewalparams): + """ + webroot_map is, uniquely, a dict, and the general-purpose configuration + restoring logic is not able to correctly parse it from the serialized + form. + """ + if "webroot_map" in renewalparams and not cli.set_by_cli("webroot_map"): + config.webroot_map = renewalparams["webroot_map"] + # To understand why webroot_path and webroot_map processing are not mutually exclusive, + # see https://github.com/certbot/certbot/pull/7095 + if "webroot_path" in renewalparams and not cli.set_by_cli("webroot_path"): + wp = renewalparams["webroot_path"] + if isinstance(wp, six.string_types): # prior to 0.1.0, webroot_path was a string + wp = [wp] + config.webroot_path = wp + + +def _restore_plugin_configs(config, renewalparams): + """Sets plugin specific values in config from renewalparams + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param configobj.Section renewalparams: Parameters from the renewal + configuration file that defines this lineage + + """ + # Now use parser to get plugin-prefixed items with correct types + # XXX: the current approach of extracting only prefixed items + # related to the actually-used installer and authenticator + # works as long as plugins don't need to read plugin-specific + # variables set by someone else (e.g., assuming Apache + # configurator doesn't need to read webroot_ variables). + # Note: if a parameter that used to be defined in the parser is no + # longer defined, stored copies of that parameter will be + # deserialized as strings by this logic even if they were + # originally meant to be some other type. + plugin_prefixes = [] # type: List[str] + if renewalparams["authenticator"] == "webroot": + _restore_webroot_config(config, renewalparams) + else: + plugin_prefixes.append(renewalparams["authenticator"]) + + if renewalparams.get("installer") is not None: + plugin_prefixes.append(renewalparams["installer"]) + + for plugin_prefix in set(plugin_prefixes): + plugin_prefix = plugin_prefix.replace('-', '_') + for config_item, config_value in six.iteritems(renewalparams): + if config_item.startswith(plugin_prefix + "_") and not cli.set_by_cli(config_item): + # Values None, True, and False need to be treated specially, + # As their types aren't handled correctly by configobj + if config_value in ("None", "True", "False"): + # bool("False") == True + # pylint: disable=eval-used + setattr(config, config_item, eval(config_value)) + else: + cast = cli.argparse_type(config_item) + setattr(config, config_item, cast(config_value)) + + +def restore_required_config_elements(config, renewalparams): + """Sets non-plugin specific values in config from renewalparams + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param configobj.Section renewalparams: parameters from the renewal + configuration file that defines this lineage + + """ + + required_items = itertools.chain( + (("pref_challs", _restore_pref_challs),), + six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)), + six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)), + six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str))) + for item_name, restore_func in required_items: + if item_name in renewalparams and not cli.set_by_cli(item_name): + value = restore_func(item_name, renewalparams[item_name]) + setattr(config, item_name, value) + + +def _restore_pref_challs(unused_name, value): + """Restores preferred challenges from a renewal config file. + + If value is a `str`, it should be a single challenge type. + + :param str unused_name: option name + :param value: option value + :type value: `list` of `str` or `str` + + :returns: converted option value to be stored in the runtime config + :rtype: `list` of `str` + + :raises errors.Error: if value can't be converted to an bool + + """ + # If pref_challs has only one element, configobj saves the value + # with a trailing comma so it's parsed as a list. If this comma is + # removed by the user, the value is parsed as a str. + value = [value] if isinstance(value, six.string_types) else value + return cli.parse_preferred_challenges(value) + + +def _restore_bool(name, value): + """Restores an boolean key-value pair from a renewal config file. + + :param str name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: bool + + :raises errors.Error: if value can't be converted to an bool + + """ + lowercase_value = value.lower() + if lowercase_value not in ("true", "false"): + raise errors.Error( + "Expected True or False for {0} but found {1}".format(name, value)) + return lowercase_value == "true" + + +def _restore_int(name, value): + """Restores an integer key-value pair from a renewal config file. + + :param str name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: int + + :raises errors.Error: if value can't be converted to an int + + """ + if name == "http01_port" and value == "None": + logger.info("updating legacy http01_port value") + return cli.flag_default("http01_port") + + try: + return int(value) + except ValueError: + raise errors.Error("Expected a numeric value for {0}".format(name)) + + +def _restore_str(unused_name, value): + """Restores an string key-value pair from a renewal config file. + + :param str unused_name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: str or None + + """ + return None if value == "None" else value + + +def should_renew(config, lineage): + "Return true if any of the circumstances for automatic renewal apply." + if config.renew_by_default: + logger.debug("Auto-renewal forced with --force-renewal...") + return True + if lineage.should_autorenew(): + logger.info("Cert is due for renewal, auto-renewing...") + return True + if config.dry_run: + logger.info("Cert not due for renewal, but simulating renewal for dry run") + return True + logger.info("Cert not yet due for renewal") + return False + + +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, 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() + + if util.is_staging(config.server): + if not util.is_staging(original_server) or now_valid: + if not config.break_my_certs: + names = ", ".join(lineage.names()) + raise errors.Error( + "You've asked to renew/replace a seemingly valid certificate with " + "a test certificate (domains: {0}). We will not do that " + "unless you use the --break-my-certs flag!".format(names)) + + +def renew_cert(config, domains, le_client, lineage): + "Renew a certificate lineage." + renewal_params = lineage.configuration["renewalparams"] + original_server = renewal_params.get("server", cli.flag_default("server")) + _avoid_invalidating_lineage(config, lineage, original_server) + if not domains: + domains = lineage.names() + # The private key is the existing lineage private key if reuse_key is set. + # Otherwise, generate a fresh private key by passing None. + new_key = os.path.normpath(lineage.privkey) if config.reuse_key else None + new_cert, new_chain, new_key, _ = le_client.obtain_certificate(domains, new_key) + if config.dry_run: + logger.debug("Dry run: skipping updating lineage at %s", + os.path.dirname(lineage.cert)) + else: + prior_version = lineage.latest_common_version() + # TODO: Check return value of save_successor + lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config) + lineage.update_all_links_to(lineage.latest_common_version()) + + hooks.renew_hook(config, domains, lineage.live_dir) + + +def report(msgs, category): + "Format a results report for a category of renewal outcomes" + lines = ("%s (%s)" % (m, category) for m in msgs) + return " " + "\n ".join(lines) + +def _renew_describe_results(config, renew_successes, renew_failures, + renew_skipped, parse_failures): + + out = [] # type: List[str] + notify = out.append + disp = zope.component.getUtility(interfaces.IDisplay) + + def notify_error(err): + """Notify and log errors.""" + notify(str(err)) + logger.error(err) + + if config.dry_run: + notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") + notify("** (The test certificates below have not been saved.)") + notify("") + if renew_skipped: + notify("The following certs are not due for renewal yet:") + notify(report(renew_skipped, "skipped")) + if not renew_successes and not renew_failures: + notify("No renewals were attempted.") + if (config.pre_hook is not None or + config.renew_hook is not None or config.post_hook is not None): + notify("No hooks were run.") + elif renew_successes and not renew_failures: + notify("Congratulations, all renewals succeeded. The following certs " + "have been renewed:") + notify(report(renew_successes, "success")) + elif renew_failures and not renew_successes: + notify_error("All renewal attempts failed. The following certs could " + "not be renewed:") + notify_error(report(renew_failures, "failure")) + elif renew_failures and renew_successes: + notify("The following certs were successfully renewed:") + notify(report(renew_successes, "success") + "\n") + notify_error("The following certs could not be renewed:") + notify_error(report(renew_failures, "failure")) + + if parse_failures: + notify("\nAdditionally, the following renewal configurations " + "were invalid: ") + notify(report(parse_failures, "parsefail")) + + if config.dry_run: + notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") + notify("** (The test certificates above have not been saved.)") + + disp.notification("\n".join(out), wrap=False) + + +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 + if any(domain not in config.webroot_map for domain in config.domains): + # If more plugins start using cli.add_domains, + # we may want to only log a warning here + raise errors.Error("Currently, the renew verb is capable of either " + "renewing all installed certificates that are due " + "to be renewed or renewing a single certificate specified " + "by its name. If you would like to renew specific " + "certificates by their domains, use the certonly command " + "instead. The renew verb may provide other options " + "for selecting certificates to renew in the future.") + + if config.certname: + conf_files = [storage.renewal_file_for_certname(config, config.certname)] + else: + conf_files = storage.renewal_conf_files(config) + + renew_successes = [] + 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) + lineage_config = copy.deepcopy(config) + lineagename = storage.lineagename_for_filename(renewal_file) + + # Note that this modifies config (to add back the configuration + # elements from within the renewal configuration file). + try: + renewal_candidate = _reconstitute(lineage_config, renewal_file) + except Exception as e: # pylint: disable=broad-except + logger.warning("Renewal configuration file %s (cert: %s) " + "produced an unexpected error: %s. Skipping.", + renewal_file, lineagename, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + parse_failures.append(renewal_file) + continue + + try: + if renewal_candidate is None: + parse_failures.append(renewal_file) + else: + # XXX: ensure that each call here replaces the previous one + zope.component.provideUtility(lineage_config) + renewal_candidate.ensure_deployed() + from certbot._internal 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.uniform(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 + # we already know it's time to renew based on should_renew + # and we have a lineage in renewal_candidate + main.renew_cert(lineage_config, plugins, renewal_candidate) + renew_successes.append(renewal_candidate.fullchain) + else: + expiry = crypto_util.notAfter(renewal_candidate.version( + "cert", renewal_candidate.latest_common_version())) + renew_skipped.append("%s expires on %s" % (renewal_candidate.fullchain, + expiry.strftime("%Y-%m-%d"))) + # Run updater interface methods + updater.run_generic_updaters(lineage_config, renewal_candidate, + plugins) + + except Exception as e: # pylint: disable=broad-except + # obtain_cert (presumably) encountered an unanticipated problem. + logger.warning("Attempting to renew cert (%s) from %s produced an " + "unexpected error: %s. Skipping.", lineagename, + renewal_file, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + renew_failures.append(renewal_candidate.fullchain) + + # Describe all the results + _renew_describe_results(config, renew_successes, renew_failures, + renew_skipped, parse_failures) + + if renew_failures or parse_failures: + raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( + len(renew_failures), len(parse_failures))) + else: + logger.debug("no renewal failures") diff --git a/certbot/_internal/reporter.py b/certbot/_internal/reporter.py new file mode 100644 index 000000000..e0063d8e5 --- /dev/null +++ b/certbot/_internal/reporter.py @@ -0,0 +1,100 @@ +"""Collects and displays information to the user.""" +from __future__ import print_function + +import collections +import logging +import sys +import textwrap + +from six.moves import queue # type: ignore # pylint: disable=import-error +import zope.interface + +from certbot import interfaces +from certbot import util + + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IReporter) +class Reporter(object): + """Collects and displays information to the user. + + :ivar `queue.PriorityQueue` messages: Messages to be displayed to + the user. + + """ + + HIGH_PRIORITY = 0 + """High priority constant. See `add_message`.""" + MEDIUM_PRIORITY = 1 + """Medium priority constant. See `add_message`.""" + LOW_PRIORITY = 2 + """Low priority constant. See `add_message`.""" + + _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') + + def __init__(self, config): + self.messages = queue.PriorityQueue() + self.config = config + + def add_message(self, msg, priority, on_crash=True): + """Adds msg to the list of messages to be printed. + + :param str msg: Message to be displayed to the user. + + :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`, + or `LOW_PRIORITY`. + + :param bool on_crash: Whether or not the message should be + printed if the program exits abnormally. + + """ + assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY + self.messages.put(self._msg_type(priority, msg, on_crash)) + logger.debug("Reporting to user: %s", msg) + + def print_messages(self): + """Prints messages to the user and clears the message queue. + + If there is an unhandled exception, only messages for which + ``on_crash`` is ``True`` are printed. + + """ + bold_on = False + if not self.messages.empty(): + no_exception = sys.exc_info()[0] is None + bold_on = sys.stdout.isatty() + if not self.config.quiet: + if bold_on: + print(util.ANSI_SGR_BOLD) + print('IMPORTANT NOTES:') + first_wrapper = textwrap.TextWrapper( + initial_indent=' - ', + subsequent_indent=(' ' * 3), + break_long_words=False, + break_on_hyphens=False) + next_wrapper = textwrap.TextWrapper( + initial_indent=first_wrapper.subsequent_indent, + subsequent_indent=first_wrapper.subsequent_indent, + break_long_words=False, + break_on_hyphens=False) + while not self.messages.empty(): + msg = self.messages.get() + if self.config.quiet: + # In --quiet mode, we only print high priority messages that + # are flagged for crash cases + if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash): + continue + if no_exception or msg.on_crash: + if bold_on and msg.priority > self.HIGH_PRIORITY: + if not self.config.quiet: + sys.stdout.write(util.ANSI_SGR_RESET) + bold_on = False + lines = msg.text.splitlines() + print(first_wrapper.fill(lines[0])) + if len(lines) > 1: + print("\n".join( + next_wrapper.fill(line) for line in lines[1:])) + if bold_on and not self.config.quiet: + sys.stdout.write(util.ANSI_SGR_RESET) diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py new file mode 100644 index 000000000..f19efe728 --- /dev/null +++ b/certbot/_internal/storage.py @@ -0,0 +1,1130 @@ +"""Renewable certificates storage.""" +import datetime +import glob +import logging +import re +import shutil +import stat + +import configobj +import parsedatetime +import pytz +import six + +import certbot +from certbot import cli +from certbot import constants +from certbot import crypto_util +from certbot._internal import error_handler +from certbot import errors +from certbot import util +from certbot.compat import os +from certbot.compat import filesystem +from certbot.plugins import common as plugins_common +from certbot.plugins import disco as plugins_disco + +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): + """Build a list of all renewal configuration files. + + :param certbot.interfaces.IConfig config: Configuration object + + :returns: list of renewal configuration files + :rtype: `list` of `str` + + """ + 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""" + path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname)) + if not os.path.exists(path): + raise errors.CertStorageError("No certificate found with name {0} (expected " + "{1}).".format(certname, path)) + return path + + +def cert_path_for_cert_name(config, cert_name): + """ If `--cert-name` was specified, but you need a value for `--cert-path`. + + :param `configuration.NamespaceConfig` config: parsed command line arguments + :param str cert_name: cert name. + + """ + cert_name_implied_conf = renewal_file_for_certname(config, cert_name) + fullchain_path = configobj.ConfigObj(cert_name_implied_conf)["fullchain"] + with open(fullchain_path) as f: + cert_path = (fullchain_path, f.read()) + return cert_path + + +def config_with_defaults(config=None): + """Merge supplied config, if provided, on top of builtin defaults.""" + defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS) + defaults_copy.merge(config if config is not None else configobj.ConfigObj()) + return defaults_copy + + +def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()): + """Parse the time specified time interval, and add it to the base_time + + The interval can be in the English-language format understood by + parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or + a sequence of such intervals like '6 months 1 week' or '3 days 12 + hours'. If an integer is found with no associated unit, it is + interpreted by default as a number of days. + + :param datetime.datetime base_time: The time to be added with the interval. + :param str interval: The time interval to parse. + + :returns: The base_time plus the interpretation of the time interval. + :rtype: :class:`datetime.datetime`""" + + if interval.strip().isdigit(): + interval += " days" + + # try to use the same timezone, but fallback to UTC + tzinfo = base_time.tzinfo or pytz.UTC + + return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0] + + +def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): + """Writes a renewal config file with the specified name and values. + + :param str o_filename: Absolute path to the previous version of config file + :param str n_filename: Absolute path to the new destination of config file + :param str archive_dir: Absolute path to the archive directory + :param dict target: Maps ALL_FOUR to their symlink paths + :param dict relevant_data: Renewal configuration options to save + + :returns: Configuration object for the new config file + :rtype: configobj.ConfigObj + + """ + config = configobj.ConfigObj(o_filename) + config["version"] = certbot.__version__ + config["archive_dir"] = archive_dir + for kind in ALL_FOUR: + config[kind] = target[kind] + + if "renewalparams" not in config: + config["renewalparams"] = {} + config.comments["renewalparams"] = ["", + "Options used in " + "the renewal process"] + + config["renewalparams"].update(relevant_data) + + for k in config["renewalparams"].keys(): + if k not in relevant_data: + del config["renewalparams"][k] + + if "renew_before_expiry" not in config: + default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] + config.initial_comment = ["renew_before_expiry = " + default_interval] + + # TODO: add human-readable comments explaining other available + # parameters + logger.debug("Writing new config %s.", n_filename) + + # Ensure that the file exists + open(n_filename, 'a').close() + + # Copy permissions from the old version of the file, if it exists. + if os.path.exists(o_filename): + current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) + filesystem.chmod(n_filename, current_permissions) + + with open(n_filename, "wb") as f: + config.write(outfile=f) + return config + + +def rename_renewal_config(prev_name, new_name, cli_config): + """Renames cli_config.certname's config to cli_config.new_certname. + + :param .NamespaceConfig cli_config: parsed command line + arguments + """ + prev_filename = renewal_filename_for_lineagename(cli_config, prev_name) + new_filename = renewal_filename_for_lineagename(cli_config, new_name) + if os.path.exists(new_filename): + raise errors.ConfigurationError("The new certificate name " + "is already in use.") + try: + filesystem.replace(prev_filename, new_filename) + except OSError: + raise errors.ConfigurationError("Please specify a valid filename " + "for the new certificate name.") + + +def update_configuration(lineagename, archive_dir, target, cli_config): + """Modifies lineagename's config to contain the specified values. + + :param str lineagename: Name of the lineage being modified + :param str archive_dir: Absolute path to the archive directory + :param dict target: Maps ALL_FOUR to their symlink paths + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: Configuration object for the updated config file + :rtype: configobj.ConfigObj + + """ + config_filename = renewal_filename_for_lineagename(cli_config, lineagename) + temp_filename = config_filename + ".new" + + # If an existing tempfile exists, delete it + if os.path.exists(temp_filename): + os.unlink(temp_filename) + + # Save only the config items that are relevant to renewal + values = relevant_values(vars(cli_config.namespace)) + write_renewal_config(config_filename, temp_filename, archive_dir, target, values) + filesystem.replace(temp_filename, config_filename) + + return configobj.ConfigObj(config_filename) + + +def get_link_target(link): + """Get an absolute path to the target of link. + + :param str link: Path to a symbolic link + + :returns: Absolute path to the target of link + :rtype: str + + :raises .CertStorageError: If link does not exists. + + """ + try: + target = os.readlink(link) + except OSError: + raise errors.CertStorageError( + "Expected {0} to be a symlink".format(link)) + + if not os.path.isabs(target): + target = os.path.join(os.path.dirname(link), target) + return os.path.abspath(target) + +def _write_live_readme_to(readme_path, is_base_dir=False): + prefix = "" + if is_base_dir: + prefix = "[cert name]/" + with open(readme_path, "w") as f: + logger.debug("Writing README to %s.", readme_path) + f.write("This directory contains your keys and certificates.\n\n" + "`{prefix}privkey.pem` : the private key for your certificate.\n" + "`{prefix}fullchain.pem`: the certificate file used in most server software.\n" + "`{prefix}chain.pem` : used for OCSP stapling in Nginx >=1.3.7.\n" + "`{prefix}cert.pem` : will break many server configurations, and " + "should not be used\n" + " without reading further documentation (see link below).\n\n" + "WARNING: DO NOT MOVE OR RENAME THESE FILES!\n" + " Certbot expects these files to remain in this location in order\n" + " to function properly!\n\n" + "We recommend not moving these files. For more information, see the Certbot\n" + "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-" + "certificates.\n".format(prefix=prefix)) + + +def _relevant(namespaces, option): + """ + Is this option one that could be restored for future renewal purposes? + + :param namespaces: plugin namespaces for configuration options + :type namespaces: `list` of `str` + :param str option: the name of the option + + :rtype: bool + """ + from certbot._internal import renewal + + return (option in renewal.CONFIG_ITEMS or + any(option.startswith(namespace) for namespace in namespaces)) + + +def relevant_values(all_values): + """Return a new dict containing only items relevant for renewal. + + :param dict all_values: The original values. + + :returns: A new dictionary containing items that can be used in renewal. + :rtype dict: + + """ + plugins = plugins_disco.PluginsRegistry.find_all() + namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] + + rv = dict( + (option, value) + for option, value in six.iteritems(all_values) + if _relevant(namespaces, option) and cli.option_was_set(option, value)) + # We always save the server value to help with forward compatibility + # and behavioral consistency when versions of Certbot with different + # server defaults are used. + rv["server"] = all_values["server"] + return rv + +def lineagename_for_filename(config_filename): + """Returns the lineagename for a configuration filename. + """ + if not config_filename.endswith(".conf"): + raise errors.CertStorageError( + "renewal config file name must end in .conf") + return os.path.basename(config_filename[:-len(".conf")]) + +def renewal_filename_for_lineagename(config, lineagename): + """Returns the lineagename for a configuration filename. + """ + return os.path.join(config.renewal_configs_dir, lineagename) + ".conf" + +def _relpath_from_file(archive_dir, from_file): + """Path to a directory from a file""" + return os.path.relpath(archive_dir, os.path.dirname(from_file)) + +def full_archive_path(config_obj, cli_config, lineagename): + """Returns the full archive path for a lineagename + + Uses cli_config to determine archive path if not available from config_obj. + + :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None) + :param configuration.NamespaceConfig cli_config: Main config file + :param str lineagename: Certificate name + """ + if config_obj and "archive_dir" in config_obj: + return config_obj["archive_dir"] + return os.path.join(cli_config.default_archive_dir, lineagename) + +def _full_live_path(cli_config, lineagename): + """Returns the full default live path for a lineagename""" + return os.path.join(cli_config.live_dir, lineagename) + +def delete_files(config, certname): + """Delete all files related to the certificate. + + If some files are not found, ignore them and continue. + """ + renewal_filename = renewal_file_for_certname(config, certname) + # file exists + full_default_archive_dir = full_archive_path(None, config, certname) + full_default_live_dir = _full_live_path(config, certname) + try: + renewal_config = configobj.ConfigObj(renewal_filename) + except configobj.ConfigObjError: + # config is corrupted + logger.warning("Could not parse %s. You may wish to manually " + "delete the contents of %s and %s.", renewal_filename, + full_default_live_dir, full_default_archive_dir) + raise errors.CertStorageError( + "error parsing {0}".format(renewal_filename)) + finally: + # we couldn't read it, but let's at least delete it + # if this was going to fail, it already would have. + os.remove(renewal_filename) + logger.debug("Removed %s", renewal_filename) + + # cert files and (hopefully) live directory + # it's not guaranteed that the files are in our default storage + # structure. so, first delete the cert files. + directory_names = set() + for kind in ALL_FOUR: + link = renewal_config.get(kind) + try: + os.remove(link) + logger.debug("Removed %s", link) + except OSError: + logger.debug("Unable to delete %s", link) + directory = os.path.dirname(link) + directory_names.add(directory) + + # if all four were in the same directory, and the only thing left + # is the README file (or nothing), delete that directory. + # this will be wrong in very few but some cases. + if len(directory_names) == 1: + # delete the README file + directory = directory_names.pop() + readme_path = os.path.join(directory, README) + try: + os.remove(readme_path) + logger.debug("Removed %s", readme_path) + except OSError: + logger.debug("Unable to delete %s", readme_path) + # if it's now empty, delete the directory + try: + os.rmdir(directory) # only removes empty directories + logger.debug("Removed %s", directory) + except OSError: + logger.debug("Unable to remove %s; may not be empty.", directory) + + # archive directory + try: + archive_path = full_archive_path(renewal_config, config, certname) + shutil.rmtree(archive_path) + logger.debug("Removed %s", archive_path) + except OSError: + logger.debug("Unable to remove %s", archive_path) + + +class RenewableCert(object): + # pylint: disable=too-many-instance-attributes,too-many-public-methods + """Renewable certificate. + + Represents a lineage of certificates that is under the management of + Certbot, indicated by the existence of an associated renewal + configuration file. + + Note that the notion of "current version" for a lineage is + maintained on disk in the structure of symbolic links, and is not + explicitly stored in any instance variable in this object. The + RenewableCert object is able to determine information about the + current (or other) version by accessing data on disk, but does not + inherently know any of this information except by examining the + symbolic links as needed. The instance variables mentioned below + point to symlinks that reflect the notion of "current version" of + each managed object, and it is these paths that should be used when + configuring servers to use the certificate managed in a lineage. + These paths are normally within the "live" directory, and their + symlink targets -- the actual cert files -- are normally found + within the "archive" directory. + + :ivar str cert: The path to the symlink representing the current + version of the certificate managed by this lineage. + :ivar str privkey: The path to the symlink representing the current + version of the private key managed by this lineage. + :ivar str chain: The path to the symlink representing the current version + of the chain managed by this lineage. + :ivar str fullchain: The path to the symlink representing the + current version of the fullchain (combined chain and cert) + managed by this lineage. + :ivar configobj.ConfigObj configuration: The renewal configuration + options associated with this lineage, obtained from parsing the + renewal configuration file and/or systemwide defaults. + + """ + def __init__(self, config_filename, cli_config, update_symlinks=False): + """Instantiate a RenewableCert object from an existing lineage. + + :param str config_filename: the path to the renewal config file + that defines this lineage. + :param .NamespaceConfig: parsed command line arguments + + :raises .CertStorageError: if the configuration file's name didn't end + in ".conf", or the file is missing or broken. + + """ + self.cli_config = cli_config + self.lineagename = lineagename_for_filename(config_filename) + + # self.configuration should be used to read parameters that + # may have been chosen based on default values from the + # systemwide renewal configuration; self.configfile should be + # used to make and save changes. + try: + self.configfile = configobj.ConfigObj(config_filename) + except configobj.ConfigObjError: + raise errors.CertStorageError( + "error parsing {0}".format(config_filename)) + # TODO: Do we actually use anything from defaults and do we want to + # read further defaults from the systemwide renewal configuration + # file at this stage? + self.configuration = config_with_defaults(self.configfile) + + if not all(x in self.configuration for x in ALL_FOUR): + raise errors.CertStorageError( + "renewal config file {0} is missing a required " + "file reference".format(self.configfile)) + + conf_version = self.configuration.get("version") + if (conf_version is not None and + util.get_strict_version(conf_version) > CURRENT_VERSION): + logger.info( + "Attempting to parse the version %s renewal configuration " + "file found at %s with version %s of Certbot. This might not " + "work.", conf_version, config_filename, certbot.__version__) + + self.cert = self.configuration["cert"] + self.privkey = self.configuration["privkey"] + self.chain = self.configuration["chain"] + self.fullchain = self.configuration["fullchain"] + self.live_dir = os.path.dirname(self.cert) + + self._fix_symlinks() + if update_symlinks: + self._update_symlinks() + self._check_symlinks() + + @property + def key_path(self): + """Duck type for self.privkey""" + return self.privkey + + @property + def cert_path(self): + """Duck type for self.cert""" + return self.cert + + @property + def chain_path(self): + """Duck type for self.chain""" + return self.chain + + @property + def fullchain_path(self): + """Duck type for self.fullchain""" + return self.fullchain + + @property + def target_expiry(self): + """The current target certificate's expiration datetime + + :returns: Expiration datetime of the current target certificate + :rtype: :class:`datetime.datetime` + """ + return crypto_util.notAfter(self.current_target("cert")) + + @property + def archive_dir(self): + """Returns the default or specified archive directory""" + return full_archive_path(self.configuration, + self.cli_config, self.lineagename) + + def relative_archive_dir(self, from_file): + """Returns the default or specified archive directory as a relative path + + Used for creating symbolic links. + """ + return _relpath_from_file(self.archive_dir, from_file) + + @property + def is_test_cert(self): + """Returns true if this is a test cert from a staging server.""" + server = self.configuration["renewalparams"].get("server", None) + if server: + return util.is_staging(server) + return False + + def _check_symlinks(self): + """Raises an exception if a symlink doesn't exist""" + for kind in ALL_FOUR: + link = getattr(self, kind) + if not os.path.islink(link): + raise errors.CertStorageError( + "expected {0} to be a symlink".format(link)) + target = get_link_target(link) + if not os.path.exists(target): + raise errors.CertStorageError("target {0} of symlink {1} does " + "not exist".format(target, link)) + + def _update_symlinks(self): + """Updates symlinks to use archive_dir""" + for kind in ALL_FOUR: + link = getattr(self, kind) + previous_link = get_link_target(link) + new_link = os.path.join(self.relative_archive_dir(link), + os.path.basename(previous_link)) + + os.unlink(link) + os.symlink(new_link, link) + + def _consistent(self): + """Are the files associated with this lineage self-consistent? + + :returns: Whether the files stored in connection with this + lineage appear to be correct and consistent with one + another. + :rtype: bool + + """ + # Each element must be referenced with an absolute path + for x in (self.cert, self.privkey, self.chain, self.fullchain): + if not os.path.isabs(x): + logger.debug("Element %s is not referenced with an " + "absolute path.", x) + return False + + # Each element must exist and be a symbolic link + for x in (self.cert, self.privkey, self.chain, self.fullchain): + if not os.path.islink(x): + logger.debug("Element %s is not a symbolic link.", x) + return False + for kind in ALL_FOUR: + link = getattr(self, kind) + target = get_link_target(link) + + # Each element's link must point within the cert lineage's + # directory within the official archive directory + if not os.path.samefile(os.path.dirname(target), self.archive_dir): + logger.debug("Element's link does not point within the " + "cert lineage's directory within the " + "official archive directory. Link: %s, " + "target directory: %s, " + "archive directory: %s. If you've specified " + "the archive directory in the renewal configuration " + "file, you may need to update links by running " + "certbot update_symlinks.", + link, os.path.dirname(target), self.archive_dir) + return False + + # The link must point to a file that exists + if not os.path.exists(target): + logger.debug("Link %s points to file %s that does not exist.", + link, target) + return False + + # The link must point to a file that follows the archive + # naming convention + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + if not pattern.match(os.path.basename(target)): + logger.debug("%s does not follow the archive naming " + "convention.", target) + return False + + # It is NOT required that the link's target be a regular + # file (it may itself be a symlink). But we should probably + # do a recursive check that ultimately the target does + # exist? + # XXX: Additional possible consistency checks (e.g. + # cryptographic validation of the chain being a chain, + # the chain matching the cert, and the cert matching + # the subject key) + # XXX: All four of the targets are in the same directory + # (This check is redundant with the check that they + # are all in the desired directory!) + # len(set(os.path.basename(self.current_target(x) + # for x in ALL_FOUR))) == 1 + return True + + def _fix(self): + """Attempt to fix defects or inconsistencies in this lineage. + + .. todo:: Currently unimplemented. + + """ + # TODO: Figure out what kinds of fixes are possible. For + # example, checking if there is a valid version that + # we can update the symlinks to. (Maybe involve + # parsing keys and certs to see if they exist and + # if a key corresponds to the subject key of a cert?) + + # TODO: In general, the symlink-reading functions below are not + # cautious enough about the possibility that links or their + # targets may not exist. (This shouldn't happen, but might + # happen as a result of random tampering by a sysadmin, or + # filesystem errors, or crashes.) + + def _previous_symlinks(self): + """Returns the kind and path of all symlinks used in recovery. + + :returns: list of (kind, symlink) tuples + :rtype: list + + """ + previous_symlinks = [] + for kind in ALL_FOUR: + link_dir = os.path.dirname(getattr(self, kind)) + link_base = "previous_{0}.pem".format(kind) + previous_symlinks.append((kind, os.path.join(link_dir, link_base))) + + return previous_symlinks + + def _fix_symlinks(self): + """Fixes symlinks in the event of an incomplete version update. + + If there is no problem with the current symlinks, this function + has no effect. + + """ + previous_symlinks = self._previous_symlinks() + if all(os.path.exists(link[1]) for link in previous_symlinks): + for kind, previous_link in previous_symlinks: + current_link = getattr(self, kind) + if os.path.lexists(current_link): + os.unlink(current_link) + os.symlink(os.readlink(previous_link), current_link) + + for _, link in previous_symlinks: + if os.path.exists(link): + os.unlink(link) + + def current_target(self, kind): + """Returns full path to which the specified item currently points. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + + :returns: The path to the current version of the specified + member. + :rtype: str or None + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + link = getattr(self, kind) + if not os.path.exists(link): + logger.debug("Expected symlink %s for %s does not exist.", + link, kind) + return None + return get_link_target(link) + + def current_version(self, kind): + """Returns numerical version of the specified item. + + For example, if kind is "chain" and the current chain link + points to a file named "chain7.pem", returns the integer 7. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + + :returns: the current version of the specified member. + :rtype: int + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + target = self.current_target(kind) + if target is None or not os.path.exists(target): + logger.debug("Current-version target for %s " + "does not exist at %s.", kind, target) + target = "" + matches = pattern.match(os.path.basename(target)) + if matches: + return int(matches.groups()[0]) + logger.debug("No matches for target %s.", kind) + return None + + def version(self, kind, version): + """The filename that corresponds to the specified version and kind. + + .. warning:: The specified version may not exist in this + lineage. There is no guarantee that the file path returned + by this method actually exists. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + :param int version: the desired version + + :returns: The path to the specified version of the specified member. + :rtype: str + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + where = os.path.dirname(self.current_target(kind)) + return os.path.join(where, "{0}{1}.pem".format(kind, version)) + + def available_versions(self, kind): + """Which alternative versions of the specified kind of item exist? + + The archive directory where the current version is stored is + consulted to obtain the list of alternatives. + + :param str kind: the lineage member item ( + ``cert``, ``privkey``, ``chain``, or ``fullchain``) + + :returns: all of the version numbers that currently exist + :rtype: `list` of `int` + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + where = os.path.dirname(self.current_target(kind)) + files = os.listdir(where) + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + matches = [pattern.match(f) for f in files] + return sorted([int(m.groups()[0]) for m in matches if m]) + + def newest_available_version(self, kind): + """Newest available version of the specified kind of item? + + :param str kind: the lineage member item (``cert``, + ``privkey``, ``chain``, or ``fullchain``) + + :returns: the newest available version of this member + :rtype: int + + """ + return max(self.available_versions(kind)) + + def latest_common_version(self): + """Newest version for which all items are available? + + :returns: the newest available version for which all members + (``cert, ``privkey``, ``chain``, and ``fullchain``) exist + :rtype: int + + """ + # TODO: this can raise CertStorageError if there is no version overlap + # (it should probably return None instead) + # TODO: this can raise a spurious AttributeError if the current + # link for any kind is missing (it should probably return None) + versions = [self.available_versions(x) for x in ALL_FOUR] + return max(n for n in versions[0] if all(n in v for v in versions[1:])) + + def next_free_version(self): + """Smallest version newer than all full or partial versions? + + :returns: the smallest version number that is larger than any + version of any item currently stored in this lineage + :rtype: int + + """ + # TODO: consider locking/mutual exclusion between updating processes + # This isn't self.latest_common_version() + 1 because we don't want + # collide with a version that might exist for one file type but not + # for the others. + return max(self.newest_available_version(x) for x in ALL_FOUR) + 1 + + def ensure_deployed(self): + """Make sure we've deployed the latest version. + + :returns: False if a change was needed, True otherwise + :rtype: bool + + May need to recover from rare interrupted / crashed states.""" + + if self.has_pending_deployment(): + 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 + return True + + + def has_pending_deployment(self): + """Is there a later version of all of the managed items? + + :returns: ``True`` if there is a complete version of this + lineage with a larger version number than the current + version, and ``False`` otherwise + :rtype: bool + + """ + # TODO: consider whether to assume consistency or treat + # inconsistent/consistent versions differently + smallest_current = min(self.current_version(x) for x in ALL_FOUR) + return smallest_current < self.latest_common_version() + + def _update_link_to(self, kind, version): + """Make the specified item point at the specified version. + + (Note that this method doesn't verify that the specified version + exists.) + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + :param int version: the desired version + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + link = getattr(self, kind) + filename = "{0}{1}.pem".format(kind, version) + # Relative rather than absolute target directory + target_directory = os.path.dirname(os.readlink(link)) + # TODO: it could be safer to make the link first under a temporary + # filename, then unlink the old link, then rename the new link + # to the old link; this ensures that this process is able to + # create symlinks. + # TODO: we might also want to check consistency of related links + # for the other corresponding items + os.unlink(link) + os.symlink(os.path.join(target_directory, filename), link) + + def update_all_links_to(self, version): + """Change all member objects to point to the specified version. + + :param int version: the desired version + + """ + with error_handler.ErrorHandler(self._fix_symlinks): + previous_links = self._previous_symlinks() + for kind, link in previous_links: + os.symlink(self.current_target(kind), link) + + for kind in ALL_FOUR: + self._update_link_to(kind, version) + + for _, link in previous_links: + os.unlink(link) + + def names(self, version=None): + """What are the subject names of this certificate? + + (If no version is specified, use the current version.) + + :param int version: the desired version number + :returns: the subject names + :rtype: `list` of `str` + :raises .CertStorageError: if could not find cert file. + + """ + if version is None: + target = self.current_target("cert") + else: + target = self.version("cert", version) + if target is None: + raise errors.CertStorageError("could not find cert file") + with open(target) as f: + return crypto_util.get_names_from_cert(f.read()) + + def ocsp_revoked(self, version=None): + # pylint: disable=no-self-use,unused-argument + """Is the specified cert version revoked according to OCSP? + + Also returns True if the cert version is declared as intended + to be revoked according to Let's Encrypt OCSP extensions. + (If no version is specified, uses the current version.) + + This method is not yet implemented and currently always returns + False. + + :param int version: the desired version number + + :returns: whether the certificate is or will be revoked + :rtype: bool + + """ + # XXX: This query and its associated network service aren't + # implemented yet, so we currently return False (indicating that the + # certificate is not revoked). + return False + + def autorenewal_is_enabled(self): + """Is automatic renewal enabled for this cert? + + If autorenew is not specified, defaults to True. + + :returns: True if automatic renewal is enabled + :rtype: bool + + """ + return ("autorenew" not in self.configuration["renewalparams"] or + self.configuration["renewalparams"].as_bool("autorenew")) + + def should_autorenew(self): + """Should we now try to autorenew the most recent cert version? + + This is a policy question and does not only depend on whether + the cert is expired. (This considers whether autorenewal is + enabled, whether the cert is revoked, and whether the time + interval for autorenewal has been reached.) + + Note that this examines the numerically most recent cert version, + not the currently deployed version. + + :returns: whether an attempt should now be made to autorenew the + most current cert version in this lineage + :rtype: bool + + """ + if self.autorenewal_is_enabled(): + # Consider whether to attempt to autorenew this cert now + + # Renewals on the basis of revocation + if self.ocsp_revoked(self.latest_common_version()): + logger.debug("Should renew, certificate is revoked.") + return True + + # Renews some period before expiry time + default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] + interval = self.configuration.get("renew_before_expiry", default_interval) + expiry = crypto_util.notAfter(self.version( + "cert", self.latest_common_version())) + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + if expiry < add_time_interval(now, interval): + logger.debug("Should renew, less than %s before certificate " + "expiry %s.", interval, + expiry.strftime("%Y-%m-%d %H:%M:%S %Z")) + return True + return False + + @classmethod + def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): + # pylint: disable=too-many-locals + """Create a new certificate lineage. + + Attempts to create a certificate lineage -- enrolled for + potential future renewal -- with the (suggested) lineage name + lineagename, and the associated cert, privkey, and chain (the + associated fullchain will be created automatically). Optional + configurator and renewalparams record the configuration that was + originally used to obtain this cert, so that it can be reused + later during automated renewal. + + Returns a new RenewableCert object referring to the created + lineage. (The actual lineage name, as well as all the relevant + file paths, will be available within this object.) + + :param str lineagename: the suggested name for this lineage + (normally the current cert's first subject DNS name) + :param str cert: the initial certificate version in PEM format + :param str privkey: the private key in PEM format + :param str chain: the certificate chain in PEM format + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: the newly-created RenewalCert object + :rtype: :class:`storage.renewableCert` + + """ + + # Examine the configuration and find the new lineage's name + for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, + cli_config.live_dir): + if not os.path.exists(i): + filesystem.makedirs(i, 0o700) + logger.debug("Creating directory %s.", i) + config_file, config_filename = util.unique_lineage_name( + cli_config.renewal_configs_dir, lineagename) + base_readme_path = os.path.join(cli_config.live_dir, README) + if not os.path.exists(base_readme_path): + _write_live_readme_to(base_readme_path, is_base_dir=True) + + # Determine where on disk everything will go + # lineagename will now potentially be modified based on which + # renewal configuration file could actually be created + lineagename = lineagename_for_filename(config_filename) + 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) + filesystem.mkdir(archive) + filesystem.mkdir(live_dir) + logger.debug("Archive directory %s and live " + "directory %s created.", archive, live_dir) + + # 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(_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 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 + with open(target["chain"], "wb") as f: + logger.debug("Writing chain to %s.", target["chain"]) + f.write(chain) + with open(target["fullchain"], "wb") as f: + # assumes that OpenSSL.crypto.dump_certificate includes + # ending newline character + logger.debug("Writing full chain to %s.", target["fullchain"]) + f.write(cert + chain) + + # Write a README file to the live directory + readme_path = os.path.join(live_dir, README) + _write_live_readme_to(readme_path) + + # Document what we've done in a new renewal config file + config_file.close() + + # Save only the config items that are relevant to renewal + values = relevant_values(vars(cli_config.namespace)) + + new_config = write_renewal_config(config_filename, config_filename, archive, + target, values) + return cls(new_config.filename, cli_config) + + def save_successor(self, prior_version, new_cert, + new_privkey, new_chain, cli_config): + """Save new cert and chain as a successor of a prior version. + + Returns the new version number that was created. + + .. note:: this function does NOT update links to deploy this + version + + :param int prior_version: the old version to which this version + is regarded as a successor (used to choose a privkey, if the + key has not changed, but otherwise this information is not + permanently recorded anywhere) + :param bytes new_cert: the new certificate, in PEM format + :param bytes new_privkey: the new private key, in PEM format, + or ``None``, if the private key has not changed + :param bytes new_chain: the new chain, in PEM format + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: the new version number that was created + :rtype: int + + """ + # XXX: assumes official archive location rather than examining links + # XXX: consider using os.open for availability of os.O_EXCL + # XXX: ensure file permissions are correct; also create directories + # if needed (ensuring their permissions are correct) + # Figure out what the new version is and hence where to save things + + self.cli_config = cli_config + target_version = self.next_free_version() + target = dict( + [(kind, + 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. + if os.path.islink(old_privkey): + old_privkey = os.readlink(old_privkey) + else: + old_privkey = "privkey{0}.pem".format(prior_version) + logger.debug("Writing symlink to old private key, %s.", old_privkey) + os.symlink(old_privkey, target["privkey"]) + else: + 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 & MASK_FOR_PRIVATE_KEY_PERMISSIONS) + # from previous privkey in this lineage. + mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) + filesystem.copy_ownership_and_apply_mode( + old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) + + # Save everything else + with open(target["cert"], "wb") as f: + logger.debug("Writing certificate to %s.", target["cert"]) + f.write(new_cert) + with open(target["chain"], "wb") as f: + logger.debug("Writing chain to %s.", target["chain"]) + f.write(new_chain) + with open(target["fullchain"], "wb") as f: + logger.debug("Writing full chain to %s.", target["fullchain"]) + f.write(new_cert + new_chain) + + symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) + # Update renewal config file + self.configfile = update_configuration( + self.lineagename, self.archive_dir, symlinks, cli_config) + self.configuration = config_with_defaults(self.configfile) + + return target_version diff --git a/certbot/_internal/updater.py b/certbot/_internal/updater.py new file mode 100644 index 000000000..58df6fcb4 --- /dev/null +++ b/certbot/_internal/updater.py @@ -0,0 +1,122 @@ +"""Updaters run at renewal""" +import logging + +from certbot import errors +from certbot import interfaces + +from certbot.plugins import selection as plug_sel +import certbot.plugins.enhancements as enhancements + +logger = logging.getLogger(__name__) + +def run_generic_updaters(config, lineage, plugins): + """Run updaters that the plugin supports + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + """ + if config.dry_run: + logger.debug("Skipping updaters in dry-run mode.") + return + try: + installer = plug_sel.get_unprepared_installer(config, plugins) + except errors.Error as e: + logger.warning("Could not choose appropriate plugin for updaters: %s", e) + return + if installer: + _run_updaters(lineage, installer, config) + _run_enhancement_updaters(lineage, installer, config) + +def run_renewal_deployer(config, lineage, installer): + """Helper function to run deployer interface method if supported by the used + installer plugin. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: `None` + :rtype: None + """ + if config.dry_run: + logger.debug("Skipping renewal deployer in dry-run mode.") + return + + if not config.disable_renew_updates and isinstance(installer, + interfaces.RenewDeployer): + installer.renew_deploy(lineage) + _run_enhancement_deployers(lineage, installer, config) + +def _run_updaters(lineage, installer, config): + """Helper function to run the updater interface methods if supported by the + used installer plugin. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: `None` + :rtype: None + """ + if not config.disable_renew_updates: + if isinstance(installer, interfaces.GenericUpdater): + installer.generic_updates(lineage) + +def _run_enhancement_updaters(lineage, installer, config): + """Iterates through known enhancement interfaces. If the installer implements + an enhancement interface and the enhance interface has an updater method, the + updater method gets run. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param config: Configuration object + :type config: interfaces.IConfig + """ + + if config.disable_renew_updates: + return + for enh in enhancements._INDEX: # pylint: disable=protected-access + if isinstance(installer, enh["class"]) and enh["updater_function"]: + getattr(installer, enh["updater_function"])(lineage) + + +def _run_enhancement_deployers(lineage, installer, config): + """Iterates through known enhancement interfaces. If the installer implements + an enhancement interface and the enhance interface has an deployer method, the + deployer method gets run. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param config: Configuration object + :type config: interfaces.IConfig + """ + + if config.disable_renew_updates: + return + for enh in enhancements._INDEX: # pylint: disable=protected-access + if isinstance(installer, enh["class"]) and enh["deployer_function"]: + getattr(installer, enh["deployer_function"])(lineage) diff --git a/certbot/account.py b/certbot/account.py deleted file mode 100644 index 992d63d38..000000000 --- a/certbot/account.py +++ /dev/null @@ -1,344 +0,0 @@ -"""Creates ACME accounts for server.""" -import datetime -import functools -import hashlib -import logging -import shutil -import socket - -import josepy as jose -import pyrfc3339 -import pytz -import six -import zope.component -from cryptography.hazmat.primitives import serialization - -from acme import fields as acme_fields -from acme import messages - -from certbot import constants -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -class Account(object): # pylint: disable=too-few-public-methods - """ACME protocol registration. - - :ivar .RegistrationResource regr: Registration Resource - :ivar .JWK key: Authorized Account Key - :ivar .Meta: Account metadata - :ivar str id: Globally unique account identifier. - - """ - - class Meta(jose.JSONObjectWithFields): - """Account metadata - - :ivar datetime.datetime creation_dt: Creation date and time (UTC). - :ivar str creation_host: FQDN of host, where account has been created. - - .. note:: ``creation_dt`` and ``creation_host`` are useful in - cross-machine migration scenarios. - - """ - creation_dt = acme_fields.RFC3339Field("creation_dt") - creation_host = jose.Field("creation_host") - - def __init__(self, regr, key, meta=None): - self.key = key - self.regr = regr - self.meta = self.Meta( - # pyrfc3339 drops microseconds, make sure __eq__ is sane - creation_dt=datetime.datetime.now( - tz=pytz.UTC).replace(microsecond=0), - creation_host=socket.getfqdn()) if meta is None else meta - - self.id = hashlib.md5( - self.key.key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo) - ).hexdigest() - # Implementation note: Email? Multiple accounts can have the - # same email address. Registration URI? Assigned by the - # server, not guaranteed to be stable over time, nor - # canonical URI can be generated. ACME protocol doesn't allow - # account key (and thus its fingerprint) to be updated... - - @property - def slug(self): - """Short account identification string, useful for UI.""" - return "{1}@{0} ({2})".format(pyrfc3339.generate( - self.meta.creation_dt), self.meta.creation_host, self.id[:4]) - - def __repr__(self): - return "<{0}({1}, {2}, {3})>".format( - self.__class__.__name__, self.regr, self.id, self.meta) - - def __eq__(self, other): - return (isinstance(other, self.__class__) and - self.key == other.key and self.regr == other.regr and - self.meta == other.meta) - - -def report_new_account(config): - """Informs the user about their new ACME account.""" - reporter = zope.component.queryUtility(interfaces.IReporter) - if reporter is None: - return - reporter.add_message( - "Your account credentials have been saved in your Certbot " - "configuration directory at {0}. You should make a secure backup " - "of this folder now. This configuration directory will also " - "contain certificates and private keys obtained by Certbot " - "so making regular backups of this folder is ideal.".format( - config.config_dir), - reporter.MEDIUM_PRIORITY) - - -class AccountMemoryStorage(interfaces.AccountStorage): - """In-memory account storage.""" - - def __init__(self, initial_accounts=None): - self.accounts = initial_accounts if initial_accounts is not None else {} - - def find_all(self): - return list(six.itervalues(self.accounts)) - - def save(self, account, client): - if account.id in self.accounts: - logger.debug("Overwriting account: %s", account.id) - self.accounts[account.id] = account - - def load(self, account_id): - try: - return self.accounts[account_id] - except KeyError: - raise errors.AccountNotFound(account_id) - -class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): - """A backwards-compatible RegistrationResource with a new-authz URI. - - Hack: Certbot versions pre-0.11.1 expect to load - new_authzr_uri as part of the account. Because people - sometimes switch between old and new versions, we will - continue to write out this field for some time so older - clients don't crash in that scenario. - """ - new_authzr_uri = jose.Field('new_authzr_uri') - -class AccountFileStorage(interfaces.AccountStorage): - """Accounts file storage. - - :ivar .IConfig config: Client configuration - - """ - def __init__(self, config): - self.config = config - util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) - - def _account_dir_path(self, account_id): - return self._account_dir_path_for_server_path(account_id, self.config.server_path) - - def _account_dir_path_for_server_path(self, account_id, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - return os.path.join(accounts_dir, account_id) - - @classmethod - def _regr_path(cls, account_dir_path): - return os.path.join(account_dir_path, "regr.json") - - @classmethod - def _key_path(cls, account_dir_path): - return os.path.join(account_dir_path, "private_key.json") - - @classmethod - def _metadata_path(cls, account_dir_path): - return os.path.join(account_dir_path, "meta.json") - - def _find_all_for_server_path(self, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - try: - candidates = os.listdir(accounts_dir) - except OSError: - return [] - - accounts = [] - for account_id in candidates: - try: - accounts.append(self._load_for_server_path(account_id, server_path)) - except errors.AccountStorageError: - logger.debug("Account loading problem", exc_info=True) - - if not accounts and server_path in constants.LE_REUSE_SERVERS: - # find all for the next link down - prev_server_path = constants.LE_REUSE_SERVERS[server_path] - prev_accounts = self._find_all_for_server_path(prev_server_path) - # if we found something, link to that - if prev_accounts: - try: - self._symlink_to_accounts_dir(prev_server_path, server_path) - except OSError: - return [] - accounts = prev_accounts - return accounts - - def find_all(self): - return self._find_all_for_server_path(self.config.server_path) - - def _symlink_to_account_dir(self, prev_server_path, server_path, account_id): - prev_account_dir = self._account_dir_path_for_server_path(account_id, prev_server_path) - new_account_dir = self._account_dir_path_for_server_path(account_id, server_path) - os.symlink(prev_account_dir, new_account_dir) - - def _symlink_to_accounts_dir(self, prev_server_path, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - if os.path.islink(accounts_dir): - os.unlink(accounts_dir) - else: - os.rmdir(accounts_dir) - prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path) - os.symlink(prev_account_dir, accounts_dir) - - def _load_for_server_path(self, account_id, server_path): - account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) - if not os.path.isdir(account_dir_path): # isdir is also true for symlinks - if server_path in constants.LE_REUSE_SERVERS: - prev_server_path = constants.LE_REUSE_SERVERS[server_path] - prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) - # we didn't error so we found something, so create a symlink to that - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - # If accounts_dir isn't empty, make an account specific symlink - if os.listdir(accounts_dir): - self._symlink_to_account_dir(prev_server_path, server_path, account_id) - else: - self._symlink_to_accounts_dir(prev_server_path, server_path) - return prev_loaded_account - else: - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) - - try: - with open(self._regr_path(account_dir_path)) as regr_file: - regr = messages.RegistrationResource.json_loads(regr_file.read()) - with open(self._key_path(account_dir_path)) as key_file: - key = jose.JWK.json_loads(key_file.read()) - with open(self._metadata_path(account_dir_path)) as metadata_file: - meta = Account.Meta.json_loads(metadata_file.read()) - except IOError as error: - raise errors.AccountStorageError(error) - - return Account(regr, key, meta) - - def load(self, account_id): - return self._load_for_server_path(account_id, self.config.server_path) - - def save(self, account, client): - self._save(account, client, regr_only=False) - - def save_regr(self, account, acme): - """Save the registration resource. - - :param Account account: account whose regr should be saved - - """ - self._save(account, acme, regr_only=True) - - def delete(self, account_id): - """Delete registration info from disk - - :param account_id: id of account which should be deleted - - """ - account_dir_path = self._account_dir_path(account_id) - if not os.path.isdir(account_dir_path): - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) - # Step 1: Delete account specific links and the directory - self._delete_account_dir_for_server_path(account_id, self.config.server_path) - - # Step 2: Remove any accounts links and directories that are now empty - if not os.listdir(self.config.accounts_dir): - self._delete_accounts_dir_for_server_path(self.config.server_path) - - def _delete_account_dir_for_server_path(self, account_id, server_path): - link_func = functools.partial(self._account_dir_path_for_server_path, account_id) - nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) - shutil.rmtree(nonsymlinked_dir) - - def _delete_accounts_dir_for_server_path(self, server_path): - link_func = self.config.accounts_dir_for_server_path - nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) - os.rmdir(nonsymlinked_dir) - - def _delete_links_and_find_target_dir(self, server_path, link_func): - """Delete symlinks and return the nonsymlinked directory path. - - :param str server_path: file path based on server - :param callable link_func: callable that returns possible links - given a server_path - - :returns: the final, non-symlinked target - :rtype: str - - """ - dir_path = link_func(server_path) - - # does an appropriate directory link to me? if so, make sure that's gone - reused_servers = {} - for k in constants.LE_REUSE_SERVERS: - reused_servers[constants.LE_REUSE_SERVERS[k]] = k - - # is there a next one up? - possible_next_link = True - while possible_next_link: - possible_next_link = False - if server_path in reused_servers: - next_server_path = reused_servers[server_path] - next_dir_path = link_func(next_server_path) - if os.path.islink(next_dir_path) and os.readlink(next_dir_path) == dir_path: - possible_next_link = True - server_path = next_server_path - dir_path = next_dir_path - - # if there's not a next one up to delete, then delete me - # and whatever I link to - while os.path.islink(dir_path): - target = os.readlink(dir_path) - os.unlink(dir_path) - dir_path = target - - return dir_path - - def _save(self, account, acme, regr_only): - account_dir_path = self._account_dir_path(account.id) - util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) - try: - with open(self._regr_path(account_dir_path), "w") as regr_file: - regr = account.regr - # If we have a value for new-authz, save it for forwards - # compatibility with older versions of Certbot. If we don't - # have a value for new-authz, this is an ACMEv2 directory where - # an older version of Certbot won't work anyway. - if hasattr(acme.directory, "new-authz"): - regr = RegistrationResourceWithNewAuthzrURI( - new_authzr_uri=acme.directory.new_authz, - body={}, - uri=regr.uri) - else: - regr = messages.RegistrationResource( - body={}, - uri=regr.uri) - regr_file.write(regr.json_dumps()) - if not regr_only: - with util.safe_open(self._key_path(account_dir_path), - "w", chmod=0o400) as key_file: - key_file.write(account.key.json_dumps()) - with open(self._metadata_path( - account_dir_path), "w") as metadata_file: - metadata_file.write(account.meta.json_dumps()) - except IOError as error: - raise errors.AccountStorageError(error) diff --git a/certbot/auth_handler.py b/certbot/auth_handler.py deleted file mode 100644 index a8c8f6e9b..000000000 --- a/certbot/auth_handler.py +++ /dev/null @@ -1,470 +0,0 @@ -"""ACME AuthHandler.""" -import logging -import time -import datetime - -import zope.component - -from acme import challenges -from acme import messages -from acme import errors as acme_errors -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Tuple -# pylint: enable=unused-import, no-name-in-module -from certbot import achallenges -from certbot import errors -from certbot import error_handler -from certbot import interfaces - -logger = logging.getLogger(__name__) - - -class AuthHandler(object): - """ACME Authorization Handler for a client. - - :ivar auth: Authenticator capable of solving - :class:`~acme.challenges.Challenge` types - :type auth: :class:`certbot.interfaces.IAuthenticator` - - :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API. - - :ivar account: Client's Account - :type account: :class:`certbot.account.Account` - - :ivar list pref_challs: sorted user specified preferred challenges - type strings with the most preferred challenge listed first - - """ - def __init__(self, auth, acme_client, account, pref_challs): - self.auth = auth - self.acme = acme_client - - self.account = account - self.pref_challs = pref_challs - - def handle_authorizations(self, orderr, best_effort=False, max_retries=30): - """ - Retrieve all authorizations, perform all challenges required to validate - these authorizations, then poll and wait for the authorization to be checked. - :param acme.messages.OrderResource orderr: must have authorizations filled in - :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) - :param int max_retries: maximum number of retries to poll authorizations - :returns: list of all validated authorizations - :rtype: List - - :raises .AuthorizationError: If unable to retrieve all authorizations - """ - authzrs = orderr.authorizations[:] - if not authzrs: - raise errors.AuthorizationError('No authorization to handle.') - - # Retrieve challenges that need to be performed to validate authorizations. - achalls = self._choose_challenges(authzrs) - if not achalls: - return authzrs - - # Starting now, challenges will be cleaned at the end no matter what. - with error_handler.ExitHandler(self._cleanup_challenges, achalls): - # To begin, let's ask the authenticator plugin to perform all challenges. - try: - resps = self.auth.perform(achalls) - - # If debug is on, wait for user input before starting the verification process. - logger.info('Waiting for verification...') - config = zope.component.getUtility(interfaces.IConfig) - if config.debug_challenges: - notify = zope.component.getUtility(interfaces.IDisplay).notification - notify('Challenges loaded. Press continue to submit to CA. ' - 'Pass "-v" for more info about challenges.', pause=True) - except errors.AuthorizationError as error: - logger.critical('Failure in setting up challenges.') - logger.info('Attempting to clean up outstanding challenges...') - raise error - # All challenges should have been processed by the authenticator. - assert len(resps) == len(achalls), 'Some challenges have not been performed.' - - # Inform the ACME CA server that challenges are available for validation. - for achall, resp in zip(achalls, resps): - self.acme.answer_challenge(achall.challb, resp) - - # Wait for authorizations to be checked. - self._poll_authorizations(authzrs, max_retries, best_effort) - - # Keep validated authorizations only. If there is none, no certificate can be issued. - authzrs_validated = [authzr for authzr in authzrs - if authzr.body.status == messages.STATUS_VALID] - if not authzrs_validated: - raise errors.AuthorizationError('All challenges have failed.') - - return authzrs_validated - - def deactivate_valid_authorizations(self, orderr): - # type: (messages.OrderResource) -> Tuple[List, List] - """ - Deactivate all `valid` authorizations in the order, so that they cannot be re-used - in subsequent orders. - :param messages.OrderResource orderr: must have authorizations filled in - :returns: tuple of list of successfully deactivated authorizations, and - list of unsuccessfully deactivated authorizations. - :rtype: tuple - """ - to_deactivate = [authzr for authzr in orderr.authorizations - if authzr.body.status == messages.STATUS_VALID] - deactivated = [] - failed = [] - - for authzr in to_deactivate: - try: - authzr = self.acme.deactivate_authorization(authzr) - deactivated.append(authzr) - except acme_errors.Error as e: - failed.append(authzr) - logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) - - return (deactivated, failed) - - def _poll_authorizations(self, authzrs, max_retries, best_effort): - """ - Poll the ACME CA server, to wait for confirmation that authorizations have their challenges - all verified. The poll may occur several times, until all authorizations are checked - (valid or invalid), or after a maximum of retries. - """ - authzrs_to_check = {index: (authzr, None) - for index, authzr in enumerate(authzrs)} - authzrs_failed_to_report = [] - # Give an initial second to the ACME CA server to check the authorizations - sleep_seconds = 1 - for _ in range(max_retries): - # Wait for appropriate time (from Retry-After, initial wait, or no wait) - if sleep_seconds > 0: - time.sleep(sleep_seconds) - # Poll all updated authorizations. - authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _) - in authzrs_to_check.items()} - # Update the original list of authzr with the updated authzrs from server. - for index, (authzr, _) in authzrs_to_check.items(): - authzrs[index] = authzr - - # Gather failed authorizations - authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values() - if authzr.body.status == messages.STATUS_INVALID] - for authzr_failed in authzrs_failed: - logger.warning('Challenge failed for domain %s', - authzr_failed.body.identifier.value) - # Accumulating all failed authzrs to build a consolidated report - # on them at the end of the polling. - authzrs_failed_to_report.extend(authzrs_failed) - - # Extract out the authorization already checked for next poll iteration. - # Poll may stop here because there is no pending authorizations anymore. - authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp) - in authzrs_to_check.items() - if authzr.body.status == messages.STATUS_PENDING} - if not authzrs_to_check: - # Polling process is finished, we can leave the loop - break - - # Be merciful with the ACME server CA, check the Retry-After header returned, - # and wait this time before polling again in next loop iteration. - # From all the pending authorizations, we take the greatest Retry-After value - # to avoid polling an authorization before its relevant Retry-After value. - retry_after = max(self.acme.retry_after(resp, 3) - for _, resp in authzrs_to_check.values()) - sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() - - # In case of failed authzrs, create a report to the user. - if authzrs_failed_to_report: - _report_failed_authzrs(authzrs_failed_to_report, self.account.key) - if not best_effort: - # Without best effort, having failed authzrs is critical and fail the process. - raise errors.AuthorizationError('Some challenges have failed.') - - if authzrs_to_check: - # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. - raise errors.AuthorizationError('All authorizations were not finalized by the CA.') - - def _choose_challenges(self, authzrs): - """ - Retrieve necessary and pending challenges to satisfy server. - NB: Necessary and already validated challenges are not retrieved, - as they can be reused for a certificate issuance. - """ - pending_authzrs = [authzr for authzr in authzrs - if authzr.body.status != messages.STATUS_VALID] - achalls = [] # type: List[achallenges.AnnotatedChallenge] - if pending_authzrs: - logger.info("Performing the following challenges:") - for authzr in pending_authzrs: - authzr_challenges = authzr.body.challenges - if self.acme.acme_version == 1: - combinations = authzr.body.combinations - else: - combinations = tuple((i,) for i in range(len(authzr_challenges))) - - path = gen_challenge_path( - authzr_challenges, - self._get_chall_pref(authzr.body.identifier.value), - combinations) - - achalls.extend(self._challenge_factory(authzr, path)) - - return achalls - - def _get_chall_pref(self, domain): - """Return list of challenge preferences. - - :param str domain: domain for which you are requesting preferences - - """ - chall_prefs = [] - # Make sure to make a copy... - plugin_pref = self.auth.get_chall_pref(domain) - if self.pref_challs: - plugin_pref_types = set(chall.typ for chall in plugin_pref) - for typ in self.pref_challs: - if typ in plugin_pref_types: - chall_prefs.append(challenges.Challenge.TYPES[typ]) - if chall_prefs: - return chall_prefs - raise errors.AuthorizationError( - "None of the preferred challenges " - "are supported by the selected plugin") - chall_prefs.extend(plugin_pref) - return chall_prefs - - def _cleanup_challenges(self, achalls): - """Cleanup challenges. - - :param achalls: annotated challenges to cleanup - :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge` - - """ - logger.info("Cleaning up challenges") - self.auth.cleanup(achalls) - - def _challenge_factory(self, authzr, path): - """Construct Namedtuple Challenges - - :param messages.AuthorizationResource authzr: authorization - - :param list path: List of indices from `challenges`. - - :returns: achalls, list of challenge type - :class:`certbot.achallenges.Indexed` - :rtype: list - - :raises .errors.Error: if challenge type is not recognized - - """ - achalls = [] - - for index in path: - challb = authzr.body.challenges[index] - achalls.append(challb_to_achall( - challb, self.account.key, authzr.body.identifier.value)) - - return achalls - - -def challb_to_achall(challb, account_key, domain): - """Converts a ChallengeBody object to an AnnotatedChallenge. - - :param .ChallengeBody challb: ChallengeBody - :param .JWK account_key: Authorized Account Key - :param str domain: Domain of the challb - - :returns: Appropriate AnnotatedChallenge - :rtype: :class:`certbot.achallenges.AnnotatedChallenge` - - """ - chall = challb.chall - logger.info("%s challenge for %s", chall.typ, domain) - - if isinstance(chall, challenges.KeyAuthorizationChallenge): - return achallenges.KeyAuthorizationAnnotatedChallenge( - challb=challb, domain=domain, account_key=account_key) - elif isinstance(chall, challenges.DNS): - return achallenges.DNS(challb=challb, domain=domain) - else: - raise errors.Error( - "Received unsupported challenge of type: {0}".format(chall.typ)) - - -def gen_challenge_path(challbs, preferences, combinations): - """Generate a plan to get authority over the identity. - - .. todo:: This can be possibly be rewritten to use resolved_combinations. - - :param tuple challbs: A tuple of challenges - (:class:`acme.messages.Challenge`) from - :class:`acme.messages.AuthorizationResource` to be - fulfilled by the client in order to prove possession of the - identifier. - - :param list preferences: List of challenge preferences for domain - (:class:`acme.challenges.Challenge` subclasses) - - :param tuple combinations: A collection of sets of challenges from - :class:`acme.messages.Challenge`, each of which would - be sufficient to prove possession of the identifier. - - :returns: tuple of indices from ``challenges``. - :rtype: tuple - - :raises certbot.errors.AuthorizationError: If a - path cannot be created that satisfies the CA given the preferences and - combinations. - - """ - if combinations: - return _find_smart_path(challbs, preferences, combinations) - return _find_dumb_path(challbs, preferences) - - -def _find_smart_path(challbs, preferences, combinations): - """Find challenge path with server hints. - - Can be called if combinations is included. Function uses a simple - ranking system to choose the combo with the lowest cost. - - """ - chall_cost = {} - max_cost = 1 - for i, chall_cls in enumerate(preferences): - chall_cost[chall_cls] = i - max_cost += i - - # max_cost is now equal to sum(indices) + 1 - - best_combo = None - # Set above completing all of the available challenges - best_combo_cost = max_cost - - combo_total = 0 - for combo in combinations: - for challenge_index in combo: - combo_total += chall_cost.get(challbs[ - challenge_index].chall.__class__, max_cost) - - if combo_total < best_combo_cost: - best_combo = combo - best_combo_cost = combo_total - - combo_total = 0 - - if not best_combo: - _report_no_chall_path(challbs) - - return best_combo - - -def _find_dumb_path(challbs, preferences): - """Find challenge path without server hints. - - Should be called if the combinations hint is not included by the - server. This function either returns a path containing all - challenges provided by the CA or raises an exception. - - """ - path = [] - for i, challb in enumerate(challbs): - # supported is set to True if the challenge type is supported - supported = next((True for pref_c in preferences - if isinstance(challb.chall, pref_c)), False) - if supported: - path.append(i) - else: - _report_no_chall_path(challbs) - - return path - - -def _report_no_chall_path(challbs): - """Logs and raises an error that no satisfiable chall path exists. - - :param challbs: challenges from the authorization that can't be satisfied - - """ - msg = ("Client with the currently selected authenticator does not support " - "any combination of challenges that will satisfy the CA.") - if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): - msg += ( - " You may need to use an authenticator " - "plugin that can do challenges over DNS.") - logger.critical(msg) - raise errors.AuthorizationError(msg) - - -_ERROR_HELP_COMMON = ( - "To fix these errors, please make sure that your domain name was entered " - "correctly and the DNS A/AAAA record(s) for that domain contain(s) the " - "right IP address.") - - -_ERROR_HELP = { - "connection": - _ERROR_HELP_COMMON + " Additionally, please check that your computer " - "has a publicly routable IP address and that no firewalls are preventing " - "the server from communicating with the client. If you're using the " - "webroot plugin, you should also verify that you are serving files " - "from the webroot path you provided.", - "dnssec": - _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for " - "your domain, please ensure that the signature is valid.", - "malformed": - "To fix these errors, please make sure that you did not provide any " - "invalid information to the client, and try running Certbot " - "again.", - "serverInternal": - "Unfortunately, an error on the ACME server prevented you from completing " - "authorization. Please try again later.", - "tls": - _ERROR_HELP_COMMON + " Additionally, please check that you have an " - "up-to-date TLS configuration that allows the server to communicate " - "with the Certbot client.", - "unauthorized": _ERROR_HELP_COMMON, - "unknownHost": _ERROR_HELP_COMMON, -} - - -def _report_failed_authzrs(failed_authzrs, account_key): - """Notifies the user about failed authorizations.""" - problems = {} # type: Dict[str, List[achallenges.AnnotatedChallenge]] - failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value) - for authzr in failed_authzrs for challb in authzr.body.challenges - if challb.error] - - for achall in failed_achalls: - problems.setdefault(achall.error.typ, []).append(achall) - - reporter = zope.component.getUtility(interfaces.IReporter) - for achalls in problems.values(): - reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) - - -def _generate_failed_chall_msg(failed_achalls): - """Creates a user friendly error message about failed challenges. - - :param list failed_achalls: A list of failed - :class:`certbot.achallenges.AnnotatedChallenge` with the same error - type. - - :returns: A formatted error message for the client. - :rtype: str - - """ - error = failed_achalls[0].error - typ = error.typ - if messages.is_acme_error(error): - typ = error.code - msg = ["The following errors were reported by the server:"] - - for achall in failed_achalls: - msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % ( - achall.domain, typ, achall.error.detail)) - - if typ in _ERROR_HELP: - msg.append("\n\n") - msg.append(_ERROR_HELP[typ]) - - return "".join(msg) diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py deleted file mode 100644 index cd228ac12..000000000 --- a/certbot/cert_manager.py +++ /dev/null @@ -1,388 +0,0 @@ -"""Tools for managing certificates.""" -import datetime -import logging -import re -import traceback - -import pytz -import zope.component - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot import ocsp -from certbot import storage -from certbot import util -from certbot.compat import os -from certbot.display import util as display_util - -logger = logging.getLogger(__name__) - -################### -# Commands -################### - -def update_live_symlinks(config): - """Update the certificate file family symlinks to use archive_dir. - - Use the information in the config file to make symlinks point to - the correct archive directory. - - .. note:: This assumes that the installation is using a Reverter object. - - :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` - - """ - for renewal_file in storage.renewal_conf_files(config): - storage.RenewableCert(renewal_file, config, update_symlinks=True) - -def rename_lineage(config): - """Rename the specified lineage to the new name. - - :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` - - """ - disp = zope.component.getUtility(interfaces.IDisplay) - - certname = get_certnames(config, "rename")[0] - - new_certname = config.new_certname - if not new_certname: - code, new_certname = disp.input( - "Enter the new name for certificate {0}".format(certname), - flag="--updated-cert-name", force_interactive=True) - if code != display_util.OK or not new_certname: - raise errors.Error("User ended interaction.") - - lineage = lineage_for_certname(config, certname) - if not lineage: - raise errors.ConfigurationError("No existing certificate with name " - "{0} found.".format(certname)) - storage.rename_renewal_config(certname, new_certname, config) - disp.notification("Successfully renamed {0} to {1}." - .format(certname, new_certname), pause=False) - -def certificates(config): - """Display information about certs configured with Certbot - - :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` - """ - parsed_certs = [] - parse_failures = [] - for renewal_file in storage.renewal_conf_files(config): - try: - renewal_candidate = storage.RenewableCert(renewal_file, config) - crypto_util.verify_renewable_cert(renewal_candidate) - parsed_certs.append(renewal_candidate) - except Exception as e: # pylint: disable=broad-except - logger.warning("Renewal configuration file %s produced an " - "unexpected error: %s. Skipping.", renewal_file, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - parse_failures.append(renewal_file) - - # Describe all the certs - _describe_certs(config, parsed_certs, parse_failures) - -def delete(config): - """Delete Certbot files associated with a certificate lineage.""" - certnames = get_certnames(config, "delete", allow_multiple=True) - for certname in certnames: - storage.delete_files(config, certname) - disp = zope.component.getUtility(interfaces.IDisplay) - disp.notification("Deleted all files relating to certificate {0}." - .format(certname), pause=False) - -################### -# Public Helpers -################### - -def lineage_for_certname(cli_config, certname): - """Find a lineage object with name certname.""" - configs_dir = cli_config.renewal_configs_dir - # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755) - try: - renewal_file = storage.renewal_file_for_certname(cli_config, certname) - except errors.CertStorageError: - return None - try: - return storage.RenewableCert(renewal_file, cli_config) - except (errors.CertStorageError, IOError): - logger.debug("Renewal conf file %s is broken.", renewal_file) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - -def domains_for_certname(config, certname): - """Find the domains in the cert with name certname.""" - lineage = lineage_for_certname(config, certname) - return lineage.names() if lineage else None - -def find_duplicative_certs(config, domains): - """Find existing certs that match the given domain names. - - This function searches for certificates whose domains are equal to - the `domains` parameter and certificates whose domains are a subset - of the domains in the `domains` parameter. If multiple certificates - are found whose names are a subset of `domains`, the one whose names - are the largest subset of `domains` is returned. - - If multiple certificates' domains are an exact match or equally - sized subsets, which matching certificates are returned is - undefined. - - :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` - :param domains: List of domain names - :type domains: `list` of `str` - - :returns: lineages representing the identically matching cert and the - largest subset if they exist - :rtype: `tuple` of `storage.RenewableCert` or `None` - - """ - def update_certs_for_domain_matches(candidate_lineage, rv): - """Return cert as identical_names_cert if it matches, - or subset_names_cert if it matches as subset - """ - # TODO: Handle these differently depending on whether they are - # expired or still valid? - identical_names_cert, subset_names_cert = rv - candidate_names = set(candidate_lineage.names()) - if candidate_names == set(domains): - identical_names_cert = candidate_lineage - elif candidate_names.issubset(set(domains)): - # This logic finds and returns the largest subset-names cert - # in the case where there are several available. - if subset_names_cert is None: - subset_names_cert = candidate_lineage - elif len(candidate_names) > len(subset_names_cert.names()): - subset_names_cert = candidate_lineage - return (identical_names_cert, subset_names_cert) - - return _search_lineages(config, update_certs_for_domain_matches, (None, None)) - -def _archive_files(candidate_lineage, filetype): - """ In order to match things like: - /etc/letsencrypt/archive/example.com/chain1.pem. - - Anonymous functions which call this function are eventually passed (in a list) to - `match_and_check_overlaps` to help specify the acceptable_matches. - - :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to - be searched. - :param str filetype: main file name prefix e.g. "fullchain" or "chain". - - :returns: Files in candidate_lineage's archive dir that match the provided filetype. - :rtype: list of str or None - """ - archive_dir = candidate_lineage.archive_dir - pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) - if re.match("{0}[0-9]*.pem".format(filetype), f)] - if pattern: - return pattern - return None - -def _acceptable_matches(): - """ Generates the list that's passed to match_and_check_overlaps. Is its own function to - make unit testing easier. - - :returns: list of functions - :rtype: list - """ - return [lambda x: x.fullchain_path, lambda x: x.cert_path, - lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")] - -def cert_path_to_lineage(cli_config): - """ If config.cert_path is defined, try to find an appropriate value for config.certname. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - - :returns: a lineage name - :rtype: str - - :raises `errors.Error`: If the specified cert path can't be matched to a lineage name. - :raises `errors.OverlappingMatchFound`: If the matched lineage's archive is shared. - """ - acceptable_matches = _acceptable_matches() - match = match_and_check_overlaps(cli_config, acceptable_matches, - lambda x: cli_config.cert_path[0], lambda x: x.lineagename) - return match[0] - -def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func): - """ Searches through all lineages for a match, and checks for duplicates. - If a duplicate is found, an error is raised, as performing operations on lineages - that have their properties incorrectly duplicated elsewhere is probably a bad idea. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - :param list acceptable_matches: a list of functions that specify acceptable matches - :param function match_func: specifies what to match - :param function rv_func: specifies what to return - - """ - def find_matches(candidate_lineage, return_value, acceptable_matches): - """Returns a list of matches using _search_lineages.""" - acceptable_matches = [func(candidate_lineage) for func in acceptable_matches] - acceptable_matches_rv = [] # type: List[str] - for item in acceptable_matches: - if isinstance(item, list): - acceptable_matches_rv += item - else: - acceptable_matches_rv.append(item) - match = match_func(candidate_lineage) - if match in acceptable_matches_rv: - return_value.append(rv_func(candidate_lineage)) - return return_value - - matched = _search_lineages(cli_config, find_matches, [], acceptable_matches) - if not matched: - raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) - elif len(matched) > 1: - raise errors.OverlappingMatchFound() - else: - return matched - -def human_readable_cert_info(config, cert, skip_filter_checks=False): - """ Returns a human readable description of info about a RenewableCert object""" - certinfo = [] - checker = ocsp.RevocationChecker() - - if config.certname and cert.lineagename != config.certname and not skip_filter_checks: - return "" - if config.domains and not set(config.domains).issubset(cert.names()): - return "" - now = pytz.UTC.fromutc(datetime.datetime.utcnow()) - - reasons = [] - if cert.is_test_cert: - reasons.append('TEST_CERT') - if cert.target_expiry <= now: - reasons.append('EXPIRED') - elif checker.ocsp_revoked(cert): - reasons.append('REVOKED') - - if reasons: - status = "INVALID: " + ", ".join(reasons) - else: - diff = cert.target_expiry - now - if diff.days == 1: - status = "VALID: 1 day" - elif diff.days < 1: - status = "VALID: {0} hour(s)".format(diff.seconds // 3600) - else: - status = "VALID: {0} days".format(diff.days) - - valid_string = "{0} ({1})".format(cert.target_expiry, status) - certinfo.append(" Certificate Name: {0}\n" - " Domains: {1}\n" - " Expiry Date: {2}\n" - " Certificate Path: {3}\n" - " Private Key Path: {4}".format( - cert.lineagename, - " ".join(cert.names()), - valid_string, - cert.fullchain, - cert.privkey)) - return "".join(certinfo) - -def get_certnames(config, verb, allow_multiple=False, custom_prompt=None): - """Get certname from flag, interactively, or error out. - """ - certname = config.certname - if certname: - certnames = [certname] - else: - disp = zope.component.getUtility(interfaces.IDisplay) - filenames = storage.renewal_conf_files(config) - choices = [storage.lineagename_for_filename(name) for name in filenames] - if not choices: - raise errors.Error("No existing certificates found.") - if allow_multiple: - if not custom_prompt: - prompt = "Which certificate(s) would you like to {0}?".format(verb) - else: - prompt = custom_prompt - code, certnames = disp.checklist( - prompt, choices, cli_flag="--cert-name", force_interactive=True) - if code != display_util.OK: - raise errors.Error("User ended interaction.") - else: - if not custom_prompt: - prompt = "Which certificate would you like to {0}?".format(verb) - else: - prompt = custom_prompt - - code, index = disp.menu( - prompt, choices, cli_flag="--cert-name", force_interactive=True) - - if code != display_util.OK or index not in range(0, len(choices)): - raise errors.Error("User ended interaction.") - certnames = [choices[index]] - return certnames - -################### -# Private Helpers -################### - -def _report_lines(msgs): - """Format a results report for a category of single-line renewal outcomes""" - return " " + "\n ".join(str(msg) for msg in msgs) - -def _report_human_readable(config, parsed_certs): - """Format a results report for a parsed cert""" - certinfo = [] - for cert in parsed_certs: - certinfo.append(human_readable_cert_info(config, cert)) - return "\n".join(certinfo) - -def _describe_certs(config, parsed_certs, parse_failures): - """Print information about the certs we know about""" - out = [] # type: List[str] - - notify = out.append - - if not parsed_certs and not parse_failures: - notify("No certs found.") - else: - if parsed_certs: - match = "matching " if config.certname or config.domains else "" - notify("Found the following {0}certs:".format(match)) - notify(_report_human_readable(config, parsed_certs)) - if parse_failures: - notify("\nThe following renewal configurations " - "were invalid:") - notify(_report_lines(parse_failures)) - - disp = zope.component.getUtility(interfaces.IDisplay) - disp.notification("\n".join(out), pause=False, wrap=False) - -def _search_lineages(cli_config, func, initial_rv, *args): - """Iterate func over unbroken lineages, allowing custom return conditions. - - Allows flexible customization of return values, including multiple - return values and complex checks. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - :param function func: function used while searching over lineages - :param initial_rv: initial return value of the function (any type) - - :returns: Whatever was specified by `func` if a match is found. - """ - configs_dir = cli_config.renewal_configs_dir - # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755) - - rv = initial_rv - for renewal_file in storage.renewal_conf_files(cli_config): - try: - candidate_lineage = storage.RenewableCert(renewal_file, cli_config) - except (errors.CertStorageError, IOError): - logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - continue - rv = func(candidate_lineage, rv, *args) - return rv diff --git a/certbot/cli.py b/certbot/cli.py index 9b116fe3f..7f09f4638 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -438,7 +438,7 @@ class HelpfulArgumentParser(object): def __init__(self, args, plugins, detect_defaults=False): - from certbot import main + from certbot._internal import main self.VERBS = { "auth": main.certonly, "certonly": main.certonly, @@ -1256,7 +1256,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis def _create_subparsers(helpful): - from certbot.client import sample_user_agent # avoid import loops + from certbot._internal.client import sample_user_agent # avoid import loops helpful.add( None, "--user-agent", default=flag_default("user_agent"), help='Set a custom user agent string for the client. User agent strings allow ' diff --git a/certbot/client.py b/certbot/client.py deleted file mode 100644 index 91b54b205..000000000 --- a/certbot/client.py +++ /dev/null @@ -1,742 +0,0 @@ -"""Certbot client API.""" -import datetime -import logging -import platform - -import OpenSSL -import josepy as jose -import zope.component -from cryptography.hazmat.backends import default_backend -# https://github.com/python/typeshed/blob/master/third_party/ -# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi -from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore - -from acme import client as acme_client -from acme import crypto_util as acme_crypto_util -from acme import errors as acme_errors -from acme import messages -from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module - -import certbot -from certbot import account -from certbot import auth_handler -from certbot import cli -from certbot import constants -from certbot import crypto_util -from certbot import eff -from certbot import error_handler -from certbot import errors -from certbot import interfaces -from certbot import storage -from certbot import util -from certbot.compat import os -from certbot.display import enhancements -from certbot.display import ops as display_ops -from certbot.plugins import selection as plugin_selection - -logger = logging.getLogger(__name__) - - -def acme_from_config_key(config, key, regr=None): - "Wrangle ACME client construction" - # TODO: Allow for other alg types besides RS256 - net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl), - user_agent=determine_user_agent(config)) - return acme_client.BackwardsCompatibleClientV2(net, key, config.server) - - -def determine_user_agent(config): - """ - Set a user_agent string in the config based on the choice of plugins. - (this wasn't knowable at construction time) - - :returns: the client's User-Agent string - :rtype: `str` - """ - - # WARNING: To ensure changes are in line with Certbot's privacy - # policy, talk to a core Certbot team member before making any - # changes here. - if config.user_agent is None: - ua = ("CertbotACMEClient/{0} ({1}; {2}{8}) Authenticator/{3} Installer/{4} " - "({5}; flags: {6}) Py/{7}") - if os.environ.get("CERTBOT_DOCS") == "1": - cli_command = "certbot(-auto)" - os_info = "OS_NAME OS_VERSION" - python_version = "major.minor.patchlevel" - else: - cli_command = cli.cli_command - os_info = util.get_os_info_ua() - python_version = platform.python_version() - ua = ua.format(certbot.__version__, cli_command, os_info, - config.authenticator, config.installer, config.verb, - ua_flags(config), python_version, - "; " + config.user_agent_comment if config.user_agent_comment else "") - else: - ua = config.user_agent - return ua - -def ua_flags(config): - "Turn some very important CLI flags into clues in the user agent." - if isinstance(config, DummyConfig): - return "FLAGS" - flags = [] - if config.duplicate: - flags.append("dup") - if config.renew_by_default: - flags.append("frn") - if config.allow_subset_of_names: - flags.append("asn") - if config.noninteractive_mode: - flags.append("n") - hook_names = ("pre", "post", "renew", "manual_auth", "manual_cleanup") - hooks = [getattr(config, h + "_hook") for h in hook_names] - if any(hooks): - flags.append("hook") - return " ".join(flags) - -class DummyConfig(object): - "Shim for computing a sample user agent." - def __init__(self): - self.authenticator = "XXX" - self.installer = "YYY" - self.user_agent = None - self.verb = "SUBCOMMAND" - - def __getattr__(self, name): - "Any config properties we might have are None." - return None - -def sample_user_agent(): - "Document what this Certbot's user agent string will be like." - - return determine_user_agent(DummyConfig()) - - -def register(config, account_storage, tos_cb=None): - """Register new account with an ACME CA. - - This function takes care of generating fresh private key, - registering the account, optionally accepting CA Terms of Service - and finally saving the account. It should be called prior to - initialization of `Client`, unless account has already been created. - - :param .IConfig config: Client configuration. - - :param .AccountStorage account_storage: Account storage where newly - registered account will be saved to. Save happens only after TOS - acceptance step, so any account private keys or - `.RegistrationResource` will not be persisted if `tos_cb` - returns ``False``. - - :param tos_cb: If ACME CA requires the user to accept a Terms of - Service before registering account, client action is - necessary. For example, a CLI tool would prompt the user - acceptance. `tos_cb` must be a callable that should accept - `.RegistrationResource` and return a `bool`: ``True`` iff the - Terms of Service present in the contained - `.Registration.terms_of_service` is accepted by the client, and - ``False`` otherwise. ``tos_cb`` will be called only if the - client action is necessary, i.e. when ``terms_of_service is not - None``. This argument is optional, if not supplied it will - default to automatic acceptance! - - :raises certbot.errors.Error: In case of any client problems, in - particular registration failure, or unaccepted Terms of Service. - :raises acme.errors.Error: In case of any protocol problems. - - :returns: Newly registered and saved account, as well as protocol - API handle (should be used in `Client` initialization). - :rtype: `tuple` of `.Account` and `acme.client.Client` - - """ - # Log non-standard actions, potentially wrong API calls - if account_storage.find_all(): - logger.info("There are already existing accounts for %s", config.server) - if config.email is None: - if not config.register_unsafely_without_email: - msg = ("No email was provided and " - "--register-unsafely-without-email was not present.") - logger.warning(msg) - raise errors.Error(msg) - if not config.dry_run: - logger.info("Registering without email!") - - # If --dry-run is used, and there is no staging account, create one with no email. - if config.dry_run: - config.email = None - - # Each new registration shall use a fresh new key - rsa_key = generate_private_key( - public_exponent=65537, - key_size=config.rsa_key_size, - backend=default_backend()) - key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key)) - acme = acme_from_config_key(config, key) - # TODO: add phone? - regr = perform_registration(acme, config, tos_cb) - - acc = account.Account(regr, key) - account.report_new_account(config) - account_storage.save(acc, acme) - - eff.handle_subscription(config) - - return acc, acme - - -def perform_registration(acme, config, tos_cb): - """ - Actually register new account, trying repeatedly if there are email - problems - - :param acme.client.Client client: ACME client object. - :param .IConfig config: Client configuration. - :param Callable tos_cb: a callback to handle Term of Service agreement. - - :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: - 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: - msg = ("The ACME server believes %s is an invalid email address. " - "Please ensure it is a valid email and attempt " - "registration again." % config.email) - raise errors.Error(msg) - else: - config.email = display_ops.get_email(invalid=True) - return perform_registration(acme, config, tos_cb) - else: - raise - - -class Client(object): - """Certbot's client. - - :ivar .IConfig config: Client configuration. - :ivar .Account account: Account registered with `register`. - :ivar .AuthHandler auth_handler: Authorizations handler that will - dispatch DV challenges to appropriate authenticators - (providing `.IAuthenticator` interface). - :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) - authenticator that can solve ACME challenges. - :ivar .IInstaller installer: Installer. - :ivar acme.client.BackwardsCompatibleClientV2 acme: Optional ACME - client API handle. You might already have one from `register`. - - """ - - def __init__(self, config, account_, auth, installer, acme=None): - """Initialize a client.""" - self.config = config - self.account = account_ - self.auth = auth - self.installer = installer - - # Initialize ACME if account is provided - if acme is None and self.account is not None: - acme = acme_from_config_key(config, self.account.key, self.account.regr) - self.acme = acme - - if auth is not None: - self.auth_handler = auth_handler.AuthHandler( - auth, self.acme, self.account, self.config.pref_challs) - else: - self.auth_handler = None - - def obtain_certificate_from_csr(self, csr, orderr=None): - """Obtain certificate. - - :param .util.CSR csr: PEM-encoded Certificate Signing - Request. The key used to generate this CSR can be different - than `authkey`. - :param acme.messages.OrderResource orderr: contains authzrs - - :returns: certificate and chain as PEM byte strings - :rtype: tuple - - """ - if self.auth_handler is None: - msg = ("Unable to obtain certificate because authenticator is " - "not set.") - logger.warning(msg) - raise errors.Error(msg) - if self.account.regr is None: - raise errors.Error("Please register with the ACME server first.") - - logger.debug("CSR: %s", csr) - - if orderr is None: - orderr = self._get_order_and_authorizations(csr.data, best_effort=False) - - deadline = datetime.datetime.now() + datetime.timedelta(seconds=90) - orderr = self.acme.finalize_order(orderr, deadline) - cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem) - return cert.encode(), chain.encode() - - def obtain_certificate(self, domains, old_keypath=None): - """Obtains a certificate from the ACME server. - - `.register` must be called before `.obtain_certificate` - - :param list domains: domains to get a certificate - - :returns: certificate as PEM string, chain as PEM string, - newly generated private key (`.util.Key`), and DER-encoded - Certificate Signing Request (`.util.CSR`). - :rtype: tuple - - """ - - # We need to determine the key path, key PEM data, CSR path, - # and CSR PEM data. For a dry run, the paths are None because - # they aren't permanently saved to disk. For a lineage with - # --reuse-key, the key path and PEM data are derived from an - # existing file. - - if old_keypath is not None: - # We've been asked to reuse a specific existing private key. - # Therefore, we'll read it now and not generate a new one in - # either case below. - # - # We read in bytes here because the type of `key.pem` - # created below is also bytes. - with open(old_keypath, "rb") as f: - keypath = old_keypath - keypem = f.read() - key = util.Key(file=keypath, pem=keypem) # type: Optional[util.Key] - logger.info("Reusing existing private key from %s.", old_keypath) - else: - # The key is set to None here but will be created below. - key = None - - # Create CSR from names - if self.config.dry_run: - key = key or util.Key(file=None, - pem=crypto_util.make_key(self.config.rsa_key_size)) - csr = util.CSR(file=None, form="pem", - data=acme_crypto_util.make_csr( - key.pem, domains, self.config.must_staple)) - else: - key = key or crypto_util.init_save_key(self.config.rsa_key_size, - self.config.key_dir) - csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir) - - orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) - authzr = orderr.authorizations - auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable - successful_domains = [d for d in domains if d in auth_domains] - - # allow_subset_of_names is currently disabled for wildcard - # certificates. The reason for this and checking allow_subset_of_names - # below is because successful_domains == domains is never true if - # domains contains a wildcard because the ACME spec forbids identifiers - # in authzs from containing a wildcard character. - if self.config.allow_subset_of_names and successful_domains != domains: - if not self.config.dry_run: - os.remove(key.file) - os.remove(csr.file) - return self.obtain_certificate(successful_domains) - else: - cert, chain = self.obtain_certificate_from_csr(csr, orderr) - - return cert, chain, key, csr - - def _get_order_and_authorizations(self, csr_pem, best_effort): - # type: (str, bool) -> List[messages.OrderResource] - """Request a new order and complete its authorizations. - - :param str csr_pem: A CSR in PEM format. - :param bool best_effort: True if failing to complete all - authorizations should not raise an exception - - :returns: order resource containing its completed authorizations - :rtype: acme.messages.OrderResource - - """ - try: - orderr = self.acme.new_order(csr_pem) - except acme_errors.WildcardUnsupportedError: - raise errors.Error("The currently selected ACME CA endpoint does" - " not support issuing wildcard certificates.") - - # For a dry run, ensure we have an order with fresh authorizations - if orderr and self.config.dry_run: - deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) - if deactivated: - logger.debug("Recreating order after authz deactivations") - orderr = self.acme.new_order(csr_pem) - if failed: - logger.warning("Certbot was unable to obtain fresh authorizations for every domain" - ". The dry run will continue, but results may not be accurate.") - - authzr = self.auth_handler.handle_authorizations(orderr, best_effort) - return orderr.update(authorizations=authzr) - - # pylint: disable=no-member - def obtain_and_enroll_certificate(self, domains, certname): - """Obtain and enroll certificate. - - Get a new certificate for the specified domains using the specified - authenticator and installer, and then create a new renewable lineage - containing it. - - :param domains: domains to request a certificate for - :type domains: `list` of `str` - :param certname: requested name of lineage - :type certname: `str` or `None` - - :returns: A new :class:`certbot.storage.RenewableCert` instance - referred to the enrolled cert lineage, False if the cert could not - be obtained, or None if doing a successful dry run. - - """ - cert, chain, key, _ = self.obtain_certificate(domains) - - if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or - self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): - logger.info( - "Non-standard path(s), might not work with crontab installed " - "by your operating system package manager") - - new_name = self._choose_lineagename(domains, certname) - - if self.config.dry_run: - logger.debug("Dry run: Skipping creating new lineage for %s", - new_name) - return None - return storage.RenewableCert.new_lineage( - new_name, cert, - key.pem, chain, - self.config) - - def _choose_lineagename(self, domains, certname): - """Chooses a name for the new lineage. - - :param domains: domains in certificate request - :type domains: `list` of `str` - :param certname: requested name of lineage - :type certname: `str` or `None` - - :returns: lineage name that should be used - :rtype: str - - """ - if certname: - return certname - elif util.is_wildcard_domain(domains[0]): - # Don't make files and directories starting with *. - return domains[0][2:] - return domains[0] - - def save_certificate(self, cert_pem, chain_pem, - cert_path, chain_path, fullchain_path): - """Saves the certificate received from the ACME server. - - :param str cert_pem: - :param str chain_pem: - :param str cert_path: Candidate path to a certificate. - :param str chain_path: Candidate path to a certificate chain. - :param str fullchain_path: Candidate path to a full cert chain. - - :returns: cert_path, chain_path, and fullchain_path as absolute - paths to the actual files - :rtype: `tuple` of `str` - - :raises IOError: If unable to find room to write the cert files - - """ - for path in cert_path, chain_path, fullchain_path: - util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) - - - cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) - - try: - cert_file.write(cert_pem) - finally: - cert_file.close() - logger.info("Server issued certificate; certificate written to %s", - abs_cert_path) - - chain_file, abs_chain_path =\ - _open_pem_file('chain_path', chain_path) - fullchain_file, abs_fullchain_path =\ - _open_pem_file('fullchain_path', fullchain_path) - - _save_chain(chain_pem, chain_file) - _save_chain(cert_pem + chain_pem, fullchain_file) - - return abs_cert_path, abs_chain_path, abs_fullchain_path - - def deploy_certificate(self, domains, privkey_path, - cert_path, chain_path, fullchain_path): - """Install certificate - - :param list domains: list of domains to install the certificate - :param str privkey_path: path to certificate private key - :param str cert_path: certificate file path (optional) - :param str chain_path: chain file path - - """ - if self.installer is None: - logger.warning("No installer specified, client is unable to deploy" - "the certificate") - raise errors.Error("No installer available") - - chain_path = None if chain_path is None else os.path.abspath(chain_path) - - msg = ("Unable to install the certificate") - with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): - for dom in domains: - self.installer.deploy_cert( - domain=dom, cert_path=os.path.abspath(cert_path), - key_path=os.path.abspath(privkey_path), - chain_path=chain_path, - fullchain_path=fullchain_path) - self.installer.save() # needed by the Apache plugin - - self.installer.save("Deployed ACME Certificate") - - msg = ("We were unable to install your certificate, " - "however, we successfully restored your " - "server to its prior configuration.") - with error_handler.ErrorHandler(self._rollback_and_restart, msg): - # sites may have been enabled / final cleanup - self.installer.restart() - - def enhance_config(self, domains, chain_path, ask_redirect=True): - """Enhance the configuration. - - :param list domains: list of domains to configure - :param chain_path: chain file path - :type chain_path: `str` or `None` - - :raises .errors.Error: if no installer is specified in the - client. - - """ - if self.installer is None: - logger.warning("No installer is specified, there isn't any " - "configuration to enhance.") - raise errors.Error("No installer available") - - enhanced = False - enhancement_info = ( - ("hsts", "ensure-http-header", "Strict-Transport-Security"), - ("redirect", "redirect", None), - ("staple", "staple-ocsp", chain_path), - ("uir", "ensure-http-header", "Upgrade-Insecure-Requests"),) - supported = self.installer.supported_enhancements() - - for config_name, enhancement_name, option in enhancement_info: - config_value = getattr(self.config, config_name) - if enhancement_name in supported: - if ask_redirect: - if config_name == "redirect" and config_value is None: - config_value = enhancements.ask(enhancement_name) - if not config_value: - logger.warning("Future versions of Certbot will automatically " - "configure the webserver so that all requests redirect to secure " - "HTTPS access. You can control this behavior and disable this " - "warning with the --redirect and --no-redirect flags.") - if config_value: - self.apply_enhancement(domains, enhancement_name, option) - enhanced = True - elif config_value: - logger.warning( - "Option %s is not supported by the selected installer. " - "Skipping enhancement.", config_name) - - msg = ("We were unable to restart web server") - if enhanced: - with error_handler.ErrorHandler(self._rollback_and_restart, msg): - self.installer.restart() - - def apply_enhancement(self, domains, enhancement, options=None): - """Applies an enhancement on all domains. - - :param list domains: list of ssl_vhosts (as strings) - :param str enhancement: name of enhancement, e.g. ensure-http-header - :param str options: options to enhancement, e.g. Strict-Transport-Security - - .. note:: When more `options` are needed, make options a list. - - :raises .errors.PluginError: If Enhancement is not supported, or if - there is any other problem with the enhancement. - - - """ - msg = ("We were unable to set up enhancement %s for your server, " - "however, we successfully installed your certificate." - % (enhancement)) - with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): - for dom in domains: - try: - self.installer.enhance(dom, enhancement, options) - except errors.PluginEnhancementAlreadyPresent: - if enhancement == "ensure-http-header": - logger.warning("Enhancement %s was already set.", - options) - else: - logger.warning("Enhancement %s was already set.", - enhancement) - except errors.PluginError: - logger.warning("Unable to set enhancement %s for %s", - enhancement, dom) - raise - - self.installer.save("Add enhancement %s" % (enhancement)) - - def _recovery_routine_with_msg(self, success_msg): - """Calls the installer's recovery routine and prints success_msg - - :param str success_msg: message to show on successful recovery - - """ - self.installer.recovery_routine() - reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message(success_msg, reporter.HIGH_PRIORITY) - - def _rollback_and_restart(self, success_msg): - """Rollback the most recent checkpoint and restart the webserver - - :param str success_msg: message to show on successful rollback - - """ - logger.critical("Rolling back to previous server configuration...") - reporter = zope.component.getUtility(interfaces.IReporter) - try: - self.installer.rollback_checkpoints() - self.installer.restart() - except: - reporter.add_message( - "An error occurred and we failed to restore your config and " - "restart your server. Please post to " - "https://community.letsencrypt.org/c/help " - "with details about your configuration and this error you received.", - reporter.HIGH_PRIORITY) - raise - reporter.add_message(success_msg, reporter.HIGH_PRIORITY) - - -def validate_key_csr(privkey, csr=None): - """Validate Key and CSR files. - - Verifies that the client key and csr arguments are valid and correspond to - one another. This does not currently check the names in the CSR due to - the inability to read SANs from CSRs in python crypto libraries. - - If csr is left as None, only the key will be validated. - - :param privkey: Key associated with CSR - :type privkey: :class:`certbot.util.Key` - - :param .util.CSR csr: CSR - - :raises .errors.Error: when validation fails - - """ - # TODO: Handle all of these problems appropriately - # The client can eventually do things like prompt the user - # and allow the user to take more appropriate actions - - # Key must be readable and valid. - if privkey.pem and not crypto_util.valid_privkey(privkey.pem): - raise errors.Error("The provided key is not a valid key") - - if csr: - if csr.form == "der": - csr_obj = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_ASN1, csr.data) - cert_buffer = OpenSSL.crypto.dump_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_obj - ) - csr = util.CSR(csr.file, cert_buffer, "pem") - - # If CSR is provided, it must be readable and valid. - if csr.data and not crypto_util.valid_csr(csr.data): - raise errors.Error("The provided CSR is not a valid CSR") - - # If both CSR and key are provided, the key must be the same key used - # in the CSR. - if csr.data and privkey.pem: - if not crypto_util.csr_matches_pubkey( - csr.data, privkey.pem): - raise errors.Error("The key and CSR do not match") - - -def rollback(default_installer, checkpoints, config, plugins): - """Revert configuration the specified number of checkpoints. - - :param int checkpoints: Number of checkpoints to revert. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - """ - # Misconfigurations are only a slight problems... allow the user to rollback - installer = plugin_selection.pick_installer( - config, default_installer, plugins, question="Which installer " - "should be used for rollback?") - - # No Errors occurred during init... proceed normally - # If installer is None... couldn't find an installer... there shouldn't be - # anything to rollback - if installer is not None: - installer.rollback_checkpoints(checkpoints) - installer.restart() - -def _open_pem_file(cli_arg_path, pem_path): - """Open a pem file. - - If cli_arg_path was set by the client, open that. - Otherwise, uniquify the file path. - - :param str cli_arg_path: the cli arg name, e.g. cert_path - :param str pem_path: the pem file path to open - - :returns: a tuple of file object and its absolute file path - - """ - if cli.set_by_cli(cli_arg_path): - return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ - os.path.abspath(pem_path) - uniq = util.unique_file(pem_path, 0o644, "wb") - return uniq[0], os.path.abspath(uniq[1]) - -def _save_chain(chain_pem, chain_file): - """Saves chain_pem at a unique path based on chain_path. - - :param str chain_pem: certificate chain in PEM format - :param str chain_file: chain file object - - """ - try: - chain_file.write(chain_pem) - finally: - chain_file.close() - - logger.info("Cert chain written to %s", chain_file.name) diff --git a/certbot/display/ops.py b/certbot/display/ops.py index b5f3655fe..1a36744a5 100644 --- a/certbot/display/ops.py +++ b/certbot/display/ops.py @@ -75,7 +75,7 @@ def choose_account(accounts): """Choose an account. :param list accounts: Containing at least one - :class:`~certbot.account.Account` + :class:`~certbot._internal.account.Account` """ # Note this will get more complicated once we start recording authorizations diff --git a/certbot/error_handler.py b/certbot/error_handler.py deleted file mode 100644 index 1a570e48e..000000000 --- a/certbot/error_handler.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Registers functions to be called if an exception or signal occurs.""" -import functools -import logging -import signal -import traceback - -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Callable, Dict, List, Union -# pylint: enable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -# _SIGNALS stores the signals that will be handled by the ErrorHandler. These -# signals were chosen as their default handler terminates the process and could -# potentially occur from inside Python. Signals such as SIGILL were not -# included as they could be a sign of something devious and we should terminate -# immediately. -if os.name != "nt": - _SIGNALS = [signal.SIGTERM] - for signal_code in [signal.SIGHUP, signal.SIGQUIT, - signal.SIGXCPU, signal.SIGXFSZ]: - # Adding only those signals that their default action is not Ignore. - # This is platform-dependent, so we check it dynamically. - if signal.getsignal(signal_code) != signal.SIG_IGN: - _SIGNALS.append(signal_code) -else: - # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. - # As consumed by CPython, most of handlers on theses signals are useless, in particular - # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops - # immediately the process without calling the attached handler. Besides, non-POSIX signals - # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the - # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable - # behavior in fact, and maps to the handler to SIGINT. However in this case, a - # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager - # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. - # - # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, - # https://docs.python.org/2/library/os.html#os.kill, - # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question - _SIGNALS = [] - - -class ErrorHandler(object): - """Context manager for running code that must be cleaned up on failure. - - The context manager allows you to register functions that will be called - when an exception (excluding SystemExit) or signal is encountered. - Usage:: - - handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs) - handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs) - - with handler: - do_something() - - Or for one cleanup function:: - - with ErrorHandler(func, args, kwargs): - do_something() - - If an exception is raised out of do_something, the cleanup functions will - be called in last in first out order. Then the exception is raised. - Similarly, if a signal is encountered, the cleanup functions are called - followed by the previously received signal handler. - - Each registered cleanup function is called exactly once. If a registered - function raises an exception, it is logged and the next function is called. - Signals received while the registered functions are executing are - deferred until they finish. - - """ - def __init__(self, func, *args, **kwargs): - self.call_on_regular_exit = False - self.body_executed = False - self.funcs = [] # type: List[Callable[[], Any]] - self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]] - self.received_signals = [] # type: List[int] - if func is not None: - self.register(func, *args, **kwargs) - - def __enter__(self): - self.body_executed = False - self._set_signal_handlers() - - def __exit__(self, exec_type, exec_value, trace): - self.body_executed = True - retval = False - # SystemExit is ignored to properly handle forks that don't exec - if exec_type is SystemExit: - return retval - elif exec_type is None: - if not self.call_on_regular_exit: - return retval - elif exec_type is errors.SignalExit: - logger.debug("Encountered signals: %s", self.received_signals) - retval = True - else: - logger.debug("Encountered exception:\n%s", "".join( - traceback.format_exception(exec_type, exec_value, trace))) - - self._call_registered() - self._reset_signal_handlers() - self._call_signals() - return retval - - def register(self, func, *args, **kwargs): - # type: (Callable, *Any, **Any) -> None - """Sets func to be run with the given arguments during cleanup. - - :param function func: function to be called in case of an error - - """ - self.funcs.append(functools.partial(func, *args, **kwargs)) - - def _call_registered(self): - """Calls all registered functions""" - logger.debug("Calling registered functions") - while self.funcs: - try: - self.funcs[-1]() - except Exception: # pylint: disable=broad-except - logger.error("Encountered exception during recovery: ", exc_info=True) - self.funcs.pop() - - def _set_signal_handlers(self): - """Sets signal handlers for signals in _SIGNALS.""" - for signum in _SIGNALS: - prev_handler = signal.getsignal(signum) - # If prev_handler is None, the handler was set outside of Python - if prev_handler is not None: - self.prev_handlers[signum] = prev_handler - signal.signal(signum, self._signal_handler) - - def _reset_signal_handlers(self): - """Resets signal handlers for signals in _SIGNALS.""" - for signum in self.prev_handlers: - signal.signal(signum, self.prev_handlers[signum]) - self.prev_handlers.clear() - - def _signal_handler(self, signum, unused_frame): - """Replacement function for handling received signals. - - Store the received signal. If we are executing the code block in - the body of the context manager, stop by raising signal exit. - - :param int signum: number of current signal - - """ - self.received_signals.append(signum) - if not self.body_executed: - raise errors.SignalExit - - def _call_signals(self): - """Finally call the deferred signals.""" - for signum in self.received_signals: - logger.debug("Calling signal %s", signum) - os.kill(os.getpid(), signum) - -class ExitHandler(ErrorHandler): - """Context manager for running code that must be cleaned up. - - Subclass of ErrorHandler, with the same usage and parameters. - In addition to cleaning up on all signals, also cleans up on - regular exit. - """ - def __init__(self, func, *args, **kwargs): - ErrorHandler.__init__(self, func, *args, **kwargs) - self.call_on_regular_exit = True diff --git a/certbot/lock.py b/certbot/lock.py deleted file mode 100644 index eda2a72a1..000000000 --- a/certbot/lock.py +++ /dev/null @@ -1,261 +0,0 @@ -"""Implements file locks compatible with Linux and Windows for locking files and directories.""" -import errno -import logging -try: - import fcntl # pylint: disable=import-error -except ImportError: - import msvcrt # pylint: disable=import-error - POSIX_MODE = False -else: - POSIX_MODE = True - -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem - -logger = logging.getLogger(__name__) - - -def lock_dir(dir_path): - # type: (str) -> LockFile - """Place a lock file on the directory at dir_path. - - The lock file is placed in the root of dir_path with the name - .certbot.lock. - - :param str dir_path: path to directory - - :returns: the locked LockFile object - :rtype: LockFile - - :raises errors.LockError: if unable to acquire the lock - - """ - return LockFile(os.path.join(dir_path, '.certbot.lock')) - - -class LockFile(object): - """ - Platform independent file lock system. - LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, - instance is created, the associated file is 'locked from the point of view of the OS, - meaning that if another instance of Certbot try at the same time to acquire the same lock, - it will raise an Exception. Calling release method will release the lock, and make it - available to every other instance. - Upon exit, Certbot will also release all the locks. - This allows us to protect a file or directory from being concurrently accessed - or modified by two Certbot instances. - LockFile is platform independent: it will proceed to the appropriate OS lock mechanism - depending on Linux or Windows. - """ - def __init__(self, path): - # type: (str) -> None - """ - Create a LockFile instance on the given file path, and acquire lock. - :param str path: the path to the file that will hold a lock - """ - self._path = path - mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism - self._lock_mechanism = mechanism(path) - - self.acquire() - - def __repr__(self): - # type: () -> str - repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) - if self.is_locked(): - repr_str += 'acquired>' - else: - repr_str += 'released>' - return repr_str - - def acquire(self): - # type: () -> None - """ - Acquire the lock on the file, forbidding any other Certbot instance to acquire it. - :raises errors.LockError: if unable to acquire the lock - """ - self._lock_mechanism.acquire() - - def release(self): - # type: () -> None - """ - Release the lock on the file, allowing any other Certbot instance to acquire it. - """ - self._lock_mechanism.release() - - def is_locked(self): - # type: () -> bool - """ - Check if the file is currently locked. - :return: True if the file is locked, False otherwise - """ - return self._lock_mechanism.is_locked() - - -class _BaseLockMechanism(object): - def __init__(self, path): - # type: (str) -> None - """ - Create a lock file mechanism for Unix. - :param str path: the path to the lock file - """ - self._path = path - self._fd = None # type: Optional[int] - - def is_locked(self): - # type: () -> bool - """Check if lock file is currently locked. - :return: True if the lock file is locked - :rtype: bool - """ - return self._fd is not None - - def acquire(self): # pylint: disable=missing-docstring - pass # pragma: no cover - - def release(self): # pylint: disable=missing-docstring - pass # pragma: no cover - - -class _UnixLockMechanism(_BaseLockMechanism): - """ - A UNIX lock file mechanism. - This lock file is released when the locked file is closed or the - process exits. It cannot be used to provide synchronization between - threads. It is based on the lock_file package by Martin Horcicka. - """ - def acquire(self): - # type: () -> None - """Acquire the lock.""" - while self._fd is None: - # Open the file - fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) - try: - self._try_lock(fd) - if self._lock_success(fd): - self._fd = fd - finally: - # Close the file if it is not the required one - if self._fd is None: - os.close(fd) - - def _try_lock(self, fd): - # type: (int) -> None - """ - Try to acquire the lock file without blocking. - :param int fd: file descriptor of the opened file to lock - """ - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError as err: - if err.errno in (errno.EACCES, errno.EAGAIN): - logger.debug('A lock on %s is held by another process.', self._path) - raise errors.LockError('Another instance of Certbot is already running.') - raise - - def _lock_success(self, fd): - # type: (int) -> bool - """ - Did we successfully grab the lock? - Because this class deletes the locked file when the lock is - released, it is possible another process removed and recreated - the file between us opening the file and acquiring the lock. - :param int fd: file descriptor of the opened file to lock - :returns: True if the lock was successfully acquired - :rtype: bool - """ - # Normally os module should not be imported in certbot codebase except in certbot.compat - # for the sake of compatibility over Windows and Linux. - # We make an exception here, since _lock_success is private and called only on Linux. - from os import stat, fstat # pylint: disable=os-module-forbidden - try: - stat1 = stat(self._path) - except OSError as err: - if err.errno == errno.ENOENT: - return False - raise - - stat2 = fstat(fd) - # If our locked file descriptor and the file on disk refer to - # the same device and inode, they're the same file. - return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino - - def release(self): - # type: () -> None - """Remove, close, and release the lock file.""" - # It is important the lock file is removed before it's released, - # otherwise: - # - # process A: open lock file - # process B: release lock file - # process A: lock file - # process A: check device and inode - # process B: delete file - # process C: open and lock a different file at the same path - try: - os.remove(self._path) - finally: - # Following check is done to make mypy happy: it ensure that self._fd, marked - # as Optional[int] is effectively int to make it compatible with os.close signature. - if self._fd is None: # pragma: no cover - raise TypeError('Error, self._fd is None.') - try: - os.close(self._fd) - finally: - self._fd = None - - -class _WindowsLockMechanism(_BaseLockMechanism): - """ - A Windows lock file mechanism. - By default on Windows, acquiring a file handler gives exclusive access to the process - and results in an effective lock. However, it is possible to explicitly acquire the - file handler in shared access in terms of read and write, and this is done by os.open - and io.open in Python. So an explicit lock needs to be done through the call of - msvcrt.locking, that will lock the first byte of the file. In theory, it is also - possible to access a file in shared delete access, allowing other processes to delete an - opened file. But this needs also to be done explicitly by all processes using the Windows - low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers - state that deleting a file opened by a process from another process is not possible with - os.open and io.open. - Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race - condition encountered on Linux is not possible on Windows, leading to a simpler workflow. - """ - def acquire(self): - """Acquire the lock""" - open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC - - fd = None - try: - # Under Windows, filesystem.open will raise directly an EACCES error - # if the lock file is already locked. - fd = filesystem.open(self._path, open_mode, 0o600) - msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) - except (IOError, OSError) as err: - if fd: - os.close(fd) - # Anything except EACCES is unexpected. Raise directly the error in that case. - if err.errno != errno.EACCES: - raise - logger.debug('A lock on %s is held by another process.', self._path) - raise errors.LockError('Another instance of Certbot is already running.') - - self._fd = fd - - def release(self): - """Release the lock.""" - try: - msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) - os.close(self._fd) - - try: - os.remove(self._path) - except OSError as e: - # If the lock file cannot be removed, it is not a big deal. - # Likely another instance is acquiring the lock we just released. - logger.debug(str(e)) - finally: - self._fd = None diff --git a/certbot/main.py b/certbot/main.py deleted file mode 100644 index 620e33be9..000000000 --- a/certbot/main.py +++ /dev/null @@ -1,1365 +0,0 @@ -"""Certbot main entry point.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import functools -import logging.handlers -import sys - -import configobj -import josepy as jose -import zope.component - -from acme import errors as acme_errors -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module - -import certbot -from certbot import account -from certbot import cert_manager -from certbot import cli -from certbot import client -from certbot import configuration -from certbot import constants -from certbot import crypto_util -from certbot import eff -from certbot import errors -from certbot import hooks -from certbot import interfaces -from certbot import log -from certbot import renewal -from certbot import reporter -from certbot import storage -from certbot import updater -from certbot import util -from certbot.compat import filesystem -from certbot.compat import misc -from certbot.compat import os -from certbot.display import util as display_util, ops as display_ops -from certbot.plugins import disco as plugins_disco -from certbot.plugins import enhancements -from certbot.plugins import selection as plug_sel - -USER_CANCELLED = ("User chose to cancel the operation and may " - "reinvoke the client.") - - -logger = logging.getLogger(__name__) - - -def _suggest_donation_if_appropriate(config): - """Potentially suggest a donation to support Certbot. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - assert config.verb != "renew" - if config.staging: - # --dry-run implies --staging - return - reporter_util = zope.component.getUtility(interfaces.IReporter) - msg = ("If you like Certbot, please consider supporting our work by:\n\n" - "Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n" - "Donating to EFF: https://eff.org/donate-le\n\n") - reporter_util.add_message(msg, reporter_util.LOW_PRIORITY) - -def _report_successful_dry_run(config): - """Reports on successful dry run - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - reporter_util = zope.component.getUtility(interfaces.IReporter) - assert config.verb != "renew" - reporter_util.add_message("The dry run was successful.", - reporter_util.HIGH_PRIORITY, on_crash=False) - - -def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None): - """Authenticate and enroll certificate. - - This method finds the relevant lineage, figures out what to do with it, - then performs that action. Includes calls to hooks, various reports, - checks, and requests for user input. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names to get a certificate. Defaults to `None` - :type domains: `list` of `str` - - :param certname: Name of new certificate. Defaults to `None` - :type certname: str - - :param lineage: Certificate lineage object. Defaults to `None` - :type lineage: storage.RenewableCert - - :returns: the issued certificate or `None` if doing a dry run - :rtype: storage.RenewableCert or None - - :raises errors.Error: if certificate could not be obtained - - """ - hooks.pre_hook(config) - try: - if lineage is not None: - # Renewal, where we already know the specific lineage we're - # interested in - logger.info("Renewing an existing certificate") - renewal.renew_cert(config, domains, le_client, lineage) - else: - # TREAT AS NEW REQUEST - assert domains is not None - logger.info("Obtaining a new certificate") - lineage = le_client.obtain_and_enroll_certificate(domains, certname) - if lineage is False: - raise errors.Error("Certificate could not be obtained") - elif lineage is not None: - hooks.deploy_hook(config, lineage.names(), lineage.live_dir) - finally: - hooks.post_hook(config) - - return lineage - - -def _handle_subset_cert_request(config, domains, cert): - """Figure out what to do if a previous cert had a subset of the names now requested - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param cert: Certificate object - :type cert: storage.RenewableCert - - :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname - action can be: "newcert" | "renew" | "reinstall" - :rtype: `tuple` of `str` - - """ - existing = ", ".join(cert.names()) - question = ( - "You have an existing certificate that contains a portion of " - "the domains you requested (ref: {0}){br}{br}It contains these " - "names: {1}{br}{br}You requested these names for the new " - "certificate: {2}.{br}{br}Do you want to expand and replace this existing " - "certificate with the new certificate?" - ).format(cert.configfile.filename, - existing, - ", ".join(domains), - br=os.linesep) - if config.expand or config.renew_by_default or zope.component.getUtility( - interfaces.IDisplay).yesno(question, "Expand", "Cancel", - cli_flag="--expand", - force_interactive=True): - return "renew", cert - else: - reporter_util = zope.component.getUtility(interfaces.IReporter) - reporter_util.add_message( - "To obtain a new certificate that contains these names without " - "replacing your existing certificate for {0}, you must use the " - "--duplicate option.{br}{br}" - "For example:{br}{br}{1} --duplicate {2}".format( - existing, - sys.argv[0], " ".join(sys.argv[1:]), - br=os.linesep - ), - reporter_util.HIGH_PRIORITY) - raise errors.Error(USER_CANCELLED) - - -def _handle_identical_cert_request(config, lineage): - """Figure out what to do if a lineage has the same names as a previously obtained one - - :param config: Configuration object - :type config: interfaces.IConfig - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname - action can be: "newcert" | "renew" | "reinstall" - :rtype: `tuple` of `str` - - """ - if not lineage.ensure_deployed(): - return "reinstall", lineage - if renewal.should_renew(config, lineage): - return "renew", lineage - if config.reinstall: - # Set with --reinstall, force an identical certificate to be - # reinstalled without further prompting. - return "reinstall", lineage - question = ( - "You have an existing certificate that has exactly the same " - "domains or certificate name you requested and isn't close to expiry." - "{br}(ref: {0}){br}{br}What would you like to do?" - ).format(lineage.configfile.filename, br=os.linesep) - - if config.verb == "run": - keep_opt = "Attempt to reinstall this existing certificate" - elif config.verb == "certonly": - keep_opt = "Keep the existing certificate for now" - choices = [keep_opt, - "Renew & replace the cert (limit ~5 per 7 days)"] - - display = zope.component.getUtility(interfaces.IDisplay) - response = display.menu(question, choices, - default=0, force_interactive=True) - if response[0] == display_util.CANCEL: - # TODO: Add notification related to command-line options for - # skipping the menu for this case. - raise errors.Error( - "Operation canceled. You may re-run the client.") - elif response[1] == 0: - return "reinstall", lineage - elif response[1] == 1: - return "renew", lineage - raise AssertionError('This is impossible') - - -def _find_lineage_for_domains(config, domains): - """Determine whether there are duplicated names and how to handle - them (renew, reinstall, newcert, or raising an error to stop - the client run if the user chooses to cancel the operation when - prompted). - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :returns: Two-element tuple containing desired new-certificate behavior as - a string token ("reinstall", "renew", or "newcert"), plus either - a RenewableCert instance or `None` if renewal shouldn't occur. - :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` - - :raises errors.Error: If the user would like to rerun the client again. - - """ - # Considering the possibility that the requested certificate is - # related to an existing certificate. (config.duplicate, which - # is set with --duplicate, skips all of this logic and forces any - # kind of certificate to be obtained with renewal = False.) - if config.duplicate: - return "newcert", None - # TODO: Also address superset case - ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, domains) - # XXX ^ schoen is not sure whether that correctly reads the systemwide - # configuration file. - if ident_names_cert is None and subset_names_cert is None: - return "newcert", None - - if ident_names_cert is not None: - return _handle_identical_cert_request(config, ident_names_cert) - elif subset_names_cert is not None: - return _handle_subset_cert_request(config, domains, subset_names_cert) - return None, None - -def _find_cert(config, domains, certname): - """Finds an existing certificate object given domains and/or a certificate name. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :returns: Two-element tuple of a boolean that indicates if this function should be - followed by a call to fetch a certificate from the server, and either a - RenewableCert instance or None. - :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None` - - """ - action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname) - if action == "reinstall": - logger.info("Keeping the existing certificate") - return (action != "reinstall"), lineage - -def _find_lineage_for_domains_and_certname(config, domains, certname): - """Find appropriate lineage based on given domains and/or certname. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :returns: Two-element tuple containing desired new-certificate behavior as - a string token ("reinstall", "renew", or "newcert"), plus either - a RenewableCert instance or None if renewal should not occur. - - :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` - - :raises errors.Error: If the user would like to rerun the client again. - - """ - if not certname: - return _find_lineage_for_domains(config, domains) - else: - lineage = cert_manager.lineage_for_certname(config, certname) - if lineage: - if domains: - if set(cert_manager.domains_for_certname(config, certname)) != set(domains): - _ask_user_to_confirm_new_names(config, domains, certname, - lineage.names()) # raises if no - return "renew", lineage - # unnecessarily specified domains or no domains specified - return _handle_identical_cert_request(config, lineage) - else: - if domains: - return "newcert", None - else: - raise errors.ConfigurationError("No certificate with name {0} found. " - "Use -d to specify domains, or run certbot certificates to see " - "possible certificate names.".format(certname)) - -def _get_added_removed(after, before): - """Get lists of items removed from `before` - and a lists of items added to `after` - """ - added = list(set(after) - set(before)) - removed = list(set(before) - set(after)) - added.sort() - removed.sort() - return added, removed - -def _format_list(character, strings): - """Format list with given character - """ - if not strings: - formatted = "{br}(None)" - else: - formatted = "{br}{ch} " + "{br}{ch} ".join(strings) - return formatted.format( - ch=character, - br=os.linesep - ) - -def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains): - """Ask user to confirm update cert certname to contain new_domains. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param new_domains: List of new domain names - :type new_domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :param old_domains: List of old domain names - :type old_domains: `list` of `str` - - :returns: None - :rtype: None - - :raises errors.ConfigurationError: if cert name and domains mismatch - - """ - if config.renew_with_new_domains: - return - - added, removed = _get_added_removed(new_domains, old_domains) - - msg = ("You are updating certificate {0} to include new domain(s): {1}{br}{br}" - "You are also removing previously included domain(s): {2}{br}{br}" - "Did you intend to make this change?".format( - certname, - _format_list("+", added), - _format_list("-", removed), - br=os.linesep)) - obj = zope.component.getUtility(interfaces.IDisplay) - if not obj.yesno(msg, "Update cert", "Cancel", default=True): - raise errors.ConfigurationError("Specified mismatched cert name and domains.") - -def _find_domains_or_certname(config, installer, question=None): - """Retrieve domains and certname from config or user input. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :param `str` question: Overriding dialog question to ask the user if asked - to choose from domain names. - - :returns: Two-part tuple of domains and certname - :rtype: `tuple` of list of `str` and `str` - - :raises errors.Error: Usage message, if parameters are not used correctly - - """ - domains = None - certname = config.certname - # first, try to get domains from the config - if config.domains: - domains = config.domains - # if we can't do that but we have a certname, get the domains - # with that certname - elif certname: - domains = cert_manager.domains_for_certname(config, certname) - - # that certname might not have existed, or there was a problem. - # try to get domains from the user. - if not domains: - domains = display_ops.choose_names(installer, question) - - if not domains and not certname: - raise errors.Error("Please specify --domains, or --installer that " - "will help in domain names autodiscovery, or " - "--cert-name for an existing certificate name.") - - return domains, certname - - -def _report_new_cert(config, cert_path, fullchain_path, key_path=None): - """Reports the creation of a new certificate to the user. - - :param cert_path: path to certificate - :type cert_path: str - - :param fullchain_path: path to full chain - :type fullchain_path: str - - :param key_path: path to private key, if available - :type key_path: str - - :returns: `None` - :rtype: None - - """ - if config.dry_run: - _report_successful_dry_run(config) - return - - assert cert_path and fullchain_path, "No certificates saved to report." - - expiry = crypto_util.notAfter(cert_path).date() - reporter_util = zope.component.getUtility(interfaces.IReporter) - # Print the path to fullchain.pem because that's what modern webservers - # (Nginx and Apache2.4) will want. - - verbswitch = ' with the "certonly" option' if config.verb == "run" else "" - privkey_statement = 'Your key file has been saved at:{br}{0}{br}'.format( - key_path, br=os.linesep) if key_path else "" - # XXX Perhaps one day we could detect the presence of known old webservers - # and say something more informative here. - msg = ('Congratulations! Your certificate and chain have been saved at:{br}' - '{0}{br}{1}' - 'Your cert will expire on {2}. To obtain a new or tweaked version of this ' - 'certificate in the future, simply run {3} again{4}. ' - 'To non-interactively renew *all* of your certificates, run "{3} renew"' - .format(fullchain_path, privkey_statement, expiry, cli.cli_command, verbswitch, - br=os.linesep)) - reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY) - - -def _determine_account(config): - """Determine which account to use. - - If ``config.account`` is ``None``, it will be updated based on the - user input. Same for ``config.email``. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: Account and optionally ACME client API (biproduct of new - registration). - :rtype: tuple of :class:`certbot.account.Account` and :class:`acme.client.Client` - - :raises errors.Error: If unable to register an account with ACME server - - """ - def _tos_cb(terms_of_service): - if config.tos: - return True - msg = ("Please read the Terms of Service at {0}. You " - "must agree in order to register with the ACME " - "server at {1}".format( - terms_of_service, config.server)) - obj = zope.component.getUtility(interfaces.IDisplay) - result = obj.yesno(msg, "Agree", "Cancel", - cli_flag="--agree-tos", force_interactive=True) - if not result: - raise errors.Error( - "Registration cannot proceed without accepting " - "Terms of Service.") - return None - - account_storage = account.AccountFileStorage(config) - acme = None - - if config.account is not None: - acc = account_storage.load(config.account) - else: - accounts = account_storage.find_all() - if len(accounts) > 1: - acc = display_ops.choose_account(accounts) - elif len(accounts) == 1: - acc = accounts[0] - else: # no account registered yet - if config.email is None and not config.register_unsafely_without_email: - config.email = display_ops.get_email() - try: - acc, acme = client.register( - config, account_storage, tos_cb=_tos_cb) - except errors.MissingCommandlineFlag: - raise - except errors.Error: - logger.debug("", exc_info=True) - raise errors.Error( - "Unable to register an account with ACME server") - - config.account = acc.id - return acc, acme - - -def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-branches - """Does the user want to delete their now-revoked certs? If run in non-interactive mode, - deleting happens automatically. - - :param config: parsed command line arguments - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping - archive dir is found for the specified lineage, etc ... - """ - display = zope.component.getUtility(interfaces.IDisplay) - reporter_util = zope.component.getUtility(interfaces.IReporter) - - attempt_deletion = config.delete_after_revoke - if attempt_deletion is None: - msg = ("Would you like to delete the cert(s) you just revoked, along with all earlier and " - "later versions of the cert?") - attempt_deletion = display.yesno(msg, yes_label="Yes (recommended)", no_label="No", - force_interactive=True, default=True) - - if not attempt_deletion: - reporter_util.add_message("Not deleting revoked certs.", reporter_util.LOW_PRIORITY) - return - - # config.cert_path must have been set - # config.certname may have been set - assert config.cert_path - - if not config.certname: - config.certname = cert_manager.cert_path_to_lineage(config) - - # don't delete if the archive_dir is used by some other lineage - archive_dir = storage.full_archive_path( - configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)), - config, config.certname) - try: - cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir], - lambda x: x.archive_dir, lambda x: x) - except errors.OverlappingMatchFound: - msg = ('Not deleting revoked certs due to overlapping archive dirs. More than ' - 'one lineage is using {0}'.format(archive_dir)) - reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY) - return - except Exception as e: - msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},' - 'original exception: {3}') - msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e) - raise errors.Error(msg) - - cert_manager.delete(config) - - -def _init_le_client(config, authenticator, installer): - """Initialize Let's Encrypt Client - - :param config: Configuration object - :type config: interfaces.IConfig - - :param authenticator: Acme authentication handler - :type authenticator: interfaces.IAuthenticator - :param installer: Installer object - :type installer: interfaces.IInstaller - - :returns: client: Client object - :rtype: client.Client - - """ - if authenticator is not None: - # if authenticator was given, then we will need account... - acc, acme = _determine_account(config) - logger.debug("Picked account: %r", acc) - # XXX - #crypto_util.validate_key_csr(acc.key) - else: - acc, acme = None, None - - return client.Client(config, acc, authenticator, installer, acme=acme) - - -def unregister(config, unused_plugins): - """Deactivate account on server - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - account_storage = account.AccountFileStorage(config) - accounts = account_storage.find_all() - reporter_util = zope.component.getUtility(interfaces.IReporter) - - if not accounts: - return "Could not find existing account to deactivate." - yesno = zope.component.getUtility(interfaces.IDisplay).yesno - prompt = ("Are you sure you would like to irrevocably deactivate " - "your account?") - wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort', - default=True) - - if not wants_deactivate: - return "Deactivation aborted." - - acc, acme = _determine_account(config) - cb_client = client.Client(config, acc, None, None, acme=acme) - - # delete on boulder - cb_client.acme.deactivate_registration(acc.regr) - account_files = account.AccountFileStorage(config) - # delete local account files - account_files.delete(config.account) - - reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY) - return None - - -def register(config, unused_plugins): - """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 'certbot 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 accounts: - # 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 None - - -def update_account(config, unused_plugins): - """Modify 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 - - """ - # Portion of _determine_account logic to see whether accounts already - # exist or not. - account_storage = account.AccountFileStorage(config) - accounts = account_storage.find_all() - reporter_util = zope.component.getUtility(interfaces.IReporter) - add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY) - - if not accounts: - return "Could not find an existing account to update." - if config.email is None: - if config.register_unsafely_without_email: - return ("--register-unsafely-without-email provided, however, a " - "new e-mail address must\ncurrently be provided when " - "updating a registration.") - config.email = display_ops.get_email(optional=False) - - acc, acme = _determine_account(config) - cb_client = client.Client(config, acc, None, None, acme=acme) - # We rely on an exception to interrupt this process if it didn't work. - acc_contacts = ['mailto:' + email for email in config.email.split(',')] - prev_regr_uri = acc.regr.uri - acc.regr = cb_client.acme.update_registration(acc.regr.update( - body=acc.regr.body.update(contact=acc_contacts))) - # A v1 account being used as a v2 account will result in changing the uri to - # the v2 uri. Since it's the same object on disk, put it back to the v1 uri - # so that we can also continue to use the account object with acmev1. - acc.regr = acc.regr.update(uri=prev_regr_uri) - account_storage.save_regr(acc, cb_client.acme) - eff.handle_subscription(config) - add_msg("Your e-mail address was updated to {0}.".format(config.email)) - return None - -def _install_cert(config, le_client, domains, lineage=None): - """Install a cert - - :param config: Configuration object - :type config: interfaces.IConfig - - :param le_client: Client object - :type le_client: client.Client - - :param domains: List of domains - :type domains: `list` of `str` - - :param lineage: Certificate lineage object. Defaults to `None` - :type lineage: storage.RenewableCert - - :returns: `None` - :rtype: None - - """ - path_provider = lineage if lineage else config - assert path_provider.cert_path is not None - - le_client.deploy_certificate(domains, path_provider.key_path, - path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path) - le_client.enhance_config(domains, path_provider.chain_path) - - -def install(config, plugins): - """Install a previously obtained cert in a server. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - # XXX: Update for renewer/RenewableCert - # FIXME: be consistent about whether errors are raised or returned from - # this function ... - - try: - installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "install") - except errors.PluginSelectionError as e: - return str(e) - - custom_cert = (config.key_path and config.cert_path) - if not config.certname and not custom_cert: - certname_question = "Which certificate would you like to install?" - config.certname = cert_manager.get_certnames( - config, "install", allow_multiple=False, - custom_prompt=certname_question)[0] - - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - # If cert-path is defined, populate missing (ie. not overridden) values. - # Unfortunately this can't be done in argument parser, as certificate - # manager needs the access to renewal directory paths - if config.certname: - config = _populate_from_certname(config) - elif enhancements.are_requested(config): - # Preflight config check - raise errors.ConfigurationError("One or more of the requested enhancements " - "require --cert-name to be provided") - - if config.key_path and config.cert_path: - _check_certificate_and_key(config) - domains, _ = _find_domains_or_certname(config, installer) - le_client = _init_le_client(config, authenticator=None, installer=installer) - _install_cert(config, le_client, domains) - else: - raise errors.ConfigurationError("Path to certificate or key was not defined. " - "If your certificate is managed by Certbot, please use --cert-name " - "to define which certificate you would like to install.") - - if enhancements.are_requested(config): - # In the case where we don't have certname, we have errored out already - lineage = cert_manager.lineage_for_certname(config, config.certname) - enhancements.enable(lineage, domains, installer, config) - - return None - -def _populate_from_certname(config): - """Helper function for install to populate missing config values from lineage - defined by --cert-name.""" - - lineage = cert_manager.lineage_for_certname(config, config.certname) - if not lineage: - return config - if not config.key_path: - config.namespace.key_path = lineage.key_path - if not config.cert_path: - config.namespace.cert_path = lineage.cert_path - if not config.chain_path: - config.namespace.chain_path = lineage.chain_path - if not config.fullchain_path: - config.namespace.fullchain_path = lineage.fullchain_path - return config - -def _check_certificate_and_key(config): - if not os.path.isfile(filesystem.realpath(config.cert_path)): - raise errors.ConfigurationError("Error while reading certificate from path " - "{0}".format(config.cert_path)) - if not os.path.isfile(filesystem.realpath(config.key_path)): - raise errors.ConfigurationError("Error while reading private key from path " - "{0}".format(config.key_path)) -def plugins_cmd(config, plugins): - """List server software plugins. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - logger.debug("Expected interfaces: %s", config.ifaces) - - ifaces = [] if config.ifaces is None else config.ifaces - filtered = plugins.visible().ifaces(ifaces) - logger.debug("Filtered plugins: %r", filtered) - - notify = functools.partial(zope.component.getUtility( - interfaces.IDisplay).notification, pause=False) - if not config.init and not config.prepare: - notify(str(filtered)) - return - - filtered.init(config) - verified = filtered.verify(ifaces) - logger.debug("Verified plugins: %r", verified) - - if not config.prepare: - notify(str(verified)) - return - - verified.prepare() - available = verified.available() - logger.debug("Prepared plugins: %s", available) - notify(str(available)) - - -def enhance(config, plugins): - """Add security enhancements to existing configuration - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - supported_enhancements = ["hsts", "redirect", "uir", "staple"] - # Check that at least one enhancement was requested on command line - oldstyle_enh = any([getattr(config, enh) for enh in supported_enhancements]) - if not enhancements.are_requested(config) and not oldstyle_enh: - msg = ("Please specify one or more enhancement types to configure. To list " - "the available enhancement types, run:\n\n%s --help enhance\n") - logger.warning(msg, sys.argv[0]) - raise errors.MisconfigurationError("No enhancements requested, exiting.") - - try: - installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance") - except errors.PluginSelectionError as e: - return str(e) - - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - - certname_question = ("Which certificate would you like to use to enhance " - "your configuration?") - config.certname = cert_manager.get_certnames( - config, "enhance", allow_multiple=False, - custom_prompt=certname_question)[0] - cert_domains = cert_manager.domains_for_certname(config, config.certname) - if config.noninteractive_mode: - domains = cert_domains - else: - domain_question = ("Which domain names would you like to enable the " - "selected enhancements for?") - domains = display_ops.choose_values(cert_domains, domain_question) - if not domains: - raise errors.Error("User cancelled the domain selection. No domains " - "defined, exiting.") - - lineage = cert_manager.lineage_for_certname(config, config.certname) - if not config.chain_path: - config.chain_path = lineage.chain_path - if oldstyle_enh: - le_client = _init_le_client(config, authenticator=None, installer=installer) - le_client.enhance_config(domains, config.chain_path, ask_redirect=False) - if enhancements.are_requested(config): - enhancements.enable(lineage, domains, installer, config) - - return None - - -def rollback(config, plugins): - """Rollback server configuration changes made during install. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - client.rollback(config.installer, config.checkpoints, config, plugins) - -def update_symlinks(config, unused_plugins): - """Update the certificate file family symlinks - - Use the information in the config file to make symlinks point to - the correct archive directory. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.update_live_symlinks(config) - -def rename(config, unused_plugins): - """Rename a certificate - - Use the information in the config file to rename an existing - lineage. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.rename_lineage(config) - -def delete(config, unused_plugins): - """Delete a certificate - - Use the information in the config file to delete an existing - lineage. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.delete(config) - -def certificates(config, unused_plugins): - """Display information about certs configured with Certbot - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.certificates(config) - -# TODO: coop with renewal config -def revoke(config, unused_plugins): - """Revoke a previously obtained certificate. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` or string indicating error in case of error - :rtype: None or str - - """ - # For user-agent construction - config.installer = config.authenticator = None - - if config.cert_path is None and config.certname: - config.cert_path = storage.cert_path_for_cert_name(config, config.certname) - elif not config.cert_path or (config.cert_path and config.certname): - # intentionally not supporting --cert-path & --cert-name together, - # to avoid dealing with mismatched values - raise errors.Error("Error! Exactly one of --cert-path or --cert-name must be specified!") - - if config.key_path is not None: # revocation by cert key - logger.debug("Revoking %s using cert key %s", - config.cert_path[0], config.key_path[0]) - crypto_util.verify_cert_matches_priv_key(config.cert_path[0], config.key_path[0]) - key = jose.JWK.load(config.key_path[1]) - acme = client.acme_from_config_key(config, key) - else: # revocation by account key - logger.debug("Revoking %s using Account Key", config.cert_path[0]) - acc, _ = _determine_account(config) - acme = client.acme_from_config_key(config, acc.key, acc.regr) - cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0] - logger.debug("Reason code for revocation: %s", config.reason) - try: - acme.revoke(jose.ComparableX509(cert), config.reason) - _delete_if_appropriate(config) - except acme_errors.ClientError as e: - return str(e) - - display_ops.success_revocation(config.cert_path[0]) - return None - - -def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals - """Obtain a certificate and install. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - # TODO: Make run as close to auth + install as possible - # Possible difficulties: config.csr was hacked into auth - try: - installer, authenticator = plug_sel.choose_configurator_plugins(config, plugins, "run") - except errors.PluginSelectionError as e: - return str(e) - - # Preflight check for enhancement support by the selected installer - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - - # TODO: Handle errors from _init_le_client? - le_client = _init_le_client(config, authenticator, installer) - - domains, certname = _find_domains_or_certname(config, installer) - should_get_cert, lineage = _find_cert(config, domains, certname) - - new_lineage = lineage - if should_get_cert: - new_lineage = _get_and_save_cert(le_client, config, domains, - certname, lineage) - - cert_path = new_lineage.cert_path if new_lineage else None - fullchain_path = new_lineage.fullchain_path if new_lineage else None - key_path = new_lineage.key_path if new_lineage else None - _report_new_cert(config, cert_path, fullchain_path, key_path) - - _install_cert(config, le_client, domains, new_lineage) - - if enhancements.are_requested(config) and new_lineage: - enhancements.enable(new_lineage, domains, installer, config) - - if lineage is None or not should_get_cert: - display_ops.success_installation(domains) - else: - display_ops.success_renewal(domains) - - _suggest_donation_if_appropriate(config) - return None - - -def _csr_get_and_save_cert(config, le_client): - """Obtain a cert using a user-supplied CSR - - This works differently in the CSR case (for now) because we don't - have the privkey, and therefore can't construct the files for a lineage. - So we just save the cert & chain to disk :/ - - :param config: Configuration object - :type config: interfaces.IConfig - - :param client: Client object - :type client: client.Client - - :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files - :rtype: `tuple` of `str` - - """ - csr, _ = config.actual_csr - cert, chain = le_client.obtain_certificate_from_csr(csr) - if config.dry_run: - logger.debug( - "Dry run: skipping saving certificate to %s", config.cert_path) - return None, None - cert_path, _, fullchain_path = le_client.save_certificate( - cert, chain, os.path.normpath(config.cert_path), - os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path)) - return cert_path, fullchain_path - -def renew_cert(config, plugins, lineage): - """Renew & save an existing cert. Do not install it. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :returns: `None` - :rtype: None - - :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass - - """ - try: - # installers are used in auth mode to determine domain names - installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") - except errors.PluginSelectionError as e: - logger.info("Could not choose appropriate plugin: %s", e) - raise - le_client = _init_le_client(config, auth, installer) - - renewed_lineage = _get_and_save_cert(le_client, config, lineage=lineage) - - notify = zope.component.getUtility(interfaces.IDisplay).notification - if installer is None: - notify("new certificate deployed without reload, fullchain is {0}".format( - lineage.fullchain), pause=False) - else: - # In case of a renewal, reload server to pick up new certificate. - # In principle we could have a configuration option to inhibit this - # from happening. - # Run deployer - updater.run_renewal_deployer(config, renewed_lineage, installer) - installer.restart() - notify("new certificate deployed with reload of {0} server; fullchain is {1}".format( - config.installer, lineage.fullchain), pause=False) - -def certonly(config, plugins): - """Authenticate & obtain cert, but do not install it. - - This implements the 'certonly' subcommand. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - :raises errors.Error: If specified plugin could not be used - - """ - # SETUP: Select plugins and construct a client instance - try: - # installers are used in auth mode to determine domain names - installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") - except errors.PluginSelectionError as e: - logger.info("Could not choose appropriate plugin: %s", e) - raise - - le_client = _init_le_client(config, auth, installer) - - if config.csr: - cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client) - _report_new_cert(config, cert_path, fullchain_path) - _suggest_donation_if_appropriate(config) - return - - domains, certname = _find_domains_or_certname(config, installer) - should_get_cert, lineage = _find_cert(config, domains, certname) - - if not should_get_cert: - notify = zope.component.getUtility(interfaces.IDisplay).notification - notify("Certificate not yet due for renewal; no action taken.", pause=False) - return - - lineage = _get_and_save_cert(le_client, config, domains, certname, lineage) - - cert_path = lineage.cert_path if lineage else None - fullchain_path = lineage.fullchain_path if lineage else None - key_path = lineage.key_path if lineage else None - _report_new_cert(config, cert_path, fullchain_path, key_path) - _suggest_donation_if_appropriate(config) - -def renew(config, unused_plugins): - """Renew previously-obtained certificates. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - try: - renewal.handle_renewal_request(config) - finally: - hooks.run_saved_post_hooks() - - -def make_or_verify_needed_dirs(config): - """Create or verify existence of config, work, and hook directories. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) - util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) - - hook_dirs = (config.renewal_pre_hooks_dir, - config.renewal_deploy_hooks_dir, - config.renewal_post_hooks_dir,) - for hook_dir in hook_dirs: - util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) - - -def set_displayer(config): - """Set the displayer - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - if config.quiet: - config.noninteractive_mode = True - displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) \ - # type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay] - elif config.noninteractive_mode: - displayer = display_util.NoninteractiveDisplay(sys.stdout) - else: - displayer = display_util.FileDisplay(sys.stdout, - config.force_interactive) - zope.component.provideUtility(displayer) - - -def main(cli_args=None): - """Command line argument parsing and main script execution. - - :returns: result of requested command - - :raises errors.Error: OS errors triggered by wrong permissions - :raises errors.Error: error if plugin command is not supported - - """ - if not cli_args: - cli_args = sys.argv[1:] - - log.pre_arg_parse_setup() - - plugins = plugins_disco.PluginsRegistry.find_all() - logger.debug("certbot version: %s", certbot.__version__) - # do not log `config`, as it contains sensitive data (e.g. revoke --key)! - logger.debug("Arguments: %r", cli_args) - logger.debug("Discovered plugins: %r", plugins) - - # note: arg parser internally handles --help (and exits afterwards) - args = cli.prepare_and_parse_args(plugins, cli_args) - config = configuration.NamespaceConfig(args) - zope.component.provideUtility(config) - - # On windows, shell without administrative right cannot create symlinks required by certbot. - # So we check the rights before continuing. - misc.raise_for_non_administrative_windows_rights() - - try: - log.post_arg_parse_setup(config) - make_or_verify_needed_dirs(config) - except errors.Error: - # Let plugins_cmd be run as un-privileged user. - if config.func != plugins_cmd: - raise - - set_displayer(config) - - # Reporter - report = reporter.Reporter(config) - zope.component.provideUtility(report) - util.atexit_register(report.print_messages) - - return config.func(config, plugins) - - -if __name__ == "__main__": - err_string = main() - if err_string: - logger.warning("Exiting with message %s", err_string) - sys.exit(err_string) # pragma: no cover diff --git a/certbot/notify.py b/certbot/notify.py deleted file mode 100644 index dda0a85af..000000000 --- a/certbot/notify.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Send e-mail notification to system administrators.""" - -import email -import smtplib -import socket -import subprocess - - -def notify(subject, whom, what): - """Send email notification. - - Try to notify the addressee (``whom``) by e-mail, with Subject: - defined by ``subject`` and message body by ``what``. - - """ - msg = email.message_from_string(what) - msg.add_header("From", "Certbot renewal agent ") - msg.add_header("To", whom) - msg.add_header("Subject", subject) - msg = msg.as_string() - try: - lmtp = smtplib.LMTP() - lmtp.connect() - lmtp.sendmail("root", [whom], msg) - except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused, - smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error): - # We should try using /usr/sbin/sendmail in this case - try: - proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"], - stdin=subprocess.PIPE) - proc.communicate(msg) - except OSError: - return False - return True diff --git a/certbot/ocsp.py b/certbot/ocsp.py deleted file mode 100644 index 1cc1e7529..000000000 --- a/certbot/ocsp.py +++ /dev/null @@ -1,292 +0,0 @@ -"""Tools for checking certificate revocation.""" -import logging -import re -from datetime import datetime, timedelta -from subprocess import Popen, PIPE - -try: - # Only cryptography>=2.5 has ocsp module - # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error - getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') -except (ImportError, AttributeError): # pragma: no cover - ocsp = None # type: ignore -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -import pytz -import requests - -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module -from certbot import crypto_util -from certbot import errors -from certbot.storage import RenewableCert # pylint: disable=unused-import -from certbot import util - -logger = logging.getLogger(__name__) - - -class RevocationChecker(object): - """This class figures out OCSP checking on this system, and performs it.""" - - def __init__(self, enforce_openssl_binary_usage=False): - self.broken = False - self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp - - if self.use_openssl_binary: - if not util.exe_exists("openssl"): - logger.info("openssl not installed, can't check revocation") - self.broken = True - return - - # New versions of openssl want -header var=val, old ones want -header var val - test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) - _out, err = test_host_format.communicate() - if "Missing =" in err: - self.host_args = lambda host: ["Host=" + host] - else: - self.host_args = lambda host: ["Host", host] - - def ocsp_revoked(self, cert): - # type: (RenewableCert) -> bool - """Get revoked status for a particular cert version. - - .. todo:: Make this a non-blocking call - - :param `.storage.RenewableCert` cert: Certificate object - :returns: True if revoked; False if valid or the check failed or cert is expired. - :rtype: bool - - """ - cert_path, chain_path = cert.cert, cert.chain - - if self.broken: - return False - - # Let's Encrypt doesn't update OCSP for expired certificates, - # so don't check OCSP if the cert is expired. - # https://github.com/certbot/certbot/issues/7152 - now = pytz.UTC.fromutc(datetime.utcnow()) - if cert.target_expiry <= now: - return False - - url, host = _determine_ocsp_server(cert_path) - if not host or not url: - return False - - if self.use_openssl_binary: - return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) - return _check_ocsp_cryptography(cert_path, chain_path, url) - - def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): - # type: (str, str, str, str) -> bool - # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! - cmd = ["openssl", "ocsp", - "-no_nonce", - "-issuer", chain_path, - "-cert", cert_path, - "-url", url, - "-CAfile", chain_path, - "-verify_other", chain_path, - "-trust_other", - "-header"] + self.host_args(host) - logger.debug("Querying OCSP for %s", cert_path) - logger.debug(" ".join(cmd)) - try: - output, err = util.run_script(cmd, log=logger.debug) - except errors.SubprocessError: - logger.info("OCSP check failed for %s (are we offline?)", cert_path) - return False - return _translate_ocsp_query(cert_path, output, err) - - -def _determine_ocsp_server(cert_path): - # type: (str) -> Tuple[Optional[str], Optional[str]] - """Extract the OCSP server host from a certificate. - - :param str cert_path: Path to the cert we're checking OCSP for - :rtype tuple: - :returns: (OCSP server URL or None, OCSP server host or None) - - """ - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - try: - extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) - ocsp_oid = x509.AuthorityInformationAccessOID.OCSP - descriptions = [description for description in extension.value - if description.access_method == ocsp_oid] - - url = descriptions[0].access_location.value - except (x509.ExtensionNotFound, IndexError): - logger.info("Cannot extract OCSP URI from %s", cert_path) - return None, None - - url = url.rstrip() - host = url.partition("://")[2].rstrip("/") - - if host: - return url, host - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None - - -def _check_ocsp_cryptography(cert_path, chain_path, url): - # type: (str, str, str) -> bool - # Retrieve OCSP response - with open(chain_path, 'rb') as file_handler: - issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - builder = ocsp.OCSPRequestBuilder() - builder = builder.add_certificate(cert, issuer, hashes.SHA1()) - request = builder.build() - request_binary = request.public_bytes(serialization.Encoding.DER) - try: - response = requests.post(url, data=request_binary, - headers={'Content-Type': 'application/ocsp-request'}) - except requests.exceptions.RequestException: - logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True) - return False - if response.status_code != 200: - logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code) - return False - - response_ocsp = ocsp.load_der_ocsp_response(response.content) - - # Check OCSP response validity - if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: - logger.error("Invalid OCSP response status for %s: %s", - cert_path, response_ocsp.response_status) - return False - - # Check OCSP signature - try: - _check_ocsp_response(response_ocsp, request, issuer, cert_path) - except UnsupportedAlgorithm as e: - logger.error(str(e)) - except errors.Error as e: - logger.error(str(e)) - except InvalidSignature: - logger.error('Invalid signature on OCSP response for %s', cert_path) - except AssertionError as error: - logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) - else: - # Check OCSP certificate status - logger.debug("OCSP certificate status for %s is: %s", - cert_path, response_ocsp.certificate_status) - return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED - - return False - - -def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): - """Verify that the OCSP is valid for serveral criterias""" - # Assert OCSP response corresponds to the certificate we are talking about - if response_ocsp.serial_number != request_ocsp.serial_number: - raise AssertionError('the certificate in response does not correspond ' - 'to the certificate in request') - - # Assert signature is valid - _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path) - - # Assert issuer in response is the expected one - if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) - or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash - or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): - raise AssertionError('the issuer does not correspond to issuer of the certificate.') - - # In following checks, two situations can occur: - # * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate - # * nextUpdate is not set, and requirement is thisUpdate < now - # NB1: We add a validity period tolerance to handle clock time inconsistencies, - # value is 5 min like for OpenSSL. - # NB2: Another check is to verify that thisUpdate is not too old, it is optional - # for OpenSSL, so we do not do it here. - # See OpenSSL implementation as a reference: - # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 - now = datetime.utcnow() # thisUpdate/nextUpdate are expressed in UTC/GMT time zone - if not response_ocsp.this_update: - raise AssertionError('param thisUpdate is not set.') - if response_ocsp.this_update > now + timedelta(minutes=5): - raise AssertionError('param thisUpdate is in the future.') - if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5): - raise AssertionError('param nextUpdate is in the past.') - - -def _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path): - """Verify an OCSP response signature against certificate issuer or responder""" - if response_ocsp.responder_name == issuer_cert.subject: - # Case where the OCSP responder is also the certificate issuer - logger.debug('OCSP response for certificate %s is signed by the certificate\'s issuer.', - cert_path) - responder_cert = issuer_cert - else: - # Case where the OCSP responder is not the certificate issuer - logger.debug('OCSP response for certificate %s is delegated to an external responder.', - cert_path) - - responder_certs = [cert for cert in response_ocsp.certificates - if cert.subject == response_ocsp.responder_name] - if not responder_certs: - raise AssertionError('no matching responder certificate could be found') - - # We suppose here that the ACME server support only one certificate in the OCSP status - # request. This is currently the case for LetsEncrypt servers. - # See https://github.com/letsencrypt/boulder/issues/2331 - responder_cert = responder_certs[0] - - if responder_cert.issuer != issuer_cert.subject: - raise AssertionError('responder certificate is not signed ' - 'by the certificate\'s issuer') - - try: - extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) - delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value - except (x509.ExtensionNotFound, IndexError): - delegate_authorized = False - if not delegate_authorized: - raise AssertionError('responder is not authorized by issuer to sign OCSP responses') - - # Following line may raise UnsupportedAlgorithm - chosen_hash = responder_cert.signature_hash_algorithm - # For a delegate OCSP responder, we need first check that its certificate is effectively - # signed by the certificate issuer. - crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, - responder_cert.tbs_certificate_bytes, chosen_hash) - - # Following line may raise UnsupportedAlgorithm - chosen_hash = response_ocsp.signature_hash_algorithm - # We check that the OSCP response is effectively signed by the responder - # (an authorized delegate one or the certificate issuer itself). - crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature, - response_ocsp.tbs_response_bytes, chosen_hash) - - -def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): - """Parse openssl's weird output to work out what it means.""" - - states = ("good", "revoked", "unknown") - patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states] - good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns) - - warning = good.group(1) if good else None - - if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: - logger.info("Revocation status for %s is unknown", cert_path) - logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) - return False - elif good and not warning: - return False - elif revoked: - warning = revoked.group(1) - if warning: - logger.info("OCSP revocation warning: %s", warning) - return True - else: - logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) - return False diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index cdcde2c76..8fa642ced 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -178,7 +178,7 @@ class InstallerTest(test_util.ConfigTestCase): class AddrTest(unittest.TestCase): - """Tests for certbot.client.plugins.common.Addr.""" + """Tests for certbot._internal.client.plugins.common.Addr.""" def setUp(self): from certbot.plugins.common import Addr diff --git a/certbot/plugins/enhancements.py b/certbot/plugins/enhancements.py index 7ca096610..353ff44d5 100644 --- a/certbot/plugins/enhancements.py +++ b/certbot/plugins/enhancements.py @@ -51,7 +51,7 @@ def enable(lineage, domains, installer, config): Run enable method for each requested enhancement that is supported. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str @@ -112,7 +112,7 @@ class AutoHSTSEnhancement(object): Implementation of this method should increase the max-age value. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert .. note:: prepare() method inherited from `interfaces.IPlugin` might need to be called manually within implementation of this interface method @@ -126,7 +126,7 @@ class AutoHSTSEnhancement(object): Long max-age value should be set in implementation of this method. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert """ @abc.abstractmethod @@ -137,7 +137,7 @@ class AutoHSTSEnhancement(object): over the subsequent runs of Certbot renew. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str diff --git a/certbot/renewal.py b/certbot/renewal.py deleted file mode 100644 index b9b45ee40..000000000 --- a/certbot/renewal.py +++ /dev/null @@ -1,476 +0,0 @@ -"""Functionality for autorenewal and associated juggling of configurations""" -from __future__ import print_function - -import copy -import itertools -import logging -import random -import sys -import time -import traceback - -import OpenSSL -import six -import zope.component - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import cli -from certbot import crypto_util -from certbot import errors -from certbot import hooks -from certbot import interfaces -from certbot import storage -from certbot import updater -from certbot import util -from certbot.compat import os -from certbot.plugins import disco as plugins_disco - -logger = logging.getLogger(__name__) - -# These are the items which get pulled out of a renewal configuration -# file's renewalparams and actually used in the client configuration -# during the renewal process. We have to record their types here because -# the renewal configuration process loses this information. -STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent", - "server", "account", "authenticator", "installer", - "renew_hook", "pre_hook", "post_hook", "http01_address"] -INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"] -BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key", - "autorenew"] - -CONFIG_ITEMS = set(itertools.chain( - BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',))) - - -def _reconstitute(config, full_path): - """Try to instantiate a RenewableCert, updating config with relevant items. - - This is specifically for use in renewal and enforces several checks - and policies to ensure that we can try to proceed with the renewal - request. The config argument is modified by including relevant options - read from the renewal configuration file. - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param str full_path: Absolute path to the configuration file that - defines this lineage - - :returns: the RenewableCert object or None if a fatal error occurred - :rtype: `storage.RenewableCert` or NoneType - - """ - try: - renewal_candidate = storage.RenewableCert(full_path, config) - except (errors.CertStorageError, IOError): - logger.warning("", exc_info=True) - logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - if "renewalparams" not in renewal_candidate.configuration: - logger.warning("Renewal configuration file %s lacks " - "renewalparams. Skipping.", full_path) - return None - renewalparams = renewal_candidate.configuration["renewalparams"] - if "authenticator" not in renewalparams: - logger.warning("Renewal configuration file %s does not specify " - "an authenticator. Skipping.", full_path) - return None - # Now restore specific values along with their data types, if - # those elements are present. - try: - restore_required_config_elements(config, renewalparams) - _restore_plugin_configs(config, renewalparams) - except (ValueError, errors.Error) as error: - logger.warning( - "An error occurred while parsing %s. The error was %s. " - "Skipping the file.", full_path, str(error)) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - - try: - config.domains = [util.enforce_domain_sanity(d) - for d in renewal_candidate.names()] - except errors.ConfigurationError as error: - logger.warning("Renewal configuration file %s references a cert " - "that contains an invalid domain name. The problem " - "was: %s. Skipping.", full_path, error) - return None - - return renewal_candidate - - -def _restore_webroot_config(config, renewalparams): - """ - webroot_map is, uniquely, a dict, and the general-purpose configuration - restoring logic is not able to correctly parse it from the serialized - form. - """ - if "webroot_map" in renewalparams and not cli.set_by_cli("webroot_map"): - config.webroot_map = renewalparams["webroot_map"] - # To understand why webroot_path and webroot_map processing are not mutually exclusive, - # see https://github.com/certbot/certbot/pull/7095 - if "webroot_path" in renewalparams and not cli.set_by_cli("webroot_path"): - wp = renewalparams["webroot_path"] - if isinstance(wp, six.string_types): # prior to 0.1.0, webroot_path was a string - wp = [wp] - config.webroot_path = wp - - -def _restore_plugin_configs(config, renewalparams): - """Sets plugin specific values in config from renewalparams - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param configobj.Section renewalparams: Parameters from the renewal - configuration file that defines this lineage - - """ - # Now use parser to get plugin-prefixed items with correct types - # XXX: the current approach of extracting only prefixed items - # related to the actually-used installer and authenticator - # works as long as plugins don't need to read plugin-specific - # variables set by someone else (e.g., assuming Apache - # configurator doesn't need to read webroot_ variables). - # Note: if a parameter that used to be defined in the parser is no - # longer defined, stored copies of that parameter will be - # deserialized as strings by this logic even if they were - # originally meant to be some other type. - plugin_prefixes = [] # type: List[str] - if renewalparams["authenticator"] == "webroot": - _restore_webroot_config(config, renewalparams) - else: - plugin_prefixes.append(renewalparams["authenticator"]) - - if renewalparams.get("installer") is not None: - plugin_prefixes.append(renewalparams["installer"]) - - for plugin_prefix in set(plugin_prefixes): - plugin_prefix = plugin_prefix.replace('-', '_') - for config_item, config_value in six.iteritems(renewalparams): - if config_item.startswith(plugin_prefix + "_") and not cli.set_by_cli(config_item): - # Values None, True, and False need to be treated specially, - # As their types aren't handled correctly by configobj - if config_value in ("None", "True", "False"): - # bool("False") == True - # pylint: disable=eval-used - setattr(config, config_item, eval(config_value)) - else: - cast = cli.argparse_type(config_item) - setattr(config, config_item, cast(config_value)) - - -def restore_required_config_elements(config, renewalparams): - """Sets non-plugin specific values in config from renewalparams - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param configobj.Section renewalparams: parameters from the renewal - configuration file that defines this lineage - - """ - - required_items = itertools.chain( - (("pref_challs", _restore_pref_challs),), - six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)), - six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)), - six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str))) - for item_name, restore_func in required_items: - if item_name in renewalparams and not cli.set_by_cli(item_name): - value = restore_func(item_name, renewalparams[item_name]) - setattr(config, item_name, value) - - -def _restore_pref_challs(unused_name, value): - """Restores preferred challenges from a renewal config file. - - If value is a `str`, it should be a single challenge type. - - :param str unused_name: option name - :param value: option value - :type value: `list` of `str` or `str` - - :returns: converted option value to be stored in the runtime config - :rtype: `list` of `str` - - :raises errors.Error: if value can't be converted to an bool - - """ - # If pref_challs has only one element, configobj saves the value - # with a trailing comma so it's parsed as a list. If this comma is - # removed by the user, the value is parsed as a str. - value = [value] if isinstance(value, six.string_types) else value - return cli.parse_preferred_challenges(value) - - -def _restore_bool(name, value): - """Restores an boolean key-value pair from a renewal config file. - - :param str name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: bool - - :raises errors.Error: if value can't be converted to an bool - - """ - lowercase_value = value.lower() - if lowercase_value not in ("true", "false"): - raise errors.Error( - "Expected True or False for {0} but found {1}".format(name, value)) - return lowercase_value == "true" - - -def _restore_int(name, value): - """Restores an integer key-value pair from a renewal config file. - - :param str name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: int - - :raises errors.Error: if value can't be converted to an int - - """ - if name == "http01_port" and value == "None": - logger.info("updating legacy http01_port value") - return cli.flag_default("http01_port") - - try: - return int(value) - except ValueError: - raise errors.Error("Expected a numeric value for {0}".format(name)) - - -def _restore_str(unused_name, value): - """Restores an string key-value pair from a renewal config file. - - :param str unused_name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: str or None - - """ - return None if value == "None" else value - - -def should_renew(config, lineage): - "Return true if any of the circumstances for automatic renewal apply." - if config.renew_by_default: - logger.debug("Auto-renewal forced with --force-renewal...") - return True - if lineage.should_autorenew(): - logger.info("Cert is due for renewal, auto-renewing...") - return True - if config.dry_run: - logger.info("Cert not due for renewal, but simulating renewal for dry run") - return True - logger.info("Cert not yet due for renewal") - return False - - -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, 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() - - if util.is_staging(config.server): - if not util.is_staging(original_server) or now_valid: - if not config.break_my_certs: - names = ", ".join(lineage.names()) - raise errors.Error( - "You've asked to renew/replace a seemingly valid certificate with " - "a test certificate (domains: {0}). We will not do that " - "unless you use the --break-my-certs flag!".format(names)) - - -def renew_cert(config, domains, le_client, lineage): - "Renew a certificate lineage." - renewal_params = lineage.configuration["renewalparams"] - original_server = renewal_params.get("server", cli.flag_default("server")) - _avoid_invalidating_lineage(config, lineage, original_server) - if not domains: - domains = lineage.names() - # The private key is the existing lineage private key if reuse_key is set. - # Otherwise, generate a fresh private key by passing None. - new_key = os.path.normpath(lineage.privkey) if config.reuse_key else None - new_cert, new_chain, new_key, _ = le_client.obtain_certificate(domains, new_key) - if config.dry_run: - logger.debug("Dry run: skipping updating lineage at %s", - os.path.dirname(lineage.cert)) - else: - prior_version = lineage.latest_common_version() - # TODO: Check return value of save_successor - lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config) - lineage.update_all_links_to(lineage.latest_common_version()) - - hooks.renew_hook(config, domains, lineage.live_dir) - - -def report(msgs, category): - "Format a results report for a category of renewal outcomes" - lines = ("%s (%s)" % (m, category) for m in msgs) - return " " + "\n ".join(lines) - -def _renew_describe_results(config, renew_successes, renew_failures, - renew_skipped, parse_failures): - - out = [] # type: List[str] - notify = out.append - disp = zope.component.getUtility(interfaces.IDisplay) - - def notify_error(err): - """Notify and log errors.""" - notify(str(err)) - logger.error(err) - - if config.dry_run: - notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") - notify("** (The test certificates below have not been saved.)") - notify("") - if renew_skipped: - notify("The following certs are not due for renewal yet:") - notify(report(renew_skipped, "skipped")) - if not renew_successes and not renew_failures: - notify("No renewals were attempted.") - if (config.pre_hook is not None or - config.renew_hook is not None or config.post_hook is not None): - notify("No hooks were run.") - elif renew_successes and not renew_failures: - notify("Congratulations, all renewals succeeded. The following certs " - "have been renewed:") - notify(report(renew_successes, "success")) - elif renew_failures and not renew_successes: - notify_error("All renewal attempts failed. The following certs could " - "not be renewed:") - notify_error(report(renew_failures, "failure")) - elif renew_failures and renew_successes: - notify("The following certs were successfully renewed:") - notify(report(renew_successes, "success") + "\n") - notify_error("The following certs could not be renewed:") - notify_error(report(renew_failures, "failure")) - - if parse_failures: - notify("\nAdditionally, the following renewal configurations " - "were invalid: ") - notify(report(parse_failures, "parsefail")) - - if config.dry_run: - notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") - notify("** (The test certificates above have not been saved.)") - - disp.notification("\n".join(out), wrap=False) - - -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 - if any(domain not in config.webroot_map for domain in config.domains): - # If more plugins start using cli.add_domains, - # we may want to only log a warning here - raise errors.Error("Currently, the renew verb is capable of either " - "renewing all installed certificates that are due " - "to be renewed or renewing a single certificate specified " - "by its name. If you would like to renew specific " - "certificates by their domains, use the certonly command " - "instead. The renew verb may provide other options " - "for selecting certificates to renew in the future.") - - if config.certname: - conf_files = [storage.renewal_file_for_certname(config, config.certname)] - else: - conf_files = storage.renewal_conf_files(config) - - renew_successes = [] - 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) - lineage_config = copy.deepcopy(config) - lineagename = storage.lineagename_for_filename(renewal_file) - - # Note that this modifies config (to add back the configuration - # elements from within the renewal configuration file). - try: - renewal_candidate = _reconstitute(lineage_config, renewal_file) - except Exception as e: # pylint: disable=broad-except - logger.warning("Renewal configuration file %s (cert: %s) " - "produced an unexpected error: %s. Skipping.", - renewal_file, lineagename, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - parse_failures.append(renewal_file) - continue - - try: - if renewal_candidate is None: - parse_failures.append(renewal_file) - else: - # XXX: ensure that each call here replaces the previous one - zope.component.provideUtility(lineage_config) - renewal_candidate.ensure_deployed() - 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.uniform(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 - # we already know it's time to renew based on should_renew - # and we have a lineage in renewal_candidate - main.renew_cert(lineage_config, plugins, renewal_candidate) - renew_successes.append(renewal_candidate.fullchain) - else: - expiry = crypto_util.notAfter(renewal_candidate.version( - "cert", renewal_candidate.latest_common_version())) - renew_skipped.append("%s expires on %s" % (renewal_candidate.fullchain, - expiry.strftime("%Y-%m-%d"))) - # Run updater interface methods - updater.run_generic_updaters(lineage_config, renewal_candidate, - plugins) - - except Exception as e: # pylint: disable=broad-except - # obtain_cert (presumably) encountered an unanticipated problem. - logger.warning("Attempting to renew cert (%s) from %s produced an " - "unexpected error: %s. Skipping.", lineagename, - renewal_file, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - renew_failures.append(renewal_candidate.fullchain) - - # Describe all the results - _renew_describe_results(config, renew_successes, renew_failures, - renew_skipped, parse_failures) - - if renew_failures or parse_failures: - raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( - len(renew_failures), len(parse_failures))) - else: - logger.debug("no renewal failures") diff --git a/certbot/reporter.py b/certbot/reporter.py deleted file mode 100644 index e0063d8e5..000000000 --- a/certbot/reporter.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Collects and displays information to the user.""" -from __future__ import print_function - -import collections -import logging -import sys -import textwrap - -from six.moves import queue # type: ignore # pylint: disable=import-error -import zope.interface - -from certbot import interfaces -from certbot import util - - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IReporter) -class Reporter(object): - """Collects and displays information to the user. - - :ivar `queue.PriorityQueue` messages: Messages to be displayed to - the user. - - """ - - HIGH_PRIORITY = 0 - """High priority constant. See `add_message`.""" - MEDIUM_PRIORITY = 1 - """Medium priority constant. See `add_message`.""" - LOW_PRIORITY = 2 - """Low priority constant. See `add_message`.""" - - _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') - - def __init__(self, config): - self.messages = queue.PriorityQueue() - self.config = config - - def add_message(self, msg, priority, on_crash=True): - """Adds msg to the list of messages to be printed. - - :param str msg: Message to be displayed to the user. - - :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`, - or `LOW_PRIORITY`. - - :param bool on_crash: Whether or not the message should be - printed if the program exits abnormally. - - """ - assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY - self.messages.put(self._msg_type(priority, msg, on_crash)) - logger.debug("Reporting to user: %s", msg) - - def print_messages(self): - """Prints messages to the user and clears the message queue. - - If there is an unhandled exception, only messages for which - ``on_crash`` is ``True`` are printed. - - """ - bold_on = False - if not self.messages.empty(): - no_exception = sys.exc_info()[0] is None - bold_on = sys.stdout.isatty() - if not self.config.quiet: - if bold_on: - print(util.ANSI_SGR_BOLD) - print('IMPORTANT NOTES:') - first_wrapper = textwrap.TextWrapper( - initial_indent=' - ', - subsequent_indent=(' ' * 3), - break_long_words=False, - break_on_hyphens=False) - next_wrapper = textwrap.TextWrapper( - initial_indent=first_wrapper.subsequent_indent, - subsequent_indent=first_wrapper.subsequent_indent, - break_long_words=False, - break_on_hyphens=False) - while not self.messages.empty(): - msg = self.messages.get() - if self.config.quiet: - # In --quiet mode, we only print high priority messages that - # are flagged for crash cases - if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash): - continue - if no_exception or msg.on_crash: - if bold_on and msg.priority > self.HIGH_PRIORITY: - if not self.config.quiet: - sys.stdout.write(util.ANSI_SGR_RESET) - bold_on = False - lines = msg.text.splitlines() - print(first_wrapper.fill(lines[0])) - if len(lines) > 1: - print("\n".join( - next_wrapper.fill(line) for line in lines[1:])) - if bold_on and not self.config.quiet: - sys.stdout.write(util.ANSI_SGR_RESET) diff --git a/certbot/storage.py b/certbot/storage.py deleted file mode 100644 index 9bf85fa0f..000000000 --- a/certbot/storage.py +++ /dev/null @@ -1,1130 +0,0 @@ -"""Renewable certificates storage.""" -import datetime -import glob -import logging -import re -import shutil -import stat - -import configobj -import parsedatetime -import pytz -import six - -import certbot -from certbot import cli -from certbot import constants -from certbot import crypto_util -from certbot import error_handler -from certbot import errors -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem -from certbot.plugins import common as plugins_common -from certbot.plugins import disco as plugins_disco - -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): - """Build a list of all renewal configuration files. - - :param certbot.interfaces.IConfig config: Configuration object - - :returns: list of renewal configuration files - :rtype: `list` of `str` - - """ - 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""" - path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname)) - if not os.path.exists(path): - raise errors.CertStorageError("No certificate found with name {0} (expected " - "{1}).".format(certname, path)) - return path - - -def cert_path_for_cert_name(config, cert_name): - """ If `--cert-name` was specified, but you need a value for `--cert-path`. - - :param `configuration.NamespaceConfig` config: parsed command line arguments - :param str cert_name: cert name. - - """ - cert_name_implied_conf = renewal_file_for_certname(config, cert_name) - fullchain_path = configobj.ConfigObj(cert_name_implied_conf)["fullchain"] - with open(fullchain_path) as f: - cert_path = (fullchain_path, f.read()) - return cert_path - - -def config_with_defaults(config=None): - """Merge supplied config, if provided, on top of builtin defaults.""" - defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS) - defaults_copy.merge(config if config is not None else configobj.ConfigObj()) - return defaults_copy - - -def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()): - """Parse the time specified time interval, and add it to the base_time - - The interval can be in the English-language format understood by - parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or - a sequence of such intervals like '6 months 1 week' or '3 days 12 - hours'. If an integer is found with no associated unit, it is - interpreted by default as a number of days. - - :param datetime.datetime base_time: The time to be added with the interval. - :param str interval: The time interval to parse. - - :returns: The base_time plus the interpretation of the time interval. - :rtype: :class:`datetime.datetime`""" - - if interval.strip().isdigit(): - interval += " days" - - # try to use the same timezone, but fallback to UTC - tzinfo = base_time.tzinfo or pytz.UTC - - return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0] - - -def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): - """Writes a renewal config file with the specified name and values. - - :param str o_filename: Absolute path to the previous version of config file - :param str n_filename: Absolute path to the new destination of config file - :param str archive_dir: Absolute path to the archive directory - :param dict target: Maps ALL_FOUR to their symlink paths - :param dict relevant_data: Renewal configuration options to save - - :returns: Configuration object for the new config file - :rtype: configobj.ConfigObj - - """ - config = configobj.ConfigObj(o_filename) - config["version"] = certbot.__version__ - config["archive_dir"] = archive_dir - for kind in ALL_FOUR: - config[kind] = target[kind] - - if "renewalparams" not in config: - config["renewalparams"] = {} - config.comments["renewalparams"] = ["", - "Options used in " - "the renewal process"] - - config["renewalparams"].update(relevant_data) - - for k in config["renewalparams"].keys(): - if k not in relevant_data: - del config["renewalparams"][k] - - if "renew_before_expiry" not in config: - default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] - config.initial_comment = ["renew_before_expiry = " + default_interval] - - # TODO: add human-readable comments explaining other available - # parameters - logger.debug("Writing new config %s.", n_filename) - - # Ensure that the file exists - open(n_filename, 'a').close() - - # Copy permissions from the old version of the file, if it exists. - if os.path.exists(o_filename): - current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) - filesystem.chmod(n_filename, current_permissions) - - with open(n_filename, "wb") as f: - config.write(outfile=f) - return config - - -def rename_renewal_config(prev_name, new_name, cli_config): - """Renames cli_config.certname's config to cli_config.new_certname. - - :param .NamespaceConfig cli_config: parsed command line - arguments - """ - prev_filename = renewal_filename_for_lineagename(cli_config, prev_name) - new_filename = renewal_filename_for_lineagename(cli_config, new_name) - if os.path.exists(new_filename): - raise errors.ConfigurationError("The new certificate name " - "is already in use.") - try: - filesystem.replace(prev_filename, new_filename) - except OSError: - raise errors.ConfigurationError("Please specify a valid filename " - "for the new certificate name.") - - -def update_configuration(lineagename, archive_dir, target, cli_config): - """Modifies lineagename's config to contain the specified values. - - :param str lineagename: Name of the lineage being modified - :param str archive_dir: Absolute path to the archive directory - :param dict target: Maps ALL_FOUR to their symlink paths - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: Configuration object for the updated config file - :rtype: configobj.ConfigObj - - """ - config_filename = renewal_filename_for_lineagename(cli_config, lineagename) - temp_filename = config_filename + ".new" - - # If an existing tempfile exists, delete it - if os.path.exists(temp_filename): - os.unlink(temp_filename) - - # Save only the config items that are relevant to renewal - values = relevant_values(vars(cli_config.namespace)) - write_renewal_config(config_filename, temp_filename, archive_dir, target, values) - filesystem.replace(temp_filename, config_filename) - - return configobj.ConfigObj(config_filename) - - -def get_link_target(link): - """Get an absolute path to the target of link. - - :param str link: Path to a symbolic link - - :returns: Absolute path to the target of link - :rtype: str - - :raises .CertStorageError: If link does not exists. - - """ - try: - target = os.readlink(link) - except OSError: - raise errors.CertStorageError( - "Expected {0} to be a symlink".format(link)) - - if not os.path.isabs(target): - target = os.path.join(os.path.dirname(link), target) - return os.path.abspath(target) - -def _write_live_readme_to(readme_path, is_base_dir=False): - prefix = "" - if is_base_dir: - prefix = "[cert name]/" - with open(readme_path, "w") as f: - logger.debug("Writing README to %s.", readme_path) - f.write("This directory contains your keys and certificates.\n\n" - "`{prefix}privkey.pem` : the private key for your certificate.\n" - "`{prefix}fullchain.pem`: the certificate file used in most server software.\n" - "`{prefix}chain.pem` : used for OCSP stapling in Nginx >=1.3.7.\n" - "`{prefix}cert.pem` : will break many server configurations, and " - "should not be used\n" - " without reading further documentation (see link below).\n\n" - "WARNING: DO NOT MOVE OR RENAME THESE FILES!\n" - " Certbot expects these files to remain in this location in order\n" - " to function properly!\n\n" - "We recommend not moving these files. For more information, see the Certbot\n" - "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-" - "certificates.\n".format(prefix=prefix)) - - -def _relevant(namespaces, option): - """ - Is this option one that could be restored for future renewal purposes? - - :param namespaces: plugin namespaces for configuration options - :type namespaces: `list` of `str` - :param str option: the name of the option - - :rtype: bool - """ - from certbot import renewal - - return (option in renewal.CONFIG_ITEMS or - any(option.startswith(namespace) for namespace in namespaces)) - - -def relevant_values(all_values): - """Return a new dict containing only items relevant for renewal. - - :param dict all_values: The original values. - - :returns: A new dictionary containing items that can be used in renewal. - :rtype dict: - - """ - plugins = plugins_disco.PluginsRegistry.find_all() - namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] - - rv = dict( - (option, value) - for option, value in six.iteritems(all_values) - if _relevant(namespaces, option) and cli.option_was_set(option, value)) - # We always save the server value to help with forward compatibility - # and behavioral consistency when versions of Certbot with different - # server defaults are used. - rv["server"] = all_values["server"] - return rv - -def lineagename_for_filename(config_filename): - """Returns the lineagename for a configuration filename. - """ - if not config_filename.endswith(".conf"): - raise errors.CertStorageError( - "renewal config file name must end in .conf") - return os.path.basename(config_filename[:-len(".conf")]) - -def renewal_filename_for_lineagename(config, lineagename): - """Returns the lineagename for a configuration filename. - """ - return os.path.join(config.renewal_configs_dir, lineagename) + ".conf" - -def _relpath_from_file(archive_dir, from_file): - """Path to a directory from a file""" - return os.path.relpath(archive_dir, os.path.dirname(from_file)) - -def full_archive_path(config_obj, cli_config, lineagename): - """Returns the full archive path for a lineagename - - Uses cli_config to determine archive path if not available from config_obj. - - :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None) - :param configuration.NamespaceConfig cli_config: Main config file - :param str lineagename: Certificate name - """ - if config_obj and "archive_dir" in config_obj: - return config_obj["archive_dir"] - return os.path.join(cli_config.default_archive_dir, lineagename) - -def _full_live_path(cli_config, lineagename): - """Returns the full default live path for a lineagename""" - return os.path.join(cli_config.live_dir, lineagename) - -def delete_files(config, certname): - """Delete all files related to the certificate. - - If some files are not found, ignore them and continue. - """ - renewal_filename = renewal_file_for_certname(config, certname) - # file exists - full_default_archive_dir = full_archive_path(None, config, certname) - full_default_live_dir = _full_live_path(config, certname) - try: - renewal_config = configobj.ConfigObj(renewal_filename) - except configobj.ConfigObjError: - # config is corrupted - logger.warning("Could not parse %s. You may wish to manually " - "delete the contents of %s and %s.", renewal_filename, - full_default_live_dir, full_default_archive_dir) - raise errors.CertStorageError( - "error parsing {0}".format(renewal_filename)) - finally: - # we couldn't read it, but let's at least delete it - # if this was going to fail, it already would have. - os.remove(renewal_filename) - logger.debug("Removed %s", renewal_filename) - - # cert files and (hopefully) live directory - # it's not guaranteed that the files are in our default storage - # structure. so, first delete the cert files. - directory_names = set() - for kind in ALL_FOUR: - link = renewal_config.get(kind) - try: - os.remove(link) - logger.debug("Removed %s", link) - except OSError: - logger.debug("Unable to delete %s", link) - directory = os.path.dirname(link) - directory_names.add(directory) - - # if all four were in the same directory, and the only thing left - # is the README file (or nothing), delete that directory. - # this will be wrong in very few but some cases. - if len(directory_names) == 1: - # delete the README file - directory = directory_names.pop() - readme_path = os.path.join(directory, README) - try: - os.remove(readme_path) - logger.debug("Removed %s", readme_path) - except OSError: - logger.debug("Unable to delete %s", readme_path) - # if it's now empty, delete the directory - try: - os.rmdir(directory) # only removes empty directories - logger.debug("Removed %s", directory) - except OSError: - logger.debug("Unable to remove %s; may not be empty.", directory) - - # archive directory - try: - archive_path = full_archive_path(renewal_config, config, certname) - shutil.rmtree(archive_path) - logger.debug("Removed %s", archive_path) - except OSError: - logger.debug("Unable to remove %s", archive_path) - - -class RenewableCert(object): - # pylint: disable=too-many-instance-attributes,too-many-public-methods - """Renewable certificate. - - Represents a lineage of certificates that is under the management of - Certbot, indicated by the existence of an associated renewal - configuration file. - - Note that the notion of "current version" for a lineage is - maintained on disk in the structure of symbolic links, and is not - explicitly stored in any instance variable in this object. The - RenewableCert object is able to determine information about the - current (or other) version by accessing data on disk, but does not - inherently know any of this information except by examining the - symbolic links as needed. The instance variables mentioned below - point to symlinks that reflect the notion of "current version" of - each managed object, and it is these paths that should be used when - configuring servers to use the certificate managed in a lineage. - These paths are normally within the "live" directory, and their - symlink targets -- the actual cert files -- are normally found - within the "archive" directory. - - :ivar str cert: The path to the symlink representing the current - version of the certificate managed by this lineage. - :ivar str privkey: The path to the symlink representing the current - version of the private key managed by this lineage. - :ivar str chain: The path to the symlink representing the current version - of the chain managed by this lineage. - :ivar str fullchain: The path to the symlink representing the - current version of the fullchain (combined chain and cert) - managed by this lineage. - :ivar configobj.ConfigObj configuration: The renewal configuration - options associated with this lineage, obtained from parsing the - renewal configuration file and/or systemwide defaults. - - """ - def __init__(self, config_filename, cli_config, update_symlinks=False): - """Instantiate a RenewableCert object from an existing lineage. - - :param str config_filename: the path to the renewal config file - that defines this lineage. - :param .NamespaceConfig: parsed command line arguments - - :raises .CertStorageError: if the configuration file's name didn't end - in ".conf", or the file is missing or broken. - - """ - self.cli_config = cli_config - self.lineagename = lineagename_for_filename(config_filename) - - # self.configuration should be used to read parameters that - # may have been chosen based on default values from the - # systemwide renewal configuration; self.configfile should be - # used to make and save changes. - try: - self.configfile = configobj.ConfigObj(config_filename) - except configobj.ConfigObjError: - raise errors.CertStorageError( - "error parsing {0}".format(config_filename)) - # TODO: Do we actually use anything from defaults and do we want to - # read further defaults from the systemwide renewal configuration - # file at this stage? - self.configuration = config_with_defaults(self.configfile) - - if not all(x in self.configuration for x in ALL_FOUR): - raise errors.CertStorageError( - "renewal config file {0} is missing a required " - "file reference".format(self.configfile)) - - conf_version = self.configuration.get("version") - if (conf_version is not None and - util.get_strict_version(conf_version) > CURRENT_VERSION): - logger.info( - "Attempting to parse the version %s renewal configuration " - "file found at %s with version %s of Certbot. This might not " - "work.", conf_version, config_filename, certbot.__version__) - - self.cert = self.configuration["cert"] - self.privkey = self.configuration["privkey"] - self.chain = self.configuration["chain"] - self.fullchain = self.configuration["fullchain"] - self.live_dir = os.path.dirname(self.cert) - - self._fix_symlinks() - if update_symlinks: - self._update_symlinks() - self._check_symlinks() - - @property - def key_path(self): - """Duck type for self.privkey""" - return self.privkey - - @property - def cert_path(self): - """Duck type for self.cert""" - return self.cert - - @property - def chain_path(self): - """Duck type for self.chain""" - return self.chain - - @property - def fullchain_path(self): - """Duck type for self.fullchain""" - return self.fullchain - - @property - def target_expiry(self): - """The current target certificate's expiration datetime - - :returns: Expiration datetime of the current target certificate - :rtype: :class:`datetime.datetime` - """ - return crypto_util.notAfter(self.current_target("cert")) - - @property - def archive_dir(self): - """Returns the default or specified archive directory""" - return full_archive_path(self.configuration, - self.cli_config, self.lineagename) - - def relative_archive_dir(self, from_file): - """Returns the default or specified archive directory as a relative path - - Used for creating symbolic links. - """ - return _relpath_from_file(self.archive_dir, from_file) - - @property - def is_test_cert(self): - """Returns true if this is a test cert from a staging server.""" - server = self.configuration["renewalparams"].get("server", None) - if server: - return util.is_staging(server) - return False - - def _check_symlinks(self): - """Raises an exception if a symlink doesn't exist""" - for kind in ALL_FOUR: - link = getattr(self, kind) - if not os.path.islink(link): - raise errors.CertStorageError( - "expected {0} to be a symlink".format(link)) - target = get_link_target(link) - if not os.path.exists(target): - raise errors.CertStorageError("target {0} of symlink {1} does " - "not exist".format(target, link)) - - def _update_symlinks(self): - """Updates symlinks to use archive_dir""" - for kind in ALL_FOUR: - link = getattr(self, kind) - previous_link = get_link_target(link) - new_link = os.path.join(self.relative_archive_dir(link), - os.path.basename(previous_link)) - - os.unlink(link) - os.symlink(new_link, link) - - def _consistent(self): - """Are the files associated with this lineage self-consistent? - - :returns: Whether the files stored in connection with this - lineage appear to be correct and consistent with one - another. - :rtype: bool - - """ - # Each element must be referenced with an absolute path - for x in (self.cert, self.privkey, self.chain, self.fullchain): - if not os.path.isabs(x): - logger.debug("Element %s is not referenced with an " - "absolute path.", x) - return False - - # Each element must exist and be a symbolic link - for x in (self.cert, self.privkey, self.chain, self.fullchain): - if not os.path.islink(x): - logger.debug("Element %s is not a symbolic link.", x) - return False - for kind in ALL_FOUR: - link = getattr(self, kind) - target = get_link_target(link) - - # Each element's link must point within the cert lineage's - # directory within the official archive directory - if not os.path.samefile(os.path.dirname(target), self.archive_dir): - logger.debug("Element's link does not point within the " - "cert lineage's directory within the " - "official archive directory. Link: %s, " - "target directory: %s, " - "archive directory: %s. If you've specified " - "the archive directory in the renewal configuration " - "file, you may need to update links by running " - "certbot update_symlinks.", - link, os.path.dirname(target), self.archive_dir) - return False - - # The link must point to a file that exists - if not os.path.exists(target): - logger.debug("Link %s points to file %s that does not exist.", - link, target) - return False - - # The link must point to a file that follows the archive - # naming convention - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - if not pattern.match(os.path.basename(target)): - logger.debug("%s does not follow the archive naming " - "convention.", target) - return False - - # It is NOT required that the link's target be a regular - # file (it may itself be a symlink). But we should probably - # do a recursive check that ultimately the target does - # exist? - # XXX: Additional possible consistency checks (e.g. - # cryptographic validation of the chain being a chain, - # the chain matching the cert, and the cert matching - # the subject key) - # XXX: All four of the targets are in the same directory - # (This check is redundant with the check that they - # are all in the desired directory!) - # len(set(os.path.basename(self.current_target(x) - # for x in ALL_FOUR))) == 1 - return True - - def _fix(self): - """Attempt to fix defects or inconsistencies in this lineage. - - .. todo:: Currently unimplemented. - - """ - # TODO: Figure out what kinds of fixes are possible. For - # example, checking if there is a valid version that - # we can update the symlinks to. (Maybe involve - # parsing keys and certs to see if they exist and - # if a key corresponds to the subject key of a cert?) - - # TODO: In general, the symlink-reading functions below are not - # cautious enough about the possibility that links or their - # targets may not exist. (This shouldn't happen, but might - # happen as a result of random tampering by a sysadmin, or - # filesystem errors, or crashes.) - - def _previous_symlinks(self): - """Returns the kind and path of all symlinks used in recovery. - - :returns: list of (kind, symlink) tuples - :rtype: list - - """ - previous_symlinks = [] - for kind in ALL_FOUR: - link_dir = os.path.dirname(getattr(self, kind)) - link_base = "previous_{0}.pem".format(kind) - previous_symlinks.append((kind, os.path.join(link_dir, link_base))) - - return previous_symlinks - - def _fix_symlinks(self): - """Fixes symlinks in the event of an incomplete version update. - - If there is no problem with the current symlinks, this function - has no effect. - - """ - previous_symlinks = self._previous_symlinks() - if all(os.path.exists(link[1]) for link in previous_symlinks): - for kind, previous_link in previous_symlinks: - current_link = getattr(self, kind) - if os.path.lexists(current_link): - os.unlink(current_link) - os.symlink(os.readlink(previous_link), current_link) - - for _, link in previous_symlinks: - if os.path.exists(link): - os.unlink(link) - - def current_target(self, kind): - """Returns full path to which the specified item currently points. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - - :returns: The path to the current version of the specified - member. - :rtype: str or None - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - link = getattr(self, kind) - if not os.path.exists(link): - logger.debug("Expected symlink %s for %s does not exist.", - link, kind) - return None - return get_link_target(link) - - def current_version(self, kind): - """Returns numerical version of the specified item. - - For example, if kind is "chain" and the current chain link - points to a file named "chain7.pem", returns the integer 7. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - - :returns: the current version of the specified member. - :rtype: int - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - target = self.current_target(kind) - if target is None or not os.path.exists(target): - logger.debug("Current-version target for %s " - "does not exist at %s.", kind, target) - target = "" - matches = pattern.match(os.path.basename(target)) - if matches: - return int(matches.groups()[0]) - logger.debug("No matches for target %s.", kind) - return None - - def version(self, kind, version): - """The filename that corresponds to the specified version and kind. - - .. warning:: The specified version may not exist in this - lineage. There is no guarantee that the file path returned - by this method actually exists. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - :param int version: the desired version - - :returns: The path to the specified version of the specified member. - :rtype: str - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - where = os.path.dirname(self.current_target(kind)) - return os.path.join(where, "{0}{1}.pem".format(kind, version)) - - def available_versions(self, kind): - """Which alternative versions of the specified kind of item exist? - - The archive directory where the current version is stored is - consulted to obtain the list of alternatives. - - :param str kind: the lineage member item ( - ``cert``, ``privkey``, ``chain``, or ``fullchain``) - - :returns: all of the version numbers that currently exist - :rtype: `list` of `int` - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - where = os.path.dirname(self.current_target(kind)) - files = os.listdir(where) - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - matches = [pattern.match(f) for f in files] - return sorted([int(m.groups()[0]) for m in matches if m]) - - def newest_available_version(self, kind): - """Newest available version of the specified kind of item? - - :param str kind: the lineage member item (``cert``, - ``privkey``, ``chain``, or ``fullchain``) - - :returns: the newest available version of this member - :rtype: int - - """ - return max(self.available_versions(kind)) - - def latest_common_version(self): - """Newest version for which all items are available? - - :returns: the newest available version for which all members - (``cert, ``privkey``, ``chain``, and ``fullchain``) exist - :rtype: int - - """ - # TODO: this can raise CertStorageError if there is no version overlap - # (it should probably return None instead) - # TODO: this can raise a spurious AttributeError if the current - # link for any kind is missing (it should probably return None) - versions = [self.available_versions(x) for x in ALL_FOUR] - return max(n for n in versions[0] if all(n in v for v in versions[1:])) - - def next_free_version(self): - """Smallest version newer than all full or partial versions? - - :returns: the smallest version number that is larger than any - version of any item currently stored in this lineage - :rtype: int - - """ - # TODO: consider locking/mutual exclusion between updating processes - # This isn't self.latest_common_version() + 1 because we don't want - # collide with a version that might exist for one file type but not - # for the others. - return max(self.newest_available_version(x) for x in ALL_FOUR) + 1 - - def ensure_deployed(self): - """Make sure we've deployed the latest version. - - :returns: False if a change was needed, True otherwise - :rtype: bool - - May need to recover from rare interrupted / crashed states.""" - - if self.has_pending_deployment(): - 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 - return True - - - def has_pending_deployment(self): - """Is there a later version of all of the managed items? - - :returns: ``True`` if there is a complete version of this - lineage with a larger version number than the current - version, and ``False`` otherwise - :rtype: bool - - """ - # TODO: consider whether to assume consistency or treat - # inconsistent/consistent versions differently - smallest_current = min(self.current_version(x) for x in ALL_FOUR) - return smallest_current < self.latest_common_version() - - def _update_link_to(self, kind, version): - """Make the specified item point at the specified version. - - (Note that this method doesn't verify that the specified version - exists.) - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - :param int version: the desired version - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - link = getattr(self, kind) - filename = "{0}{1}.pem".format(kind, version) - # Relative rather than absolute target directory - target_directory = os.path.dirname(os.readlink(link)) - # TODO: it could be safer to make the link first under a temporary - # filename, then unlink the old link, then rename the new link - # to the old link; this ensures that this process is able to - # create symlinks. - # TODO: we might also want to check consistency of related links - # for the other corresponding items - os.unlink(link) - os.symlink(os.path.join(target_directory, filename), link) - - def update_all_links_to(self, version): - """Change all member objects to point to the specified version. - - :param int version: the desired version - - """ - with error_handler.ErrorHandler(self._fix_symlinks): - previous_links = self._previous_symlinks() - for kind, link in previous_links: - os.symlink(self.current_target(kind), link) - - for kind in ALL_FOUR: - self._update_link_to(kind, version) - - for _, link in previous_links: - os.unlink(link) - - def names(self, version=None): - """What are the subject names of this certificate? - - (If no version is specified, use the current version.) - - :param int version: the desired version number - :returns: the subject names - :rtype: `list` of `str` - :raises .CertStorageError: if could not find cert file. - - """ - if version is None: - target = self.current_target("cert") - else: - target = self.version("cert", version) - if target is None: - raise errors.CertStorageError("could not find cert file") - with open(target) as f: - return crypto_util.get_names_from_cert(f.read()) - - def ocsp_revoked(self, version=None): - # pylint: disable=no-self-use,unused-argument - """Is the specified cert version revoked according to OCSP? - - Also returns True if the cert version is declared as intended - to be revoked according to Let's Encrypt OCSP extensions. - (If no version is specified, uses the current version.) - - This method is not yet implemented and currently always returns - False. - - :param int version: the desired version number - - :returns: whether the certificate is or will be revoked - :rtype: bool - - """ - # XXX: This query and its associated network service aren't - # implemented yet, so we currently return False (indicating that the - # certificate is not revoked). - return False - - def autorenewal_is_enabled(self): - """Is automatic renewal enabled for this cert? - - If autorenew is not specified, defaults to True. - - :returns: True if automatic renewal is enabled - :rtype: bool - - """ - return ("autorenew" not in self.configuration["renewalparams"] or - self.configuration["renewalparams"].as_bool("autorenew")) - - def should_autorenew(self): - """Should we now try to autorenew the most recent cert version? - - This is a policy question and does not only depend on whether - the cert is expired. (This considers whether autorenewal is - enabled, whether the cert is revoked, and whether the time - interval for autorenewal has been reached.) - - Note that this examines the numerically most recent cert version, - not the currently deployed version. - - :returns: whether an attempt should now be made to autorenew the - most current cert version in this lineage - :rtype: bool - - """ - if self.autorenewal_is_enabled(): - # Consider whether to attempt to autorenew this cert now - - # Renewals on the basis of revocation - if self.ocsp_revoked(self.latest_common_version()): - logger.debug("Should renew, certificate is revoked.") - return True - - # Renews some period before expiry time - default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] - interval = self.configuration.get("renew_before_expiry", default_interval) - expiry = crypto_util.notAfter(self.version( - "cert", self.latest_common_version())) - now = pytz.UTC.fromutc(datetime.datetime.utcnow()) - if expiry < add_time_interval(now, interval): - logger.debug("Should renew, less than %s before certificate " - "expiry %s.", interval, - expiry.strftime("%Y-%m-%d %H:%M:%S %Z")) - return True - return False - - @classmethod - def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): - # pylint: disable=too-many-locals - """Create a new certificate lineage. - - Attempts to create a certificate lineage -- enrolled for - potential future renewal -- with the (suggested) lineage name - lineagename, and the associated cert, privkey, and chain (the - associated fullchain will be created automatically). Optional - configurator and renewalparams record the configuration that was - originally used to obtain this cert, so that it can be reused - later during automated renewal. - - Returns a new RenewableCert object referring to the created - lineage. (The actual lineage name, as well as all the relevant - file paths, will be available within this object.) - - :param str lineagename: the suggested name for this lineage - (normally the current cert's first subject DNS name) - :param str cert: the initial certificate version in PEM format - :param str privkey: the private key in PEM format - :param str chain: the certificate chain in PEM format - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: the newly-created RenewalCert object - :rtype: :class:`storage.renewableCert` - - """ - - # Examine the configuration and find the new lineage's name - for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, - cli_config.live_dir): - if not os.path.exists(i): - filesystem.makedirs(i, 0o700) - logger.debug("Creating directory %s.", i) - config_file, config_filename = util.unique_lineage_name( - cli_config.renewal_configs_dir, lineagename) - base_readme_path = os.path.join(cli_config.live_dir, README) - if not os.path.exists(base_readme_path): - _write_live_readme_to(base_readme_path, is_base_dir=True) - - # Determine where on disk everything will go - # lineagename will now potentially be modified based on which - # renewal configuration file could actually be created - lineagename = lineagename_for_filename(config_filename) - 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) - filesystem.mkdir(archive) - filesystem.mkdir(live_dir) - logger.debug("Archive directory %s and live " - "directory %s created.", archive, live_dir) - - # 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(_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 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 - with open(target["chain"], "wb") as f: - logger.debug("Writing chain to %s.", target["chain"]) - f.write(chain) - with open(target["fullchain"], "wb") as f: - # assumes that OpenSSL.crypto.dump_certificate includes - # ending newline character - logger.debug("Writing full chain to %s.", target["fullchain"]) - f.write(cert + chain) - - # Write a README file to the live directory - readme_path = os.path.join(live_dir, README) - _write_live_readme_to(readme_path) - - # Document what we've done in a new renewal config file - config_file.close() - - # Save only the config items that are relevant to renewal - values = relevant_values(vars(cli_config.namespace)) - - new_config = write_renewal_config(config_filename, config_filename, archive, - target, values) - return cls(new_config.filename, cli_config) - - def save_successor(self, prior_version, new_cert, - new_privkey, new_chain, cli_config): - """Save new cert and chain as a successor of a prior version. - - Returns the new version number that was created. - - .. note:: this function does NOT update links to deploy this - version - - :param int prior_version: the old version to which this version - is regarded as a successor (used to choose a privkey, if the - key has not changed, but otherwise this information is not - permanently recorded anywhere) - :param bytes new_cert: the new certificate, in PEM format - :param bytes new_privkey: the new private key, in PEM format, - or ``None``, if the private key has not changed - :param bytes new_chain: the new chain, in PEM format - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: the new version number that was created - :rtype: int - - """ - # XXX: assumes official archive location rather than examining links - # XXX: consider using os.open for availability of os.O_EXCL - # XXX: ensure file permissions are correct; also create directories - # if needed (ensuring their permissions are correct) - # Figure out what the new version is and hence where to save things - - self.cli_config = cli_config - target_version = self.next_free_version() - target = dict( - [(kind, - 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. - if os.path.islink(old_privkey): - old_privkey = os.readlink(old_privkey) - else: - old_privkey = "privkey{0}.pem".format(prior_version) - logger.debug("Writing symlink to old private key, %s.", old_privkey) - os.symlink(old_privkey, target["privkey"]) - else: - 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 & MASK_FOR_PRIVATE_KEY_PERMISSIONS) - # from previous privkey in this lineage. - mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) - filesystem.copy_ownership_and_apply_mode( - old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) - - # Save everything else - with open(target["cert"], "wb") as f: - logger.debug("Writing certificate to %s.", target["cert"]) - f.write(new_cert) - with open(target["chain"], "wb") as f: - logger.debug("Writing chain to %s.", target["chain"]) - f.write(new_chain) - with open(target["fullchain"], "wb") as f: - logger.debug("Writing full chain to %s.", target["fullchain"]) - f.write(new_cert + new_chain) - - symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) - # Update renewal config file - self.configfile = update_configuration( - self.lineagename, self.archive_dir, symlinks, cli_config) - self.configuration = config_with_defaults(self.configfile) - - return target_version diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index cad103052..29ec4fc33 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.account.""" +"""Tests for certbot._internal.account.""" import datetime import json import unittest @@ -19,10 +19,10 @@ KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class AccountTest(unittest.TestCase): - """Tests for certbot.account.Account.""" + """Tests for certbot._internal.account.Account.""" def setUp(self): - from certbot.account import Account + from certbot._internal.account import Account self.regr = mock.MagicMock() self.meta = Account.Meta( creation_host="test.certbot.org", @@ -31,9 +31,9 @@ class AccountTest(unittest.TestCase): self.acc = Account(self.regr, KEY, self.meta) self.regr.__repr__ = mock.MagicMock(return_value="i_am_a_regr") - with mock.patch("certbot.account.socket") as mock_socket: + with mock.patch("certbot._internal.account.socket") as mock_socket: mock_socket.getfqdn.return_value = "test.certbot.org" - with mock.patch("certbot.account.datetime") as mock_dt: + with mock.patch("certbot._internal.account.datetime") as mock_dt: mock_dt.datetime.now.return_value = self.meta.creation_dt self.acc_no_meta = Account(self.regr, KEY) @@ -55,18 +55,18 @@ class AccountTest(unittest.TestCase): " Date: Mon, 11 Nov 2019 10:17:16 -0800 Subject: Move log.py to _internal (#7531) Part of #5775. Methodology similar to #7528, but slightly more manual. --- certbot/_internal/log.py | 353 +++++++++++++++++++++++++++++++++++++++++++++ certbot/_internal/main.py | 2 +- certbot/log.py | 353 --------------------------------------------- certbot/tests/log_test.py | 64 ++++---- certbot/tests/main_test.py | 18 +-- docs/api/log.rst | 5 - 6 files changed, 395 insertions(+), 400 deletions(-) create mode 100644 certbot/_internal/log.py delete mode 100644 certbot/log.py delete mode 100644 docs/api/log.rst diff --git a/certbot/_internal/log.py b/certbot/_internal/log.py new file mode 100644 index 000000000..a16e2ef7e --- /dev/null +++ b/certbot/_internal/log.py @@ -0,0 +1,353 @@ +"""Logging utilities for Certbot. + +The best way to use this module is through `pre_arg_parse_setup` and +`post_arg_parse_setup`. `pre_arg_parse_setup` configures a minimal +terminal logger and ensures a detailed log is written to a secure +temporary file if Certbot exits before `post_arg_parse_setup` is called. +`post_arg_parse_setup` relies on the parsed command line arguments and +does the full logging setup with terminal and rotating file handling as +configured by the user. Any logged messages before +`post_arg_parse_setup` is called are sent to the rotating file handler. +Special care is taken by both methods to ensure all errors are logged +and properly flushed before program exit. + +""" +from __future__ import print_function + +import functools +import logging +import logging.handlers +import shutil +import sys +import tempfile +import traceback + +from acme import messages + +from certbot import constants +from certbot import errors +from certbot import util +from certbot.compat import os + +# Logging format +CLI_FMT = "%(message)s" +FILE_FMT = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" + + +logger = logging.getLogger(__name__) + + +def pre_arg_parse_setup(): + """Setup logging before command line arguments are parsed. + + Terminal logging is setup using + `certbot.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as + possible. File logging is setup so that logging messages are + buffered in memory. If Certbot exits before `post_arg_parse_setup` + is called, these buffered messages are written to a temporary file. + If Certbot doesn't exit, `post_arg_parse_setup` writes the messages + to the normal log files. + + This function also sets `logging.shutdown` to be called on program + exit which automatically flushes logging handlers and + `sys.excepthook` to properly log/display fatal exceptions. + + """ + temp_handler = TempHandler() + temp_handler.setFormatter(logging.Formatter(FILE_FMT)) + temp_handler.setLevel(logging.DEBUG) + memory_handler = MemoryHandler(temp_handler) + + stream_handler = ColoredStreamHandler() + stream_handler.setFormatter(logging.Formatter(CLI_FMT)) + stream_handler.setLevel(constants.QUIET_LOGGING_LEVEL) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) # send all records to handlers + root_logger.addHandler(memory_handler) + root_logger.addHandler(stream_handler) + + # logging.shutdown will flush the memory handler because flush() and + # close() are explicitly called + util.atexit_register(logging.shutdown) + sys.excepthook = functools.partial( + pre_arg_parse_except_hook, memory_handler, + debug='--debug' in sys.argv, log_path=temp_handler.path) + + +def post_arg_parse_setup(config): + """Setup logging after command line arguments are parsed. + + This function assumes `pre_arg_parse_setup` was called earlier and + the root logging configuration has not been modified. A rotating + file logging handler is created and the buffered log messages are + sent to that handler. Terminal logging output is set to the level + requested by the user. + + :param certbot.interface.IConfig config: Configuration object + + """ + file_handler, file_path = setup_log_file_handler( + config, 'letsencrypt.log', FILE_FMT) + logs_dir = os.path.dirname(file_path) + + root_logger = logging.getLogger() + memory_handler = stderr_handler = None + for handler in root_logger.handlers: + if isinstance(handler, ColoredStreamHandler): + stderr_handler = handler + elif isinstance(handler, MemoryHandler): + memory_handler = handler + msg = 'Previously configured logging handlers have been removed!' + assert memory_handler is not None and stderr_handler is not None, msg + + root_logger.addHandler(file_handler) + root_logger.removeHandler(memory_handler) + temp_handler = memory_handler.target + memory_handler.setTarget(file_handler) + memory_handler.flush(force=True) + memory_handler.close() + temp_handler.close() + + if config.quiet: + level = constants.QUIET_LOGGING_LEVEL + else: + level = -config.verbose_count * 10 + stderr_handler.setLevel(level) + logger.debug('Root logging level set at %d', level) + logger.info('Saving debug log to %s', file_path) + + sys.excepthook = functools.partial( + post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir) + + +def setup_log_file_handler(config, logfile, fmt): + """Setup file debug logging. + + :param certbot.interface.IConfig config: Configuration object + :param str logfile: basename for the log file + :param str fmt: logging format string + + :returns: file handler and absolute path to the log file + :rtype: tuple + + """ + # TODO: logs might contain sensitive data such as contents of the + # private key! #525 + util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) + log_file_path = os.path.join(config.logs_dir, logfile) + try: + handler = logging.handlers.RotatingFileHandler( + log_file_path, maxBytes=2 ** 20, + backupCount=config.max_log_backups) + except IOError as error: + raise errors.Error(util.PERM_ERR_FMT.format(error)) + # rotate on each invocation, rollover only possible when maxBytes + # is nonzero and backupCount is nonzero, so we set maxBytes as big + # as possible not to overrun in single CLI invocation (1MB). + handler.doRollover() # TODO: creates empty letsencrypt.log.1 file + handler.setLevel(logging.DEBUG) + handler_formatter = logging.Formatter(fmt=fmt) + handler.setFormatter(handler_formatter) + return handler, log_file_path + + +class ColoredStreamHandler(logging.StreamHandler): + """Sends colored logging output to a stream. + + If the specified stream is not a tty, the class works like the + standard `logging.StreamHandler`. Default red_level is + `logging.WARNING`. + + :ivar bool colored: True if output should be colored + :ivar bool red_level: The level at which to output + + """ + def __init__(self, stream=None): + super(ColoredStreamHandler, self).__init__(stream) + self.colored = (sys.stderr.isatty() if stream is None else + stream.isatty()) + self.red_level = logging.WARNING + + def format(self, record): + """Formats the string representation of record. + + :param logging.LogRecord record: Record to be formatted + + :returns: Formatted, string representation of record + :rtype: str + + """ + out = super(ColoredStreamHandler, self).format(record) + if self.colored and record.levelno >= self.red_level: + return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) + return out + + +class MemoryHandler(logging.handlers.MemoryHandler): + """Buffers logging messages in memory until the buffer is flushed. + + This differs from `logging.handlers.MemoryHandler` in that flushing + only happens when flush(force=True) is called. + + """ + def __init__(self, target=None, capacity=10000): + # capacity doesn't matter because should_flush() is overridden + super(MemoryHandler, self).__init__(capacity, target=target) + + def close(self): + """Close the memory handler, but don't set the target to None.""" + # This allows the logging module which may only have a weak + # reference to the target handler to properly flush and close it. + target = self.target + super(MemoryHandler, self).close() + self.target = target + + def flush(self, force=False): # pylint: disable=arguments-differ + """Flush the buffer if force=True. + + If force=False, this call is a noop. + + :param bool force: True if the buffer should be flushed. + + """ + # This method allows flush() calls in logging.shutdown to be a + # noop so we can control when this handler is flushed. + if force: + super(MemoryHandler, self).flush() + + def shouldFlush(self, record): + """Should the buffer be automatically flushed? + + :param logging.LogRecord record: log record to be considered + + :returns: False because the buffer should never be auto-flushed + :rtype: bool + + """ + return False + + +class TempHandler(logging.StreamHandler): + """Safely logs messages to a temporary file. + + The file is created with permissions 600. If no log records are sent + to this handler, the temporary file is deleted when the handler is + closed. + + :ivar str path: file system path to the temporary log file + + """ + def __init__(self): + self._workdir = tempfile.mkdtemp() + self.path = os.path.join(self._workdir, 'log') + stream = util.safe_open(self.path, mode='w', chmod=0o600) + super(TempHandler, self).__init__(stream) + self._delete = True + + def emit(self, record): + """Log the specified logging record. + + :param logging.LogRecord record: Record to be formatted + + """ + self._delete = False + super(TempHandler, self).emit(record) + + def close(self): + """Close the handler and the temporary log file. + + The temporary log file is deleted if it wasn't used. + + """ + self.acquire() + try: + # StreamHandler.close() doesn't close the stream to allow a + # stream like stderr to be used + self.stream.close() + if self._delete: + shutil.rmtree(self._workdir) + self._delete = False + super(TempHandler, self).close() + finally: + self.release() + + +def pre_arg_parse_except_hook(memory_handler, *args, **kwargs): + """A simple wrapper around post_arg_parse_except_hook. + + The additional functionality provided by this wrapper is the memory + handler will be flushed before Certbot exits. This allows us to + write logging messages to a temporary file if we crashed before + logging was fully configured. + + Since sys.excepthook isn't called on SystemExit exceptions, the + memory handler will not be flushed in this case which prevents us + from creating temporary log files when argparse exits because a + command line argument was invalid or -h, --help, or --version was + provided on the command line. + + :param MemoryHandler memory_handler: memory handler to flush + :param tuple args: args for post_arg_parse_except_hook + :param dict kwargs: kwargs for post_arg_parse_except_hook + + """ + try: + post_arg_parse_except_hook(*args, **kwargs) + finally: + # flush() is called here so messages logged during + # post_arg_parse_except_hook are also flushed. + memory_handler.flush(force=True) + + +def post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path): + """Logs fatal exceptions and reports them to the user. + + If debug is True, the full exception and traceback is shown to the + user, otherwise, it is suppressed. sys.exit is always called with a + nonzero status. + + :param type exc_type: type of the raised exception + :param BaseException exc_value: raised exception + :param traceback trace: traceback of where the exception was raised + :param bool debug: True if the traceback should be shown to the user + :param str log_path: path to file or directory containing the log + + """ + exc_info = (exc_type, exc_value, trace) + # constants.QUIET_LOGGING_LEVEL or higher should be used to + # display message the user, otherwise, a lower level like + # logger.DEBUG should be used + if debug or not issubclass(exc_type, Exception): + assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR + logger.error('Exiting abnormally:', exc_info=exc_info) + else: + logger.debug('Exiting abnormally:', exc_info=exc_info) + if issubclass(exc_type, errors.Error): + sys.exit(exc_value) + logger.error('An unexpected error occurred:') + if messages.is_acme_error(exc_value): + # Remove the ACME error prefix from the exception + _, _, exc_str = str(exc_value).partition(':: ') + logger.error(exc_str) + else: + traceback.print_exception(exc_type, exc_value, None) + exit_with_log_path(log_path) + + +def exit_with_log_path(log_path): + """Print a message about the log location and exit. + + The message is printed to stderr and the program will exit with a + nonzero status. + + :param str log_path: path to file or directory containing the log + + """ + msg = 'Please see the ' + if os.path.isdir(log_path): + msg += 'logfiles in {0} '.format(log_path) + else: + msg += "logfile '{0}' ".format(log_path) + msg += 'for more details.' + sys.exit(msg) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index 1552131e8..c98da5bf7 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -25,7 +25,7 @@ from certbot import eff from certbot import errors from certbot import hooks from certbot import interfaces -from certbot import log +from certbot._internal import log from certbot._internal import renewal from certbot._internal import reporter from certbot._internal import storage diff --git a/certbot/log.py b/certbot/log.py deleted file mode 100644 index a16e2ef7e..000000000 --- a/certbot/log.py +++ /dev/null @@ -1,353 +0,0 @@ -"""Logging utilities for Certbot. - -The best way to use this module is through `pre_arg_parse_setup` and -`post_arg_parse_setup`. `pre_arg_parse_setup` configures a minimal -terminal logger and ensures a detailed log is written to a secure -temporary file if Certbot exits before `post_arg_parse_setup` is called. -`post_arg_parse_setup` relies on the parsed command line arguments and -does the full logging setup with terminal and rotating file handling as -configured by the user. Any logged messages before -`post_arg_parse_setup` is called are sent to the rotating file handler. -Special care is taken by both methods to ensure all errors are logged -and properly flushed before program exit. - -""" -from __future__ import print_function - -import functools -import logging -import logging.handlers -import shutil -import sys -import tempfile -import traceback - -from acme import messages - -from certbot import constants -from certbot import errors -from certbot import util -from certbot.compat import os - -# Logging format -CLI_FMT = "%(message)s" -FILE_FMT = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" - - -logger = logging.getLogger(__name__) - - -def pre_arg_parse_setup(): - """Setup logging before command line arguments are parsed. - - Terminal logging is setup using - `certbot.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as - possible. File logging is setup so that logging messages are - buffered in memory. If Certbot exits before `post_arg_parse_setup` - is called, these buffered messages are written to a temporary file. - If Certbot doesn't exit, `post_arg_parse_setup` writes the messages - to the normal log files. - - This function also sets `logging.shutdown` to be called on program - exit which automatically flushes logging handlers and - `sys.excepthook` to properly log/display fatal exceptions. - - """ - temp_handler = TempHandler() - temp_handler.setFormatter(logging.Formatter(FILE_FMT)) - temp_handler.setLevel(logging.DEBUG) - memory_handler = MemoryHandler(temp_handler) - - stream_handler = ColoredStreamHandler() - stream_handler.setFormatter(logging.Formatter(CLI_FMT)) - stream_handler.setLevel(constants.QUIET_LOGGING_LEVEL) - - root_logger = logging.getLogger() - root_logger.setLevel(logging.DEBUG) # send all records to handlers - root_logger.addHandler(memory_handler) - root_logger.addHandler(stream_handler) - - # logging.shutdown will flush the memory handler because flush() and - # close() are explicitly called - util.atexit_register(logging.shutdown) - sys.excepthook = functools.partial( - pre_arg_parse_except_hook, memory_handler, - debug='--debug' in sys.argv, log_path=temp_handler.path) - - -def post_arg_parse_setup(config): - """Setup logging after command line arguments are parsed. - - This function assumes `pre_arg_parse_setup` was called earlier and - the root logging configuration has not been modified. A rotating - file logging handler is created and the buffered log messages are - sent to that handler. Terminal logging output is set to the level - requested by the user. - - :param certbot.interface.IConfig config: Configuration object - - """ - file_handler, file_path = setup_log_file_handler( - config, 'letsencrypt.log', FILE_FMT) - logs_dir = os.path.dirname(file_path) - - root_logger = logging.getLogger() - memory_handler = stderr_handler = None - for handler in root_logger.handlers: - if isinstance(handler, ColoredStreamHandler): - stderr_handler = handler - elif isinstance(handler, MemoryHandler): - memory_handler = handler - msg = 'Previously configured logging handlers have been removed!' - assert memory_handler is not None and stderr_handler is not None, msg - - root_logger.addHandler(file_handler) - root_logger.removeHandler(memory_handler) - temp_handler = memory_handler.target - memory_handler.setTarget(file_handler) - memory_handler.flush(force=True) - memory_handler.close() - temp_handler.close() - - if config.quiet: - level = constants.QUIET_LOGGING_LEVEL - else: - level = -config.verbose_count * 10 - stderr_handler.setLevel(level) - logger.debug('Root logging level set at %d', level) - logger.info('Saving debug log to %s', file_path) - - sys.excepthook = functools.partial( - post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir) - - -def setup_log_file_handler(config, logfile, fmt): - """Setup file debug logging. - - :param certbot.interface.IConfig config: Configuration object - :param str logfile: basename for the log file - :param str fmt: logging format string - - :returns: file handler and absolute path to the log file - :rtype: tuple - - """ - # TODO: logs might contain sensitive data such as contents of the - # private key! #525 - util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) - log_file_path = os.path.join(config.logs_dir, logfile) - try: - handler = logging.handlers.RotatingFileHandler( - log_file_path, maxBytes=2 ** 20, - backupCount=config.max_log_backups) - except IOError as error: - raise errors.Error(util.PERM_ERR_FMT.format(error)) - # rotate on each invocation, rollover only possible when maxBytes - # is nonzero and backupCount is nonzero, so we set maxBytes as big - # as possible not to overrun in single CLI invocation (1MB). - handler.doRollover() # TODO: creates empty letsencrypt.log.1 file - handler.setLevel(logging.DEBUG) - handler_formatter = logging.Formatter(fmt=fmt) - handler.setFormatter(handler_formatter) - return handler, log_file_path - - -class ColoredStreamHandler(logging.StreamHandler): - """Sends colored logging output to a stream. - - If the specified stream is not a tty, the class works like the - standard `logging.StreamHandler`. Default red_level is - `logging.WARNING`. - - :ivar bool colored: True if output should be colored - :ivar bool red_level: The level at which to output - - """ - def __init__(self, stream=None): - super(ColoredStreamHandler, self).__init__(stream) - self.colored = (sys.stderr.isatty() if stream is None else - stream.isatty()) - self.red_level = logging.WARNING - - def format(self, record): - """Formats the string representation of record. - - :param logging.LogRecord record: Record to be formatted - - :returns: Formatted, string representation of record - :rtype: str - - """ - out = super(ColoredStreamHandler, self).format(record) - if self.colored and record.levelno >= self.red_level: - return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) - return out - - -class MemoryHandler(logging.handlers.MemoryHandler): - """Buffers logging messages in memory until the buffer is flushed. - - This differs from `logging.handlers.MemoryHandler` in that flushing - only happens when flush(force=True) is called. - - """ - def __init__(self, target=None, capacity=10000): - # capacity doesn't matter because should_flush() is overridden - super(MemoryHandler, self).__init__(capacity, target=target) - - def close(self): - """Close the memory handler, but don't set the target to None.""" - # This allows the logging module which may only have a weak - # reference to the target handler to properly flush and close it. - target = self.target - super(MemoryHandler, self).close() - self.target = target - - def flush(self, force=False): # pylint: disable=arguments-differ - """Flush the buffer if force=True. - - If force=False, this call is a noop. - - :param bool force: True if the buffer should be flushed. - - """ - # This method allows flush() calls in logging.shutdown to be a - # noop so we can control when this handler is flushed. - if force: - super(MemoryHandler, self).flush() - - def shouldFlush(self, record): - """Should the buffer be automatically flushed? - - :param logging.LogRecord record: log record to be considered - - :returns: False because the buffer should never be auto-flushed - :rtype: bool - - """ - return False - - -class TempHandler(logging.StreamHandler): - """Safely logs messages to a temporary file. - - The file is created with permissions 600. If no log records are sent - to this handler, the temporary file is deleted when the handler is - closed. - - :ivar str path: file system path to the temporary log file - - """ - def __init__(self): - self._workdir = tempfile.mkdtemp() - self.path = os.path.join(self._workdir, 'log') - stream = util.safe_open(self.path, mode='w', chmod=0o600) - super(TempHandler, self).__init__(stream) - self._delete = True - - def emit(self, record): - """Log the specified logging record. - - :param logging.LogRecord record: Record to be formatted - - """ - self._delete = False - super(TempHandler, self).emit(record) - - def close(self): - """Close the handler and the temporary log file. - - The temporary log file is deleted if it wasn't used. - - """ - self.acquire() - try: - # StreamHandler.close() doesn't close the stream to allow a - # stream like stderr to be used - self.stream.close() - if self._delete: - shutil.rmtree(self._workdir) - self._delete = False - super(TempHandler, self).close() - finally: - self.release() - - -def pre_arg_parse_except_hook(memory_handler, *args, **kwargs): - """A simple wrapper around post_arg_parse_except_hook. - - The additional functionality provided by this wrapper is the memory - handler will be flushed before Certbot exits. This allows us to - write logging messages to a temporary file if we crashed before - logging was fully configured. - - Since sys.excepthook isn't called on SystemExit exceptions, the - memory handler will not be flushed in this case which prevents us - from creating temporary log files when argparse exits because a - command line argument was invalid or -h, --help, or --version was - provided on the command line. - - :param MemoryHandler memory_handler: memory handler to flush - :param tuple args: args for post_arg_parse_except_hook - :param dict kwargs: kwargs for post_arg_parse_except_hook - - """ - try: - post_arg_parse_except_hook(*args, **kwargs) - finally: - # flush() is called here so messages logged during - # post_arg_parse_except_hook are also flushed. - memory_handler.flush(force=True) - - -def post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path): - """Logs fatal exceptions and reports them to the user. - - If debug is True, the full exception and traceback is shown to the - user, otherwise, it is suppressed. sys.exit is always called with a - nonzero status. - - :param type exc_type: type of the raised exception - :param BaseException exc_value: raised exception - :param traceback trace: traceback of where the exception was raised - :param bool debug: True if the traceback should be shown to the user - :param str log_path: path to file or directory containing the log - - """ - exc_info = (exc_type, exc_value, trace) - # constants.QUIET_LOGGING_LEVEL or higher should be used to - # display message the user, otherwise, a lower level like - # logger.DEBUG should be used - if debug or not issubclass(exc_type, Exception): - assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR - logger.error('Exiting abnormally:', exc_info=exc_info) - else: - logger.debug('Exiting abnormally:', exc_info=exc_info) - if issubclass(exc_type, errors.Error): - sys.exit(exc_value) - logger.error('An unexpected error occurred:') - if messages.is_acme_error(exc_value): - # Remove the ACME error prefix from the exception - _, _, exc_str = str(exc_value).partition(':: ') - logger.error(exc_str) - else: - traceback.print_exception(exc_type, exc_value, None) - exit_with_log_path(log_path) - - -def exit_with_log_path(log_path): - """Print a message about the log location and exit. - - The message is printed to stderr and the program will exit with a - nonzero status. - - :param str log_path: path to file or directory containing the log - - """ - msg = 'Please see the ' - if os.path.isdir(log_path): - msg += 'logfiles in {0} '.format(log_path) - else: - msg += "logfile '{0}' ".format(log_path) - msg += 'for more details.' - sys.exit(msg) diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index a4812cb54..cd2cb01f1 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.log.""" +"""Tests for certbot._internal.log.""" import logging import logging.handlers import sys @@ -20,17 +20,17 @@ from certbot.tests import util as test_util class PreArgParseSetupTest(unittest.TestCase): - """Tests for certbot.log.pre_arg_parse_setup.""" + """Tests for certbot._internal.log.pre_arg_parse_setup.""" @classmethod def _call(cls, *args, **kwargs): # pylint: disable=unused-argument - from certbot.log import pre_arg_parse_setup + from certbot._internal.log import pre_arg_parse_setup return pre_arg_parse_setup() - @mock.patch('certbot.log.sys') - @mock.patch('certbot.log.pre_arg_parse_except_hook') - @mock.patch('certbot.log.logging.getLogger') - @mock.patch('certbot.log.util.atexit_register') + @mock.patch('certbot._internal.log.sys') + @mock.patch('certbot._internal.log.pre_arg_parse_except_hook') + @mock.patch('certbot._internal.log.logging.getLogger') + @mock.patch('certbot._internal.log.util.atexit_register') def test_it(self, mock_register, mock_get, mock_except_hook, mock_sys): mock_sys.argv = ['--debug'] mock_sys.version_info = sys.version_info @@ -58,11 +58,11 @@ class PreArgParseSetupTest(unittest.TestCase): class PostArgParseSetupTest(test_util.ConfigTestCase): - """Tests for certbot.log.post_arg_parse_setup.""" + """Tests for certbot._internal.log.post_arg_parse_setup.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import post_arg_parse_setup + from certbot._internal.log import post_arg_parse_setup return post_arg_parse_setup(*args, **kwargs) def setUp(self): @@ -73,9 +73,9 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): self.config.verbose_count = constants.CLI_DEFAULTS['verbose_count'] self.devnull = open(os.devnull, 'w') - from certbot.log import ColoredStreamHandler + from certbot._internal.log import ColoredStreamHandler self.stream_handler = ColoredStreamHandler(six.StringIO()) - from certbot.log import MemoryHandler, TempHandler + from certbot._internal.log import MemoryHandler, TempHandler self.temp_handler = TempHandler() self.temp_path = self.temp_handler.path self.memory_handler = MemoryHandler(self.temp_handler) @@ -90,11 +90,11 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): super(PostArgParseSetupTest, self).tearDown() def test_common(self): - with mock.patch('certbot.log.logging.getLogger') as mock_get_logger: + with mock.patch('certbot._internal.log.logging.getLogger') as mock_get_logger: mock_get_logger.return_value = self.root_logger - except_hook_path = 'certbot.log.post_arg_parse_except_hook' + except_hook_path = 'certbot._internal.log.post_arg_parse_except_hook' with mock.patch(except_hook_path) as mock_except_hook: - with mock.patch('certbot.log.sys') as mock_sys: + with mock.patch('certbot._internal.log.sys') as mock_sys: mock_sys.version_info = sys.version_info self._call(self.config) @@ -124,11 +124,11 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): class SetupLogFileHandlerTest(test_util.ConfigTestCase): - """Tests for certbot.log.setup_log_file_handler.""" + """Tests for certbot._internal.log.setup_log_file_handler.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import setup_log_file_handler + from certbot._internal.log import setup_log_file_handler return setup_log_file_handler(*args, **kwargs) def setUp(self): @@ -167,7 +167,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): backup_path = os.path.join(self.config.logs_dir, log_file + '.1') self.assertEqual(os.path.exists(backup_path), should_rollover) - @mock.patch('certbot.log.logging.handlers.RotatingFileHandler') + @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler') def test_max_log_backups_used(self, mock_handler): self._call(self.config, 'test.log', '%(message)s') backup_count = mock_handler.call_args[1]['backupCount'] @@ -175,7 +175,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): class ColoredStreamHandlerTest(unittest.TestCase): - """Tests for certbot.log.ColoredStreamHandler""" + """Tests for certbot._internal.log.ColoredStreamHandler""" def setUp(self): self.stream = six.StringIO() @@ -183,7 +183,7 @@ class ColoredStreamHandlerTest(unittest.TestCase): self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) - from certbot.log import ColoredStreamHandler + from certbot._internal.log import ColoredStreamHandler self.handler = ColoredStreamHandler(self.stream) self.logger.addHandler(self.handler) @@ -207,7 +207,7 @@ class ColoredStreamHandlerTest(unittest.TestCase): class MemoryHandlerTest(unittest.TestCase): - """Tests for certbot.log.MemoryHandler""" + """Tests for certbot._internal.log.MemoryHandler""" def setUp(self): self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) @@ -215,7 +215,7 @@ class MemoryHandlerTest(unittest.TestCase): self.stream = six.StringIO() self.stream_handler = logging.StreamHandler(self.stream) - from certbot.log import MemoryHandler + from certbot._internal.log import MemoryHandler self.handler = MemoryHandler(self.stream_handler) self.logger.addHandler(self.handler) @@ -250,10 +250,10 @@ class MemoryHandlerTest(unittest.TestCase): class TempHandlerTest(unittest.TestCase): - """Tests for certbot.log.TempHandler.""" + """Tests for certbot._internal.log.TempHandler.""" def setUp(self): self.closed = False - from certbot.log import TempHandler + from certbot._internal.log import TempHandler self.handler = TempHandler() def tearDown(self): @@ -274,13 +274,13 @@ class TempHandlerTest(unittest.TestCase): class PreArgParseExceptHookTest(unittest.TestCase): - """Tests for certbot.log.pre_arg_parse_except_hook.""" + """Tests for certbot._internal.log.pre_arg_parse_except_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import pre_arg_parse_except_hook + from certbot._internal.log import pre_arg_parse_except_hook return pre_arg_parse_except_hook(*args, **kwargs) - @mock.patch('certbot.log.post_arg_parse_except_hook') + @mock.patch('certbot._internal.log.post_arg_parse_except_hook') def test_it(self, mock_post_arg_parse_except_hook): memory_handler = mock.MagicMock() args = ('some', 'args',) @@ -294,10 +294,10 @@ class PreArgParseExceptHookTest(unittest.TestCase): class PostArgParseExceptHookTest(unittest.TestCase): - """Tests for certbot.log.post_arg_parse_except_hook.""" + """Tests for certbot._internal.log.post_arg_parse_except_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import post_arg_parse_except_hook + from certbot._internal.log import post_arg_parse_except_hook return post_arg_parse_except_hook(*args, **kwargs) def setUp(self): @@ -353,9 +353,9 @@ class PostArgParseExceptHookTest(unittest.TestCase): raise error_type(self.error_msg) except BaseException: exc_info = sys.exc_info() - with mock.patch('certbot.log.logger') as mock_logger: + with mock.patch('certbot._internal.log.logger') as mock_logger: mock_logger.error.side_effect = write_err - with mock.patch('certbot.log.sys.stderr', mock_err): + with mock.patch('certbot._internal.log.sys.stderr', mock_err): try: self._call( *exc_info, debug=debug, log_path=self.log_path) @@ -387,10 +387,10 @@ class PostArgParseExceptHookTest(unittest.TestCase): class ExitWithLogPathTest(test_util.TempDirTestCase): - """Tests for certbot.log.exit_with_log_path.""" + """Tests for certbot._internal.log.exit_with_log_path.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import exit_with_log_path + from certbot._internal.log import exit_with_log_path return exit_with_log_path(*args, **kwargs) def test_log_file(self): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index d5d96eec7..85a977b94 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -591,14 +591,14 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue(message in str(exc)) self.assertTrue(exc is not None) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_noninteractive(self, _): args = ['-n', 'certonly'] self._cli_missing_flag(args, "specify a plugin") args.extend(['--standalone', '-d', 'eg.is']) self._cli_missing_flag(args, "register before running") - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') @mock.patch('certbot._internal.main._report_new_cert') @mock.patch('certbot._internal.main.client.acme_client.Client') @mock.patch('certbot._internal.main._determine_account') @@ -653,7 +653,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') @mock.patch('certbot._internal.main._install_cert') @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @mock.patch('certbot._internal.main.plug_sel.pick_installer') @@ -704,7 +704,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue(mock_getcert.called) self.assertTrue(mock_inst.called) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') @mock.patch('certbot._internal.main._report_new_cert') @mock.patch('certbot.util.exe_exists') def test_configurator_selection(self, mock_exe_exists, _, __): @@ -744,7 +744,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._call(["auth", "--standalone"]) self.assertEqual(1, mock_certonly.call_count) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_rollback(self, _): _, _, _, client = self._call(['rollback']) self.assertEqual(1, client.rollback.call_count) @@ -768,7 +768,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._call_no_clientmock(['delete']) self.assertEqual(1, mock_cert_manager.call_count) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_plugins(self, _): flags = ['--init', '--prepare', '--authenticators', '--installers'] for args in itertools.chain( @@ -1052,7 +1052,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('fullchain.pem' in cert_msg) self.assertTrue('donate' in get_utility().add_message.call_args[0][0]) - @mock.patch('certbot.log.logging.handlers.RotatingFileHandler.doRollover') + @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler.doRollover') @mock.patch('certbot.crypto_util.notAfter') def test_certonly_renewal_triggers(self, _, __): # --dry-run should force renewal @@ -1121,7 +1121,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('No renewals were attempted.' in stdout.getvalue()) self.assertTrue('The following certs are not due for renewal yet:' in stdout.getvalue()) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_quiet_renew(self, _): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run"] @@ -1372,7 +1372,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met jose.ComparableX509(cert), mock.ANY) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_register(self, _): with mock.patch('certbot._internal.main.client') as mocked_client: acc = mock.MagicMock() diff --git a/docs/api/log.rst b/docs/api/log.rst deleted file mode 100644 index 41311de90..000000000 --- a/docs/api/log.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.log` ----------------------- - -.. automodule:: certbot.log - :members: -- cgit v1.2.3 From e38aa65cae8be7336728935083d417b0e1f581b3 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 11 Nov 2019 10:19:28 -0800 Subject: Move items in certbot/display to _internal (#7532) * Move display/completer.py to _internal/ * Move display/dummy_readline.py to _internal/ * Move display/enhancements.py to _internal/ * Create __init__.py in _internal/display --- certbot/_internal/client.py | 2 +- certbot/_internal/display/__init__.py | 1 + certbot/_internal/display/completer.py | 61 +++++++++++++++++++++++++++ certbot/_internal/display/dummy_readline.py | 21 ++++++++++ certbot/_internal/display/enhancements.py | 64 +++++++++++++++++++++++++++++ certbot/display/completer.py | 61 --------------------------- certbot/display/dummy_readline.py | 21 ---------- certbot/display/enhancements.py | 64 ----------------------------- certbot/display/util.py | 2 +- certbot/tests/display/completer_test.py | 16 ++++---- certbot/tests/display/enhancements_test.py | 12 +++--- docs/api/display.rst | 6 --- 12 files changed, 163 insertions(+), 168 deletions(-) create mode 100644 certbot/_internal/display/__init__.py create mode 100644 certbot/_internal/display/completer.py create mode 100644 certbot/_internal/display/dummy_readline.py create mode 100644 certbot/_internal/display/enhancements.py delete mode 100644 certbot/display/completer.py delete mode 100644 certbot/display/dummy_readline.py delete mode 100644 certbot/display/enhancements.py diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py index 7a0daf77b..3ecd2edf0 100644 --- a/certbot/_internal/client.py +++ b/certbot/_internal/client.py @@ -30,7 +30,7 @@ from certbot import interfaces from certbot._internal import storage from certbot import util from certbot.compat import os -from certbot.display import enhancements +from certbot._internal.display import enhancements from certbot.display import ops as display_ops from certbot.plugins import selection as plugin_selection diff --git a/certbot/_internal/display/__init__.py b/certbot/_internal/display/__init__.py new file mode 100644 index 000000000..9d39dce92 --- /dev/null +++ b/certbot/_internal/display/__init__.py @@ -0,0 +1 @@ +"""Certbot display utilities.""" diff --git a/certbot/_internal/display/completer.py b/certbot/_internal/display/completer.py new file mode 100644 index 000000000..3be06bec1 --- /dev/null +++ b/certbot/_internal/display/completer.py @@ -0,0 +1,61 @@ +"""Provides Tab completion when prompting users for a path.""" +import glob +# readline module is not available on all systems +try: + import readline +except ImportError: + import certbot._internal.display.dummy_readline as readline # type: ignore + + +class Completer(object): + """Provides Tab completion when prompting users for a path. + + This class is meant to be used with readline to provide Tab + completion for users entering paths. The complete method can be + passed to readline.set_completer directly, however, this function + works best as a context manager. For example: + + with Completer(): + raw_input() + + In this example, Tab completion will be available during the call to + raw_input above, however, readline will be restored to its previous + state when exiting the body of the with statement. + + """ + + def __init__(self): + self._iter = self._original_completer = self._original_delims = None + + def complete(self, text, state): + """Provides path completion for use with readline. + + :param str text: text to offer completions for + :param int state: which completion to return + + :returns: possible completion for text or ``None`` if all + completions have been returned + :rtype: str + + """ + if state == 0: + self._iter = glob.iglob(text + '*') + return next(self._iter, None) + + def __enter__(self): + self._original_completer = readline.get_completer() + self._original_delims = readline.get_completer_delims() + + readline.set_completer(self.complete) + readline.set_completer_delims(' \t\n;') + + # readline can be implemented using GNU readline, pyreadline or libedit + # which have different configuration syntax + if readline.__doc__ is not None and 'libedit' in readline.__doc__: + readline.parse_and_bind('bind ^I rl_complete') + else: + readline.parse_and_bind('tab: complete') + + def __exit__(self, unused_type, unused_value, unused_traceback): + readline.set_completer_delims(self._original_delims) + readline.set_completer(self._original_completer) diff --git a/certbot/_internal/display/dummy_readline.py b/certbot/_internal/display/dummy_readline.py new file mode 100644 index 000000000..fb3d807bb --- /dev/null +++ b/certbot/_internal/display/dummy_readline.py @@ -0,0 +1,21 @@ +"""A dummy module with no effect for use on systems without readline.""" + + +def get_completer(): + """An empty implementation of readline.get_completer.""" + + +def get_completer_delims(): + """An empty implementation of readline.get_completer_delims.""" + + +def parse_and_bind(unused_command): + """An empty implementation of readline.parse_and_bind.""" + + +def set_completer(unused_function=None): + """An empty implementation of readline.set_completer.""" + + +def set_completer_delims(unused_delims): + """An empty implementation of readline.set_completer_delims.""" diff --git a/certbot/_internal/display/enhancements.py b/certbot/_internal/display/enhancements.py new file mode 100644 index 000000000..0f6b6c57d --- /dev/null +++ b/certbot/_internal/display/enhancements.py @@ -0,0 +1,64 @@ +"""Certbot Enhancement Display""" +import logging + +import zope.component + +from certbot import errors +from certbot import interfaces +from certbot.display import util as display_util + + +logger = logging.getLogger(__name__) + +# Define a helper function to avoid verbose code +util = zope.component.getUtility + + +def ask(enhancement): + """Display the enhancement to the user. + + :param str enhancement: One of the + :class:`certbot.CONFIG.ENHANCEMENTS` enhancements + + :returns: True if feature is desired, False otherwise + :rtype: bool + + :raises .errors.Error: if the enhancement provided is not supported + + """ + try: + # Call the appropriate function based on the enhancement + return DISPATCH[enhancement]() + except KeyError: + logger.error("Unsupported enhancement given to ask(): %s", enhancement) + raise errors.Error("Unsupported Enhancement") + + +def redirect_by_default(): + """Determines whether the user would like to redirect to HTTPS. + + :returns: True if redirect is desired, False otherwise + :rtype: bool + + """ + choices = [ + ("No redirect", "Make no further changes to the webserver configuration."), + ("Redirect", "Make all requests redirect to secure HTTPS access. " + "Choose this for new sites, or if you're confident your site works on HTTPS. " + "You can undo this change by editing your web server's configuration."), + ] + + code, selection = util(interfaces.IDisplay).menu( + "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", + choices, default=0, + cli_flag="--redirect / --no-redirect", force_interactive=True) + + if code != display_util.OK: + return False + + return selection == 1 + + +DISPATCH = { + "redirect": redirect_by_default +} diff --git a/certbot/display/completer.py b/certbot/display/completer.py deleted file mode 100644 index 509a1051a..000000000 --- a/certbot/display/completer.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Provides Tab completion when prompting users for a path.""" -import glob -# readline module is not available on all systems -try: - import readline -except ImportError: - import certbot.display.dummy_readline as readline # type: ignore - - -class Completer(object): - """Provides Tab completion when prompting users for a path. - - This class is meant to be used with readline to provide Tab - completion for users entering paths. The complete method can be - passed to readline.set_completer directly, however, this function - works best as a context manager. For example: - - with Completer(): - raw_input() - - In this example, Tab completion will be available during the call to - raw_input above, however, readline will be restored to its previous - state when exiting the body of the with statement. - - """ - - def __init__(self): - self._iter = self._original_completer = self._original_delims = None - - def complete(self, text, state): - """Provides path completion for use with readline. - - :param str text: text to offer completions for - :param int state: which completion to return - - :returns: possible completion for text or ``None`` if all - completions have been returned - :rtype: str - - """ - if state == 0: - self._iter = glob.iglob(text + '*') - return next(self._iter, None) - - def __enter__(self): - self._original_completer = readline.get_completer() - self._original_delims = readline.get_completer_delims() - - readline.set_completer(self.complete) - readline.set_completer_delims(' \t\n;') - - # readline can be implemented using GNU readline, pyreadline or libedit - # which have different configuration syntax - if readline.__doc__ is not None and 'libedit' in readline.__doc__: - readline.parse_and_bind('bind ^I rl_complete') - else: - readline.parse_and_bind('tab: complete') - - def __exit__(self, unused_type, unused_value, unused_traceback): - readline.set_completer_delims(self._original_delims) - readline.set_completer(self._original_completer) diff --git a/certbot/display/dummy_readline.py b/certbot/display/dummy_readline.py deleted file mode 100644 index fb3d807bb..000000000 --- a/certbot/display/dummy_readline.py +++ /dev/null @@ -1,21 +0,0 @@ -"""A dummy module with no effect for use on systems without readline.""" - - -def get_completer(): - """An empty implementation of readline.get_completer.""" - - -def get_completer_delims(): - """An empty implementation of readline.get_completer_delims.""" - - -def parse_and_bind(unused_command): - """An empty implementation of readline.parse_and_bind.""" - - -def set_completer(unused_function=None): - """An empty implementation of readline.set_completer.""" - - -def set_completer_delims(unused_delims): - """An empty implementation of readline.set_completer_delims.""" diff --git a/certbot/display/enhancements.py b/certbot/display/enhancements.py deleted file mode 100644 index 0f6b6c57d..000000000 --- a/certbot/display/enhancements.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Certbot Enhancement Display""" -import logging - -import zope.component - -from certbot import errors -from certbot import interfaces -from certbot.display import util as display_util - - -logger = logging.getLogger(__name__) - -# Define a helper function to avoid verbose code -util = zope.component.getUtility - - -def ask(enhancement): - """Display the enhancement to the user. - - :param str enhancement: One of the - :class:`certbot.CONFIG.ENHANCEMENTS` enhancements - - :returns: True if feature is desired, False otherwise - :rtype: bool - - :raises .errors.Error: if the enhancement provided is not supported - - """ - try: - # Call the appropriate function based on the enhancement - return DISPATCH[enhancement]() - except KeyError: - logger.error("Unsupported enhancement given to ask(): %s", enhancement) - raise errors.Error("Unsupported Enhancement") - - -def redirect_by_default(): - """Determines whether the user would like to redirect to HTTPS. - - :returns: True if redirect is desired, False otherwise - :rtype: bool - - """ - choices = [ - ("No redirect", "Make no further changes to the webserver configuration."), - ("Redirect", "Make all requests redirect to secure HTTPS access. " - "Choose this for new sites, or if you're confident your site works on HTTPS. " - "You can undo this change by editing your web server's configuration."), - ] - - code, selection = util(interfaces.IDisplay).menu( - "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", - choices, default=0, - cli_flag="--redirect / --no-redirect", force_interactive=True) - - if code != display_util.OK: - return False - - return selection == 1 - - -DISPATCH = { - "redirect": redirect_by_default -} diff --git a/certbot/display/util.py b/certbot/display/util.py index aa9f0583b..d67e33dc8 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -10,7 +10,7 @@ from certbot import errors from certbot import interfaces from certbot.compat import misc from certbot.compat import os -from certbot.display import completer +from certbot._internal.display import completer logger = logging.getLogger(__name__) diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 73d17946e..262e0b344 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -1,8 +1,8 @@ -"""Test certbot.display.completer.""" +"""Test certbot._internal.display.completer.""" try: import readline # pylint: disable=import-error except ImportError: - import certbot.display.dummy_readline as readline # type: ignore + import certbot._internal.display.dummy_readline as readline # type: ignore import string import sys import unittest @@ -18,7 +18,7 @@ import certbot.tests.util as test_util # pylint: disable=ungrouped-imports class CompleterTest(test_util.TempDirTestCase): - """Test certbot.display.completer.Completer.""" + """Test certbot._internal.display.completer.Completer.""" def setUp(self): super(CompleterTest, self).setUp() @@ -40,7 +40,7 @@ class CompleterTest(test_util.TempDirTestCase): pass def test_complete(self): - from certbot.display import completer + from certbot._internal.display import completer my_completer = completer.Completer() num_paths = len(self.paths) @@ -64,7 +64,7 @@ class CompleterTest(test_util.TempDirTestCase): sys.modules['readline'] = original_readline def test_context_manager_with_unmocked_readline(self): - from certbot.display import completer + from certbot._internal.display import completer reload_module(completer) original_completer = readline.get_completer() @@ -76,18 +76,18 @@ class CompleterTest(test_util.TempDirTestCase): self.assertEqual(readline.get_completer(), original_completer) self.assertEqual(readline.get_completer_delims(), original_delims) - @mock.patch('certbot.display.completer.readline', autospec=True) + @mock.patch('certbot._internal.display.completer.readline', autospec=True) def test_context_manager_libedit(self, mock_readline): mock_readline.__doc__ = 'libedit' self._test_context_manager_with_mock_readline(mock_readline) - @mock.patch('certbot.display.completer.readline', autospec=True) + @mock.patch('certbot._internal.display.completer.readline', autospec=True) def test_context_manager_readline(self, mock_readline): mock_readline.__doc__ = 'GNU readline' self._test_context_manager_with_mock_readline(mock_readline) def _test_context_manager_with_mock_readline(self, mock_readline): - from certbot.display import completer + from certbot._internal.display import completer mock_readline.parse_and_bind.side_effect = enable_tab_completion diff --git a/certbot/tests/display/enhancements_test.py b/certbot/tests/display/enhancements_test.py index b8321d940..edace29b1 100644 --- a/certbot/tests/display/enhancements_test.py +++ b/certbot/tests/display/enhancements_test.py @@ -18,10 +18,10 @@ class AskTest(unittest.TestCase): @classmethod def _call(cls, enhancement): - from certbot.display.enhancements import ask + from certbot._internal.display.enhancements import ask return ask(enhancement) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_redirect(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call("redirect")) @@ -34,20 +34,20 @@ class RedirectTest(unittest.TestCase): """Test the redirect_by_default method.""" @classmethod def _call(cls): - from certbot.display.enhancements import redirect_by_default + from certbot._internal.display.enhancements import redirect_by_default return redirect_by_default() - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_secure(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call()) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 1) self.assertFalse(self._call()) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_easy(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) self.assertFalse(self._call()) diff --git a/docs/api/display.rst b/docs/api/display.rst index 1a18e6534..70038786c 100644 --- a/docs/api/display.rst +++ b/docs/api/display.rst @@ -15,9 +15,3 @@ .. automodule:: certbot.display.ops :members: - -:mod:`certbot.display.enhancements` -======================================= - -.. automodule:: certbot.display.enhancements - :members: -- cgit v1.2.3 From d290fe464ed2304293fbb39c5ae33aa1e60e462a Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 11 Nov 2019 10:20:05 -0800 Subject: Move eff.py to _internal (#7530) * Move eff.py to _internal * missed a few certbot.effs in tests * remove sublime autocompletion * fix messy scripting --- certbot/_internal/client.py | 2 +- certbot/_internal/eff.py | 98 ++++++++++++++++++++++++++++++++++++++++++++ certbot/_internal/main.py | 2 +- certbot/eff.py | 98 -------------------------------------------- certbot/tests/client_test.py | 20 ++++----- certbot/tests/eff_test.py | 22 +++++----- certbot/tests/main_test.py | 4 +- docs/api/eff.rst | 5 --- 8 files changed, 123 insertions(+), 128 deletions(-) create mode 100644 certbot/_internal/eff.py delete mode 100644 certbot/eff.py delete mode 100644 docs/api/eff.rst diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py index 3ecd2edf0..88a90aa32 100644 --- a/certbot/_internal/client.py +++ b/certbot/_internal/client.py @@ -23,7 +23,7 @@ from certbot._internal import auth_handler from certbot import cli from certbot import constants from certbot import crypto_util -from certbot import eff +from certbot._internal import eff from certbot._internal import error_handler from certbot import errors from certbot import interfaces diff --git a/certbot/_internal/eff.py b/certbot/_internal/eff.py new file mode 100644 index 000000000..433cdc8cd --- /dev/null +++ b/certbot/_internal/eff.py @@ -0,0 +1,98 @@ +"""Subscribes users to the EFF newsletter.""" +import logging + +import requests +import zope.component + +from certbot import constants +from certbot import interfaces + + +logger = logging.getLogger(__name__) + + +def handle_subscription(config): + """High level function to take care of EFF newsletter subscriptions. + + The user may be asked if they want to sign up for the newsletter if + they have not already specified. + + :param .IConfig config: Client configuration. + + """ + if config.email is None: + if config.eff_email: + _report_failure("you didn't provide an e-mail address") + return + if config.eff_email is None: + config.eff_email = _want_subscription() + if config.eff_email: + subscribe(config.email) + + +def _want_subscription(): + """Does the user want to be subscribed to the EFF newsletter? + + :returns: True if we should subscribe the user, otherwise, False + :rtype: bool + + """ + prompt = ( + 'Would you be willing to share your email address with the ' + "Electronic Frontier Foundation, a founding partner of the Let's " + 'Encrypt project and the non-profit organization that develops ' + "Certbot? We'd like to send you email about our work encrypting " + "the web, EFF news, campaigns, and ways to support digital freedom. ") + display = zope.component.getUtility(interfaces.IDisplay) + return display.yesno(prompt, default=False) + + +def subscribe(email): + """Subscribe the user to the EFF mailing list. + + :param str email: the e-mail address to subscribe + + """ + url = constants.EFF_SUBSCRIBE_URI + data = {'data_type': 'json', + 'email': email, + 'form_id': 'eff_supporters_library_subscribe_form'} + logger.debug('Sending POST request to %s:\n%s', url, data) + _check_response(requests.post(url, data=data)) + + +def _check_response(response): + """Check for errors in the server's response. + + If an error occurred, it will be reported to the user. + + :param requests.Response response: the server's response to the + subscription request + + """ + logger.debug('Received response:\n%s', response.content) + try: + response.raise_for_status() + if not response.json()['status']: + _report_failure('your e-mail address appears to be invalid') + except requests.exceptions.HTTPError: + _report_failure() + except (ValueError, KeyError): + _report_failure('there was a problem with the server response') + + +def _report_failure(reason=None): + """Notify the user of failing to sign them up for the newsletter. + + :param reason: a phrase describing what the problem was + beginning with a lowercase letter and no closing punctuation + :type reason: `str` or `None` + + """ + msg = ['We were unable to subscribe you the EFF mailing list'] + if reason is not None: + msg.append(' because ') + msg.append(reason) + msg.append('. You can try again later by visiting https://act.eff.org.') + reporter = zope.component.getUtility(interfaces.IReporter) + reporter.add_message(''.join(msg), reporter.LOW_PRIORITY) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index c98da5bf7..a86436557 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -21,7 +21,7 @@ from certbot._internal import client from certbot import configuration from certbot import constants from certbot import crypto_util -from certbot import eff +from certbot._internal import eff from certbot import errors from certbot import hooks from certbot import interfaces diff --git a/certbot/eff.py b/certbot/eff.py deleted file mode 100644 index 433cdc8cd..000000000 --- a/certbot/eff.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Subscribes users to the EFF newsletter.""" -import logging - -import requests -import zope.component - -from certbot import constants -from certbot import interfaces - - -logger = logging.getLogger(__name__) - - -def handle_subscription(config): - """High level function to take care of EFF newsletter subscriptions. - - The user may be asked if they want to sign up for the newsletter if - they have not already specified. - - :param .IConfig config: Client configuration. - - """ - if config.email is None: - if config.eff_email: - _report_failure("you didn't provide an e-mail address") - return - if config.eff_email is None: - config.eff_email = _want_subscription() - if config.eff_email: - subscribe(config.email) - - -def _want_subscription(): - """Does the user want to be subscribed to the EFF newsletter? - - :returns: True if we should subscribe the user, otherwise, False - :rtype: bool - - """ - prompt = ( - 'Would you be willing to share your email address with the ' - "Electronic Frontier Foundation, a founding partner of the Let's " - 'Encrypt project and the non-profit organization that develops ' - "Certbot? We'd like to send you email about our work encrypting " - "the web, EFF news, campaigns, and ways to support digital freedom. ") - display = zope.component.getUtility(interfaces.IDisplay) - return display.yesno(prompt, default=False) - - -def subscribe(email): - """Subscribe the user to the EFF mailing list. - - :param str email: the e-mail address to subscribe - - """ - url = constants.EFF_SUBSCRIBE_URI - data = {'data_type': 'json', - 'email': email, - 'form_id': 'eff_supporters_library_subscribe_form'} - logger.debug('Sending POST request to %s:\n%s', url, data) - _check_response(requests.post(url, data=data)) - - -def _check_response(response): - """Check for errors in the server's response. - - If an error occurred, it will be reported to the user. - - :param requests.Response response: the server's response to the - subscription request - - """ - logger.debug('Received response:\n%s', response.content) - try: - response.raise_for_status() - if not response.json()['status']: - _report_failure('your e-mail address appears to be invalid') - except requests.exceptions.HTTPError: - _report_failure() - except (ValueError, KeyError): - _report_failure('there was a problem with the server response') - - -def _report_failure(reason=None): - """Notify the user of failing to sign them up for the newsletter. - - :param reason: a phrase describing what the problem was - beginning with a lowercase letter and no closing punctuation - :type reason: `str` or `None` - - """ - msg = ['We were unable to subscribe you the EFF mailing list'] - if reason is not None: - msg.append(' because ') - msg.append(reason) - msg.append('. You can try again later by visiting https://act.eff.org.') - reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message(''.join(msg), reporter.LOW_PRIORITY) diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index b98d53008..e7cf84c3c 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -89,7 +89,7 @@ class RegisterTest(test_util.ConfigTestCase): with mock.patch("certbot._internal.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._internal.eff.handle_subscription") as mock_handle: with mock.patch("certbot._internal.account.report_new_account"): mock_client().new_account_and_tos.side_effect = errors.Error self.assertRaises(errors.Error, self._call) @@ -103,7 +103,7 @@ class RegisterTest(test_util.ConfigTestCase): with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.account.report_new_account"): - with mock.patch("certbot.eff.handle_subscription"): + with mock.patch("certbot._internal.eff.handle_subscription"): self._call() @mock.patch("certbot._internal.account.report_new_account") @@ -115,7 +115,7 @@ class RegisterTest(test_util.ConfigTestCase): mx_err = messages.Error.with_code('invalidContact', detail=msg) with mock.patch("certbot._internal.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: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self._call() self.assertEqual(mock_get_email.call_count, 1) @@ -129,7 +129,7 @@ class RegisterTest(test_util.ConfigTestCase): mx_err = messages.Error.with_code('invalidContact', detail=msg) with mock.patch("certbot._internal.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._internal.eff.handle_subscription"): mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self.assertRaises(errors.Error, self._call) @@ -139,7 +139,7 @@ class RegisterTest(test_util.ConfigTestCase): @mock.patch("certbot._internal.client.logger") def test_without_email(self, mock_logger): - with mock.patch("certbot.eff.handle_subscription") as mock_handle: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_clnt: mock_clnt().external_account_required.side_effect = self._false_mock with mock.patch("certbot._internal.account.report_new_account"): @@ -156,7 +156,7 @@ class RegisterTest(test_util.ConfigTestCase): """Tests dry-run for no staging account, expect account created with no email""" with mock.patch("certbot._internal.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._internal.eff.handle_subscription"): with mock.patch("certbot._internal.account.report_new_account"): self.config.dry_run = True self._call() @@ -171,7 +171,7 @@ class RegisterTest(test_util.ConfigTestCase): side_effect=self._new_acct_dir_mock ) mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription"): + with mock.patch("certbot._internal.eff.handle_subscription"): target = "certbot._internal.client.messages.ExternalAccountBinding.from_data" with mock.patch(target) as mock_eab_from_data: self.config.eab_kid = "test-kid" @@ -183,7 +183,7 @@ class RegisterTest(test_util.ConfigTestCase): def test_without_eab_arguments(self): with mock.patch("certbot._internal.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._internal.eff.handle_subscription"): target = "certbot._internal.client.messages.ExternalAccountBinding.from_data" with mock.patch(target) as mock_eab_from_data: self.config.eab_kid = None @@ -196,7 +196,7 @@ class RegisterTest(test_util.ConfigTestCase): with mock.patch("certbot._internal.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._internal.eff.handle_subscription"): with mock.patch("certbot._internal.client.messages.ExternalAccountBinding.from_data"): self.config.eab_kid = None self.config.eab_hmac_key = None @@ -212,7 +212,7 @@ class RegisterTest(test_util.ConfigTestCase): 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: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self.assertRaises(messages.Error, self._call) self.assertFalse(mock_handle.called) diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index cf79ffc70..5e4cadcfa 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.eff.""" +"""Tests for certbot._internal.eff.""" import unittest import mock @@ -9,7 +9,7 @@ import certbot.tests.util as test_util class HandleSubscriptionTest(test_util.ConfigTestCase): - """Tests for certbot.eff.handle_subscription.""" + """Tests for certbot._internal.eff.handle_subscription.""" def setUp(self): super(HandleSubscriptionTest, self).setUp() self.email = 'certbot@example.org' @@ -17,11 +17,11 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.config.eff_email = None def _call(self): - from certbot.eff import handle_subscription + from certbot._internal.eff import handle_subscription return handle_subscription(self.config) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_failure(self, mock_subscribe, mock_get_utility): self.config.email = None self.config.eff_email = True @@ -32,7 +32,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): expected_part = "because you didn't provide an e-mail address" self.assertTrue(expected_part in actual) - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_no_subscribe_with_no_prompt(self, mock_subscribe): self.config.eff_email = False with test_util.patch_get_utility() as mock_get_utility: @@ -41,7 +41,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self._assert_no_get_utility_calls(mock_get_utility) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility): self.config.eff_email = True self._call() @@ -53,7 +53,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.assertFalse(mock_get_utility().add_message.called) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility): mock_get_utility().yesno.return_value = True self._call() @@ -66,7 +66,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.assertEqual(mock_subscribe.call_args[0][0], self.email) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility): mock_get_utility().yesno.return_value = False self._call() @@ -84,18 +84,18 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): class SubscribeTest(unittest.TestCase): - """Tests for certbot.eff.subscribe.""" + """Tests for certbot._internal.eff.subscribe.""" def setUp(self): self.email = 'certbot@example.org' self.json = {'status': True} self.response = mock.Mock(ok=True) self.response.json.return_value = self.json - @mock.patch('certbot.eff.requests.post') + @mock.patch('certbot._internal.eff.requests.post') def _call(self, mock_post): mock_post.return_value = self.response - from certbot.eff import subscribe + from certbot._internal.eff import subscribe subscribe(self.email) self._check_post_call(mock_post) diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 85a977b94..1cad43823 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -1432,7 +1432,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met 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._internal.eff.handle_subscription') as mock_handle: with mock.patch('certbot._internal.main._determine_account') as mocked_det: with mock.patch('certbot._internal.main.account') as mocked_account: with mock.patch('certbot._internal.main.client') as mocked_client: @@ -1464,7 +1464,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met 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: + with mock.patch('certbot._internal.eff.handle_subscription') as mock_handle: with mock.patch('certbot._internal.main._determine_account') as mocked_det: with mock.patch('certbot._internal.main.account') as mocked_account: with mock.patch('certbot._internal.main.client') as mocked_client: diff --git a/docs/api/eff.rst b/docs/api/eff.rst deleted file mode 100644 index 2924b256d..000000000 --- a/docs/api/eff.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.eff` ----------------------- - -.. automodule:: certbot.eff - :members: -- cgit v1.2.3 From 641b60b8f09ec805b72cf5c23bdc73b71052f5f2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 11 Nov 2019 14:04:26 -0800 Subject: Remove TLS-SNI objects in ACME (#7535) * fixes #7214 * update changelog * remove unused import --- CHANGELOG.md | 3 + acme/acme/__init__.py | 27 -------- acme/acme/challenges.py | 152 ------------------------------------------- acme/acme/challenges_test.py | 146 ----------------------------------------- acme/acme/standalone.py | 75 --------------------- acme/acme/standalone_test.py | 135 -------------------------------------- acme/acme/test_util.py | 6 -- pytest.ini | 1 - 8 files changed, 3 insertions(+), 542 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826a59dd2..7806b2ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Certbot's `config_changes` subcommand has been removed * `certbot.plugins.common.TLSSNI01` has been removed. +* Deprecated attributes related to the TLS-SNI-01 challenge in + `acme.challenges` and `acme.standalone` + have been removed. * The functions `certbot.client.view_config_changes`, `certbot.main.config_changes`, `certbot.plugins.common.Installer.view_config_changes`, diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index 7439712b0..8a034470a 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -21,30 +21,3 @@ for mod in list(sys.modules): # preserved (acme.jose.* is josepy.*) if mod == 'josepy' or mod.startswith('josepy.'): sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod] - - -# This class takes a similar approach to the cryptography project to deprecate attributes -# in public modules. See the _ModuleWithDeprecation class here: -# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 -class _TLSSNI01DeprecationModule(object): - """ - Internal class delegating to a module, and displaying warnings when - attributes related to TLS-SNI-01 are accessed. - """ - def __init__(self, module): - self.__dict__['_module'] = module - - def __getattr__(self, attr): - if 'TLSSNI01' in attr or attr == 'BaseRequestHandlerWithLogging': - warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr), - DeprecationWarning, stacklevel=2) - return getattr(self._module, attr) - - def __setattr__(self, attr, value): # pragma: no cover - setattr(self._module, attr, value) - - def __delattr__(self, attr): # pragma: no cover - delattr(self._module, attr) - - def __dir__(self): # pragma: no cover - return ['_module'] + dir(self._module) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 78991608a..f417bc47c 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -3,19 +3,13 @@ import abc import functools import hashlib import logging -import socket -import sys from cryptography.hazmat.primitives import hashes # type: ignore import josepy as jose -import OpenSSL import requests import six -from acme import errors -from acme import crypto_util from acme import fields -from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -367,148 +361,6 @@ class HTTP01(KeyAuthorizationChallenge): return self.key_authorization(account_key) -@ChallengeResponse.register -class TLSSNI01Response(KeyAuthorizationChallengeResponse): - """ACME tls-sni-01 challenge response.""" - typ = "tls-sni-01" - - DOMAIN_SUFFIX = b".acme.invalid" - """Domain name suffix.""" - - PORT = 443 - """Verification port as defined by the protocol. - - You can override it (e.g. for testing) by passing ``port`` to - `simple_verify`. - - """ - - @property - def z(self): # pylint: disable=invalid-name - """``z`` value used for verification. - - :rtype bytes: - - """ - return hashlib.sha256( - self.key_authorization.encode("utf-8")).hexdigest().lower().encode() - - @property - def z_domain(self): - """Domain name used for verification, generated from `z`. - - :rtype bytes: - - """ - return self.z[:32] + b'.' + self.z[32:] + self.DOMAIN_SUFFIX - - def gen_cert(self, key=None, bits=2048): - """Generate tls-sni-01 certificate. - - :param OpenSSL.crypto.PKey key: Optional private key used in - certificate generation. If not provided (``None``), then - fresh key will be generated. - :param int bits: Number of bits for newly generated key. - - :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey` - - """ - if key is None: - key = OpenSSL.crypto.PKey() - key.generate_key(OpenSSL.crypto.TYPE_RSA, bits) - return crypto_util.gen_ss_cert(key, [ - # z_domain is too big to fit into CN, hence first dummy domain - 'dummy', self.z_domain.decode()], force_san=True), key - - def probe_cert(self, domain, **kwargs): - """Probe tls-sni-01 challenge certificate. - - :param unicode domain: - - """ - # TODO: domain is not necessary if host is provided - if "host" not in kwargs: - host = socket.gethostbyname(domain) - logger.debug('%s resolved to %s', domain, host) - kwargs["host"] = host - - kwargs.setdefault("port", self.PORT) - kwargs["name"] = self.z_domain - # TODO: try different methods? - return crypto_util.probe_sni(**kwargs) - - def verify_cert(self, cert): - """Verify tls-sni-01 challenge certificate. - - :param OpensSSL.crypto.X509 cert: Challenge certificate. - - :returns: Whether the certificate was successfully verified. - :rtype: bool - - """ - # pylint: disable=protected-access - sans = crypto_util._pyopenssl_cert_or_req_san(cert) - logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans) - return self.z_domain.decode() in sans - - def simple_verify(self, chall, domain, account_public_key, - cert=None, **kwargs): - """Simple verify. - - Verify ``validation`` using ``account_public_key``, optionally - probe tls-sni-01 certificate and check using `verify_cert`. - - :param .challenges.TLSSNI01 chall: Corresponding challenge. - :param str domain: Domain name being validated. - :param JWK account_public_key: - :param OpenSSL.crypto.X509 cert: Optional certificate. If not - provided (``None``) certificate will be retrieved using - `probe_cert`. - :param int port: Port used to probe the certificate. - - - :returns: ``True`` iff client's control of the domain has been - verified. - :rtype: bool - - """ - if not self.verify(chall, account_public_key): - logger.debug("Verification of key authorization in response failed") - return False - - if cert is None: - try: - cert = self.probe_cert(domain=domain, **kwargs) - except errors.Error as error: - logger.debug(str(error), exc_info=True) - return False - - return self.verify_cert(cert) - - -@Challenge.register # pylint: disable=too-many-ancestors -class TLSSNI01(KeyAuthorizationChallenge): - """ACME tls-sni-01 challenge.""" - response_cls = TLSSNI01Response - typ = response_cls.typ - - # boulder#962, ietf-wg-acme#22 - #n = jose.Field("n", encoder=int, decoder=int) - - def validation(self, account_key, **kwargs): - """Generate validation. - - :param JWK account_key: - :param OpenSSL.crypto.PKey cert_key: Optional private key used - in certificate generation. If not provided (``None``), then - fresh key will be generated. - - :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey` - - """ - return self.response(account_key).gen_cert(key=kwargs.get('cert_key')) - - @ChallengeResponse.register class TLSALPN01Response(KeyAuthorizationChallengeResponse): """ACME TLS-ALPN-01 challenge response. @@ -617,7 +469,3 @@ class DNSResponse(ChallengeResponse): """ return chall.check_validation(self.validation, account_public_key) - - -# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. -sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 9d3a92fa5..94641eaac 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -3,12 +3,10 @@ import unittest import josepy as jose import mock -import OpenSSL import requests from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import -from acme import errors from acme import test_util CERT = test_util.load_comparable_cert('cert.pem') @@ -259,150 +257,6 @@ class HTTP01Test(unittest.TestCase): self.msg.update(token=b'..').good_token) -class TLSSNI01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes - - def setUp(self): - from acme.challenges import TLSSNI01 - self.chall = TLSSNI01( - token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e')) - - self.response = self.chall.response(KEY) - self.jmsg = { - 'resource': 'challenge', - 'type': 'tls-sni-01', - 'keyAuthorization': self.response.key_authorization, - } - - # pylint: disable=invalid-name - label1 = b'dc38d9c3fa1a4fdcc3a5501f2d38583f' - label2 = b'b7793728f084394f2a1afd459556bb5c' - self.z = label1 + label2 - self.z_domain = label1 + b'.' + label2 + b'.acme.invalid' - self.domain = 'foo.com' - - def test_z_and_domain(self): - self.assertEqual(self.z, self.response.z) - self.assertEqual(self.z_domain, self.response.z_domain) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.response.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSSNI01Response - self.assertEqual(self.response, TLSSNI01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSSNI01Response - hash(TLSSNI01Response.from_json(self.jmsg)) - - @mock.patch('acme.challenges.socket.gethostbyname') - @mock.patch('acme.challenges.crypto_util.probe_sni') - def test_probe_cert(self, mock_probe_sni, mock_gethostbyname): - mock_gethostbyname.return_value = '127.0.0.1' - self.response.probe_cert('foo.com') - mock_gethostbyname.assert_called_once_with('foo.com') - mock_probe_sni.assert_called_once_with( - host='127.0.0.1', port=self.response.PORT, - name=self.z_domain) - - self.response.probe_cert('foo.com', host='8.8.8.8') - mock_probe_sni.assert_called_with( - host='8.8.8.8', port=mock.ANY, name=mock.ANY) - - self.response.probe_cert('foo.com', port=1234) - mock_probe_sni.assert_called_with( - host=mock.ANY, port=1234, name=mock.ANY) - - self.response.probe_cert('foo.com', bar='baz') - mock_probe_sni.assert_called_with( - host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz') - - self.response.probe_cert('foo.com', name=b'xxx') - mock_probe_sni.assert_called_with( - host=mock.ANY, port=mock.ANY, - name=self.z_domain) - - def test_gen_verify_cert(self): - key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem') - cert, key2 = self.response.gen_cert(key1) - self.assertEqual(key1, key2) - self.assertTrue(self.response.verify_cert(cert)) - - def test_gen_verify_cert_gen_key(self): - cert, key = self.response.gen_cert() - self.assertTrue(isinstance(key, OpenSSL.crypto.PKey)) - self.assertTrue(self.response.verify_cert(cert)) - - def test_verify_bad_cert(self): - self.assertFalse(self.response.verify_cert( - test_util.load_cert('cert.pem'))) - - def test_simple_verify_bad_key_authorization(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - self.response.simple_verify(self.chall, "local", key2.public_key()) - - @mock.patch('acme.challenges.TLSSNI01Response.verify_cert', autospec=True) - def test_simple_verify(self, mock_verify_cert): - mock_verify_cert.return_value = mock.sentinel.verification - self.assertEqual( - mock.sentinel.verification, self.response.simple_verify( - self.chall, self.domain, KEY.public_key(), - cert=mock.sentinel.cert)) - mock_verify_cert.assert_called_once_with( - self.response, mock.sentinel.cert) - - @mock.patch('acme.challenges.TLSSNI01Response.probe_cert') - def test_simple_verify_false_on_probe_error(self, mock_probe_cert): - mock_probe_cert.side_effect = errors.Error - self.assertFalse(self.response.simple_verify( - self.chall, self.domain, KEY.public_key())) - - -class TLSSNI01Test(unittest.TestCase): - - def setUp(self): - self.jmsg = { - 'type': 'tls-sni-01', - 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', - } - from acme.challenges import TLSSNI01 - self.msg = TLSSNI01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSSNI01 - self.assertEqual(self.msg, TLSSNI01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSSNI01 - hash(TLSSNI01.from_json(self.jmsg)) - - def test_from_json_invalid_token_length(self): - from acme.challenges import TLSSNI01 - self.jmsg['token'] = jose.encode_b64jose(b'abcd') - self.assertRaises( - jose.DeserializationError, TLSSNI01.from_json, self.jmsg) - - @mock.patch('acme.challenges.TLSSNI01Response.gen_cert') - def test_validation(self, mock_gen_cert): - mock_gen_cert.return_value = ('cert', 'key') - self.assertEqual(('cert', 'key'), self.msg.validation( - KEY, cert_key=mock.sentinel.cert_key)) - mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key) - - def test_deprecation_message(self): - with mock.patch('acme.warnings.warn') as mock_warn: - from acme.challenges import TLSSNI01 - assert TLSSNI01 - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - - class TLSALPN01ResponseTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 69c35fb6f..b23b9b843 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -1,24 +1,17 @@ """Support for standalone client challenge solvers. """ -import argparse import collections import functools import logging -import os import socket -import sys import threading -import warnings from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error from six.moves import http_client # pylint: disable=import-error from six.moves import socketserver # type: ignore # pylint: disable=import-error -import OpenSSL - from acme import challenges from acme import crypto_util from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -133,35 +126,6 @@ class BaseDualNetworkedServers(object): self.threads = [] -class TLSSNI01Server(TLSServer, ACMEServerMixin): - """TLSSNI01 Server.""" - - def __init__(self, server_address, certs, ipv6=False): - TLSServer.__init__( - self, server_address, BaseRequestHandlerWithLogging, certs=certs, ipv6=ipv6) - - -class TLSSNI01DualNetworkedServers(BaseDualNetworkedServers): - """TLSSNI01Server Wrapper. Tries everything for both. Failures for one don't - affect the other.""" - - def __init__(self, *args, **kwargs): - BaseDualNetworkedServers.__init__(self, TLSSNI01Server, *args, **kwargs) - - -class BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler): - """BaseRequestHandler with logging.""" - - def log_message(self, format, *args): # pylint: disable=redefined-builtin - """Log arbitrary message.""" - logger.debug("%s - - %s", self.client_address[0], format % args) - - def handle(self): - """Handle request.""" - self.log_message("Incoming request") - socketserver.BaseRequestHandler.handle(self) - - class HTTPServer(BaseHTTPServer.HTTPServer): """Generic HTTP Server.""" @@ -264,42 +228,3 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ return functools.partial( cls, simple_http_resources=simple_http_resources) - - -def simple_tls_sni_01_server(cli_args, forever=True): - """Run simple standalone TLSSNI01 server.""" - warnings.warn( - 'simple_tls_sni_01_server is deprecated and will be removed soon.', - DeprecationWarning, stacklevel=2) - logging.basicConfig(level=logging.DEBUG) - - parser = argparse.ArgumentParser() - parser.add_argument( - "-p", "--port", default=0, help="Port to serve at. By default " - "picks random free port.") - args = parser.parse_args(cli_args[1:]) - - certs = {} - - _, hosts, _ = next(os.walk('.')) # type: ignore # https://github.com/python/mypy/issues/465 - for host in hosts: - with open(os.path.join(host, "cert.pem")) as cert_file: - cert_contents = cert_file.read() - with open(os.path.join(host, "key.pem")) as key_file: - key_contents = key_file.read() - certs[host.encode()] = ( - OpenSSL.crypto.load_privatekey( - OpenSSL.crypto.FILETYPE_PEM, key_contents), - OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert_contents)) - - server = TLSSNI01Server(('', int(args.port)), certs=certs) - logger.info("Serving at https://%s:%s...", *server.socket.getsockname()[:2]) - if forever: # pragma: no cover - server.serve_forever() - else: - server.handle_request() - - -# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. -sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index c14f82d39..9f9249b07 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -1,14 +1,7 @@ """Tests for acme.standalone.""" -import multiprocessing -import os -import shutil import socket import threading -import tempfile import unittest -import warnings -import time -from contextlib import closing from six.moves import http_client # pylint: disable=import-error from six.moves import socketserver # type: ignore # pylint: disable=import-error @@ -18,8 +11,6 @@ import mock import requests from acme import challenges -from acme import crypto_util -from acme import errors from acme import test_util from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module @@ -42,44 +33,6 @@ class TLSServerTest(unittest.TestCase): server.server_close() -class TLSSNI01ServerTest(unittest.TestCase): - """Test for acme.standalone.TLSSNI01Server.""" - - - def setUp(self): - self.certs = {b'localhost': ( - test_util.load_pyopenssl_private_key('rsa2048_key.pem'), - test_util.load_cert('rsa2048_cert.pem'), - )} - from acme.standalone import TLSSNI01Server - self.server = TLSSNI01Server(('localhost', 0), certs=self.certs) - self.thread = threading.Thread(target=self.server.serve_forever) - self.thread.start() - - def tearDown(self): - self.server.shutdown() - self.thread.join() - - def test_it(self): - host, port = self.server.socket.getsockname()[:2] - cert = crypto_util.probe_sni( - b'localhost', host=host, port=port, timeout=1) - self.assertEqual(jose.ComparableX509(cert), - jose.ComparableX509(self.certs[b'localhost'][1])) - - -class BaseRequestHandlerWithLoggingTest(unittest.TestCase): - """Test for acme.standalone.BaseRequestHandlerWithLogging.""" - - def test_it(self): - with mock.patch('acme.standalone.warnings.warn') as mock_warn: - # pylint: disable=unused-variable - from acme.standalone import BaseRequestHandlerWithLogging - self.assertTrue(mock_warn.called) - msg = mock_warn.call_args[0][0] - self.assertTrue(msg.startswith('BaseRequestHandlerWithLogging')) - - class HTTP01ServerTest(unittest.TestCase): """Tests for acme.standalone.HTTP01Server.""" @@ -183,33 +136,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase): prev_port = port -class TLSSNI01DualNetworkedServersTest(unittest.TestCase): - """Test for acme.standalone.TLSSNI01DualNetworkedServers.""" - - - def setUp(self): - self.certs = {b'localhost': ( - test_util.load_pyopenssl_private_key('rsa2048_key.pem'), - test_util.load_cert('rsa2048_cert.pem'), - )} - from acme.standalone import TLSSNI01DualNetworkedServers - self.servers = TLSSNI01DualNetworkedServers(('localhost', 0), certs=self.certs) - self.servers.serve_forever() - - def tearDown(self): - self.servers.shutdown_and_server_close() - - def test_connect(self): - socknames = self.servers.getsocknames() - # connect to all addresses - for sockname in socknames: - host, port = sockname[:2] - cert = crypto_util.probe_sni( - b'localhost', host=host, port=port, timeout=1) - self.assertEqual(jose.ComparableX509(cert), - jose.ComparableX509(self.certs[b'localhost'][1])) - - class HTTP01DualNetworkedServersTest(unittest.TestCase): """Tests for acme.standalone.HTTP01DualNetworkedServers.""" @@ -260,66 +186,5 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): self.assertFalse(self._test_http01(add=False)) -class TestSimpleTLSSNI01Server(unittest.TestCase): - """Tests for acme.standalone.simple_tls_sni_01_server.""" - - - def setUp(self): - # mirror ../examples/standalone - self.test_cwd = tempfile.mkdtemp() - localhost_dir = os.path.join(self.test_cwd, 'localhost') - os.makedirs(localhost_dir) - shutil.copy(test_util.vector_path('rsa2048_cert.pem'), - os.path.join(localhost_dir, 'cert.pem')) - shutil.copy(test_util.vector_path('rsa2048_key.pem'), - os.path.join(localhost_dir, 'key.pem')) - - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: - sock.bind(('', 0)) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.port = sock.getsockname()[1] - - self.process = multiprocessing.Process(target=_simple_tls_sni_01_server_no_warnings, - args=(['path', '-p', str(self.port)],)) - self.old_cwd = os.getcwd() - os.chdir(self.test_cwd) - - def tearDown(self): - os.chdir(self.old_cwd) - if self.process.is_alive(): - self.process.terminate() - self.process.join(timeout=5) - # Check that we didn't timeout waiting for the process to - # terminate. - self.assertNotEqual(self.process.exitcode, None) - shutil.rmtree(self.test_cwd) - - @mock.patch('acme.standalone.TLSSNI01Server.handle_request') - def test_mock(self, handle): - _simple_tls_sni_01_server_no_warnings(cli_args=['path', '-p', str(self.port)], - forever=False) - self.assertEqual(handle.call_count, 1) - - def test_live(self): - self.process.start() - cert = None - for _ in range(50): - time.sleep(0.1) - try: - cert = crypto_util.probe_sni(b'localhost', b'127.0.0.1', self.port) - break - except errors.Error: # pragma: no cover - pass - self.assertEqual(jose.ComparableX509(cert), - test_util.load_comparable_cert('rsa2048_cert.pem')) - - -def _simple_tls_sni_01_server_no_warnings(*args, **kwargs): - with warnings.catch_warnings(): - warnings.filterwarnings('ignore', 'simple_tls.*') - from acme.standalone import simple_tls_sni_01_server - return simple_tls_sni_01_server(*args, **kwargs) - - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py index 6d9cbc8dc..6737bff4e 100644 --- a/acme/acme/test_util.py +++ b/acme/acme/test_util.py @@ -12,12 +12,6 @@ import josepy as jose from OpenSSL import crypto -def vector_path(*names): - """Path to a test vector.""" - return pkg_resources.resource_filename( - __name__, os.path.join('testdata', *names)) - - def load_vector(*names): """Load contents of a test vector.""" # luckily, resource_string opens file in binary mode diff --git a/pytest.ini b/pytest.ini index 54ae4e9d6..6c2404056 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,7 +13,6 @@ filterwarnings = error ignore:decodestring:DeprecationWarning - ignore:(TLSSNI01|TLS-SNI-01):DeprecationWarning ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* ignore:.*get_systemd_os_info:DeprecationWarning -- cgit v1.2.3 From 08c1de34bd4106ad610577b550856f5838fb4a5b Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 11 Nov 2019 15:14:18 -0800 Subject: Move items in certbot/plugins to _internal (#7533) * Create and initialize _internal/plugins * Move plugins/manual.py to _internal/ * Move plugins/disco.py to _internal/ * Move plugins/selection.py to _internal/ * Move plugins/webroot.py to _internal/ * Move plugins/null.py to _internal/ * Move plugins/standalone.py to _internal/ * add missed internalization * shorten line * Update outdated init comment --- certbot/_internal/client.py | 2 +- certbot/_internal/main.py | 4 +- certbot/_internal/plugins/__init__.py | 1 + certbot/_internal/plugins/disco.py | 289 +++++++++++++++++++++++++++ certbot/_internal/plugins/manual.py | 185 +++++++++++++++++ certbot/_internal/plugins/null.py | 56 ++++++ certbot/_internal/plugins/selection.py | 339 ++++++++++++++++++++++++++++++++ certbot/_internal/plugins/standalone.py | 210 ++++++++++++++++++++ certbot/_internal/plugins/webroot.py | 288 +++++++++++++++++++++++++++ certbot/_internal/renewal.py | 2 +- certbot/_internal/storage.py | 2 +- certbot/_internal/updater.py | 2 +- certbot/cli.py | 4 +- certbot/plugins/__init__.py | 2 +- certbot/plugins/disco.py | 289 --------------------------- certbot/plugins/disco_test.py | 26 +-- certbot/plugins/enhancements_test.py | 2 +- certbot/plugins/manual.py | 185 ----------------- certbot/plugins/manual_test.py | 6 +- certbot/plugins/null.py | 56 ------ certbot/plugins/null_test.py | 6 +- certbot/plugins/selection.py | 339 -------------------------------- certbot/plugins/selection_test.py | 36 ++-- certbot/plugins/standalone.py | 210 -------------------- certbot/plugins/standalone_test.py | 10 +- certbot/plugins/webroot.py | 288 --------------------------- certbot/plugins/webroot_test.py | 12 +- certbot/tests/cli_test.py | 2 +- certbot/tests/main_test.py | 10 +- certbot/tests/renewal_test.py | 3 +- certbot/tests/renewupdater_test.py | 10 +- certbot/tests/storage_test.py | 2 +- docs/api/plugins/disco.rst | 5 - docs/api/plugins/manual.rst | 5 - docs/api/plugins/selection.rst | 5 - docs/api/plugins/standalone.rst | 5 - docs/api/plugins/webroot.rst | 5 - setup.py | 8 +- 38 files changed, 1444 insertions(+), 1467 deletions(-) create mode 100644 certbot/_internal/plugins/__init__.py create mode 100644 certbot/_internal/plugins/disco.py create mode 100644 certbot/_internal/plugins/manual.py create mode 100644 certbot/_internal/plugins/null.py create mode 100644 certbot/_internal/plugins/selection.py create mode 100644 certbot/_internal/plugins/standalone.py create mode 100644 certbot/_internal/plugins/webroot.py delete mode 100644 certbot/plugins/disco.py delete mode 100644 certbot/plugins/manual.py delete mode 100644 certbot/plugins/null.py delete mode 100644 certbot/plugins/selection.py delete mode 100644 certbot/plugins/standalone.py delete mode 100644 certbot/plugins/webroot.py delete mode 100644 docs/api/plugins/disco.rst delete mode 100644 docs/api/plugins/manual.rst delete mode 100644 docs/api/plugins/selection.rst delete mode 100644 docs/api/plugins/standalone.rst delete mode 100644 docs/api/plugins/webroot.rst diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py index 88a90aa32..37d61a7a4 100644 --- a/certbot/_internal/client.py +++ b/certbot/_internal/client.py @@ -32,7 +32,7 @@ from certbot import util from certbot.compat import os from certbot._internal.display import enhancements from certbot.display import ops as display_ops -from certbot.plugins import selection as plugin_selection +from certbot._internal.plugins import selection as plugin_selection logger = logging.getLogger(__name__) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index a86436557..de68e8553 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -35,9 +35,9 @@ from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os from certbot.display import util as display_util, ops as display_ops -from certbot.plugins import disco as plugins_disco +from certbot._internal.plugins import disco as plugins_disco from certbot.plugins import enhancements -from certbot.plugins import selection as plug_sel +from certbot._internal.plugins import selection as plug_sel USER_CANCELLED = ("User chose to cancel the operation and may " "reinvoke the client.") diff --git a/certbot/_internal/plugins/__init__.py b/certbot/_internal/plugins/__init__.py new file mode 100644 index 000000000..7831eab61 --- /dev/null +++ b/certbot/_internal/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot plugins.""" diff --git a/certbot/_internal/plugins/disco.py b/certbot/_internal/plugins/disco.py new file mode 100644 index 000000000..ec2bff8b7 --- /dev/null +++ b/certbot/_internal/plugins/disco.py @@ -0,0 +1,289 @@ +"""Utilities for plugins discovery and selection.""" +import collections +import itertools +import logging + +import pkg_resources +import six + +import zope.interface +import zope.interface.verify + +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from certbot import constants +from certbot import errors +from certbot import interfaces + + +logger = logging.getLogger(__name__) + + +class PluginEntryPoint(object): + """Plugin entry point.""" + + PREFIX_FREE_DISTRIBUTIONS = [ + "certbot", + "certbot-apache", + "certbot-dns-cloudflare", + "certbot-dns-cloudxns", + "certbot-dns-digitalocean", + "certbot-dns-dnsimple", + "certbot-dns-dnsmadeeasy", + "certbot-dns-gehirn", + "certbot-dns-google", + "certbot-dns-linode", + "certbot-dns-luadns", + "certbot-dns-nsone", + "certbot-dns-ovh", + "certbot-dns-rfc2136", + "certbot-dns-route53", + "certbot-dns-sakuracloud", + "certbot-nginx", + ] + """Distributions for which prefix will be omitted.""" + + # this object is mutable, don't allow it to be hashed! + __hash__ = None # type: ignore + + def __init__(self, entry_point): + self.name = self.entry_point_to_plugin_name(entry_point) + self.plugin_cls = entry_point.load() + self.entry_point = entry_point + self._initialized = None + self._prepared = None + + @classmethod + def entry_point_to_plugin_name(cls, entry_point): + """Unique plugin name for an ``entry_point``""" + if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: + return entry_point.name + return entry_point.dist.key + ":" + entry_point.name + + @property + def description(self): + """Description of the plugin.""" + return self.plugin_cls.description + + @property + def description_with_name(self): + """Description with name. Handy for UI.""" + return "{0} ({1})".format(self.description, self.name) + + @property + def long_description(self): + """Long description of the plugin.""" + try: + return self.plugin_cls.long_description + except AttributeError: + return self.description + + @property + def hidden(self): + """Should this plugin be hidden from UI?""" + return getattr(self.plugin_cls, "hidden", False) + + def ifaces(self, *ifaces_groups): + """Does plugin implements specified interface groups?""" + return not ifaces_groups or any( + all(iface.implementedBy(self.plugin_cls) + for iface in ifaces) + for ifaces in ifaces_groups) + + @property + def initialized(self): + """Has the plugin been initialized already?""" + return self._initialized is not None + + def init(self, config=None): + """Memoized plugin initialization.""" + if not self.initialized: + self.entry_point.require() # fetch extras! + self._initialized = self.plugin_cls(config, self.name) + return self._initialized + + def verify(self, ifaces): + """Verify that the plugin conforms to the specified interfaces.""" + assert self.initialized + for iface in ifaces: # zope.interface.providedBy(plugin) + try: + zope.interface.verify.verifyObject(iface, self.init()) + except zope.interface.exceptions.BrokenImplementation as error: + if iface.implementedBy(self.plugin_cls): + logger.debug( + "%s implements %s but object does not verify: %s", + self.plugin_cls, iface.__name__, error, exc_info=True) + return False + return True + + @property + def prepared(self): + """Has the plugin been prepared already?""" + if not self.initialized: + logger.debug(".prepared called on uninitialized %r", self) + return self._prepared is not None + + def prepare(self): + """Memoized plugin preparation.""" + assert self.initialized + if self._prepared is None: + try: + self._initialized.prepare() + except errors.MisconfigurationError as error: + logger.debug("Misconfigured %r: %s", self, error, exc_info=True) + self._prepared = error + except errors.NoInstallationError as error: + logger.debug( + "No installation (%r): %s", self, error, exc_info=True) + self._prepared = error + except errors.PluginError as error: + logger.debug("Other error:(%r): %s", self, error, exc_info=True) + self._prepared = error + else: + self._prepared = True + return self._prepared + + @property + def misconfigured(self): + """Is plugin misconfigured?""" + return isinstance(self._prepared, errors.MisconfigurationError) + + @property + def problem(self): + """Return the Exception raised during plugin setup, or None if all is well""" + if isinstance(self._prepared, Exception): + return self._prepared + return None + + @property + def available(self): + """Is plugin available, i.e. prepared or misconfigured?""" + return self._prepared is True or self.misconfigured + + def __repr__(self): + return "PluginEntryPoint#{0}".format(self.name) + + def __str__(self): + lines = [ + "* {0}".format(self.name), + "Description: {0}".format(self.plugin_cls.description), + "Interfaces: {0}".format(", ".join( + iface.__name__ for iface in zope.interface.implementedBy( + self.plugin_cls))), + "Entry point: {0}".format(self.entry_point), + ] + + if self.initialized: + lines.append("Initialized: {0}".format(self.init())) + if self.prepared: + lines.append("Prep: {0}".format(self.prepare())) + + return "\n".join(lines) + + +class PluginsRegistry(collections.Mapping): + """Plugins registry.""" + + def __init__(self, plugins): + # plugins are sorted so the same order is used between runs. + # This prevents deadlock caused by plugins acquiring a lock + # and ensures at least one concurrent Certbot instance will run + # successfully. + + # Pylint checks for super init, but also claims the super + # has no __init__member + # pylint: disable=super-init-not-called + self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) + + @classmethod + def find_all(cls): + """Find plugins using setuptools entry points.""" + plugins = {} # type: Dict[str, PluginEntryPoint] + # pylint: disable=not-callable + entry_points = itertools.chain( + pkg_resources.iter_entry_points( + constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), + pkg_resources.iter_entry_points( + constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),) + for entry_point in entry_points: + plugin_ep = PluginEntryPoint(entry_point) + assert plugin_ep.name not in plugins, ( + "PREFIX_FREE_DISTRIBUTIONS messed up") + # providedBy | pylint: disable=no-member + if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): + plugins[plugin_ep.name] = plugin_ep + else: # pragma: no cover + logger.warning( + "%r does not provide IPluginFactory, skipping", plugin_ep) + return cls(plugins) + + def __getitem__(self, name): + return self._plugins[name] + + def __iter__(self): + return iter(self._plugins) + + def __len__(self): + return len(self._plugins) + + def init(self, config): + """Initialize all plugins in the registry.""" + return [plugin_ep.init(config) for plugin_ep + in six.itervalues(self._plugins)] + + def filter(self, pred): + """Filter plugins based on predicate.""" + return type(self)(dict((name, plugin_ep) for name, plugin_ep + in six.iteritems(self._plugins) if pred(plugin_ep))) + + def visible(self): + """Filter plugins based on visibility.""" + return self.filter(lambda plugin_ep: not plugin_ep.hidden) + + def ifaces(self, *ifaces_groups): + """Filter plugins based on interfaces.""" + return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) + + def verify(self, ifaces): + """Filter plugins based on verification.""" + return self.filter(lambda p_ep: p_ep.verify(ifaces)) + + def prepare(self): + """Prepare all plugins in the registry.""" + return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)] + + def available(self): + """Filter plugins based on availability.""" + return self.filter(lambda p_ep: p_ep.available) + # successfully prepared + misconfigured + + def find_init(self, plugin): + """Find an initialized plugin. + + This is particularly useful for finding a name for the plugin + (although `.IPluginFactory.__call__` takes ``name`` as one of + the arguments, ``IPlugin.name`` is not part of the interface):: + + # plugin is an instance providing IPlugin, initialized + # somewhere else in the code + plugin_registry.find_init(plugin).name + + Returns ``None`` if ``plugin`` is not found in the registry. + + """ + # use list instead of set because PluginEntryPoint is not hashable + candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins) + if plugin_ep.initialized and plugin_ep.init() is plugin] + assert len(candidates) <= 1 + if candidates: + return candidates[0] + return None + + def __repr__(self): + return "{0}({1})".format( + self.__class__.__name__, ','.join( + repr(p_ep) for p_ep in six.itervalues(self._plugins))) + + def __str__(self): + if not self._plugins: + return "No plugins" + return "\n\n".join(str(p_ep) for p_ep in six.itervalues(self._plugins)) diff --git a/certbot/_internal/plugins/manual.py b/certbot/_internal/plugins/manual.py new file mode 100644 index 000000000..4bb11de3f --- /dev/null +++ b/certbot/_internal/plugins/manual.py @@ -0,0 +1,185 @@ +"""Manual authenticator plugin""" +import zope.component +import zope.interface + +from acme import challenges +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +from certbot import achallenges # pylint: disable=unused-import +from certbot import errors +from certbot import hooks +from certbot import interfaces +from certbot import reverter +from certbot.compat import os +from certbot.plugins import common + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Manual authenticator + + This plugin allows the user to perform the domain validation + challenge(s) themselves. This either be done manually by the user or + through shell scripts provided to Certbot. + + """ + + description = 'Manual configuration or run your own shell scripts' + hidden = True + long_description = ( + 'Authenticate through manual configuration or custom shell scripts. ' + 'When using shell scripts, an authenticator script must be provided. ' + 'The environment variables available to this script depend on the ' + 'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' + 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' + 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' + 'resource requested when performing an HTTP-01 challenge. An additional ' + 'cleanup script can also be provided and can use the additional variable ' + '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') + _DNS_INSTRUCTIONS = """\ +Please deploy a DNS TXT record under the name +{domain} with the following value: + +{validation} + +Before continuing, verify the record is deployed.""" + _HTTP_INSTRUCTIONS = """\ +Create a file containing just this data: + +{validation} + +And make it available on your web server at this URL: + +{uri} +""" + _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ +(This must be set up in addition to the previous challenges; do not remove, +replace, or undo the previous challenge tasks yet.) +""" + _SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS = """ +(This must be set up in addition to the previous challenges; do not remove, +replace, or undo the previous challenge tasks yet. Note that you might be +asked to create multiple distinct TXT records with the same name. This is +permitted by DNS standards.) +""" + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.reverter = reverter.Reverter(self.config) + self.reverter.recovery_routine() + self.env = dict() \ + # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] + self.subsequent_dns_challenge = False + self.subsequent_any_challenge = False + + @classmethod + def add_parser_arguments(cls, add): + add('auth-hook', + help='Path or command to execute for the authentication script') + add('cleanup-hook', + help='Path or command to execute for the cleanup script') + add('public-ip-logging-ok', action='store_true', + help='Automatically allows public IP logging (default: Ask)') + + def prepare(self): # pylint: disable=missing-docstring + if self.config.noninteractive_mode and not self.conf('auth-hook'): + raise errors.PluginError( + 'An authentication script must be provided with --{0} when ' + 'using the manual plugin non-interactively.'.format( + self.option_name('auth-hook'))) + self._validate_hooks() + + def _validate_hooks(self): + if self.config.validate_hooks: + for name in ('auth-hook', 'cleanup-hook'): + hook = self.conf(name) + if hook is not None: + hook_prefix = self.option_name(name)[:-len('-hook')] + hooks.validate_hook(hook, hook_prefix) + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return ( + 'This plugin allows the user to customize setup for domain ' + 'validation challenges either through shell scripts provided by ' + 'the user or by performing the setup manually.') + + def get_chall_pref(self, domain): + # pylint: disable=missing-docstring,no-self-use,unused-argument + return [challenges.HTTP01, challenges.DNS01] + + def perform(self, achalls): # pylint: disable=missing-docstring + self._verify_ip_logging_ok() + if self.conf('auth-hook'): + perform_achall = self._perform_achall_with_script + else: + perform_achall = self._perform_achall_manually + + responses = [] + for achall in achalls: + perform_achall(achall) + responses.append(achall.response(achall.account_key)) + return responses + + def _verify_ip_logging_ok(self): + if not self.conf('public-ip-logging-ok'): + cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok')) + msg = ('NOTE: The IP of this machine will be publicly logged as ' + "having requested this certificate. If you're running " + 'certbot in manual mode on a machine that is not your ' + "server, please ensure you're okay with that.\n\n" + 'Are you OK with your IP being logged?') + display = zope.component.getUtility(interfaces.IDisplay) + if display.yesno(msg, cli_flag=cli_flag, force_interactive=True): + setattr(self.config, self.dest('public-ip-logging-ok'), True) + else: + raise errors.PluginError('Must agree to IP logging to proceed') + + def _perform_achall_with_script(self, achall): + env = dict(CERTBOT_DOMAIN=achall.domain, + CERTBOT_VALIDATION=achall.validation(achall.account_key)) + if isinstance(achall.chall, challenges.HTTP01): + env['CERTBOT_TOKEN'] = achall.chall.encode('token') + else: + os.environ.pop('CERTBOT_TOKEN', None) + os.environ.update(env) + _, out = self._execute_hook('auth-hook') + env['CERTBOT_AUTH_OUTPUT'] = out.strip() + self.env[achall] = env + + def _perform_achall_manually(self, achall): + validation = achall.validation(achall.account_key) + if isinstance(achall.chall, challenges.HTTP01): + msg = self._HTTP_INSTRUCTIONS.format( + achall=achall, encoded_token=achall.chall.encode('token'), + port=self.config.http01_port, + uri=achall.chall.uri(achall.domain), validation=validation) + else: + assert isinstance(achall.chall, challenges.DNS01) + msg = self._DNS_INSTRUCTIONS.format( + domain=achall.validation_domain_name(achall.domain), + validation=validation) + if isinstance(achall.chall, challenges.DNS01): + if self.subsequent_dns_challenge: + # 2nd or later dns-01 challenge + msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS + self.subsequent_dns_challenge = True + elif self.subsequent_any_challenge: + # 2nd or later challenge of another type + msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS + display = zope.component.getUtility(interfaces.IDisplay) + display.notification(msg, wrap=False, force_interactive=True) + self.subsequent_any_challenge = True + + def cleanup(self, achalls): # pylint: disable=missing-docstring + if self.conf('cleanup-hook'): + for achall in achalls: + env = self.env.pop(achall) + if 'CERTBOT_TOKEN' not in env: + os.environ.pop('CERTBOT_TOKEN', None) + os.environ.update(env) + self._execute_hook('cleanup-hook') + self.reverter.recovery_routine() + + def _execute_hook(self, hook_name): + return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) diff --git a/certbot/_internal/plugins/null.py b/certbot/_internal/plugins/null.py new file mode 100644 index 000000000..6deb358f1 --- /dev/null +++ b/certbot/_internal/plugins/null.py @@ -0,0 +1,56 @@ +"""Null plugin.""" +import logging + +import zope.component +import zope.interface + +from certbot import interfaces +from certbot.plugins import common + + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class Installer(common.Plugin): + """Null installer.""" + + description = "Null Installer" + hidden = True + + # pylint: disable=missing-docstring,no-self-use + + def prepare(self): + pass # pragma: no cover + + def more_info(self): + return "Installer that doesn't do anything (for testing)." + + def get_all_names(self): + return [] + + def deploy_cert(self, domain, cert_path, key_path, + chain_path=None, fullchain_path=None): + pass # pragma: no cover + + def enhance(self, domain, enhancement, options=None): + pass # pragma: no cover + + def supported_enhancements(self): + return [] + + def save(self, title=None, temporary=False): + pass # pragma: no cover + + def rollback_checkpoints(self, rollback=1): + pass # pragma: no cover + + def recovery_routine(self): + pass # pragma: no cover + + def config_test(self): + pass # pragma: no cover + + def restart(self): + pass # pragma: no cover diff --git a/certbot/_internal/plugins/selection.py b/certbot/_internal/plugins/selection.py new file mode 100644 index 000000000..ae3d8448b --- /dev/null +++ b/certbot/_internal/plugins/selection.py @@ -0,0 +1,339 @@ +"""Decide which plugins to use for authentication & installation""" +from __future__ import print_function + +import logging + +import six +import zope.component + +from certbot import errors +from certbot import interfaces +from certbot.compat import os +from certbot.display import util as display_util + +logger = logging.getLogger(__name__) +z_util = zope.component.getUtility + +def pick_configurator( + config, default, plugins, + question="How would you like to authenticate and install " + "certificates?"): + """Pick configurator plugin.""" + return pick_plugin( + config, default, plugins, question, + (interfaces.IAuthenticator, interfaces.IInstaller)) + + +def pick_installer(config, default, plugins, + question="How would you like to install certificates?"): + """Pick installer plugin.""" + return pick_plugin( + config, default, plugins, question, (interfaces.IInstaller,)) + + +def pick_authenticator( + config, default, plugins, question="How would you " + "like to authenticate with the ACME CA?"): + """Pick authentication plugin.""" + return pick_plugin( + config, default, plugins, question, (interfaces.IAuthenticator,)) + +def get_unprepared_installer(config, plugins): + """ + Get an unprepared interfaces.IInstaller object. + + :param certbot.interfaces.IConfig config: Configuration + :param certbot._internal.plugins.disco.PluginsRegistry plugins: + All plugins registered as entry points. + + :returns: Unprepared installer plugin or None + :rtype: IPlugin or None + """ + + _, req_inst = cli_plugin_requests(config) + if not req_inst: + return None + installers = plugins.filter(lambda p_ep: p_ep.name == req_inst) + installers.init(config) + installers = installers.verify((interfaces.IInstaller,)) + if len(installers) > 1: + raise errors.PluginSelectionError( + "Found multiple installers with the name %s, Certbot is unable to " + "determine which one to use. Skipping." % req_inst) + if installers: + inst = list(installers.values())[0] + logger.debug("Selecting plugin: %s", inst) + return inst.init(config) + else: + raise errors.PluginSelectionError( + "Could not select or initialize the requested installer %s." % req_inst) + +def pick_plugin(config, default, plugins, question, ifaces): + """Pick plugin. + + :param certbot.interfaces.IConfig: Configuration + :param str default: Plugin name supplied by user or ``None``. + :param certbot._internal.plugins.disco.PluginsRegistry plugins: + All plugins registered as entry points. + :param str question: Question to be presented to the user in case + multiple candidates are found. + :param list ifaces: Interfaces that plugins must provide. + + :returns: Initialized plugin. + :rtype: IPlugin + + """ + if default is not None: + # throw more UX-friendly error if default not in plugins + filtered = plugins.filter(lambda p_ep: p_ep.name == default) + else: + if config.noninteractive_mode: + # it's really bad to auto-select the single available plugin in + # non-interactive mode, because an update could later add a second + # available plugin + raise errors.MissingCommandlineFlag( + "Missing command line flags. For non-interactive execution, " + "you will need to specify a plugin on the command line. Run " + "with '--help plugins' to see a list of options, and see " + "https://eff.org/letsencrypt-plugins for more detail on what " + "the plugins do and how to use them.") + + filtered = plugins.visible().ifaces(ifaces) + + filtered.init(config) + verified = filtered.verify(ifaces) + verified.prepare() + prepared = verified.available() + + if len(prepared) > 1: + logger.debug("Multiple candidate plugins: %s", prepared) + plugin_ep = choose_plugin(list(six.itervalues(prepared)), question) + if plugin_ep is None: + return None + return plugin_ep.init() + elif len(prepared) == 1: + plugin_ep = list(prepared.values())[0] + logger.debug("Single candidate plugin: %s", plugin_ep) + if plugin_ep.misconfigured: + return None + return plugin_ep.init() + else: + logger.debug("No candidate plugin") + return None + + +def choose_plugin(prepared, question): + """Allow the user to choose their plugin. + + :param list prepared: List of `~.PluginEntryPoint`. + :param str question: Question to be presented to the user. + + :returns: Plugin entry point chosen by the user. + :rtype: `~.PluginEntryPoint` + + """ + opts = [plugin_ep.description_with_name + + (" [Misconfigured]" if plugin_ep.misconfigured else "") + for plugin_ep in prepared] + names = set(plugin_ep.name for plugin_ep in prepared) + + while True: + disp = z_util(interfaces.IDisplay) + if "CERTBOT_AUTO" in os.environ and names == set(("apache", "nginx")): + # The possibility of being offered exactly apache and nginx here + # is new interactivity brought by https://github.com/certbot/certbot/issues/4079, + # so set apache as a default for those kinds of non-interactive use + # (the user will get a warning to set --non-interactive or --force-interactive) + apache_idx = [n for n, p in enumerate(prepared) if p.name == "apache"][0] + code, index = disp.menu(question, opts, default=apache_idx) + else: + code, index = disp.menu(question, opts, force_interactive=True) + + if code == display_util.OK: + plugin_ep = prepared[index] + if plugin_ep.misconfigured: + z_util(interfaces.IDisplay).notification( + "The selected plugin encountered an error while parsing " + "your server configuration and cannot be used. The error " + "was:\n\n{0}".format(plugin_ep.prepare()), pause=False) + else: + return plugin_ep + else: + return None + +noninstaller_plugins = ["webroot", "manual", "standalone", "dns-cloudflare", "dns-cloudxns", + "dns-digitalocean", "dns-dnsimple", "dns-dnsmadeeasy", "dns-gehirn", + "dns-google", "dns-linode", "dns-luadns", "dns-nsone", "dns-ovh", + "dns-rfc2136", "dns-route53", "dns-sakuracloud"] + +def record_chosen_plugins(config, plugins, auth, inst): + "Update the config entries to reflect the plugins we actually selected." + config.authenticator = plugins.find_init(auth).name if auth else None + config.installer = plugins.find_init(inst).name if inst else None + logger.info("Plugins selected: Authenticator %s, Installer %s", + config.authenticator, config.installer) + + +def choose_configurator_plugins(config, plugins, verb): + # pylint: disable=too-many-branches + """ + Figure out which configurator we're going to use, modifies + config.authenticator and config.installer strings to reflect that choice if + necessary. + + :raises errors.PluginSelectionError if there was a problem + + :returns: (an `IAuthenticator` or None, an `IInstaller` or None) + :rtype: tuple + """ + + req_auth, req_inst = cli_plugin_requests(config) + installer_question = None + + if verb == "enhance": + installer_question = ("Which installer would you like to use to " + "configure the selected enhancements?") + + # Which plugins do we need? + if verb == "run": + need_inst = need_auth = True + from certbot.cli import cli_command + if req_auth in noninstaller_plugins and not req_inst: + msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' + '{1} {2} certonly --{0}{1}{1}' + '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins' + '{1} and "--help plugins" for more information.)'.format( + req_auth, os.linesep, cli_command)) + + raise errors.MissingCommandlineFlag(msg) + else: + need_inst = need_auth = False + if verb == "certonly": + need_auth = True + if verb == "install" or verb == "enhance": + need_inst = True + if config.authenticator: + logger.warning("Specifying an authenticator doesn't make sense when " + "running Certbot with verb \"%s\"", verb) + # Try to meet the user's request and/or ask them to pick plugins + authenticator = installer = None + if verb == "run" and req_auth == req_inst: + # Unless the user has explicitly asked for different auth/install, + # only consider offering a single choice + authenticator = installer = pick_configurator(config, req_inst, plugins) + else: + if need_inst or req_inst: + installer = pick_installer(config, req_inst, plugins, installer_question) + if need_auth: + authenticator = pick_authenticator(config, req_auth, plugins) + logger.debug("Selected authenticator %s and installer %s", authenticator, installer) + + # Report on any failures + if need_inst and not installer: + diagnose_configurator_problem("installer", req_inst, plugins) + if need_auth and not authenticator: + diagnose_configurator_problem("authenticator", req_auth, plugins) + + record_chosen_plugins(config, plugins, authenticator, installer) + return installer, authenticator + + +def set_configurator(previously, now): + """ + Setting configurators multiple ways is okay, as long as they all agree + :param str previously: previously identified request for the installer/authenticator + :param str requested: the request currently being processed + """ + if not now: + # we're not actually setting anything + return previously + if previously: + if previously != now: + msg = "Too many flags setting configurators/installers/authenticators {0} -> {1}" + raise errors.PluginSelectionError(msg.format(repr(previously), repr(now))) + return now + + +def cli_plugin_requests(config): # pylint: disable=too-many-branches + """ + Figure out which plugins the user requested with CLI and config options + + :returns: (requested authenticator string or None, requested installer string or None) + :rtype: tuple + """ + req_inst = req_auth = config.configurator + req_inst = set_configurator(req_inst, config.installer) + req_auth = set_configurator(req_auth, config.authenticator) + + if config.nginx: + req_inst = set_configurator(req_inst, "nginx") + req_auth = set_configurator(req_auth, "nginx") + if config.apache: + req_inst = set_configurator(req_inst, "apache") + req_auth = set_configurator(req_auth, "apache") + if config.standalone: + req_auth = set_configurator(req_auth, "standalone") + if config.webroot: + req_auth = set_configurator(req_auth, "webroot") + if config.manual: + req_auth = set_configurator(req_auth, "manual") + if config.dns_cloudflare: + req_auth = set_configurator(req_auth, "dns-cloudflare") + if config.dns_cloudxns: + req_auth = set_configurator(req_auth, "dns-cloudxns") + if config.dns_digitalocean: + req_auth = set_configurator(req_auth, "dns-digitalocean") + if config.dns_dnsimple: + req_auth = set_configurator(req_auth, "dns-dnsimple") + if config.dns_dnsmadeeasy: + req_auth = set_configurator(req_auth, "dns-dnsmadeeasy") + if config.dns_gehirn: + req_auth = set_configurator(req_auth, "dns-gehirn") + if config.dns_google: + req_auth = set_configurator(req_auth, "dns-google") + if config.dns_linode: + req_auth = set_configurator(req_auth, "dns-linode") + if config.dns_luadns: + req_auth = set_configurator(req_auth, "dns-luadns") + if config.dns_nsone: + req_auth = set_configurator(req_auth, "dns-nsone") + if config.dns_ovh: + req_auth = set_configurator(req_auth, "dns-ovh") + if config.dns_rfc2136: + req_auth = set_configurator(req_auth, "dns-rfc2136") + if config.dns_route53: + req_auth = set_configurator(req_auth, "dns-route53") + if config.dns_sakuracloud: + req_auth = set_configurator(req_auth, "dns-sakuracloud") + logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst) + return req_auth, req_inst + + +def diagnose_configurator_problem(cfg_type, requested, plugins): + """ + Raise the most helpful error message about a plugin being unavailable + + :param str cfg_type: either "installer" or "authenticator" + :param str requested: the plugin that was requested + :param .PluginsRegistry plugins: available plugins + + :raises error.PluginSelectionError: if there was a problem + """ + + if requested: + if requested not in plugins: + msg = "The requested {0} plugin does not appear to be installed".format(requested) + else: + msg = ("The {0} plugin is not working; there may be problems with " + "your existing configuration.\nThe error was: {1!r}" + .format(requested, plugins[requested].problem)) + elif cfg_type == "installer": + from certbot.cli import cli_command + msg = ('Certbot doesn\'t know how to automatically configure the web ' + 'server on this system. However, it can still get a certificate for ' + 'you. Please run "{0} certonly" to do so. You\'ll need to ' + 'manually configure your web server to use the resulting ' + 'certificate.').format(cli_command) + else: + msg = "{0} could not be determined or is not installed".format(cfg_type) + raise errors.PluginSelectionError(msg) diff --git a/certbot/_internal/plugins/standalone.py b/certbot/_internal/plugins/standalone.py new file mode 100644 index 000000000..9723116c1 --- /dev/null +++ b/certbot/_internal/plugins/standalone.py @@ -0,0 +1,210 @@ +"""Standalone Authenticator.""" +import collections +import logging +import socket +# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +from socket import errno as socket_errors # type: ignore + +import OpenSSL # pylint: disable=unused-import +import six +import zope.interface + +from acme import challenges +from acme import standalone as acme_standalone +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING + +from certbot import achallenges # pylint: disable=unused-import +from certbot import errors +from certbot import interfaces + +from certbot.plugins import common + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + ServedType = DefaultDict[ + acme_standalone.BaseDualNetworkedServers, + Set[achallenges.KeyAuthorizationAnnotatedChallenge] + ] + +class ServerManager(object): + """Standalone servers manager. + + Manager for `ACMEServer` and `ACMETLSServer` instances. + + `certs` and `http_01_resources` correspond to + `acme.crypto_util.SSLSocket.certs` and + `acme.crypto_util.SSLSocket.http_01_resources` respectively. All + created servers share the same certificates and resources, so if + you're running both TLS and non-TLS instances, HTTP01 handlers + will serve the same URLs! + + """ + def __init__(self, certs, http_01_resources): + self._instances = {} # type: Dict[int, acme_standalone.BaseDualNetworkedServers] + self.certs = certs + self.http_01_resources = http_01_resources + + def run(self, port, challenge_type, listenaddr=""): + """Run ACME server on specified ``port``. + + This method is idempotent, i.e. all calls with the same pair of + ``(port, challenge_type)`` will reuse the same server. + + :param int port: Port to run the server on. + :param challenge_type: Subclass of `acme.challenges.Challenge`, + currently only `acme.challenge.HTTP01`. + :param str listenaddr: (optional) The address to listen on. Defaults to all addrs. + + :returns: DualNetworkedServers instance. + :rtype: ACMEServerMixin + + """ + assert challenge_type == challenges.HTTP01 + if port in self._instances: + return self._instances[port] + + address = (listenaddr, port) + try: + servers = acme_standalone.HTTP01DualNetworkedServers( + address, self.http_01_resources) + except socket.error as error: + raise errors.StandaloneBindError(error, port) + + servers.serve_forever() + + # if port == 0, then random free port on OS is taken + # pylint: disable=no-member + # both servers, if they exist, have the same port + real_port = servers.getsocknames()[0][1] + self._instances[real_port] = servers + return servers + + def stop(self, port): + """Stop ACME server running on the specified ``port``. + + :param int port: + + """ + instance = self._instances[port] + for sockname in instance.getsocknames(): + logger.debug("Stopping server at %s:%d...", + *sockname[:2]) + instance.shutdown_and_server_close() + del self._instances[port] + + def running(self): + """Return all running instances. + + Once the server is stopped using `stop`, it will not be + returned. + + :returns: Mapping from ``port`` to ``servers``. + :rtype: tuple + + """ + return self._instances.copy() + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Standalone Authenticator. + + This authenticator creates its own ephemeral TCP listener on the + necessary port in order to respond to incoming http-01 + challenges from the certificate authority. Therefore, it does not + rely on any existing server program. + """ + + description = "Spin up a temporary webserver" + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + + self.served = collections.defaultdict(set) # type: ServedType + + # Stuff below is shared across threads (i.e. servers read + # values, main thread writes). Due to the nature of CPython's + # GIL, the operations are safe, c.f. + # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe + self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] + self.http_01_resources = set() \ + # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] + + self.servers = ServerManager(self.certs, self.http_01_resources) + + @classmethod + def add_parser_arguments(cls, add): + pass # No additional argument for the standalone plugin parser + + def more_info(self): # pylint: disable=missing-docstring + return("This authenticator creates its own ephemeral TCP listener " + "on the necessary port in order to respond to incoming " + "http-01 challenges from the certificate authority. Therefore, " + "it does not rely on any existing server program.") + + def prepare(self): # pylint: disable=missing-docstring + pass + + def get_chall_pref(self, domain): + # pylint: disable=unused-argument,missing-docstring + return [challenges.HTTP01] + + def perform(self, achalls): # pylint: disable=missing-docstring + return [self._try_perform_single(achall) for achall in achalls] + + def _try_perform_single(self, achall): + while True: + try: + return self._perform_single(achall) + except errors.StandaloneBindError as error: + _handle_perform_error(error) + + def _perform_single(self, achall): + servers, response = self._perform_http_01(achall) + self.served[servers].add(achall) + return response + + def _perform_http_01(self, achall): + port = self.config.http01_port + addr = self.config.http01_address + servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr) + response, validation = achall.response_and_validation() + resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource( + chall=achall.chall, response=response, validation=validation) + self.http_01_resources.add(resource) + return servers, response + + def cleanup(self, achalls): # pylint: disable=missing-docstring + # reduce self.served and close servers if no challenges are served + for unused_servers, server_achalls in self.served.items(): + for achall in achalls: + if achall in server_achalls: + server_achalls.remove(achall) + for port, servers in six.iteritems(self.servers.running()): + if not self.served[servers]: + self.servers.stop(port) + + +def _handle_perform_error(error): + if error.socket_error.errno == socket_errors.EACCES: + raise errors.PluginError( + "Could not bind TCP port {0} because you don't have " + "the appropriate permissions (for example, you " + "aren't running this program as " + "root).".format(error.port)) + elif error.socket_error.errno == socket_errors.EADDRINUSE: + display = zope.component.getUtility(interfaces.IDisplay) + msg = ( + "Could not bind TCP port {0} because it is already in " + "use by another process on this system (such as a web " + "server). Please stop the program in question and " + "then try again.".format(error.port)) + should_retry = display.yesno(msg, "Retry", + "Cancel", default=False) + if not should_retry: + raise errors.PluginError(msg) + else: + raise error diff --git a/certbot/_internal/plugins/webroot.py b/certbot/_internal/plugins/webroot.py new file mode 100644 index 000000000..33e6530a1 --- /dev/null +++ b/certbot/_internal/plugins/webroot.py @@ -0,0 +1,288 @@ +"""Webroot plugin.""" +import argparse +import collections +import errno +import json +import logging + +import six +import zope.component +import zope.interface + +from acme import challenges # pylint: disable=unused-import +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict, Set, DefaultDict, List +# pylint: enable=unused-import, no-name-in-module + +from certbot import achallenges # pylint: disable=unused-import +from certbot import cli +from certbot import errors +from certbot import interfaces +from certbot.compat import os +from certbot.compat import filesystem +from certbot.display import ops +from certbot.display import util as display_util +from certbot.plugins import common +from certbot.plugins import util +from certbot.util import safe_open + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Webroot Authenticator.""" + + description = "Place files in webroot directory" + + MORE_INFO = """\ +Authenticator plugin that performs http-01 challenge by saving +necessary validation resources to appropriate paths on the file +system. It expects that there is some other HTTP server configured +to serve all files under specified web root ({0}).""" + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return self.MORE_INFO.format(self.conf("path")) + + @classmethod + def add_parser_arguments(cls, add): + add("path", "-w", default=[], action=_WebrootPathAction, + help="public_html / webroot path. This can be specified multiple " + "times to handle different domains; each domain will have " + "the webroot path that preceded it. For instance: `-w " + "/var/www/example -d example.com -d www.example.com -w " + "/var/www/thing -d thing.net -d m.thing.net` (default: Ask)") + add("map", default={}, action=_WebrootMapAction, + help="JSON dictionary mapping domains to webroot paths; this " + "implies -d for each entry. You may need to escape this from " + "your shell. E.g.: --webroot-map " + '\'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}\' ' + "This option is merged with, but takes precedence over, -w / " + "-d entries. At present, if you put webroot-map in a config " + "file, it needs to be on a single line, like: webroot-map = " + '{"example.com":"/var/www"}.') + + def get_chall_pref(self, domain): # pragma: no cover + # pylint: disable=missing-docstring,no-self-use,unused-argument + return [challenges.HTTP01] + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.full_roots = {} # type: Dict[str, str] + self.performed = collections.defaultdict(set) \ + # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] + # stack of dirs successfully created by this authenticator + self._created_dirs = [] # type: List[str] + + def prepare(self): # pylint: disable=missing-docstring + pass + + def perform(self, achalls): # pylint: disable=missing-docstring + self._set_webroots(achalls) + + self._create_challenge_dirs() + + return [self._perform_single(achall) for achall in achalls] + + def _set_webroots(self, achalls): + if self.conf("path"): + webroot_path = self.conf("path")[-1] + logger.info("Using the webroot path %s for all unmatched domains.", + webroot_path) + for achall in achalls: + self.conf("map").setdefault(achall.domain, webroot_path) + else: + known_webroots = list(set(six.itervalues(self.conf("map")))) + for achall in achalls: + if achall.domain not in self.conf("map"): + new_webroot = self._prompt_for_webroot(achall.domain, + known_webroots) + # Put the most recently input + # webroot first for easy selection + try: + known_webroots.remove(new_webroot) + except ValueError: + pass + known_webroots.insert(0, new_webroot) + self.conf("map")[achall.domain] = new_webroot + + def _prompt_for_webroot(self, domain, known_webroots): + webroot = None + + while webroot is None: + if known_webroots: + # Only show the menu if we have options for it + webroot = self._prompt_with_webroot_list(domain, known_webroots) + if webroot is None: + webroot = self._prompt_for_new_webroot(domain) + else: + # Allow prompt to raise PluginError instead of looping forever + webroot = self._prompt_for_new_webroot(domain, True) + + return webroot + + def _prompt_with_webroot_list(self, domain, known_webroots): + display = zope.component.getUtility(interfaces.IDisplay) + path_flag = "--" + self.option_name("path") + + while True: + code, index = display.menu( + "Select the webroot for {0}:".format(domain), + ["Enter a new webroot"] + known_webroots, + cli_flag=path_flag, force_interactive=True) + if code == display_util.CANCEL: + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + else: # code == display_util.OK + return None if index == 0 else known_webroots[index - 1] + + def _prompt_for_new_webroot(self, domain, allowraise=False): + code, webroot = ops.validated_directory( + _validate_webroot, + "Input the webroot for {0}:".format(domain), + force_interactive=True) + if code == display_util.CANCEL: + if not allowraise: + return None + else: + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + else: # code == display_util.OK + return _validate_webroot(webroot) + + def _create_challenge_dirs(self): + path_map = self.conf("map") + if not path_map: + raise errors.PluginError( + "Missing parts of webroot configuration; please set either " + "--webroot-path and --domains, or --webroot-map. Run with " + " --help webroot for examples.") + for name, path in path_map.items(): + self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) + logger.debug("Creating root challenges validation dir at %s", + self.full_roots[name]) + + # Change the permissions to be writable (GH #1389) + # Umask is used instead of chmod to ensure the client can also + # run as non-root (GH #1795) + old_umask = os.umask(0o022) + try: + # We ignore the last prefix in the next iteration, + # as it does not correspond to a folder path ('/' or 'C:') + for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): + try: + # Set owner as parent directory if possible, apply mode for Linux/Windows. + # For Linux, this is coupled with the "umask" call above because + # os.mkdir's "mode" parameter may not always work: + # https://docs.python.org/3/library/os.html#os.mkdir + filesystem.mkdir(prefix, 0o755) + self._created_dirs.append(prefix) + try: + filesystem.copy_ownership_and_apply_mode( + path, prefix, 0o755, copy_user=True, copy_group=True) + except (OSError, AttributeError) as exception: + logger.info("Unable to change owner and uid of webroot directory") + logger.debug("Error was: %s", exception) + except OSError as exception: + if exception.errno not in (errno.EEXIST, errno.EISDIR): + raise errors.PluginError( + "Couldn't create root for {0} http-01 " + "challenge responses: {1}".format(name, exception)) + finally: + os.umask(old_umask) + + def _get_validation_path(self, root_path, achall): + return os.path.join(root_path, achall.chall.encode("token")) + + def _perform_single(self, achall): + response, validation = achall.response_and_validation() + + root_path = self.full_roots[achall.domain] + validation_path = self._get_validation_path(root_path, achall) + logger.debug("Attempting to save validation to %s", validation_path) + + # Change permissions to be world-readable, owner-writable (GH #1795) + old_umask = os.umask(0o022) + + try: + with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: + validation_file.write(validation.encode()) + finally: + os.umask(old_umask) + + self.performed[root_path].add(achall) + return response + + def cleanup(self, achalls): # pylint: disable=missing-docstring + for achall in achalls: + root_path = self.full_roots.get(achall.domain, None) + if root_path is not None: + validation_path = self._get_validation_path(root_path, achall) + logger.debug("Removing %s", validation_path) + os.remove(validation_path) + self.performed[root_path].remove(achall) + + not_removed = [] # type: List[str] + while self._created_dirs: + path = self._created_dirs.pop() + try: + os.rmdir(path) + except OSError as exc: + not_removed.insert(0, path) + logger.info("Challenge directory %s was not empty, didn't remove", path) + logger.debug("Error was: %s", exc) + self._created_dirs = not_removed + logger.debug("All challenges cleaned up") + + +class _WebrootMapAction(argparse.Action): + """Action class for parsing webroot_map.""" + + def __call__(self, parser, namespace, webroot_map, option_string=None): + for domains, webroot_path in six.iteritems(json.loads(webroot_map)): + webroot_path = _validate_webroot(webroot_path) + namespace.webroot_map.update( + (d, webroot_path) for d in cli.add_domains(namespace, domains)) + + +class _WebrootPathAction(argparse.Action): + """Action class for parsing webroot_path.""" + + def __init__(self, *args, **kwargs): + super(_WebrootPathAction, self).__init__(*args, **kwargs) + self._domain_before_webroot = False + + def __call__(self, parser, namespace, webroot_path, option_string=None): + if self._domain_before_webroot: + raise errors.PluginError( + "If you specify multiple webroot paths, " + "one of them must precede all domain flags") + + if namespace.webroot_path: + # Apply previous webroot to all matched + # domains before setting the new webroot path + prev_webroot = namespace.webroot_path[-1] + for domain in namespace.domains: + namespace.webroot_map.setdefault(domain, prev_webroot) + elif namespace.domains: + self._domain_before_webroot = True + + namespace.webroot_path.append(_validate_webroot(webroot_path)) + + +def _validate_webroot(webroot_path): + """Validates and returns the absolute path of webroot_path. + + :param str webroot_path: path to the webroot directory + + :returns: absolute path of webroot_path + :rtype: str + + """ + if not os.path.isdir(webroot_path): + raise errors.PluginError(webroot_path + " does not exist or is not a directory") + + return os.path.abspath(webroot_path) diff --git a/certbot/_internal/renewal.py b/certbot/_internal/renewal.py index c11e675c1..31833c806 100644 --- a/certbot/_internal/renewal.py +++ b/certbot/_internal/renewal.py @@ -24,7 +24,7 @@ from certbot._internal import storage from certbot._internal import updater from certbot import util from certbot.compat import os -from certbot.plugins import disco as plugins_disco +from certbot._internal.plugins import disco as plugins_disco logger = logging.getLogger(__name__) diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py index f19efe728..9684cc195 100644 --- a/certbot/_internal/storage.py +++ b/certbot/_internal/storage.py @@ -21,7 +21,7 @@ from certbot import util from certbot.compat import os from certbot.compat import filesystem from certbot.plugins import common as plugins_common -from certbot.plugins import disco as plugins_disco +from certbot._internal.plugins import disco as plugins_disco logger = logging.getLogger(__name__) diff --git a/certbot/_internal/updater.py b/certbot/_internal/updater.py index 58df6fcb4..50db0e21c 100644 --- a/certbot/_internal/updater.py +++ b/certbot/_internal/updater.py @@ -4,7 +4,7 @@ import logging from certbot import errors from certbot import interfaces -from certbot.plugins import selection as plug_sel +from certbot._internal.plugins import selection as plug_sel import certbot.plugins.enhancements as enhancements logger = logging.getLogger(__name__) diff --git a/certbot/cli.py b/certbot/cli.py index 7f09f4638..103347735 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -21,7 +21,7 @@ from acme.magic_typing import Any, Dict, Optional import certbot import certbot.plugins.enhancements as enhancements -import certbot.plugins.selection as plugin_selection +import certbot._internal.plugins.selection as plugin_selection from certbot import constants from certbot import crypto_util from certbot import errors @@ -30,7 +30,7 @@ from certbot import interfaces from certbot import util from certbot.compat import os from certbot.display import util as display_util -from certbot.plugins import disco as plugins_disco +from certbot._internal.plugins import disco as plugins_disco logger = logging.getLogger(__name__) diff --git a/certbot/plugins/__init__.py b/certbot/plugins/__init__.py index 7b1aca2b4..7831eab61 100644 --- a/certbot/plugins/__init__.py +++ b/certbot/plugins/__init__.py @@ -1 +1 @@ -"""Certbot client.plugins.""" +"""Certbot plugins.""" diff --git a/certbot/plugins/disco.py b/certbot/plugins/disco.py deleted file mode 100644 index ec2bff8b7..000000000 --- a/certbot/plugins/disco.py +++ /dev/null @@ -1,289 +0,0 @@ -"""Utilities for plugins discovery and selection.""" -import collections -import itertools -import logging - -import pkg_resources -import six - -import zope.interface -import zope.interface.verify - -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from certbot import constants -from certbot import errors -from certbot import interfaces - - -logger = logging.getLogger(__name__) - - -class PluginEntryPoint(object): - """Plugin entry point.""" - - PREFIX_FREE_DISTRIBUTIONS = [ - "certbot", - "certbot-apache", - "certbot-dns-cloudflare", - "certbot-dns-cloudxns", - "certbot-dns-digitalocean", - "certbot-dns-dnsimple", - "certbot-dns-dnsmadeeasy", - "certbot-dns-gehirn", - "certbot-dns-google", - "certbot-dns-linode", - "certbot-dns-luadns", - "certbot-dns-nsone", - "certbot-dns-ovh", - "certbot-dns-rfc2136", - "certbot-dns-route53", - "certbot-dns-sakuracloud", - "certbot-nginx", - ] - """Distributions for which prefix will be omitted.""" - - # this object is mutable, don't allow it to be hashed! - __hash__ = None # type: ignore - - def __init__(self, entry_point): - self.name = self.entry_point_to_plugin_name(entry_point) - self.plugin_cls = entry_point.load() - self.entry_point = entry_point - self._initialized = None - self._prepared = None - - @classmethod - def entry_point_to_plugin_name(cls, entry_point): - """Unique plugin name for an ``entry_point``""" - if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: - return entry_point.name - return entry_point.dist.key + ":" + entry_point.name - - @property - def description(self): - """Description of the plugin.""" - return self.plugin_cls.description - - @property - def description_with_name(self): - """Description with name. Handy for UI.""" - return "{0} ({1})".format(self.description, self.name) - - @property - def long_description(self): - """Long description of the plugin.""" - try: - return self.plugin_cls.long_description - except AttributeError: - return self.description - - @property - def hidden(self): - """Should this plugin be hidden from UI?""" - return getattr(self.plugin_cls, "hidden", False) - - def ifaces(self, *ifaces_groups): - """Does plugin implements specified interface groups?""" - return not ifaces_groups or any( - all(iface.implementedBy(self.plugin_cls) - for iface in ifaces) - for ifaces in ifaces_groups) - - @property - def initialized(self): - """Has the plugin been initialized already?""" - return self._initialized is not None - - def init(self, config=None): - """Memoized plugin initialization.""" - if not self.initialized: - self.entry_point.require() # fetch extras! - self._initialized = self.plugin_cls(config, self.name) - return self._initialized - - def verify(self, ifaces): - """Verify that the plugin conforms to the specified interfaces.""" - assert self.initialized - for iface in ifaces: # zope.interface.providedBy(plugin) - try: - zope.interface.verify.verifyObject(iface, self.init()) - except zope.interface.exceptions.BrokenImplementation as error: - if iface.implementedBy(self.plugin_cls): - logger.debug( - "%s implements %s but object does not verify: %s", - self.plugin_cls, iface.__name__, error, exc_info=True) - return False - return True - - @property - def prepared(self): - """Has the plugin been prepared already?""" - if not self.initialized: - logger.debug(".prepared called on uninitialized %r", self) - return self._prepared is not None - - def prepare(self): - """Memoized plugin preparation.""" - assert self.initialized - if self._prepared is None: - try: - self._initialized.prepare() - except errors.MisconfigurationError as error: - logger.debug("Misconfigured %r: %s", self, error, exc_info=True) - self._prepared = error - except errors.NoInstallationError as error: - logger.debug( - "No installation (%r): %s", self, error, exc_info=True) - self._prepared = error - except errors.PluginError as error: - logger.debug("Other error:(%r): %s", self, error, exc_info=True) - self._prepared = error - else: - self._prepared = True - return self._prepared - - @property - def misconfigured(self): - """Is plugin misconfigured?""" - return isinstance(self._prepared, errors.MisconfigurationError) - - @property - def problem(self): - """Return the Exception raised during plugin setup, or None if all is well""" - if isinstance(self._prepared, Exception): - return self._prepared - return None - - @property - def available(self): - """Is plugin available, i.e. prepared or misconfigured?""" - return self._prepared is True or self.misconfigured - - def __repr__(self): - return "PluginEntryPoint#{0}".format(self.name) - - def __str__(self): - lines = [ - "* {0}".format(self.name), - "Description: {0}".format(self.plugin_cls.description), - "Interfaces: {0}".format(", ".join( - iface.__name__ for iface in zope.interface.implementedBy( - self.plugin_cls))), - "Entry point: {0}".format(self.entry_point), - ] - - if self.initialized: - lines.append("Initialized: {0}".format(self.init())) - if self.prepared: - lines.append("Prep: {0}".format(self.prepare())) - - return "\n".join(lines) - - -class PluginsRegistry(collections.Mapping): - """Plugins registry.""" - - def __init__(self, plugins): - # plugins are sorted so the same order is used between runs. - # This prevents deadlock caused by plugins acquiring a lock - # and ensures at least one concurrent Certbot instance will run - # successfully. - - # Pylint checks for super init, but also claims the super - # has no __init__member - # pylint: disable=super-init-not-called - self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) - - @classmethod - def find_all(cls): - """Find plugins using setuptools entry points.""" - plugins = {} # type: Dict[str, PluginEntryPoint] - # pylint: disable=not-callable - entry_points = itertools.chain( - pkg_resources.iter_entry_points( - constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), - pkg_resources.iter_entry_points( - constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),) - for entry_point in entry_points: - plugin_ep = PluginEntryPoint(entry_point) - assert plugin_ep.name not in plugins, ( - "PREFIX_FREE_DISTRIBUTIONS messed up") - # providedBy | pylint: disable=no-member - if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): - plugins[plugin_ep.name] = plugin_ep - else: # pragma: no cover - logger.warning( - "%r does not provide IPluginFactory, skipping", plugin_ep) - return cls(plugins) - - def __getitem__(self, name): - return self._plugins[name] - - def __iter__(self): - return iter(self._plugins) - - def __len__(self): - return len(self._plugins) - - def init(self, config): - """Initialize all plugins in the registry.""" - return [plugin_ep.init(config) for plugin_ep - in six.itervalues(self._plugins)] - - def filter(self, pred): - """Filter plugins based on predicate.""" - return type(self)(dict((name, plugin_ep) for name, plugin_ep - in six.iteritems(self._plugins) if pred(plugin_ep))) - - def visible(self): - """Filter plugins based on visibility.""" - return self.filter(lambda plugin_ep: not plugin_ep.hidden) - - def ifaces(self, *ifaces_groups): - """Filter plugins based on interfaces.""" - return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) - - def verify(self, ifaces): - """Filter plugins based on verification.""" - return self.filter(lambda p_ep: p_ep.verify(ifaces)) - - def prepare(self): - """Prepare all plugins in the registry.""" - return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)] - - def available(self): - """Filter plugins based on availability.""" - return self.filter(lambda p_ep: p_ep.available) - # successfully prepared + misconfigured - - def find_init(self, plugin): - """Find an initialized plugin. - - This is particularly useful for finding a name for the plugin - (although `.IPluginFactory.__call__` takes ``name`` as one of - the arguments, ``IPlugin.name`` is not part of the interface):: - - # plugin is an instance providing IPlugin, initialized - # somewhere else in the code - plugin_registry.find_init(plugin).name - - Returns ``None`` if ``plugin`` is not found in the registry. - - """ - # use list instead of set because PluginEntryPoint is not hashable - candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins) - if plugin_ep.initialized and plugin_ep.init() is plugin] - assert len(candidates) <= 1 - if candidates: - return candidates[0] - return None - - def __repr__(self): - return "{0}({1})".format( - self.__class__.__name__, ','.join( - repr(p_ep) for p_ep in six.itervalues(self._plugins))) - - def __str__(self): - if not self._plugins: - return "No plugins" - return "\n\n".join(str(p_ep) for p_ep in six.itervalues(self._plugins)) diff --git a/certbot/plugins/disco_test.py b/certbot/plugins/disco_test.py index 720b90b16..f739512f0 100644 --- a/certbot/plugins/disco_test.py +++ b/certbot/plugins/disco_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.disco.""" +"""Tests for certbot._internal.plugins.disco.""" import functools import string import unittest @@ -12,21 +12,21 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import errors from certbot import interfaces -from certbot.plugins import standalone -from certbot.plugins import webroot +from certbot._internal.plugins import standalone +from certbot._internal.plugins import webroot EP_SA = pkg_resources.EntryPoint( - "sa", "certbot.plugins.standalone", + "sa", "certbot._internal.plugins.standalone", attrs=("Authenticator",), dist=mock.MagicMock(key="certbot")) EP_WR = pkg_resources.EntryPoint( - "wr", "certbot.plugins.webroot", + "wr", "certbot._internal.plugins.webroot", attrs=("Authenticator",), dist=mock.MagicMock(key="certbot")) class PluginEntryPointTest(unittest.TestCase): - """Tests for certbot.plugins.disco.PluginEntryPoint.""" + """Tests for certbot._internal.plugins.disco.PluginEntryPoint.""" def setUp(self): self.ep1 = pkg_resources.EntryPoint( @@ -40,11 +40,11 @@ class PluginEntryPointTest(unittest.TestCase): self.ep3 = pkg_resources.EntryPoint( "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - from certbot.plugins.disco import PluginEntryPoint + from certbot._internal.plugins.disco import PluginEntryPoint self.plugin_ep = PluginEntryPoint(EP_SA) def test_entry_point_to_plugin_name(self): - from certbot.plugins.disco import PluginEntryPoint + from certbot._internal.plugins.disco import PluginEntryPoint names = { self.ep1: "p1:ep1", @@ -119,7 +119,7 @@ class PluginEntryPointTest(unittest.TestCase): self.plugin_ep._initialized = plugin = mock.MagicMock() exceptions = zope.interface.exceptions - with mock.patch("certbot.plugins." + with mock.patch("certbot._internal.plugins." "disco.zope.interface") as mock_zope: mock_zope.exceptions = exceptions @@ -183,11 +183,11 @@ class PluginEntryPointTest(unittest.TestCase): class PluginsRegistryTest(unittest.TestCase): - """Tests for certbot.plugins.disco.PluginsRegistry.""" + """Tests for certbot._internal.plugins.disco.PluginsRegistry.""" @classmethod def _create_new_registry(cls, plugins): - from certbot.plugins.disco import PluginsRegistry + from certbot._internal.plugins.disco import PluginsRegistry return PluginsRegistry(plugins) def setUp(self): @@ -198,8 +198,8 @@ class PluginsRegistryTest(unittest.TestCase): self.reg = self._create_new_registry(self.plugins) def test_find_all(self): - from certbot.plugins.disco import PluginsRegistry - with mock.patch("certbot.plugins.disco.pkg_resources") as mock_pkg: + from certbot._internal.plugins.disco import PluginsRegistry + with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]), iter([EP_WR])] plugins = PluginsRegistry.find_all() diff --git a/certbot/plugins/enhancements_test.py b/certbot/plugins/enhancements_test.py index 22f6f54e9..86482184e 100644 --- a/certbot/plugins/enhancements_test.py +++ b/certbot/plugins/enhancements_test.py @@ -3,7 +3,7 @@ import unittest import mock from certbot.plugins import enhancements -from certbot.plugins import null +from certbot._internal.plugins import null import certbot.tests.util as test_util diff --git a/certbot/plugins/manual.py b/certbot/plugins/manual.py deleted file mode 100644 index 4bb11de3f..000000000 --- a/certbot/plugins/manual.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Manual authenticator plugin""" -import zope.component -import zope.interface - -from acme import challenges -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - -from certbot import achallenges # pylint: disable=unused-import -from certbot import errors -from certbot import hooks -from certbot import interfaces -from certbot import reverter -from certbot.compat import os -from certbot.plugins import common - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Manual authenticator - - This plugin allows the user to perform the domain validation - challenge(s) themselves. This either be done manually by the user or - through shell scripts provided to Certbot. - - """ - - description = 'Manual configuration or run your own shell scripts' - hidden = True - long_description = ( - 'Authenticate through manual configuration or custom shell scripts. ' - 'When using shell scripts, an authenticator script must be provided. ' - 'The environment variables available to this script depend on the ' - 'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' - 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' - 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' - 'resource requested when performing an HTTP-01 challenge. An additional ' - 'cleanup script can also be provided and can use the additional variable ' - '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') - _DNS_INSTRUCTIONS = """\ -Please deploy a DNS TXT record under the name -{domain} with the following value: - -{validation} - -Before continuing, verify the record is deployed.""" - _HTTP_INSTRUCTIONS = """\ -Create a file containing just this data: - -{validation} - -And make it available on your web server at this URL: - -{uri} -""" - _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ -(This must be set up in addition to the previous challenges; do not remove, -replace, or undo the previous challenge tasks yet.) -""" - _SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS = """ -(This must be set up in addition to the previous challenges; do not remove, -replace, or undo the previous challenge tasks yet. Note that you might be -asked to create multiple distinct TXT records with the same name. This is -permitted by DNS standards.) -""" - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.reverter = reverter.Reverter(self.config) - self.reverter.recovery_routine() - self.env = dict() \ - # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] - self.subsequent_dns_challenge = False - self.subsequent_any_challenge = False - - @classmethod - def add_parser_arguments(cls, add): - add('auth-hook', - help='Path or command to execute for the authentication script') - add('cleanup-hook', - help='Path or command to execute for the cleanup script') - add('public-ip-logging-ok', action='store_true', - help='Automatically allows public IP logging (default: Ask)') - - def prepare(self): # pylint: disable=missing-docstring - if self.config.noninteractive_mode and not self.conf('auth-hook'): - raise errors.PluginError( - 'An authentication script must be provided with --{0} when ' - 'using the manual plugin non-interactively.'.format( - self.option_name('auth-hook'))) - self._validate_hooks() - - def _validate_hooks(self): - if self.config.validate_hooks: - for name in ('auth-hook', 'cleanup-hook'): - hook = self.conf(name) - if hook is not None: - hook_prefix = self.option_name(name)[:-len('-hook')] - hooks.validate_hook(hook, hook_prefix) - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return ( - 'This plugin allows the user to customize setup for domain ' - 'validation challenges either through shell scripts provided by ' - 'the user or by performing the setup manually.') - - def get_chall_pref(self, domain): - # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.HTTP01, challenges.DNS01] - - def perform(self, achalls): # pylint: disable=missing-docstring - self._verify_ip_logging_ok() - if self.conf('auth-hook'): - perform_achall = self._perform_achall_with_script - else: - perform_achall = self._perform_achall_manually - - responses = [] - for achall in achalls: - perform_achall(achall) - responses.append(achall.response(achall.account_key)) - return responses - - def _verify_ip_logging_ok(self): - if not self.conf('public-ip-logging-ok'): - cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok')) - msg = ('NOTE: The IP of this machine will be publicly logged as ' - "having requested this certificate. If you're running " - 'certbot in manual mode on a machine that is not your ' - "server, please ensure you're okay with that.\n\n" - 'Are you OK with your IP being logged?') - display = zope.component.getUtility(interfaces.IDisplay) - if display.yesno(msg, cli_flag=cli_flag, force_interactive=True): - setattr(self.config, self.dest('public-ip-logging-ok'), True) - else: - raise errors.PluginError('Must agree to IP logging to proceed') - - def _perform_achall_with_script(self, achall): - env = dict(CERTBOT_DOMAIN=achall.domain, - CERTBOT_VALIDATION=achall.validation(achall.account_key)) - if isinstance(achall.chall, challenges.HTTP01): - env['CERTBOT_TOKEN'] = achall.chall.encode('token') - else: - os.environ.pop('CERTBOT_TOKEN', None) - os.environ.update(env) - _, out = self._execute_hook('auth-hook') - env['CERTBOT_AUTH_OUTPUT'] = out.strip() - self.env[achall] = env - - def _perform_achall_manually(self, achall): - validation = achall.validation(achall.account_key) - if isinstance(achall.chall, challenges.HTTP01): - msg = self._HTTP_INSTRUCTIONS.format( - achall=achall, encoded_token=achall.chall.encode('token'), - port=self.config.http01_port, - uri=achall.chall.uri(achall.domain), validation=validation) - else: - assert isinstance(achall.chall, challenges.DNS01) - msg = self._DNS_INSTRUCTIONS.format( - domain=achall.validation_domain_name(achall.domain), - validation=validation) - if isinstance(achall.chall, challenges.DNS01): - if self.subsequent_dns_challenge: - # 2nd or later dns-01 challenge - msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS - self.subsequent_dns_challenge = True - elif self.subsequent_any_challenge: - # 2nd or later challenge of another type - msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS - display = zope.component.getUtility(interfaces.IDisplay) - display.notification(msg, wrap=False, force_interactive=True) - self.subsequent_any_challenge = True - - def cleanup(self, achalls): # pylint: disable=missing-docstring - if self.conf('cleanup-hook'): - for achall in achalls: - env = self.env.pop(achall) - if 'CERTBOT_TOKEN' not in env: - os.environ.pop('CERTBOT_TOKEN', None) - os.environ.update(env) - self._execute_hook('cleanup-hook') - self.reverter.recovery_routine() - - def _execute_hook(self, hook_name): - return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py index 16d240316..8796c30f1 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/plugins/manual_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.manual""" +"""Tests for certbot._internal.plugins.manual""" import unittest import sys @@ -15,7 +15,7 @@ from certbot.tests import util as test_util class AuthenticatorTest(test_util.TempDirTestCase): - """Tests for certbot.plugins.manual.Authenticator.""" + """Tests for certbot._internal.plugins.manual.Authenticator.""" def setUp(self): super(AuthenticatorTest, self).setUp() @@ -39,7 +39,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.tempdir, "temp_checkpoint_dir"), in_progress_dir=os.path.join(self.tempdir, "in_progess")) - from certbot.plugins.manual import Authenticator + from certbot._internal.plugins.manual import Authenticator self.auth = Authenticator(self.config, name='manual') def test_prepare_no_hook_noninteractive(self): diff --git a/certbot/plugins/null.py b/certbot/plugins/null.py deleted file mode 100644 index 6deb358f1..000000000 --- a/certbot/plugins/null.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Null plugin.""" -import logging - -import zope.component -import zope.interface - -from certbot import interfaces -from certbot.plugins import common - - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class Installer(common.Plugin): - """Null installer.""" - - description = "Null Installer" - hidden = True - - # pylint: disable=missing-docstring,no-self-use - - def prepare(self): - pass # pragma: no cover - - def more_info(self): - return "Installer that doesn't do anything (for testing)." - - def get_all_names(self): - return [] - - def deploy_cert(self, domain, cert_path, key_path, - chain_path=None, fullchain_path=None): - pass # pragma: no cover - - def enhance(self, domain, enhancement, options=None): - pass # pragma: no cover - - def supported_enhancements(self): - return [] - - def save(self, title=None, temporary=False): - pass # pragma: no cover - - def rollback_checkpoints(self, rollback=1): - pass # pragma: no cover - - def recovery_routine(self): - pass # pragma: no cover - - def config_test(self): - pass # pragma: no cover - - def restart(self): - pass # pragma: no cover diff --git a/certbot/plugins/null_test.py b/certbot/plugins/null_test.py index d5de33fb3..41cd45a93 100644 --- a/certbot/plugins/null_test.py +++ b/certbot/plugins/null_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.null.""" +"""Tests for certbot._internal.plugins.null.""" import unittest import six @@ -6,10 +6,10 @@ import mock class InstallerTest(unittest.TestCase): - """Tests for certbot.plugins.null.Installer.""" + """Tests for certbot._internal.plugins.null.Installer.""" def setUp(self): - from certbot.plugins.null import Installer + from certbot._internal.plugins.null import Installer self.installer = Installer(config=mock.MagicMock(), name="null") def test_it(self): diff --git a/certbot/plugins/selection.py b/certbot/plugins/selection.py deleted file mode 100644 index 39a6b01fc..000000000 --- a/certbot/plugins/selection.py +++ /dev/null @@ -1,339 +0,0 @@ -"""Decide which plugins to use for authentication & installation""" -from __future__ import print_function - -import logging - -import six -import zope.component - -from certbot import errors -from certbot import interfaces -from certbot.compat import os -from certbot.display import util as display_util - -logger = logging.getLogger(__name__) -z_util = zope.component.getUtility - -def pick_configurator( - config, default, plugins, - question="How would you like to authenticate and install " - "certificates?"): - """Pick configurator plugin.""" - return pick_plugin( - config, default, plugins, question, - (interfaces.IAuthenticator, interfaces.IInstaller)) - - -def pick_installer(config, default, plugins, - question="How would you like to install certificates?"): - """Pick installer plugin.""" - return pick_plugin( - config, default, plugins, question, (interfaces.IInstaller,)) - - -def pick_authenticator( - config, default, plugins, question="How would you " - "like to authenticate with the ACME CA?"): - """Pick authentication plugin.""" - return pick_plugin( - config, default, plugins, question, (interfaces.IAuthenticator,)) - -def get_unprepared_installer(config, plugins): - """ - Get an unprepared interfaces.IInstaller object. - - :param certbot.interfaces.IConfig config: Configuration - :param certbot.plugins.disco.PluginsRegistry plugins: - All plugins registered as entry points. - - :returns: Unprepared installer plugin or None - :rtype: IPlugin or None - """ - - _, req_inst = cli_plugin_requests(config) - if not req_inst: - return None - installers = plugins.filter(lambda p_ep: p_ep.name == req_inst) - installers.init(config) - installers = installers.verify((interfaces.IInstaller,)) - if len(installers) > 1: - raise errors.PluginSelectionError( - "Found multiple installers with the name %s, Certbot is unable to " - "determine which one to use. Skipping." % req_inst) - if installers: - inst = list(installers.values())[0] - logger.debug("Selecting plugin: %s", inst) - return inst.init(config) - else: - raise errors.PluginSelectionError( - "Could not select or initialize the requested installer %s." % req_inst) - -def pick_plugin(config, default, plugins, question, ifaces): - """Pick plugin. - - :param certbot.interfaces.IConfig: Configuration - :param str default: Plugin name supplied by user or ``None``. - :param certbot.plugins.disco.PluginsRegistry plugins: - All plugins registered as entry points. - :param str question: Question to be presented to the user in case - multiple candidates are found. - :param list ifaces: Interfaces that plugins must provide. - - :returns: Initialized plugin. - :rtype: IPlugin - - """ - if default is not None: - # throw more UX-friendly error if default not in plugins - filtered = plugins.filter(lambda p_ep: p_ep.name == default) - else: - if config.noninteractive_mode: - # it's really bad to auto-select the single available plugin in - # non-interactive mode, because an update could later add a second - # available plugin - raise errors.MissingCommandlineFlag( - "Missing command line flags. For non-interactive execution, " - "you will need to specify a plugin on the command line. Run " - "with '--help plugins' to see a list of options, and see " - "https://eff.org/letsencrypt-plugins for more detail on what " - "the plugins do and how to use them.") - - filtered = plugins.visible().ifaces(ifaces) - - filtered.init(config) - verified = filtered.verify(ifaces) - verified.prepare() - prepared = verified.available() - - if len(prepared) > 1: - logger.debug("Multiple candidate plugins: %s", prepared) - plugin_ep = choose_plugin(list(six.itervalues(prepared)), question) - if plugin_ep is None: - return None - return plugin_ep.init() - elif len(prepared) == 1: - plugin_ep = list(prepared.values())[0] - logger.debug("Single candidate plugin: %s", plugin_ep) - if plugin_ep.misconfigured: - return None - return plugin_ep.init() - else: - logger.debug("No candidate plugin") - return None - - -def choose_plugin(prepared, question): - """Allow the user to choose their plugin. - - :param list prepared: List of `~.PluginEntryPoint`. - :param str question: Question to be presented to the user. - - :returns: Plugin entry point chosen by the user. - :rtype: `~.PluginEntryPoint` - - """ - opts = [plugin_ep.description_with_name + - (" [Misconfigured]" if plugin_ep.misconfigured else "") - for plugin_ep in prepared] - names = set(plugin_ep.name for plugin_ep in prepared) - - while True: - disp = z_util(interfaces.IDisplay) - if "CERTBOT_AUTO" in os.environ and names == set(("apache", "nginx")): - # The possibility of being offered exactly apache and nginx here - # is new interactivity brought by https://github.com/certbot/certbot/issues/4079, - # so set apache as a default for those kinds of non-interactive use - # (the user will get a warning to set --non-interactive or --force-interactive) - apache_idx = [n for n, p in enumerate(prepared) if p.name == "apache"][0] - code, index = disp.menu(question, opts, default=apache_idx) - else: - code, index = disp.menu(question, opts, force_interactive=True) - - if code == display_util.OK: - plugin_ep = prepared[index] - if plugin_ep.misconfigured: - z_util(interfaces.IDisplay).notification( - "The selected plugin encountered an error while parsing " - "your server configuration and cannot be used. The error " - "was:\n\n{0}".format(plugin_ep.prepare()), pause=False) - else: - return plugin_ep - else: - return None - -noninstaller_plugins = ["webroot", "manual", "standalone", "dns-cloudflare", "dns-cloudxns", - "dns-digitalocean", "dns-dnsimple", "dns-dnsmadeeasy", "dns-gehirn", - "dns-google", "dns-linode", "dns-luadns", "dns-nsone", "dns-ovh", - "dns-rfc2136", "dns-route53", "dns-sakuracloud"] - -def record_chosen_plugins(config, plugins, auth, inst): - "Update the config entries to reflect the plugins we actually selected." - config.authenticator = plugins.find_init(auth).name if auth else None - config.installer = plugins.find_init(inst).name if inst else None - logger.info("Plugins selected: Authenticator %s, Installer %s", - config.authenticator, config.installer) - - -def choose_configurator_plugins(config, plugins, verb): - # pylint: disable=too-many-branches - """ - Figure out which configurator we're going to use, modifies - config.authenticator and config.installer strings to reflect that choice if - necessary. - - :raises errors.PluginSelectionError if there was a problem - - :returns: (an `IAuthenticator` or None, an `IInstaller` or None) - :rtype: tuple - """ - - req_auth, req_inst = cli_plugin_requests(config) - installer_question = None - - if verb == "enhance": - installer_question = ("Which installer would you like to use to " - "configure the selected enhancements?") - - # Which plugins do we need? - if verb == "run": - need_inst = need_auth = True - from certbot.cli import cli_command - if req_auth in noninstaller_plugins and not req_inst: - msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' - '{1} {2} certonly --{0}{1}{1}' - '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins' - '{1} and "--help plugins" for more information.)'.format( - req_auth, os.linesep, cli_command)) - - raise errors.MissingCommandlineFlag(msg) - else: - need_inst = need_auth = False - if verb == "certonly": - need_auth = True - if verb == "install" or verb == "enhance": - need_inst = True - if config.authenticator: - logger.warning("Specifying an authenticator doesn't make sense when " - "running Certbot with verb \"%s\"", verb) - # Try to meet the user's request and/or ask them to pick plugins - authenticator = installer = None - if verb == "run" and req_auth == req_inst: - # Unless the user has explicitly asked for different auth/install, - # only consider offering a single choice - authenticator = installer = pick_configurator(config, req_inst, plugins) - else: - if need_inst or req_inst: - installer = pick_installer(config, req_inst, plugins, installer_question) - if need_auth: - authenticator = pick_authenticator(config, req_auth, plugins) - logger.debug("Selected authenticator %s and installer %s", authenticator, installer) - - # Report on any failures - if need_inst and not installer: - diagnose_configurator_problem("installer", req_inst, plugins) - if need_auth and not authenticator: - diagnose_configurator_problem("authenticator", req_auth, plugins) - - record_chosen_plugins(config, plugins, authenticator, installer) - return installer, authenticator - - -def set_configurator(previously, now): - """ - Setting configurators multiple ways is okay, as long as they all agree - :param str previously: previously identified request for the installer/authenticator - :param str requested: the request currently being processed - """ - if not now: - # we're not actually setting anything - return previously - if previously: - if previously != now: - msg = "Too many flags setting configurators/installers/authenticators {0} -> {1}" - raise errors.PluginSelectionError(msg.format(repr(previously), repr(now))) - return now - - -def cli_plugin_requests(config): # pylint: disable=too-many-branches - """ - Figure out which plugins the user requested with CLI and config options - - :returns: (requested authenticator string or None, requested installer string or None) - :rtype: tuple - """ - req_inst = req_auth = config.configurator - req_inst = set_configurator(req_inst, config.installer) - req_auth = set_configurator(req_auth, config.authenticator) - - if config.nginx: - req_inst = set_configurator(req_inst, "nginx") - req_auth = set_configurator(req_auth, "nginx") - if config.apache: - req_inst = set_configurator(req_inst, "apache") - req_auth = set_configurator(req_auth, "apache") - if config.standalone: - req_auth = set_configurator(req_auth, "standalone") - if config.webroot: - req_auth = set_configurator(req_auth, "webroot") - if config.manual: - req_auth = set_configurator(req_auth, "manual") - if config.dns_cloudflare: - req_auth = set_configurator(req_auth, "dns-cloudflare") - if config.dns_cloudxns: - req_auth = set_configurator(req_auth, "dns-cloudxns") - if config.dns_digitalocean: - req_auth = set_configurator(req_auth, "dns-digitalocean") - if config.dns_dnsimple: - req_auth = set_configurator(req_auth, "dns-dnsimple") - if config.dns_dnsmadeeasy: - req_auth = set_configurator(req_auth, "dns-dnsmadeeasy") - if config.dns_gehirn: - req_auth = set_configurator(req_auth, "dns-gehirn") - if config.dns_google: - req_auth = set_configurator(req_auth, "dns-google") - if config.dns_linode: - req_auth = set_configurator(req_auth, "dns-linode") - if config.dns_luadns: - req_auth = set_configurator(req_auth, "dns-luadns") - if config.dns_nsone: - req_auth = set_configurator(req_auth, "dns-nsone") - if config.dns_ovh: - req_auth = set_configurator(req_auth, "dns-ovh") - if config.dns_rfc2136: - req_auth = set_configurator(req_auth, "dns-rfc2136") - if config.dns_route53: - req_auth = set_configurator(req_auth, "dns-route53") - if config.dns_sakuracloud: - req_auth = set_configurator(req_auth, "dns-sakuracloud") - logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst) - return req_auth, req_inst - - -def diagnose_configurator_problem(cfg_type, requested, plugins): - """ - Raise the most helpful error message about a plugin being unavailable - - :param str cfg_type: either "installer" or "authenticator" - :param str requested: the plugin that was requested - :param .PluginsRegistry plugins: available plugins - - :raises error.PluginSelectionError: if there was a problem - """ - - if requested: - if requested not in plugins: - msg = "The requested {0} plugin does not appear to be installed".format(requested) - else: - msg = ("The {0} plugin is not working; there may be problems with " - "your existing configuration.\nThe error was: {1!r}" - .format(requested, plugins[requested].problem)) - elif cfg_type == "installer": - from certbot.cli import cli_command - msg = ('Certbot doesn\'t know how to automatically configure the web ' - 'server on this system. However, it can still get a certificate for ' - 'you. Please run "{0} certonly" to do so. You\'ll need to ' - 'manually configure your web server to use the resulting ' - 'certificate.').format(cli_command) - else: - msg = "{0} could not be determined or is not installed".format(cfg_type) - raise errors.PluginSelectionError(msg) diff --git a/certbot/plugins/selection_test.py b/certbot/plugins/selection_test.py index a2a171c1d..9de7f7941 100644 --- a/certbot/plugins/selection_test.py +++ b/certbot/plugins/selection_test.py @@ -11,40 +11,40 @@ from certbot import errors from certbot import interfaces from certbot.compat import os from certbot.display import util as display_util -from certbot.plugins.disco import PluginsRegistry +from certbot._internal.plugins.disco import PluginsRegistry from certbot.tests import util as test_util class ConveniencePickPluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.pick_*.""" + """Tests for certbot._internal.plugins.selection.pick_*.""" def _test(self, fun, ifaces): config = mock.Mock() default = mock.Mock() plugins = mock.Mock() - with mock.patch("certbot.plugins.selection.pick_plugin") as mock_p: + with mock.patch("certbot._internal.plugins.selection.pick_plugin") as mock_p: mock_p.return_value = "foo" self.assertEqual("foo", fun(config, default, plugins, "Question?")) mock_p.assert_called_once_with( config, default, plugins, "Question?", ifaces) def test_authenticator(self): - from certbot.plugins.selection import pick_authenticator + from certbot._internal.plugins.selection import pick_authenticator self._test(pick_authenticator, (interfaces.IAuthenticator,)) def test_installer(self): - from certbot.plugins.selection import pick_installer + from certbot._internal.plugins.selection import pick_installer self._test(pick_installer, (interfaces.IInstaller,)) def test_configurator(self): - from certbot.plugins.selection import pick_configurator + from certbot._internal.plugins.selection import pick_configurator self._test(pick_configurator, (interfaces.IAuthenticator, interfaces.IInstaller)) class PickPluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.pick_plugin.""" + """Tests for certbot._internal.plugins.selection.pick_plugin.""" def setUp(self): self.config = mock.Mock(noninteractive_mode=False) @@ -54,7 +54,7 @@ class PickPluginTest(unittest.TestCase): self.ifaces = [] # type: List[interfaces.IPlugin] def _call(self): - from certbot.plugins.selection import pick_plugin + from certbot._internal.plugins.selection import pick_plugin return pick_plugin(self.config, self.default, self.reg, self.question, self.ifaces) @@ -95,7 +95,7 @@ class PickPluginTest(unittest.TestCase): "bar": plugin_ep, "baz": plugin_ep, } - with mock.patch("certbot.plugins.selection.choose_plugin") as mock_choose: + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = plugin_ep self.assertEqual("foo", self._call()) mock_choose.assert_called_once_with( @@ -107,13 +107,13 @@ class PickPluginTest(unittest.TestCase): "baz": None, } - with mock.patch("certbot.plugins.selection.choose_plugin") as mock_choose: + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = None self.assertTrue(self._call() is None) class ChoosePluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.choose_plugin.""" + """Tests for certbot._internal.plugins.selection.choose_plugin.""" def setUp(self): zope.component.provideUtility(display_util.FileDisplay(sys.stdout, @@ -130,17 +130,17 @@ class ChoosePluginTest(unittest.TestCase): ] def _call(self): - from certbot.plugins.selection import choose_plugin + from certbot._internal.plugins.selection import choose_plugin return choose_plugin(self.plugins, "Question?") - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_selection(self, mock_util): mock_util().menu.side_effect = [(display_util.OK, 0), (display_util.OK, 1)] self.assertEqual(self.mock_stand, self._call()) self.assertEqual(mock_util().notification.call_count, 1) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_more_info(self, mock_util): mock_util().menu.side_effect = [ (display_util.OK, 1), @@ -148,12 +148,12 @@ class ChoosePluginTest(unittest.TestCase): self.assertEqual(self.mock_stand, self._call()) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_no_choice(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 0) self.assertTrue(self._call() is None) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_new_interaction_avoidance(self, mock_util): mock_nginx = mock.Mock( description_with_name="n", misconfigured=False) @@ -174,7 +174,7 @@ class ChoosePluginTest(unittest.TestCase): self.assertTrue("default" in mock_util().menu.call_args[1]) class GetUnpreparedInstallerTest(test_util.ConfigTestCase): - """Tests for certbot.plugins.selection.get_unprepared_installer.""" + """Tests for certbot._internal.plugins.selection.get_unprepared_installer.""" def setUp(self): super(GetUnpreparedInstallerTest, self).setUp() @@ -192,7 +192,7 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase): }) def _call(self): - from certbot.plugins.selection import get_unprepared_installer + from certbot._internal.plugins.selection import get_unprepared_installer return get_unprepared_installer(self.config, self.plugins) def test_no_installer_defined(self): diff --git a/certbot/plugins/standalone.py b/certbot/plugins/standalone.py deleted file mode 100644 index 9723116c1..000000000 --- a/certbot/plugins/standalone.py +++ /dev/null @@ -1,210 +0,0 @@ -"""Standalone Authenticator.""" -import collections -import logging -import socket -# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi -from socket import errno as socket_errors # type: ignore - -import OpenSSL # pylint: disable=unused-import -import six -import zope.interface - -from acme import challenges -from acme import standalone as acme_standalone -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING - -from certbot import achallenges # pylint: disable=unused-import -from certbot import errors -from certbot import interfaces - -from certbot.plugins import common - -logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - ServedType = DefaultDict[ - acme_standalone.BaseDualNetworkedServers, - Set[achallenges.KeyAuthorizationAnnotatedChallenge] - ] - -class ServerManager(object): - """Standalone servers manager. - - Manager for `ACMEServer` and `ACMETLSServer` instances. - - `certs` and `http_01_resources` correspond to - `acme.crypto_util.SSLSocket.certs` and - `acme.crypto_util.SSLSocket.http_01_resources` respectively. All - created servers share the same certificates and resources, so if - you're running both TLS and non-TLS instances, HTTP01 handlers - will serve the same URLs! - - """ - def __init__(self, certs, http_01_resources): - self._instances = {} # type: Dict[int, acme_standalone.BaseDualNetworkedServers] - self.certs = certs - self.http_01_resources = http_01_resources - - def run(self, port, challenge_type, listenaddr=""): - """Run ACME server on specified ``port``. - - This method is idempotent, i.e. all calls with the same pair of - ``(port, challenge_type)`` will reuse the same server. - - :param int port: Port to run the server on. - :param challenge_type: Subclass of `acme.challenges.Challenge`, - currently only `acme.challenge.HTTP01`. - :param str listenaddr: (optional) The address to listen on. Defaults to all addrs. - - :returns: DualNetworkedServers instance. - :rtype: ACMEServerMixin - - """ - assert challenge_type == challenges.HTTP01 - if port in self._instances: - return self._instances[port] - - address = (listenaddr, port) - try: - servers = acme_standalone.HTTP01DualNetworkedServers( - address, self.http_01_resources) - except socket.error as error: - raise errors.StandaloneBindError(error, port) - - servers.serve_forever() - - # if port == 0, then random free port on OS is taken - # pylint: disable=no-member - # both servers, if they exist, have the same port - real_port = servers.getsocknames()[0][1] - self._instances[real_port] = servers - return servers - - def stop(self, port): - """Stop ACME server running on the specified ``port``. - - :param int port: - - """ - instance = self._instances[port] - for sockname in instance.getsocknames(): - logger.debug("Stopping server at %s:%d...", - *sockname[:2]) - instance.shutdown_and_server_close() - del self._instances[port] - - def running(self): - """Return all running instances. - - Once the server is stopped using `stop`, it will not be - returned. - - :returns: Mapping from ``port`` to ``servers``. - :rtype: tuple - - """ - return self._instances.copy() - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Standalone Authenticator. - - This authenticator creates its own ephemeral TCP listener on the - necessary port in order to respond to incoming http-01 - challenges from the certificate authority. Therefore, it does not - rely on any existing server program. - """ - - description = "Spin up a temporary webserver" - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - - self.served = collections.defaultdict(set) # type: ServedType - - # Stuff below is shared across threads (i.e. servers read - # values, main thread writes). Due to the nature of CPython's - # GIL, the operations are safe, c.f. - # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe - self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] - self.http_01_resources = set() \ - # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] - - self.servers = ServerManager(self.certs, self.http_01_resources) - - @classmethod - def add_parser_arguments(cls, add): - pass # No additional argument for the standalone plugin parser - - def more_info(self): # pylint: disable=missing-docstring - return("This authenticator creates its own ephemeral TCP listener " - "on the necessary port in order to respond to incoming " - "http-01 challenges from the certificate authority. Therefore, " - "it does not rely on any existing server program.") - - def prepare(self): # pylint: disable=missing-docstring - pass - - def get_chall_pref(self, domain): - # pylint: disable=unused-argument,missing-docstring - return [challenges.HTTP01] - - def perform(self, achalls): # pylint: disable=missing-docstring - return [self._try_perform_single(achall) for achall in achalls] - - def _try_perform_single(self, achall): - while True: - try: - return self._perform_single(achall) - except errors.StandaloneBindError as error: - _handle_perform_error(error) - - def _perform_single(self, achall): - servers, response = self._perform_http_01(achall) - self.served[servers].add(achall) - return response - - def _perform_http_01(self, achall): - port = self.config.http01_port - addr = self.config.http01_address - servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr) - response, validation = achall.response_and_validation() - resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource( - chall=achall.chall, response=response, validation=validation) - self.http_01_resources.add(resource) - return servers, response - - def cleanup(self, achalls): # pylint: disable=missing-docstring - # reduce self.served and close servers if no challenges are served - for unused_servers, server_achalls in self.served.items(): - for achall in achalls: - if achall in server_achalls: - server_achalls.remove(achall) - for port, servers in six.iteritems(self.servers.running()): - if not self.served[servers]: - self.servers.stop(port) - - -def _handle_perform_error(error): - if error.socket_error.errno == socket_errors.EACCES: - raise errors.PluginError( - "Could not bind TCP port {0} because you don't have " - "the appropriate permissions (for example, you " - "aren't running this program as " - "root).".format(error.port)) - elif error.socket_error.errno == socket_errors.EADDRINUSE: - display = zope.component.getUtility(interfaces.IDisplay) - msg = ( - "Could not bind TCP port {0} because it is already in " - "use by another process on this system (such as a web " - "server). Please stop the program in question and " - "then try again.".format(error.port)) - should_retry = display.yesno(msg, "Retry", - "Cancel", default=False) - if not should_retry: - raise errors.PluginError(msg) - else: - raise error diff --git a/certbot/plugins/standalone_test.py b/certbot/plugins/standalone_test.py index e3e0c1f10..c9dabb8b4 100644 --- a/certbot/plugins/standalone_test.py +++ b/certbot/plugins/standalone_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.standalone.""" +"""Tests for certbot._internal.plugins.standalone.""" import socket # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi from socket import errno as socket_errors # type: ignore @@ -22,10 +22,10 @@ from certbot.tests import util as test_util class ServerManagerTest(unittest.TestCase): - """Tests for certbot.plugins.standalone.ServerManager.""" + """Tests for certbot._internal.plugins.standalone.ServerManager.""" def setUp(self): - from certbot.plugins.standalone import ServerManager + from certbot._internal.plugins.standalone import ServerManager self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] self.http_01_resources = {} \ # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] @@ -82,10 +82,10 @@ def get_open_port(): class AuthenticatorTest(unittest.TestCase): - """Tests for certbot.plugins.standalone.Authenticator.""" + """Tests for certbot._internal.plugins.standalone.Authenticator.""" def setUp(self): - from certbot.plugins.standalone import Authenticator + from certbot._internal.plugins.standalone import Authenticator self.config = mock.MagicMock(http01_port=get_open_port()) self.auth = Authenticator(self.config, name="standalone") diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py deleted file mode 100644 index 33e6530a1..000000000 --- a/certbot/plugins/webroot.py +++ /dev/null @@ -1,288 +0,0 @@ -"""Webroot plugin.""" -import argparse -import collections -import errno -import json -import logging - -import six -import zope.component -import zope.interface - -from acme import challenges # pylint: disable=unused-import -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Set, DefaultDict, List -# pylint: enable=unused-import, no-name-in-module - -from certbot import achallenges # pylint: disable=unused-import -from certbot import cli -from certbot import errors -from certbot import interfaces -from certbot.compat import os -from certbot.compat import filesystem -from certbot.display import ops -from certbot.display import util as display_util -from certbot.plugins import common -from certbot.plugins import util -from certbot.util import safe_open - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Webroot Authenticator.""" - - description = "Place files in webroot directory" - - MORE_INFO = """\ -Authenticator plugin that performs http-01 challenge by saving -necessary validation resources to appropriate paths on the file -system. It expects that there is some other HTTP server configured -to serve all files under specified web root ({0}).""" - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return self.MORE_INFO.format(self.conf("path")) - - @classmethod - def add_parser_arguments(cls, add): - add("path", "-w", default=[], action=_WebrootPathAction, - help="public_html / webroot path. This can be specified multiple " - "times to handle different domains; each domain will have " - "the webroot path that preceded it. For instance: `-w " - "/var/www/example -d example.com -d www.example.com -w " - "/var/www/thing -d thing.net -d m.thing.net` (default: Ask)") - add("map", default={}, action=_WebrootMapAction, - help="JSON dictionary mapping domains to webroot paths; this " - "implies -d for each entry. You may need to escape this from " - "your shell. E.g.: --webroot-map " - '\'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}\' ' - "This option is merged with, but takes precedence over, -w / " - "-d entries. At present, if you put webroot-map in a config " - "file, it needs to be on a single line, like: webroot-map = " - '{"example.com":"/var/www"}.') - - def get_chall_pref(self, domain): # pragma: no cover - # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.HTTP01] - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.full_roots = {} # type: Dict[str, str] - self.performed = collections.defaultdict(set) \ - # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] - # stack of dirs successfully created by this authenticator - self._created_dirs = [] # type: List[str] - - def prepare(self): # pylint: disable=missing-docstring - pass - - def perform(self, achalls): # pylint: disable=missing-docstring - self._set_webroots(achalls) - - self._create_challenge_dirs() - - return [self._perform_single(achall) for achall in achalls] - - def _set_webroots(self, achalls): - if self.conf("path"): - webroot_path = self.conf("path")[-1] - logger.info("Using the webroot path %s for all unmatched domains.", - webroot_path) - for achall in achalls: - self.conf("map").setdefault(achall.domain, webroot_path) - else: - known_webroots = list(set(six.itervalues(self.conf("map")))) - for achall in achalls: - if achall.domain not in self.conf("map"): - new_webroot = self._prompt_for_webroot(achall.domain, - known_webroots) - # Put the most recently input - # webroot first for easy selection - try: - known_webroots.remove(new_webroot) - except ValueError: - pass - known_webroots.insert(0, new_webroot) - self.conf("map")[achall.domain] = new_webroot - - def _prompt_for_webroot(self, domain, known_webroots): - webroot = None - - while webroot is None: - if known_webroots: - # Only show the menu if we have options for it - webroot = self._prompt_with_webroot_list(domain, known_webroots) - if webroot is None: - webroot = self._prompt_for_new_webroot(domain) - else: - # Allow prompt to raise PluginError instead of looping forever - webroot = self._prompt_for_new_webroot(domain, True) - - return webroot - - def _prompt_with_webroot_list(self, domain, known_webroots): - display = zope.component.getUtility(interfaces.IDisplay) - path_flag = "--" + self.option_name("path") - - while True: - code, index = display.menu( - "Select the webroot for {0}:".format(domain), - ["Enter a new webroot"] + known_webroots, - cli_flag=path_flag, force_interactive=True) - if code == display_util.CANCEL: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return None if index == 0 else known_webroots[index - 1] - - def _prompt_for_new_webroot(self, domain, allowraise=False): - code, webroot = ops.validated_directory( - _validate_webroot, - "Input the webroot for {0}:".format(domain), - force_interactive=True) - if code == display_util.CANCEL: - if not allowraise: - return None - else: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return _validate_webroot(webroot) - - def _create_challenge_dirs(self): - path_map = self.conf("map") - if not path_map: - raise errors.PluginError( - "Missing parts of webroot configuration; please set either " - "--webroot-path and --domains, or --webroot-map. Run with " - " --help webroot for examples.") - for name, path in path_map.items(): - self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) - logger.debug("Creating root challenges validation dir at %s", - self.full_roots[name]) - - # Change the permissions to be writable (GH #1389) - # Umask is used instead of chmod to ensure the client can also - # run as non-root (GH #1795) - old_umask = os.umask(0o022) - try: - # We ignore the last prefix in the next iteration, - # as it does not correspond to a folder path ('/' or 'C:') - for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): - try: - # Set owner as parent directory if possible, apply mode for Linux/Windows. - # For Linux, this is coupled with the "umask" call above because - # os.mkdir's "mode" parameter may not always work: - # https://docs.python.org/3/library/os.html#os.mkdir - filesystem.mkdir(prefix, 0o755) - self._created_dirs.append(prefix) - try: - filesystem.copy_ownership_and_apply_mode( - path, prefix, 0o755, copy_user=True, copy_group=True) - except (OSError, AttributeError) as exception: - logger.info("Unable to change owner and uid of webroot directory") - logger.debug("Error was: %s", exception) - except OSError as exception: - if exception.errno not in (errno.EEXIST, errno.EISDIR): - raise errors.PluginError( - "Couldn't create root for {0} http-01 " - "challenge responses: {1}".format(name, exception)) - finally: - os.umask(old_umask) - - def _get_validation_path(self, root_path, achall): - return os.path.join(root_path, achall.chall.encode("token")) - - def _perform_single(self, achall): - response, validation = achall.response_and_validation() - - root_path = self.full_roots[achall.domain] - validation_path = self._get_validation_path(root_path, achall) - logger.debug("Attempting to save validation to %s", validation_path) - - # Change permissions to be world-readable, owner-writable (GH #1795) - old_umask = os.umask(0o022) - - try: - with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: - validation_file.write(validation.encode()) - finally: - os.umask(old_umask) - - self.performed[root_path].add(achall) - return response - - def cleanup(self, achalls): # pylint: disable=missing-docstring - for achall in achalls: - root_path = self.full_roots.get(achall.domain, None) - if root_path is not None: - validation_path = self._get_validation_path(root_path, achall) - logger.debug("Removing %s", validation_path) - os.remove(validation_path) - self.performed[root_path].remove(achall) - - not_removed = [] # type: List[str] - while self._created_dirs: - path = self._created_dirs.pop() - try: - os.rmdir(path) - except OSError as exc: - not_removed.insert(0, path) - logger.info("Challenge directory %s was not empty, didn't remove", path) - logger.debug("Error was: %s", exc) - self._created_dirs = not_removed - logger.debug("All challenges cleaned up") - - -class _WebrootMapAction(argparse.Action): - """Action class for parsing webroot_map.""" - - def __call__(self, parser, namespace, webroot_map, option_string=None): - for domains, webroot_path in six.iteritems(json.loads(webroot_map)): - webroot_path = _validate_webroot(webroot_path) - namespace.webroot_map.update( - (d, webroot_path) for d in cli.add_domains(namespace, domains)) - - -class _WebrootPathAction(argparse.Action): - """Action class for parsing webroot_path.""" - - def __init__(self, *args, **kwargs): - super(_WebrootPathAction, self).__init__(*args, **kwargs) - self._domain_before_webroot = False - - def __call__(self, parser, namespace, webroot_path, option_string=None): - if self._domain_before_webroot: - raise errors.PluginError( - "If you specify multiple webroot paths, " - "one of them must precede all domain flags") - - if namespace.webroot_path: - # Apply previous webroot to all matched - # domains before setting the new webroot path - prev_webroot = namespace.webroot_path[-1] - for domain in namespace.domains: - namespace.webroot_map.setdefault(domain, prev_webroot) - elif namespace.domains: - self._domain_before_webroot = True - - namespace.webroot_path.append(_validate_webroot(webroot_path)) - - -def _validate_webroot(webroot_path): - """Validates and returns the absolute path of webroot_path. - - :param str webroot_path: path to the webroot directory - - :returns: absolute path of webroot_path - :rtype: str - - """ - if not os.path.isdir(webroot_path): - raise errors.PluginError(webroot_path + " does not exist or is not a directory") - - return os.path.abspath(webroot_path) diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py index d1aea5817..70501f812 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/plugins/webroot_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.webroot.""" +"""Tests for certbot._internal.plugins.webroot.""" from __future__ import print_function @@ -27,13 +27,13 @@ KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class AuthenticatorTest(unittest.TestCase): - """Tests for certbot.plugins.webroot.Authenticator.""" + """Tests for certbot._internal.plugins.webroot.Authenticator.""" achall = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) def setUp(self): - from certbot.plugins.webroot import Authenticator + from certbot._internal.plugins.webroot import Authenticator # On Linux directories created by tempfile.mkdtemp inherit their permissions from their # parent directory. So the actual permissions are inconsistent over various tests env. # To circumvent this, a dedicated sub-workspace is created under the workspace, using @@ -147,7 +147,7 @@ class AuthenticatorTest(unittest.TestCase): self.assertRaises(errors.PluginError, self.auth.perform, []) filesystem.chmod(self.path, 0o700) - @mock.patch("certbot.plugins.webroot.filesystem.copy_ownership_and_apply_mode") + @mock.patch("certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode") def test_failed_chown(self, mock_ownership): mock_ownership.side_effect = OSError(errno.EACCES, "msg") self.auth.perform([self.achall]) # exception caught and logged @@ -262,7 +262,7 @@ class WebrootActionTest(unittest.TestCase): challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) def setUp(self): - from certbot.plugins.webroot import Authenticator + from certbot._internal.plugins.webroot import Authenticator self.path = tempfile.mkdtemp() self.parser = argparse.ArgumentParser() self.parser.add_argument("-d", "--domains", @@ -308,7 +308,7 @@ class WebrootActionTest(unittest.TestCase): self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) def _get_config_after_perform(self, config): - from certbot.plugins.webroot import Authenticator + from certbot._internal.plugins.webroot import Authenticator auth = Authenticator(config, "webroot") auth.perform([self.achall]) return auth.config diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index a2491f153..0adf33921 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -16,7 +16,7 @@ from certbot import constants from certbot import errors from certbot.compat import os from certbot.compat import filesystem -from certbot.plugins import disco +from certbot._internal.plugins import disco from certbot.tests.util import TempDirTestCase PLUGINS = disco.PluginsRegistry.find_all() diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 1cad43823..177790028 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -33,10 +33,10 @@ from certbot._internal import updater from certbot import util from certbot.compat import os from certbot.compat import filesystem -from certbot.plugins import disco +from certbot._internal.plugins import disco from certbot.plugins import enhancements -from certbot.plugins import manual -from certbot.plugins import null +from certbot._internal.plugins import manual +from certbot._internal.plugins import null CERT_PATH = test_util.vector_path('cert_512.pem') CERT = test_util.vector_path('cert_512.pem') @@ -1220,7 +1220,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._test_renew_common(renewalparams=renewalparams, error_expected=True, names=names, assert_oc_called=False) - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') def test_renew_with_configurator(self, mock_sel): mock_sel.return_value = (mock.MagicMock(), mock.MagicMock()) renewalparams = {'authenticator': 'webroot'} @@ -1493,7 +1493,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met email in mock_utility().add_message.call_args[0][0]) self.assertTrue(mock_handle.called) - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') @mock.patch('certbot._internal.updater._run_updaters') def test_plugin_selection_error(self, mock_run, mock_choose): mock_choose.side_effect = errors.PluginSelectionError diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index 162146d7e..612a5fcb2 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -31,7 +31,8 @@ class RenewalTest(test_util.ConfigTestCase): @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_webroot_params_conservation(self, mock_set_by_cli): # For more details about why this test is important, see: - # certbot.plugins.webroot_test::WebrootActionTest::test_webroot_map_partial_without_perform + # certbot._internal.plugins.webroot_test:: + # WebrootActionTest::test_webroot_map_partial_without_perform from certbot._internal import renewal mock_set_by_cli.return_value = False diff --git a/certbot/tests/renewupdater_test.py b/certbot/tests/renewupdater_test.py index 9ac55a48d..42b723c94 100644 --- a/certbot/tests/renewupdater_test.py +++ b/certbot/tests/renewupdater_test.py @@ -22,8 +22,8 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) @mock.patch('certbot._internal.main._get_and_save_cert') - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') @test_util.patch_get_utility() def test_server_updates(self, _, mock_geti, mock_select, mock_getsave): mock_getsave.return_value = mock.MagicMock() @@ -64,7 +64,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.assertEqual(mock_log.call_args[0][0], "Skipping renewal deployer in dry-run mode.") - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates(self, mock_geti): mock_geti.return_value = self.mockinstaller updater.run_generic_updaters(self.config, mock.MagicMock(), None) @@ -76,7 +76,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.mockinstaller) self.assertTrue(self.mockinstaller.deploy_autohsts.called) - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates_not_called(self, mock_geti): self.config.disable_renew_updates = True mock_geti.return_value = self.mockinstaller @@ -89,7 +89,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.mockinstaller) self.assertFalse(self.mockinstaller.deploy_autohsts.called) - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_no_updater(self, mock_geti): FAKEINDEX = [ { diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 183d9a020..0c525fcc6 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -44,7 +44,7 @@ class RelevantValuesTest(unittest.TestCase): return relevant_values(*args, **kwargs) @mock.patch("certbot.cli.option_was_set") - @mock.patch("certbot.plugins.disco.PluginsRegistry.find_all") + @mock.patch("certbot._internal.plugins.disco.PluginsRegistry.find_all") def test_namespace(self, mock_find_all, mock_option_was_set): mock_find_all.return_value = ["certbot-foo:bar"] mock_option_was_set.return_value = True diff --git a/docs/api/plugins/disco.rst b/docs/api/plugins/disco.rst deleted file mode 100644 index 1a27f0f69..000000000 --- a/docs/api/plugins/disco.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.disco` --------------------------------- - -.. automodule:: certbot.plugins.disco - :members: diff --git a/docs/api/plugins/manual.rst b/docs/api/plugins/manual.rst deleted file mode 100644 index eea443499..000000000 --- a/docs/api/plugins/manual.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.manual` ---------------------------------- - -.. automodule:: certbot.plugins.manual - :members: diff --git a/docs/api/plugins/selection.rst b/docs/api/plugins/selection.rst deleted file mode 100644 index 6211bf9c0..000000000 --- a/docs/api/plugins/selection.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.selection` ------------------------------------- - -.. automodule:: certbot.plugins.selection - :members: diff --git a/docs/api/plugins/standalone.rst b/docs/api/plugins/standalone.rst deleted file mode 100644 index 60aa48b4f..000000000 --- a/docs/api/plugins/standalone.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.standalone` -------------------------------------- - -.. automodule:: certbot.plugins.standalone - :members: diff --git a/docs/api/plugins/webroot.rst b/docs/api/plugins/webroot.rst deleted file mode 100644 index e1f4523f7..000000000 --- a/docs/api/plugins/webroot.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.webroot` ----------------------------------- - -.. automodule:: certbot.plugins.webroot - :members: diff --git a/setup.py b/setup.py index 3ae2fe282..b230f3ba0 100644 --- a/setup.py +++ b/setup.py @@ -168,10 +168,10 @@ setup( 'certbot = certbot._internal.main:main', ], 'certbot.plugins': [ - 'manual = certbot.plugins.manual:Authenticator', - 'null = certbot.plugins.null:Installer', - 'standalone = certbot.plugins.standalone:Authenticator', - 'webroot = certbot.plugins.webroot:Authenticator', + 'manual = certbot._internal.plugins.manual:Authenticator', + 'null = certbot._internal.plugins.null:Installer', + 'standalone = certbot._internal.plugins.standalone:Authenticator', + 'webroot = certbot._internal.plugins.webroot:Authenticator', ], }, ) -- cgit v1.2.3 From 4792e1ee217eda2d7a2e2a1b0e5c4da9a1892841 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 11 Nov 2019 15:41:40 -0800 Subject: Move constants.py to _internal (#7534) * Don't call core constants from nginx plugin * Move constants.py to _internal/ * Move ENHANCEMENTS from now-internal constants to public plugins.enhancements * Update display.enhancements.ask from its 2015 comment --- certbot-apache/certbot_apache/configurator.py | 4 +- .../configurators/common.py | 2 +- .../certbot_compatibility_test/util.py | 2 +- certbot-nginx/certbot_nginx/configurator.py | 20 +- certbot/_internal/account.py | 2 +- certbot/_internal/client.py | 2 +- certbot/_internal/constants.py | 220 +++++++++++++++++++ certbot/_internal/display/enhancements.py | 2 +- certbot/_internal/eff.py | 2 +- certbot/_internal/log.py | 4 +- certbot/_internal/main.py | 2 +- certbot/_internal/plugins/disco.py | 2 +- certbot/_internal/storage.py | 2 +- certbot/cli.py | 2 +- certbot/configuration.py | 6 +- certbot/constants.py | 232 --------------------- certbot/display/util.py | 2 +- certbot/interfaces.py | 6 +- certbot/plugins/common.py | 2 +- certbot/plugins/common_test.py | 4 +- certbot/plugins/enhancements.py | 13 +- certbot/reverter.py | 2 +- certbot/tests/account_test.py | 2 +- certbot/tests/cli_test.py | 2 +- certbot/tests/configuration_test.py | 2 +- certbot/tests/eff_test.py | 2 +- certbot/tests/log_test.py | 2 +- certbot/tests/main_test.py | 2 +- certbot/tests/util.py | 2 +- certbot/util.py | 2 +- docs/api/constants.rst | 9 - 31 files changed, 267 insertions(+), 293 deletions(-) create mode 100644 certbot/_internal/constants.py delete mode 100644 certbot/constants.py delete mode 100644 docs/api/constants.rst diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 6ff7d328e..57cd4a9b4 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -1612,9 +1612,9 @@ class ApacheConfigurator(common.Installer): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~certbot.constants.ENHANCEMENTS` + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` documentation for appropriate parameter. :raises .errors.PluginError: If Enhancement is not supported, or if diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index 8f90d37c2..f8d106f21 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -4,7 +4,7 @@ import os import shutil import tempfile -from certbot import constants +from certbot._internal import constants from certbot_compatibility_test import errors from certbot_compatibility_test import util diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index a96ead21f..4f93e5561 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -9,7 +9,7 @@ import tarfile import josepy as jose from certbot.tests import util as test_util -from certbot import constants +from certbot._internal import constants from certbot_compatibility_test import errors diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index fe5c7da35..177c0ea40 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -17,7 +17,6 @@ from acme import challenges from acme import crypto_util as acme_crypto_util from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module -from certbot import constants as core_constants from certbot import crypto_util from certbot import errors from certbot import interfaces @@ -101,9 +100,6 @@ class NginxConfigurator(common.Installer): openssl_version = kwargs.pop("openssl_version", None) super(NginxConfigurator, self).__init__(*args, **kwargs) - # Verify that all directories and files exist with proper permissions - self._verify_setup() - # Files to save self.save_notes = "" @@ -708,9 +704,9 @@ class NginxConfigurator(common.Installer): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~certbot.constants.ENHANCEMENTS` + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` documentation for appropriate parameter. """ @@ -929,18 +925,6 @@ class NginxConfigurator(common.Installer): except errors.SubprocessError as err: raise errors.MisconfigurationError(str(err)) - def _verify_setup(self): - """Verify the setup to ensure safe operating environment. - - Make sure that files/directories are setup with appropriate permissions - Aim for defensive coding... make sure all input files - have permissions of root. - - """ - util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE) - util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE) - util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE) - def _nginx_version(self): """Return results of nginx -V diff --git a/certbot/_internal/account.py b/certbot/_internal/account.py index 992d63d38..12f6a3e8a 100644 --- a/certbot/_internal/account.py +++ b/certbot/_internal/account.py @@ -16,7 +16,7 @@ from cryptography.hazmat.primitives import serialization from acme import fields as acme_fields from acme import messages -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py index 37d61a7a4..f694a0ae7 100644 --- a/certbot/_internal/client.py +++ b/certbot/_internal/client.py @@ -21,7 +21,7 @@ import certbot from certbot._internal import account from certbot._internal import auth_handler from certbot import cli -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot._internal import eff from certbot._internal import error_handler diff --git a/certbot/_internal/constants.py b/certbot/_internal/constants.py new file mode 100644 index 000000000..d8c3c2ae1 --- /dev/null +++ b/certbot/_internal/constants.py @@ -0,0 +1,220 @@ +"""Certbot constants.""" +import logging + +import pkg_resources + +from acme import challenges + +from certbot.compat import misc +from certbot.compat import os + +SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" +"""Setuptools entry point group name for plugins.""" + +OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" +"""Plugins Setuptools entry point before rename.""" + +CLI_DEFAULTS = dict( + config_files=[ + os.path.join(misc.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"), + ], + + # Main parser + verbose_count=-int(logging.INFO / 10), + text_mode=False, + max_log_backups=1000, + noninteractive_mode=False, + force_interactive=False, + domains=[], + certname=None, + dry_run=False, + register_unsafely_without_email=False, + update_registration=False, + email=None, + eff_email=None, + reinstall=False, + expand=False, + renew_by_default=False, + renew_with_new_domains=False, + autorenew=True, + allow_subset_of_names=False, + tos=False, + account=None, + duplicate=False, + os_packages_only=False, + no_self_upgrade=False, + no_permissions_check=False, + no_bootstrap=False, + quiet=False, + staging=False, + debug=False, + debug_challenges=False, + no_verify_ssl=False, + http01_port=challenges.HTTP01Response.PORT, + http01_address="", + https_port=443, + break_my_certs=False, + rsa_key_size=2048, + must_staple=False, + redirect=None, + auto_hsts=False, + hsts=None, + uir=None, + staple=None, + strict_permissions=False, + pref_challs=[], + validate_hooks=True, + 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, + user_agent=None, + user_agent_comment=None, + csr=None, + reason=0, + delete_after_revoke=None, + rollback_checkpoints=1, + init=False, + prepare=False, + ifaces=None, + + # Path parsers + auth_cert_path="./cert.pem", + auth_chain_path="./chain.pem", + key_path=None, + config_dir=misc.get_default_folder('config'), + work_dir=misc.get_default_folder('work'), + logs_dir=misc.get_default_folder('logs'), + server="https://acme-v02.api.letsencrypt.org/directory", + + # Plugins parsers + configurator=None, + authenticator=None, + installer=None, + apache=False, + nginx=False, + standalone=False, + manual=False, + webroot=False, + dns_cloudflare=False, + dns_cloudxns=False, + dns_digitalocean=False, + dns_dnsimple=False, + dns_dnsmadeeasy=False, + dns_gehirn=False, + dns_google=False, + dns_linode=False, + dns_luadns=False, + dns_nsone=False, + dns_ovh=False, + dns_rfc2136=False, + dns_route53=False, + dns_sakuracloud=False + +) +STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory" + +# The set of reasons for revoking a certificate is defined in RFC 5280 in +# section 5.3.1. The reasons that users are allowed to submit are restricted to +# those accepted by the ACME server implementation. They are listed in +# `letsencrypt.boulder.revocation.reasons.go`. +REVOCATION_REASONS = { + "unspecified": 0, + "keycompromise": 1, + "affiliationchanged": 3, + "superseded": 4, + "cessationofoperation": 5} + +"""Defaults for CLI flags and `.IConfig` attributes.""" + +QUIET_LOGGING_LEVEL = logging.WARNING +"""Logging level to use in quiet mode.""" + +RENEWER_DEFAULTS = dict( + renewer_enabled="yes", + renew_before_expiry="30 days", + # This value should ensure that there is never a deployment delay by + # default. + deploy_before_expiry="99 years", +) +"""Defaults for renewer script.""" + +ARCHIVE_DIR = "archive" +"""Archive directory, relative to `IConfig.config_dir`.""" + +CONFIG_DIRS_MODE = 0o755 +"""Directory mode for ``.IConfig.config_dir`` et al.""" + +ACCOUNTS_DIR = "accounts" +"""Directory where all accounts are saved.""" + +LE_REUSE_SERVERS = { + os.path.normpath('acme-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-v01.api.letsencrypt.org/directory'), + os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-staging.api.letsencrypt.org/directory') +} +"""Servers that can reuse accounts from other servers.""" + +BACKUP_DIR = "backups" +"""Directory (relative to `IConfig.work_dir`) where backups are kept.""" + +CSR_DIR = "csr" +"""See `.IConfig.csr_dir`.""" + +IN_PROGRESS_DIR = "IN_PROGRESS" +"""Directory used before a permanent checkpoint is finalized (relative to +`IConfig.work_dir`).""" + +KEY_DIR = "keys" +"""Directory (relative to `IConfig.config_dir`) where keys are saved.""" + +LIVE_DIR = "live" +"""Live directory, relative to `IConfig.config_dir`.""" + +TEMP_CHECKPOINT_DIR = "temp_checkpoint" +"""Temporary checkpoint directory (relative to `IConfig.work_dir`).""" + +RENEWAL_CONFIGS_DIR = "renewal" +"""Renewal configs directory, relative to `IConfig.config_dir`.""" + +RENEWAL_HOOKS_DIR = "renewal-hooks" +"""Basename of directory containing hooks to run with the renew command.""" + +RENEWAL_PRE_HOOKS_DIR = "pre" +"""Basename of directory containing pre-hooks to run with the renew command.""" + +RENEWAL_DEPLOY_HOOKS_DIR = "deploy" +"""Basename of directory containing deploy-hooks to run with the renew command.""" + +RENEWAL_POST_HOOKS_DIR = "post" +"""Basename of directory containing post-hooks to run with the renew command.""" + +FORCE_INTERACTIVE_FLAG = "--force-interactive" +"""Flag to disable TTY checking in IDisplay.""" + +EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot" +"""EFF URI used to submit the e-mail address of users who opt-in.""" + +SSL_DHPARAMS_DEST = "ssl-dhparams.pem" +"""Name of the ssl_dhparams file as saved in `IConfig.config_dir`.""" + +SSL_DHPARAMS_SRC = pkg_resources.resource_filename( + "certbot", "ssl-dhparams.pem") +"""Path to the nginx ssl_dhparams file found in the Certbot distribution.""" + +UPDATED_SSL_DHPARAMS_DIGEST = ".updated-ssl-dhparams-pem-digest.txt" +"""Name of the hash of the updated or informed ssl_dhparams as saved in `IConfig.config_dir`.""" + +ALL_SSL_DHPARAMS_HASHES = [ + '9ba6429597aeed2d8617a7705b56e96d044f64b07971659382e426675105654b', +] +"""SHA256 hashes of the contents of all versions of SSL_DHPARAMS_SRC""" diff --git a/certbot/_internal/display/enhancements.py b/certbot/_internal/display/enhancements.py index 0f6b6c57d..5498b9547 100644 --- a/certbot/_internal/display/enhancements.py +++ b/certbot/_internal/display/enhancements.py @@ -18,7 +18,7 @@ def ask(enhancement): """Display the enhancement to the user. :param str enhancement: One of the - :class:`certbot.CONFIG.ENHANCEMENTS` enhancements + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` enhancements :returns: True if feature is desired, False otherwise :rtype: bool diff --git a/certbot/_internal/eff.py b/certbot/_internal/eff.py index 433cdc8cd..a0692009f 100644 --- a/certbot/_internal/eff.py +++ b/certbot/_internal/eff.py @@ -4,7 +4,7 @@ import logging import requests import zope.component -from certbot import constants +from certbot._internal import constants from certbot import interfaces diff --git a/certbot/_internal/log.py b/certbot/_internal/log.py index a16e2ef7e..2109e0427 100644 --- a/certbot/_internal/log.py +++ b/certbot/_internal/log.py @@ -24,7 +24,7 @@ import traceback from acme import messages -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import util from certbot.compat import os @@ -41,7 +41,7 @@ def pre_arg_parse_setup(): """Setup logging before command line arguments are parsed. Terminal logging is setup using - `certbot.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as + `certbot._internal.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as possible. File logging is setup so that logging messages are buffered in memory. If Certbot exits before `post_arg_parse_setup` is called, these buffered messages are written to a temporary file. diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index de68e8553..dd5f7fe4a 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -19,7 +19,7 @@ from certbot._internal import cert_manager from certbot import cli from certbot._internal import client from certbot import configuration -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot._internal import eff from certbot import errors diff --git a/certbot/_internal/plugins/disco.py b/certbot/_internal/plugins/disco.py index ec2bff8b7..0bee88ae1 100644 --- a/certbot/_internal/plugins/disco.py +++ b/certbot/_internal/plugins/disco.py @@ -10,7 +10,7 @@ import zope.interface import zope.interface.verify from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import interfaces diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py index 9684cc195..b7b878dab 100644 --- a/certbot/_internal/storage.py +++ b/certbot/_internal/storage.py @@ -13,7 +13,7 @@ import six import certbot from certbot import cli -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot._internal import error_handler from certbot import errors diff --git a/certbot/cli.py b/certbot/cli.py index 103347735..739eadd8a 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -22,7 +22,7 @@ from acme.magic_typing import Any, Dict, Optional import certbot import certbot.plugins.enhancements as enhancements import certbot._internal.plugins.selection as plugin_selection -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot import errors from certbot import hooks diff --git a/certbot/configuration.py b/certbot/configuration.py index cc9cb2d98..48579eb1c 100644 --- a/certbot/configuration.py +++ b/certbot/configuration.py @@ -4,7 +4,7 @@ import copy import zope.interface from six.moves.urllib import parse # pylint: disable=relative-import -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import interfaces from certbot import util @@ -20,7 +20,7 @@ class NamespaceConfig(object): :class:`certbot.interfaces.IConfig`. However, note that the following attributes are dynamically resolved using :attr:`~certbot.interfaces.IConfig.work_dir` and relative - paths defined in :py:mod:`certbot.constants`: + paths defined in :py:mod:`certbot._internal.constants`: - `accounts_dir` - `csr_dir` @@ -30,7 +30,7 @@ class NamespaceConfig(object): And the following paths are dynamically resolved using :attr:`~certbot.interfaces.IConfig.config_dir` and relative - paths defined in :py:mod:`certbot.constants`: + paths defined in :py:mod:`certbot._internal.constants`: - `default_archive_dir` - `live_dir` diff --git a/certbot/constants.py b/certbot/constants.py deleted file mode 100644 index 10cd58ca1..000000000 --- a/certbot/constants.py +++ /dev/null @@ -1,232 +0,0 @@ -"""Certbot constants.""" -import logging - -import pkg_resources - -from acme import challenges - -from certbot.compat import misc -from certbot.compat import os - -SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" -"""Setuptools entry point group name for plugins.""" - -OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" -"""Plugins Setuptools entry point before rename.""" - -CLI_DEFAULTS = dict( - config_files=[ - os.path.join(misc.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"), - ], - - # Main parser - verbose_count=-int(logging.INFO / 10), - text_mode=False, - max_log_backups=1000, - noninteractive_mode=False, - force_interactive=False, - domains=[], - certname=None, - dry_run=False, - register_unsafely_without_email=False, - update_registration=False, - email=None, - eff_email=None, - reinstall=False, - expand=False, - renew_by_default=False, - renew_with_new_domains=False, - autorenew=True, - allow_subset_of_names=False, - tos=False, - account=None, - duplicate=False, - os_packages_only=False, - no_self_upgrade=False, - no_permissions_check=False, - no_bootstrap=False, - quiet=False, - staging=False, - debug=False, - debug_challenges=False, - no_verify_ssl=False, - http01_port=challenges.HTTP01Response.PORT, - http01_address="", - https_port=443, - break_my_certs=False, - rsa_key_size=2048, - must_staple=False, - redirect=None, - auto_hsts=False, - hsts=None, - uir=None, - staple=None, - strict_permissions=False, - pref_challs=[], - validate_hooks=True, - 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, - user_agent=None, - user_agent_comment=None, - csr=None, - reason=0, - delete_after_revoke=None, - rollback_checkpoints=1, - init=False, - prepare=False, - ifaces=None, - - # Path parsers - auth_cert_path="./cert.pem", - auth_chain_path="./chain.pem", - key_path=None, - config_dir=misc.get_default_folder('config'), - work_dir=misc.get_default_folder('work'), - logs_dir=misc.get_default_folder('logs'), - server="https://acme-v02.api.letsencrypt.org/directory", - - # Plugins parsers - configurator=None, - authenticator=None, - installer=None, - apache=False, - nginx=False, - standalone=False, - manual=False, - webroot=False, - dns_cloudflare=False, - dns_cloudxns=False, - dns_digitalocean=False, - dns_dnsimple=False, - dns_dnsmadeeasy=False, - dns_gehirn=False, - dns_google=False, - dns_linode=False, - dns_luadns=False, - dns_nsone=False, - dns_ovh=False, - dns_rfc2136=False, - dns_route53=False, - dns_sakuracloud=False - -) -STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory" - -# The set of reasons for revoking a certificate is defined in RFC 5280 in -# section 5.3.1. The reasons that users are allowed to submit are restricted to -# those accepted by the ACME server implementation. They are listed in -# `letsencrypt.boulder.revocation.reasons.go`. -REVOCATION_REASONS = { - "unspecified": 0, - "keycompromise": 1, - "affiliationchanged": 3, - "superseded": 4, - "cessationofoperation": 5} - -"""Defaults for CLI flags and `.IConfig` attributes.""" - -QUIET_LOGGING_LEVEL = logging.WARNING -"""Logging level to use in quiet mode.""" - -RENEWER_DEFAULTS = dict( - renewer_enabled="yes", - renew_before_expiry="30 days", - # This value should ensure that there is never a deployment delay by - # default. - deploy_before_expiry="99 years", -) -"""Defaults for renewer script.""" - - -ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] -"""List of possible :class:`certbot.interfaces.IInstaller` -enhancements. - -List of expected options parameters: -- redirect: None -- ensure-http-header: name of header (i.e. Strict-Transport-Security) -- ocsp-stapling: certificate chain file path - -""" - -ARCHIVE_DIR = "archive" -"""Archive directory, relative to `IConfig.config_dir`.""" - -CONFIG_DIRS_MODE = 0o755 -"""Directory mode for ``.IConfig.config_dir`` et al.""" - -ACCOUNTS_DIR = "accounts" -"""Directory where all accounts are saved.""" - -LE_REUSE_SERVERS = { - os.path.normpath('acme-v02.api.letsencrypt.org/directory'): - os.path.normpath('acme-v01.api.letsencrypt.org/directory'), - os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): - os.path.normpath('acme-staging.api.letsencrypt.org/directory') -} -"""Servers that can reuse accounts from other servers.""" - -BACKUP_DIR = "backups" -"""Directory (relative to `IConfig.work_dir`) where backups are kept.""" - -CSR_DIR = "csr" -"""See `.IConfig.csr_dir`.""" - -IN_PROGRESS_DIR = "IN_PROGRESS" -"""Directory used before a permanent checkpoint is finalized (relative to -`IConfig.work_dir`).""" - -KEY_DIR = "keys" -"""Directory (relative to `IConfig.config_dir`) where keys are saved.""" - -LIVE_DIR = "live" -"""Live directory, relative to `IConfig.config_dir`.""" - -TEMP_CHECKPOINT_DIR = "temp_checkpoint" -"""Temporary checkpoint directory (relative to `IConfig.work_dir`).""" - -RENEWAL_CONFIGS_DIR = "renewal" -"""Renewal configs directory, relative to `IConfig.config_dir`.""" - -RENEWAL_HOOKS_DIR = "renewal-hooks" -"""Basename of directory containing hooks to run with the renew command.""" - -RENEWAL_PRE_HOOKS_DIR = "pre" -"""Basename of directory containing pre-hooks to run with the renew command.""" - -RENEWAL_DEPLOY_HOOKS_DIR = "deploy" -"""Basename of directory containing deploy-hooks to run with the renew command.""" - -RENEWAL_POST_HOOKS_DIR = "post" -"""Basename of directory containing post-hooks to run with the renew command.""" - -FORCE_INTERACTIVE_FLAG = "--force-interactive" -"""Flag to disable TTY checking in IDisplay.""" - -EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot" -"""EFF URI used to submit the e-mail address of users who opt-in.""" - -SSL_DHPARAMS_DEST = "ssl-dhparams.pem" -"""Name of the ssl_dhparams file as saved in `IConfig.config_dir`.""" - -SSL_DHPARAMS_SRC = pkg_resources.resource_filename( - "certbot", "ssl-dhparams.pem") -"""Path to the nginx ssl_dhparams file found in the Certbot distribution.""" - -UPDATED_SSL_DHPARAMS_DIGEST = ".updated-ssl-dhparams-pem-digest.txt" -"""Name of the hash of the updated or informed ssl_dhparams as saved in `IConfig.config_dir`.""" - -ALL_SSL_DHPARAMS_HASHES = [ - '9ba6429597aeed2d8617a7705b56e96d044f64b07971659382e426675105654b', -] -"""SHA256 hashes of the contents of all versions of SSL_DHPARAMS_SRC""" diff --git a/certbot/display/util.py b/certbot/display/util.py index d67e33dc8..6cf9f9c05 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -5,7 +5,7 @@ import textwrap import zope.interface -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import interfaces from certbot.compat import misc diff --git a/certbot/interfaces.py b/certbot/interfaces.py index 2e2df8a73..8cdd247d8 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -295,10 +295,10 @@ class IInstaller(IPlugin): :param str domain: domain for which to provide enhancement :param str enhancement: An enhancement as defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: Flexible options parameter for enhancement. Check documentation of - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` for expected options for each enhancement. :raises .PluginError: If Enhancement is not supported, or if @@ -310,7 +310,7 @@ class IInstaller(IPlugin): """Returns a `collections.Iterable` of supported enhancements. :returns: supported enhancements which should be a subset of - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :rtype: :class:`collections.Iterable` of :class:`str` """ diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py index fc56972b5..843e27a1b 100644 --- a/certbot/plugins/common.py +++ b/certbot/plugins/common.py @@ -14,7 +14,7 @@ from josepy import util as jose_util from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot import errors from certbot import interfaces diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py index 8fa642ced..977500f86 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/plugins/common_test.py @@ -167,11 +167,11 @@ class InstallerTest(test_util.ConfigTestCase): self.assertTrue(os.path.isfile(self.installer.ssl_dhparams)) def _current_ssl_dhparams_hash(self): - from certbot.constants import SSL_DHPARAMS_SRC + from certbot._internal.constants import SSL_DHPARAMS_SRC return crypto_util.sha256sum(SSL_DHPARAMS_SRC) def test_current_file_hash_in_all_hashes(self): - from certbot.constants import ALL_SSL_DHPARAMS_HASHES + from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES self.assertTrue(self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES, "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" " with the sha256 hash of self.config.ssl_dhparams when it is updated.") diff --git a/certbot/plugins/enhancements.py b/certbot/plugins/enhancements.py index 353ff44d5..8896c1a98 100644 --- a/certbot/plugins/enhancements.py +++ b/certbot/plugins/enhancements.py @@ -2,10 +2,21 @@ import abc import six -from certbot import constants +from certbot._internal import constants from acme.magic_typing import Dict, List, Any # pylint: disable=unused-import, no-name-in-module +ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] +"""List of possible :class:`certbot.interfaces.IInstaller` +enhancements. + +List of expected options parameters: +- redirect: None +- ensure-http-header: name of header (i.e. Strict-Transport-Security) +- ocsp-stapling: certificate chain file path + +""" + def enabled_enhancements(config): """ Generator to yield the enabled new style enhancements. diff --git a/certbot/reverter.py b/certbot/reverter.py index ac2804164..9118fef94 100644 --- a/certbot/reverter.py +++ b/certbot/reverter.py @@ -9,7 +9,7 @@ import traceback import six -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import util from certbot.compat import os diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 29ec4fc33..0f0b47ec4 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -278,7 +278,7 @@ class AccountFileStorageTest(test_util.ConfigTestCase): self._set_server('https://acme-staging.api.letsencrypt.org/directory') self.storage.save(self.acc, self.mock_client) # ensure v2 isn't already linked to it - with mock.patch('certbot.constants.LE_REUSE_SERVERS', {}): + with mock.patch('certbot._internal.constants.LE_REUSE_SERVERS', {}): self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory') self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id) diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 0adf33921..8a398188c 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -12,7 +12,7 @@ from acme import challenges import certbot.tests.util as test_util from certbot import cli -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot.compat import os from certbot.compat import filesystem diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index aa07a580f..e1e090fb5 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -3,7 +3,7 @@ import unittest import mock -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot.compat import misc from certbot.compat import os diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index 5e4cadcfa..b53187f47 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -4,7 +4,7 @@ import unittest import mock import requests -from certbot import constants +from certbot._internal import constants import certbot.tests.util as test_util diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index cd2cb01f1..36c473bd2 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -11,7 +11,7 @@ import six from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot import util from certbot.compat import filesystem diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 177790028..835951b70 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -24,7 +24,7 @@ import certbot.tests.util as test_util from certbot._internal import account from certbot import cli from certbot import configuration -from certbot import constants +from certbot._internal import constants from certbot import crypto_util from certbot import errors from certbot import interfaces # pylint: disable=unused-import diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 38779f564..00452fef9 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -20,7 +20,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from certbot import configuration -from certbot import constants +from certbot._internal import constants from certbot import interfaces from certbot._internal import lock from certbot._internal import storage diff --git a/certbot/util.py b/certbot/util.py index fdcc57d27..5d8aa8f22 100644 --- a/certbot/util.py +++ b/certbot/util.py @@ -19,7 +19,7 @@ import six from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module -from certbot import constants +from certbot._internal import constants from certbot import errors from certbot._internal import lock from certbot.compat import os diff --git a/docs/api/constants.rst b/docs/api/constants.rst deleted file mode 100644 index 99ecc240a..000000000 --- a/docs/api/constants.rst +++ /dev/null @@ -1,9 +0,0 @@ -:mod:`certbot.constants` ------------------------------------ - -.. automodule:: certbot.constants - :members: - :exclude-members: SSL_DHPARAMS_SRC - -.. autodata:: SSL_DHPARAMS_SRC - :annotation: = '/path/to/certbot/ssl-dhparams.pem' -- cgit v1.2.3 From c26d459d0fc300d76adf6f0a7c7549d9e4d8cc72 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 12 Nov 2019 22:52:44 +0100 Subject: Remove python2 and certbot-auto references in how to set up a Certbot build environment. (#7549) Fixes #7548. This PR udpdates installation instructions to get rid of python2 and certbot-auto in the how-to explaining the Certbot development environment setup. Instead, Python 3 is used, and appropriate instructions for APT and RPM based distributions are provided. --- docs/contributing.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index cc0a74b43..2a98658e4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -36,29 +36,36 @@ run Certbot in Docker. You can find instructions for how to do this :ref:`here install dependencies and set up a virtual environment where you can run Certbot. +Install the OS system dependencies required to run Certbot. + .. code-block:: shell - cd certbot - ./certbot-auto --debug --os-packages-only - python tools/venv.py + # For APT-based distributions (e.g. Debian, Ubuntu ...) + sudo apt update + sudo apt install python3-dev python3-venv gcc libaugeas0 libssl-dev \ + libffi-dev ca-certificates openssl + # For RPM-based distributions (e.g. Fedora, CentOS ...) + # NB1: old distributions will use yum instead of dnf + # NB2: RHEL-based distributions use python3X-devel instead of python3-devel (e.g. python36-devel) + sudo dnf install python3-devel gcc augeas-libs openssl-devel libffi-devel \ + redhat-rpm-config ca-certificates openssl -If you have Python3 available and want to use it, run the ``venv3.py`` script. +Set up the Python virtual environment that will host your Certbot local instance. .. code-block:: shell + cd certbot python tools/venv3.py .. note:: You may need to repeat this when Certbot's dependencies change or when a new plugin is introduced. You can now run the copy of Certbot from git either by executing -``venv/bin/certbot``, or by activating the virtual environment. You can do the +``venv3/bin/certbot``, or by activating the virtual environment. You can do the latter by running: .. code-block:: shell - source venv/bin/activate - # or source venv3/bin/activate After running this command, ``certbot`` and development tools like ``ipdb``, -- cgit v1.2.3 From 75acdeb6454429d6a1704a10f3bfe649a074b227 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 13 Nov 2019 18:43:50 +0100 Subject: [Windows] Fix certbot renew task failure under NT AUTHORITY\SYSTEM account (#7536) Turned out that the scheduled task that runs `certbot renew` twice a day, is failing. Without any kind of log of course, otherwise it would not be fun. It can be revealed by opening a powershell under the `NT AUTHORITY\SYSTEM` account, under which the scheduled task is run. Under theses circumstances, the bug is revealed: Certbot breaks when trying to invoke `certbot.compat.filesystem._get_current_user()`. Indeed the logic there implied to call `win32api.GetUserNameEx(win32api.NameSamCompatible)` and this function does not return always a useful value. For normal account, it will be typically `DOMAIN_OR_MACHINE_NAME\YOUR_USER_NAME` (e.g. `My Machine\Adrien Ferrand`). But for the account `NT AUTHORITY\SYSTEM`, it will return `MACHINE_NAME\DOMAIN$`, which is a nonsense and makes fail the resolution of the actual SID of the account at the end of `_get_current_user()`. This PR fixes this behavior by using an explicit construction of the account name that works both for normal users and `SYSTEM`. * Use a different way to resolve current user account, that works both for normal users and SYSTEM. * Add a comment to run Certbot under NT AUTHORITY\SYSTEM --- certbot/compat/filesystem.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py index 69a3a63c5..5fba440cc 100644 --- a/certbot/compat/filesystem.py +++ b/certbot/compat/filesystem.py @@ -588,7 +588,11 @@ def _get_current_user(): """ Return the pySID corresponding to the current user. """ - account_name = win32api.GetUserNameEx(win32api.NameSamCompatible) + # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx, + # because this function returns nonsense values when Certbot is run under NT AUTHORITY\SYSTEM. + # To run Certbot under NT AUTHORITY\SYSTEM, you can open a shell using the instructions here: + # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/ + account_name = r"{0}\{1}".format(win32api.GetDomainName(), win32api.GetUserName()) # LookupAccountName() expects the system name as first parameter. By passing None to it, # we instruct Windows to first search the matching account in the machine local accounts, # then into the primary domain accounts, if the machine has joined a domain, then finally -- cgit v1.2.3 From 595b1b212ef83c45173f141c137a45cf0a469a52 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 13 Nov 2019 19:04:45 +0100 Subject: [Windows] Avoid letsencrypt.log permissions error during scheduled certbot renew task (#7537) While coding for #7536, I ran into another issue. It appears that Certbot logs generated during the scheduled task execution have wrong permissions that make them almost unusable: they do not have an owner, and their ACL contains nonsense values (non existant accounts name). The class `logging.handler.RotatingFileHandler` is responsible for these logs, and become mad when it is in a Python process run under a scheduled task owned by `SYSTEM`. This is precisely our case here. This PR avoids (but not fix) the issue, by changing the owner of the scheduled task from `SYSTEM` to the `Administrators` group, that appears to work fine. * Use Administrators group instead of SYSTEM to run the certbot renew task --- windows-installer/renew-up.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/windows-installer/renew-up.ps1 b/windows-installer/renew-up.ps1 index c6a5fd9ea..224458748 100644 --- a/windows-installer/renew-up.ps1 +++ b/windows-installer/renew-up.ps1 @@ -8,8 +8,10 @@ $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfil $delay = New-TimeSpan -Hours 12 $triggerAM = New-ScheduledTaskTrigger -Daily -At 12am -RandomDelay $delay $triggerPM = New-ScheduledTaskTrigger -Daily -At 12pm -RandomDelay $delay -# NB: For now scheduled task is set up under SYSTEM account because Certbot Installer installs Certbot for all users. +# NB: For now scheduled task is set up under Administrators group account because Certbot Installer installs Certbot for all users. # If in the future we allow the Installer to install Certbot for one specific user, the scheduled task will need to # switch to this user, since Certbot will be available only for him. -$principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest +$adminsSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544") +$adminsGroupID = $adminsSID.Translate([System.Security.Principal.NTAccount]).Value +$principal = New-ScheduledTaskPrincipal -GroupId $adminsGroupID -RunLevel Highest Register-ScheduledTask -Action $action -Trigger $triggerAM,$triggerPM -TaskName $taskName -Description "Execute twice a day the 'certbot renew' command, to renew managed certificates if needed." -Principal $principal -- cgit v1.2.3 From 46d5f7a8601d026e1a986dd3f7a1dc4b056ca886 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 13 Nov 2019 10:19:27 -0800 Subject: Move configuration.py to _internal (#7542) Part of #5775. Methodology similar to #7528. Also refactors NGINX test util to use certbot.tests.util.ConfigTestCase. * refactor nginx tests to no longer rely on certbot.configuration internals * Move configuration.py to _internal --- .../configurators/apache/common.py | 2 +- .../configurators/nginx/common.py | 2 +- .../certbot_nginx/tests/configurator_test.py | 4 +- certbot-nginx/certbot_nginx/tests/http_01_test.py | 2 +- certbot-nginx/certbot_nginx/tests/util.py | 80 +++++------ certbot/_internal/cert_manager.py | 8 +- certbot/_internal/configuration.py | 160 +++++++++++++++++++++ certbot/_internal/main.py | 2 +- certbot/configuration.py | 160 --------------------- certbot/tests/cert_manager_test.py | 2 +- certbot/tests/configuration_test.py | 16 +-- certbot/tests/main_test.py | 2 +- certbot/tests/renewal_test.py | 2 +- certbot/tests/util.py | 2 +- docs/api/configuration.rst | 5 - 15 files changed, 222 insertions(+), 227 deletions(-) create mode 100644 certbot/_internal/configuration.py delete mode 100644 certbot/configuration.py delete mode 100644 docs/api/configuration.rst diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 82195264b..050a3bdb3 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -6,7 +6,7 @@ import subprocess import mock import zope.interface -from certbot import configuration +from certbot._internal import configuration from certbot import errors as le_errors from certbot import util as certbot_util from certbot_apache import entrypoint diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 3207cf88a..39f69da19 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -7,7 +7,7 @@ import zope.interface from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from certbot import configuration +from certbot._internal import configuration from certbot_nginx import configurator from certbot_nginx import constants from certbot_compatibility_test import errors diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 237e22d8f..4a7f7cafb 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -27,7 +27,7 @@ class NginxConfiguratorTest(util.NginxTest): def setUp(self): super(NginxConfiguratorTest, self).setUp() - self.config = util.get_nginx_configurator( + self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) @mock.patch("certbot_nginx.configurator.util.exe_exists") @@ -935,7 +935,7 @@ class InstallSslOptionsConfTest(util.NginxTest): def setUp(self): super(InstallSslOptionsConfTest, self).setUp() - self.config = util.get_nginx_configurator( + self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) def _call(self): diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py index c6d35b808..d05370c68 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/http_01_test.py @@ -47,7 +47,7 @@ class HttpPerformTest(util.NginxTest): def setUp(self): super(HttpPerformTest, self).setUp() - config = util.get_nginx_configurator( + config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) from certbot_nginx import http_01 diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index c0a70368e..c1d7e9d34 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -2,14 +2,12 @@ import copy import shutil import tempfile -import unittest import josepy as jose import mock import pkg_resources import zope.component -from certbot import configuration from certbot import util from certbot.compat import os from certbot.plugins import common @@ -19,11 +17,14 @@ from certbot_nginx import configurator from certbot_nginx import nginxparser -class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods +class NginxTest(test_util.ConfigTestCase): # pylint: disable=too-few-public-methods def setUp(self): super(NginxTest, self).setUp() + self.configuration = self.config + self.config = None + self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( "etc_nginx", "certbot_nginx.tests") self.logs_dir = tempfile.mkdtemp('logs') @@ -45,6 +46,42 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods shutil.rmtree(self.work_dir) shutil.rmtree(self.logs_dir) + # pylint: disable=too-many-arguments + def get_nginx_configurator(self, config_path, config_dir, work_dir, logs_dir, + version=(1, 6, 2), openssl_version="1.0.2g"): + """Create an Nginx Configurator with the specified options.""" + + backups = os.path.join(work_dir, "backups") + + self.configuration.nginx_server_root = config_path + self.configuration.le_vhost_ext = "-le-ssl.conf" + self.configuration.config_dir = config_dir + self.configuration.work_dir = work_dir + self.configuration.logs_dir = logs_dir + self.configuration.backup_dir = backups + self.configuration.temp_checkpoint_dir = os.path.join(work_dir, "temp_checkpoints") + self.configuration.in_progress_dir = os.path.join(backups, "IN_PROGRESS") + self.configuration.server = "https://acme-server.org:443/new" + self.configuration.http01_port = 80 + self.configuration.https_port = 5001 + + with mock.patch("certbot_nginx.configurator.NginxConfigurator." + "config_test"): + with mock.patch("certbot_nginx.configurator.util." + "exe_exists") as mock_exe_exists: + mock_exe_exists.return_value = True + config = configurator.NginxConfigurator( + self.configuration, + name="nginx", + version=version, + openssl_version=openssl_version) + config.prepare() + + # Provide general config utility. + zope.component.provideUtility(self.configuration) + + return config + def get_data_filename(filename): """Gets the filename of a test data file.""" @@ -53,43 +90,6 @@ def get_data_filename(filename): "testdata", "etc_nginx", filename)) -def get_nginx_configurator( - config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2), openssl_version="1.0.2g"): - """Create an Nginx Configurator with the specified options.""" - - backups = os.path.join(work_dir, "backups") - - with mock.patch("certbot_nginx.configurator.NginxConfigurator." - "config_test"): - with mock.patch("certbot_nginx.configurator.util." - "exe_exists") as mock_exe_exists: - mock_exe_exists.return_value = True - config = configurator.NginxConfigurator( - config=mock.MagicMock( - nginx_server_root=config_path, - le_vhost_ext="-le-ssl.conf", - config_dir=config_dir, - work_dir=work_dir, - logs_dir=logs_dir, - backup_dir=backups, - temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS"), - server="https://acme-server.org:443/new", - http01_port=80, - https_port=5001, - ), - name="nginx", - version=version, - openssl_version=openssl_version) - config.prepare() - - # Provide general config utility. - nsconfig = configuration.NamespaceConfig(config.config) - zope.component.provideUtility(nsconfig) - - return config - - def filter_comments(tree): """Filter comment nodes from parsed configurations.""" diff --git a/certbot/_internal/cert_manager.py b/certbot/_internal/cert_manager.py index ed72b12e5..329b6cdff 100644 --- a/certbot/_internal/cert_manager.py +++ b/certbot/_internal/cert_manager.py @@ -33,7 +33,7 @@ def update_live_symlinks(config): .. note:: This assumes that the installation is using a Reverter object. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ for renewal_file in storage.renewal_conf_files(config): @@ -43,7 +43,7 @@ def rename_lineage(config): """Rename the specified lineage to the new name. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ disp = zope.component.getUtility(interfaces.IDisplay) @@ -70,7 +70,7 @@ def certificates(config): """Display information about certs configured with Certbot :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ parsed_certs = [] parse_failures = [] @@ -136,7 +136,7 @@ def find_duplicative_certs(config, domains): undefined. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` :param domains: List of domain names :type domains: `list` of `str` diff --git a/certbot/_internal/configuration.py b/certbot/_internal/configuration.py new file mode 100644 index 000000000..48579eb1c --- /dev/null +++ b/certbot/_internal/configuration.py @@ -0,0 +1,160 @@ +"""Certbot user-supplied configuration.""" +import copy + +import zope.interface +from six.moves.urllib import parse # pylint: disable=relative-import + +from certbot._internal import constants +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import misc +from certbot.compat import os + + +@zope.interface.implementer(interfaces.IConfig) +class NamespaceConfig(object): + """Configuration wrapper around :class:`argparse.Namespace`. + + For more documentation, including available attributes, please see + :class:`certbot.interfaces.IConfig`. However, note that + the following attributes are dynamically resolved using + :attr:`~certbot.interfaces.IConfig.work_dir` and relative + paths defined in :py:mod:`certbot._internal.constants`: + + - `accounts_dir` + - `csr_dir` + - `in_progress_dir` + - `key_dir` + - `temp_checkpoint_dir` + + And the following paths are dynamically resolved using + :attr:`~certbot.interfaces.IConfig.config_dir` and relative + paths defined in :py:mod:`certbot._internal.constants`: + + - `default_archive_dir` + - `live_dir` + - `renewal_configs_dir` + + :ivar namespace: Namespace typically produced by + :meth:`argparse.ArgumentParser.parse_args`. + :type namespace: :class:`argparse.Namespace` + + """ + + def __init__(self, namespace): + object.__setattr__(self, 'namespace', namespace) + + self.namespace.config_dir = os.path.abspath(self.namespace.config_dir) + self.namespace.work_dir = os.path.abspath(self.namespace.work_dir) + self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir) + + # Check command line parameters sanity, and error out in case of problem. + check_config_sanity(self) + + def __getattr__(self, name): + return getattr(self.namespace, name) + + def __setattr__(self, name, value): + setattr(self.namespace, name, value) + + @property + def server_path(self): + """File path based on ``server``.""" + parsed = parse.urlparse(self.namespace.server) + return (parsed.netloc + parsed.path).replace('/', os.path.sep) + + @property + def accounts_dir(self): # pylint: disable=missing-docstring + return self.accounts_dir_for_server_path(self.server_path) + + def accounts_dir_for_server_path(self, server_path): + """Path to accounts directory based on server_path""" + server_path = misc.underscores_for_unsupported_characters_in_path(server_path) + return os.path.join( + self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) + + @property + def backup_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) + + @property + def csr_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.CSR_DIR) + + @property + def in_progress_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) + + @property + def key_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.KEY_DIR) + + @property + def temp_checkpoint_dir(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) + + def __deepcopy__(self, _memo): + # Work around https://bugs.python.org/issue1515 for py26 tests :( :( + # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276 + new_ns = copy.deepcopy(self.namespace) + return type(self)(new_ns) + + @property + def default_archive_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) + + @property + def live_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) + + @property + def renewal_configs_dir(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) + + @property + def renewal_hooks_dir(self): + """Path to directory with hooks to run with the renew subcommand.""" + return os.path.join(self.namespace.config_dir, + constants.RENEWAL_HOOKS_DIR) + + @property + def renewal_pre_hooks_dir(self): + """Path to the pre-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_PRE_HOOKS_DIR) + + @property + def renewal_deploy_hooks_dir(self): + """Path to the deploy-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_DEPLOY_HOOKS_DIR) + + @property + def renewal_post_hooks_dir(self): + """Path to the post-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_POST_HOOKS_DIR) + + +def check_config_sanity(config): + """Validate command line options and display error message if + requirements are not met. + + :param config: IConfig instance holding user configuration + :type args: :class:`certbot.interfaces.IConfig` + + """ + # Port check + if config.http01_port == config.https_port: + raise errors.ConfigurationError( + "Trying to run http-01 and https-port " + "on the same port ({0})".format(config.https_port)) + + # Domain checks + if config.namespace.domains is not None: + for domain in config.namespace.domains: + # This may be redundant, but let's be paranoid + util.enforce_domain_sanity(domain) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index dd5f7fe4a..8daca6514 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -18,7 +18,7 @@ from certbot._internal import account from certbot._internal import cert_manager from certbot import cli from certbot._internal import client -from certbot import configuration +from certbot._internal import configuration from certbot._internal import constants from certbot import crypto_util from certbot._internal import eff diff --git a/certbot/configuration.py b/certbot/configuration.py deleted file mode 100644 index 48579eb1c..000000000 --- a/certbot/configuration.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Certbot user-supplied configuration.""" -import copy - -import zope.interface -from six.moves.urllib import parse # pylint: disable=relative-import - -from certbot._internal import constants -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import misc -from certbot.compat import os - - -@zope.interface.implementer(interfaces.IConfig) -class NamespaceConfig(object): - """Configuration wrapper around :class:`argparse.Namespace`. - - For more documentation, including available attributes, please see - :class:`certbot.interfaces.IConfig`. However, note that - the following attributes are dynamically resolved using - :attr:`~certbot.interfaces.IConfig.work_dir` and relative - paths defined in :py:mod:`certbot._internal.constants`: - - - `accounts_dir` - - `csr_dir` - - `in_progress_dir` - - `key_dir` - - `temp_checkpoint_dir` - - And the following paths are dynamically resolved using - :attr:`~certbot.interfaces.IConfig.config_dir` and relative - paths defined in :py:mod:`certbot._internal.constants`: - - - `default_archive_dir` - - `live_dir` - - `renewal_configs_dir` - - :ivar namespace: Namespace typically produced by - :meth:`argparse.ArgumentParser.parse_args`. - :type namespace: :class:`argparse.Namespace` - - """ - - def __init__(self, namespace): - object.__setattr__(self, 'namespace', namespace) - - self.namespace.config_dir = os.path.abspath(self.namespace.config_dir) - self.namespace.work_dir = os.path.abspath(self.namespace.work_dir) - self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir) - - # Check command line parameters sanity, and error out in case of problem. - check_config_sanity(self) - - def __getattr__(self, name): - return getattr(self.namespace, name) - - def __setattr__(self, name, value): - setattr(self.namespace, name, value) - - @property - def server_path(self): - """File path based on ``server``.""" - parsed = parse.urlparse(self.namespace.server) - return (parsed.netloc + parsed.path).replace('/', os.path.sep) - - @property - def accounts_dir(self): # pylint: disable=missing-docstring - return self.accounts_dir_for_server_path(self.server_path) - - def accounts_dir_for_server_path(self, server_path): - """Path to accounts directory based on server_path""" - server_path = misc.underscores_for_unsupported_characters_in_path(server_path) - return os.path.join( - self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) - - @property - def backup_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) - - @property - def csr_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.CSR_DIR) - - @property - def in_progress_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) - - @property - def key_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.KEY_DIR) - - @property - def temp_checkpoint_dir(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) - - def __deepcopy__(self, _memo): - # Work around https://bugs.python.org/issue1515 for py26 tests :( :( - # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276 - new_ns = copy.deepcopy(self.namespace) - return type(self)(new_ns) - - @property - def default_archive_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) - - @property - def live_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) - - @property - def renewal_configs_dir(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) - - @property - def renewal_hooks_dir(self): - """Path to directory with hooks to run with the renew subcommand.""" - return os.path.join(self.namespace.config_dir, - constants.RENEWAL_HOOKS_DIR) - - @property - def renewal_pre_hooks_dir(self): - """Path to the pre-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_PRE_HOOKS_DIR) - - @property - def renewal_deploy_hooks_dir(self): - """Path to the deploy-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_DEPLOY_HOOKS_DIR) - - @property - def renewal_post_hooks_dir(self): - """Path to the post-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_POST_HOOKS_DIR) - - -def check_config_sanity(config): - """Validate command line options and display error message if - requirements are not met. - - :param config: IConfig instance holding user configuration - :type args: :class:`certbot.interfaces.IConfig` - - """ - # Port check - if config.http01_port == config.https_port: - raise errors.ConfigurationError( - "Trying to run http-01 and https-port " - "on the same port ({0})".format(config.https_port)) - - # Domain checks - if config.namespace.domains is not None: - for domain in config.namespace.domains: - # This may be redundant, but let's be paranoid - util.enforce_domain_sanity(domain) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 4cff508e5..25dd0f45a 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -9,7 +9,7 @@ import unittest import configobj import mock -from certbot import configuration +from certbot._internal import configuration from certbot import errors from certbot.compat import os from certbot.compat import filesystem diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index e1e090fb5..11dd1b967 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.configuration.""" +"""Tests for certbot._internal.configuration.""" import unittest import mock @@ -11,18 +11,18 @@ from certbot.tests import util as test_util class NamespaceConfigTest(test_util.ConfigTestCase): - """Tests for certbot.configuration.NamespaceConfig.""" + """Tests for certbot._internal.configuration.NamespaceConfig.""" def setUp(self): super(NamespaceConfigTest, self).setUp() - self.config.foo = 'bar' + self.config.foo = 'bar' # pylint: disable=blacklisted-name self.config.server = 'https://acme-server.org:443/new' self.config.https_port = 1234 self.config.http01_port = 4321 def test_init_same_ports(self): self.config.namespace.https_port = 4321 - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig self.assertRaises(errors.Error, NamespaceConfig, self.config.namespace) def test_proxy_getattr(self): @@ -38,7 +38,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.assertEqual(['user:pass@acme.server:443', 'p', 'a', 't', 'h'], self.config.server_path.split(os.path.sep)) - @mock.patch('certbot.configuration.constants') + @mock.patch('certbot._internal.configuration.constants') def test_dynamic_dirs(self, mock_constants): mock_constants.ACCOUNTS_DIR = 'acc' mock_constants.BACKUP_DIR = 'backups' @@ -70,7 +70,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): os.path.normpath(os.path.join(self.config.work_dir, 't'))) def test_absolute_paths(self): - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig config_base = "foo" work_base = "bar" @@ -103,7 +103,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.assertTrue(os.path.isabs(config.key_dir)) self.assertTrue(os.path.isabs(config.temp_checkpoint_dir)) - @mock.patch('certbot.configuration.constants') + @mock.patch('certbot._internal.configuration.constants') def test_renewal_dynamic_dirs(self, mock_constants): mock_constants.ARCHIVE_DIR = 'a' mock_constants.LIVE_DIR = 'l' @@ -118,7 +118,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.config.config_dir, 'renewal_configs')) def test_renewal_absolute_paths(self): - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig config_base = "foo" work_base = "bar" diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 835951b70..e27284ac2 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -23,7 +23,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- import certbot.tests.util as test_util from certbot._internal import account from certbot import cli -from certbot import configuration +from certbot._internal import configuration from certbot._internal import constants from certbot import crypto_util from certbot import errors diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index 612a5fcb2..dcbaed6a2 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -4,7 +4,7 @@ import mock from acme import challenges -from certbot import configuration +from certbot._internal import configuration from certbot import errors from certbot._internal import storage diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 00452fef9..d9ff18f1c 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -19,7 +19,7 @@ from six.moves import reload_module # pylint: disable=import-error from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization -from certbot import configuration +from certbot._internal import configuration from certbot._internal import constants from certbot import interfaces from certbot._internal import lock diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst deleted file mode 100644 index 4e99c73d2..000000000 --- a/docs/api/configuration.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.configuration` --------------------------------- - -.. automodule:: certbot.configuration - :members: -- cgit v1.2.3 From 4a8ede2562d0b77c23af89a7e550ba194eb4251e Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Wed, 13 Nov 2019 20:24:37 +0200 Subject: Deprecate certbot register --update-registration (#7556) Closes #7452. --- CHANGELOG.md | 1 + .../certbot_tests/test_main.py | 5 -- certbot/_internal/constants.py | 1 - certbot/_internal/main.py | 8 --- certbot/cli.py | 6 --- certbot/tests/main_test.py | 63 ---------------------- 6 files changed, 1 insertion(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7806b2ffe..e1eaed7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). `certbot.plugins.common.Installer.view_config_changes`, `certbot.reverter.Reverter.view_config_changes`, and `certbot.util.get_systemd_os_info` have been removed +* Certbot's `register --update-registration` subcommand has been removed ### Fixed diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 50c685a57..cd4c316d2 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -62,11 +62,6 @@ def test_registration_override(context): context.certbot(['unregister']) context.certbot(['register', '--email', 'ex1@domain.org,ex2@domain.org']) - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the two following deprecated uses - context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org']) - context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org,ex2@domain.org']) - context.certbot(['update_account', '--email', 'example@domain.org']) context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org']) diff --git a/certbot/_internal/constants.py b/certbot/_internal/constants.py index d8c3c2ae1..5ac7ee72d 100644 --- a/certbot/_internal/constants.py +++ b/certbot/_internal/constants.py @@ -32,7 +32,6 @@ CLI_DEFAULTS = dict( certname=None, dry_run=False, register_unsafely_without_email=False, - update_registration=False, email=None, eff_email=None, reinstall=False, diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index 8daca6514..bf6fd2b11 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -668,14 +668,6 @@ def register(config, unused_plugins): :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 'certbot 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) diff --git a/certbot/cli.py b/certbot/cli.py index 739eadd8a..f13dedbaa 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -980,12 +980,6 @@ 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"), dest="update_registration", - help=argparse.SUPPRESS) helpful.add( ["register", "update_account", "unregister", "automation"], "-m", "--email", default=flag_default("email"), diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index e27284ac2..789c0db42 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -1400,33 +1400,6 @@ 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]) - # 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._internal.main.client') as mocked_client: - with mock.patch('certbot._internal.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( - ["register", "--update-registration", "--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_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._internal.main.account') as mocked_account: - mocked_storage = mock.MagicMock() - mocked_account.AccountFileStorage.return_value = mocked_storage - mocked_storage.find_all.return_value = ["an account"] - x = self._call_no_clientmock( - "register --update-registration " - "--register-unsafely-without-email".split()) - self.assertTrue("--register-unsafely-without-email" in x[0]) - @mock.patch('certbot._internal.main.display_ops.get_email') @test_util.patch_get_utility() def test_update_account_with_email(self, mock_utility, mock_email): @@ -1457,42 +1430,6 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met 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._internal.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._internal.eff.handle_subscription') as mock_handle: - with mock.patch('certbot._internal.main._determine_account') as mocked_det: - with mock.patch('certbot._internal.main.account') as mocked_account: - with mock.patch('certbot._internal.main.client') as mocked_client: - mocked_storage = mock.MagicMock() - mocked_account.AccountFileStorage.return_value = mocked_storage - mocked_storage.find_all.return_value = ["an account"] - mock_acc = mock.MagicMock() - mock_regr = mock_acc.regr - mocked_det.return_value = (mock_acc, "foo") - cb_client = mock.MagicMock() - mocked_client.Client.return_value = cb_client - x = self._call_no_clientmock( - ["register", "--update-registration"]) - # 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 - reg_arg = cb_client.acme.update_registration.call_args[0][0] - # Test the return value of .update() was used because - # the regr is immutable. - self.assertEqual(reg_arg, mock_regr.update()) - # 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) - @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') @mock.patch('certbot._internal.updater._run_updaters') def test_plugin_selection_error(self, mock_run, mock_choose): -- cgit v1.2.3 From 57148b7593636e6aa1ddec21e06d2c324609e69e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 13 Nov 2019 11:14:26 -0800 Subject: Fix shebang in rebuild_deps (#7557) When you try to run this script, it crashes with: ``` standard_init_linux.go:211: exec user process caused "exec format error" ``` This is caused by the script being written to have the contents: ``` \ #!/bin/sh set -e ... ``` This fixes the problem by removing the slash and moving the shebang to the first line of the string. --- letsencrypt-auto-source/rebuild_dependencies.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index 0db787a0b..d98ae8706 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -62,8 +62,7 @@ CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) # without pinned dependencies, and respecting input authoritative requirements # - `certbot plugins` is called to check we have an healthy environment # - finally current set of dependencies is extracted out of the docker using pip freeze -SCRIPT = r"""\ -#!/bin/sh +SCRIPT = r"""#!/bin/sh set -e cd /tmp/certbot -- cgit v1.2.3 From 4d4c83d4d8a0f1c323ec5fc4f993335685562cf6 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 13 Nov 2019 11:14:46 -0800 Subject: Internalize modules called by internal plugins (#7543) * Move hooks.py to _internal * Move cli.py to _internal --- certbot/_internal/cli.py | 1584 ++++++++++++++++++++++++++++++++ certbot/_internal/client.py | 2 +- certbot/_internal/hooks.py | 272 ++++++ certbot/_internal/main.py | 4 +- certbot/_internal/plugins/manual.py | 2 +- certbot/_internal/plugins/selection.py | 4 +- certbot/_internal/plugins/webroot.py | 2 +- certbot/_internal/renewal.py | 4 +- certbot/_internal/storage.py | 2 +- certbot/cli.py | 1584 -------------------------------- certbot/hooks.py | 272 ------ certbot/plugins/enhancements.py | 4 +- certbot/tests/cli_test.py | 16 +- certbot/tests/client_test.py | 2 +- certbot/tests/hook_test.py | 84 +- certbot/tests/main_test.py | 6 +- certbot/tests/renewal_test.py | 2 +- certbot/tests/storage_test.py | 6 +- docs/api/cli.rst | 5 - docs/api/hooks.rst | 5 - 20 files changed, 1926 insertions(+), 1936 deletions(-) create mode 100644 certbot/_internal/cli.py create mode 100644 certbot/_internal/hooks.py delete mode 100644 certbot/cli.py delete mode 100644 certbot/hooks.py delete mode 100644 docs/api/cli.rst delete mode 100644 docs/api/hooks.rst diff --git a/certbot/_internal/cli.py b/certbot/_internal/cli.py new file mode 100644 index 000000000..ad20f6494 --- /dev/null +++ b/certbot/_internal/cli.py @@ -0,0 +1,1584 @@ +"""Certbot command line argument & config processing.""" +# pylint: disable=too-many-lines +from __future__ import print_function + +import argparse +import copy +import glob +import logging.handlers +import sys + +import configargparse +import six +import zope.component +import zope.interface +from zope.interface import interfaces as zope_interfaces + +from acme import challenges +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Dict, Optional +# pylint: enable=unused-import, no-name-in-module + +import certbot +import certbot.plugins.enhancements as enhancements +import certbot._internal.plugins.selection as plugin_selection +from certbot._internal import constants +from certbot import crypto_util +from certbot import errors +from certbot._internal import hooks +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot.display import util as display_util +from certbot._internal.plugins import disco as plugins_disco + +logger = logging.getLogger(__name__) + +# Global, to save us from a lot of argument passing within the scope of this module +helpful_parser = None # type: Optional[HelpfulArgumentParser] + +# For help strings, figure out how the user ran us. +# When invoked from letsencrypt-auto, sys.argv[0] is something like: +# "/home/user/.local/share/certbot/bin/certbot" +# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before +# running letsencrypt-auto (and sudo stops us from seeing if they did), so it +# should only be used for purposes where inability to detect letsencrypt-auto +# fails safely + +LEAUTO = "letsencrypt-auto" +if "CERTBOT_AUTO" in os.environ: + # if we're here, this is probably going to be certbot-auto, unless the + # user saved the script under a different name + LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) + +old_path_fragment = os.path.join(".local", "share", "letsencrypt") +new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", + "eff.org", "certbot", "venv")) +if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): + cli_command = LEAUTO +else: + cli_command = "certbot" + +# Argparse's help formatting has a lot of unhelpful peculiarities, so we want +# to replace as much of it as we can... + +# This is the stub to include in help generated by argparse +SHORT_USAGE = """ + {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... + +Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, +it will attempt to use a webserver both for obtaining and installing the +certificate. """.format(cli_command) + +# This section is used for --help and --help all ; it needs information +# about installed plugins to be fully formatted +COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: + +obtain, install, and renew certificates: + (default) run Obtain & install a certificate in your current webserver + certonly Obtain or renew a certificate, but do not install it + renew Renew all previously obtained certificates that are near expiry + enhance Add security enhancements to your existing configuration + -d DOMAINS Comma-separated list of domains to obtain a certificate for + + %s + --standalone Run a standalone webserver for authentication + %s + --webroot Place files in a server's webroot folder for authentication + --manual Obtain certificates interactively, or using shell script hooks + + -n Run non-interactively + --test-cert Obtain a test certificate from a staging server + --dry-run Test "renew" or "certonly" without saving any certificates to disk + +manage certificates: + certificates Display information about certificates you have from Certbot + revoke Revoke a certificate (supply --cert-path or --cert-name) + delete Delete a certificate + +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account + --agree-tos Agree to the ACME server's Subscriber Agreement + -m EMAIL Email address for important account notifications +""" + +# This is the short help for certbot --help, where we disable argparse +# altogether +HELP_AND_VERSION_USAGE = """ +More detailed help: + + -h, --help [TOPIC] print this message, or detailed help on a topic; + the available TOPICS are: + + all, automation, commands, paths, security, testing, or any of the + subcommands or plugins (certonly, renew, install, register, nginx, + apache, standalone, webroot, etc.) + -h all print a detailed help page including all topics + --version print the version number +""" + + +# These argparse parameters should be removed when detecting defaults. +ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) + + +# These sets are used when to help detect options set by the user. +EXIT_ACTIONS = set(("help", "version",)) + + +ZERO_ARG_ACTIONS = set(("store_const", "store_true", + "store_false", "append_const", "count",)) + + +# Maps a config option to a set of config options that may have modified it. +# This dictionary is used recursively, so if A modifies B and B modifies C, +# it is determined that C was modified by the user if A was modified. +VAR_MODIFIERS = {"account": set(("server",)), + "renew_hook": set(("deploy_hook",)), + "server": set(("dry_run", "staging",)), + "webroot_map": set(("webroot_path",))} + + +def report_config_interaction(modified, modifiers): + """Registers config option interaction to be checked by set_by_cli. + + This function can be called by during the __init__ or + add_parser_arguments methods of plugins to register interactions + between config options. + + :param modified: config options that can be modified by modifiers + :type modified: iterable or str (string_types) + :param modifiers: config options that modify modified + :type modifiers: iterable or str (string_types) + + """ + if isinstance(modified, six.string_types): + modified = (modified,) + if isinstance(modifiers, six.string_types): + modifiers = (modifiers,) + + for var in modified: + VAR_MODIFIERS.setdefault(var, set()).update(modifiers) + + +class _Default(object): + """A class to use as a default to detect if a value is set by a user""" + + def __bool__(self): + return False + + def __eq__(self, other): + return isinstance(other, _Default) + + def __hash__(self): + return id(_Default) + + def __nonzero__(self): + return self.__bool__() + + +def set_by_cli(var): + """ + Return True if a particular config variable has been set by the user + (CLI or config file) including if the user explicitly set it to the + default. Returns False if the variable was assigned a default value. + """ + detector = set_by_cli.detector # type: ignore + if detector is None and helpful_parser is not None: + # Setup on first run: `detector` is a weird version of config in which + # the default value of every attribute is wrangled to be boolean-false + plugins = plugins_disco.PluginsRegistry.find_all() + # reconstructed_args == sys.argv[1:], or whatever was passed to main() + reconstructed_args = helpful_parser.args + [helpful_parser.verb] + detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore + plugins, reconstructed_args, detect_defaults=True) + # propagate plugin requests: eg --standalone modifies config.authenticator + detector.authenticator, detector.installer = ( # type: ignore + plugin_selection.cli_plugin_requests(detector)) + + if not isinstance(getattr(detector, var), _Default): + logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) + return True + + for modifier in VAR_MODIFIERS.get(var, []): + if set_by_cli(modifier): + logger.debug("Var %s=%s (set by user).", + var, VAR_MODIFIERS.get(var, [])) + return True + + return False + +# static housekeeping var +# functions attributed are not supported by mypy +# https://github.com/python/mypy/issues/2087 +set_by_cli.detector = None # type: ignore + + +def has_default_value(option, value): + """Does option have the default value? + + If the default value of option is not known, False is returned. + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if option has the default value, otherwise, False + :rtype: bool + + """ + if helpful_parser is not None: + return (option in helpful_parser.defaults and + helpful_parser.defaults[option] == value) + return False + + +def option_was_set(option, value): + """Was option set by the user or does it differ from the default? + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if the option was set, otherwise, False + :rtype: bool + + """ + return set_by_cli(option) or not has_default_value(option, value) + + +def argparse_type(variable): + """Return our argparse type function for a config variable (default: str)""" + # pylint: disable=protected-access + if helpful_parser is not None: + for action in helpful_parser.parser._actions: + if action.type is not None and action.dest == variable: + return action.type + return str + +def read_file(filename, mode="rb"): + """Returns the given file's contents. + + :param str filename: path to file + :param str mode: open mode (see `open`) + + :returns: absolute path of filename and its contents + :rtype: tuple + + :raises argparse.ArgumentTypeError: File does not exist or is not readable. + + """ + try: + filename = os.path.abspath(filename) + with open(filename, mode) as the_file: + contents = the_file.read() + return filename, contents + except IOError as exc: + raise argparse.ArgumentTypeError(exc.strerror) + + +def flag_default(name): + """Default value for CLI flag.""" + # XXX: this is an internal housekeeping notion of defaults before + # argparse has been set up; it is not accurate for all flags. Call it + # with caution. Plugin defaults are missing, and some things are using + # defaults defined in this file, not in constants.py :( + return copy.deepcopy(constants.CLI_DEFAULTS[name]) + + +def config_help(name, hidden=False): + """Extract the help message for an `.IConfig` attribute.""" + # pylint: disable=no-member + if hidden: + return argparse.SUPPRESS + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter + return field.__doc__ + + +class HelpfulArgumentGroup(object): + """Emulates an argparse group for use with HelpfulArgumentParser. + + This class is used in the add_group method of HelpfulArgumentParser. + Command line arguments can be added to the group, but help + suppression and default detection is applied by + HelpfulArgumentParser when necessary. + + """ + def __init__(self, helpful_arg_parser, topic): + self._parser = helpful_arg_parser + self._topic = topic + + def add_argument(self, *args, **kwargs): + """Add a new command line argument to the argument group.""" + self._parser.add(self._topic, *args, **kwargs) + +class CustomHelpFormatter(argparse.HelpFormatter): + """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. + + In particular we fix https://bugs.python.org/issue28742 + """ + + def _get_help_string(self, action): + helpstr = action.help + if '%(default)' not in action.help and '(default:' not in action.help: + if action.default != argparse.SUPPRESS: + defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + helpstr += ' (default: %(default)s)' + return helpstr + +# The attributes here are: +# short: a string that will be displayed by "certbot -h commands" +# opts: a string that heads the section of flags with which this command is documented, +# both for "certbot -h SUBCOMMAND" and "certbot -h all" +# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" +VERB_HELP = [ + ("run (default)", { + "short": "Obtain/renew a certificate, and install it", + "opts": "Options for obtaining & installing certificates", + "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), + "realname": "run" + }), + ("certonly", { + "short": "Obtain or renew a certificate, but do not install it", + "opts": "Options for modifying how a certificate is obtained", + "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" + "This command obtains a TLS/SSL certificate without installing it anywhere.") + }), + ("renew", { + "short": "Renew all certificates (or one specified with --cert-name)", + "opts": ("The 'renew' subcommand will attempt to renew all" + " certificates (or more precisely, certificate lineages) you have" + " previously obtained if they are close to expiry, and print a" + " summary of the results. By default, 'renew' will reuse the options" + " used to create obtain or most recently successfully renew each" + " certificate lineage. You can try it with `--dry-run` first. For" + " more fine-grained control, you can renew individual lineages with" + " the `certonly` subcommand. Hooks are available to run commands" + " before and after renewal; see" + " https://certbot.eff.org/docs/using.html#renewal for more" + " information on these."), + "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" + }), + ("certificates", { + "short": "List certificates managed by Certbot", + "opts": "List certificates managed by Certbot", + "usage": ("\n\n certbot certificates [options] ...\n\n" + "Print information about the status of certificates managed by Certbot.") + }), + ("delete", { + "short": "Clean up all files related to a certificate", + "opts": "Options for deleting a certificate", + "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" + }), + ("revoke", { + "short": "Revoke a certificate specified with --cert-path or --cert-name", + "opts": "Options for revocation of certificates", + "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " + "--cert-name example.com] [options]\n\n" + }), + ("register", { + "short": "Register for account with Let's Encrypt / other ACME server", + "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.", + "usage": "\n\n certbot unregister [options]\n\n" + }), + ("install", { + "short": "Install an arbitrary certificate in a server", + "opts": "Options for modifying how a certificate is deployed", + "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " + " --key-path /path/to/private-key [options]\n\n" + }), + ("rollback", { + "short": "Roll back server conf changes made during certificate installation", + "opts": "Options for rolling back server configuration changes", + "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" + }), + ("plugins", { + "short": "List plugins that are installed and available on your system", + "opts": 'Options for the "plugins" subcommand', + "usage": "\n\n certbot plugins [options]\n\n" + }), + ("update_symlinks", { + "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", + "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " + "or edited a renewal configuration file".format( + os.path.join(flag_default("config_dir"), "live"))), + "usage": "\n\n certbot update_symlinks [options]\n\n" + }), + ("enhance", { + "short": "Add security enhancements to your existing configuration", + "opts": ("Helps to harden the TLS configuration by adding security enhancements " + "to already existing configuration."), + "usage": "\n\n certbot enhance [options]\n\n" + }), + +] +# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful +VERB_HELP_MAP = dict(VERB_HELP) + + +class HelpfulArgumentParser(object): + """Argparse Wrapper. + + This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'certbot --help security' for security options. + + """ + + + def __init__(self, args, plugins, detect_defaults=False): + from certbot._internal import main + self.VERBS = { + "auth": main.certonly, + "certonly": main.certonly, + "run": main.run, + "install": main.install, + "plugins": main.plugins_cmd, + "register": main.register, + "update_account": main.update_account, + "unregister": main.unregister, + "renew": main.renew, + "revoke": main.revoke, + "rollback": main.rollback, + "everything": main.run, + "update_symlinks": main.update_symlinks, + "certificates": main.certificates, + "delete": main.delete, + "enhance": main.enhance, + } + + # Get notification function for printing + try: + self.notify = zope.component.getUtility( + interfaces.IDisplay).notification + except zope_interfaces.ComponentLookupError: + self.notify = display_util.NoninteractiveDisplay( + sys.stdout).notification + + + # List of topics for which additional help can be provided + HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] + + plugin_names = list(plugins) + self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore + + self.detect_defaults = detect_defaults + self.args = args + + if self.args and self.args[0] == 'help': + self.args[0] = '--help' + + self.determine_verb() + help1 = self.prescan_for_flag("-h", self.help_topics) + help2 = self.prescan_for_flag("--help", self.help_topics) + if isinstance(help1, bool) and isinstance(help2, bool): + self.help_arg = help1 or help2 + else: + self.help_arg = help1 if isinstance(help1, six.string_types) else help2 + + short_usage = self._usage_string(plugins, self.help_arg) + + self.visible_topics = self.determine_help_topics(self.help_arg) + + # elements are added by .add_group() + self.groups = {} # type: Dict[str, argparse._ArgumentGroup] + # elements are added by .parse_args() + self.defaults = {} # type: Dict[str, Any] + + self.parser = configargparse.ArgParser( + prog="certbot", + usage=short_usage, + formatter_class=CustomHelpFormatter, + args_for_setting_config_path=["-c", "--config"], + default_config_files=flag_default("config_files"), + config_arg_help_message="path to config file (default: {0})".format( + " and ".join(flag_default("config_files")))) + + # This is the only way to turn off overly verbose config flag documentation + self.parser._add_config_file_help = False # pylint: disable=protected-access + + # Help that are synonyms for --help subcommands + COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] + def _list_subcommands(self): + longest = max(len(v) for v in VERB_HELP_MAP) + + text = "The full list of available SUBCOMMANDS is:\n\n" + for verb, props in sorted(VERB_HELP): + doc = props.get("short", "") + text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) + + text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" + return text + + def _usage_string(self, plugins, help_arg): + """Make usage strings late so that plugins can be initialised late + + :param plugins: all discovered plugins + :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC + :rtype: str + :returns: a short usage string for the top of --help TOPIC) + """ + if "nginx" in plugins: + nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" + else: + nginx_doc = "(the certbot nginx plugin is not installed)" + if "apache" in plugins: + apache_doc = "--apache Use the Apache plugin for authentication & installation" + else: + apache_doc = "(the certbot apache plugin is not installed)" + + usage = SHORT_USAGE + if help_arg is True: + self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) + sys.exit(0) + elif help_arg in self.COMMANDS_TOPICS: + self.notify(usage + self._list_subcommands()) + sys.exit(0) + elif help_arg == "all": + # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at + # the top; if we're doing --help someothertopic, it's OT so it's not + usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) + else: + custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) + usage = custom if custom else usage + + return usage + + def remove_config_file_domains_for_renewal(self, parsed_args): + """Make "certbot renew" safe if domains are set in cli.ini.""" + # Works around https://github.com/certbot/certbot/issues/4096 + if self.verb == "renew": + for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access + if source.startswith("config_file") and "domains" in flags: + parsed_args.domains = _Default() if self.detect_defaults else [] + + def parse_args(self): + """Parses command line arguments and returns the result. + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + parsed_args = self.parser.parse_args(self.args) + parsed_args.func = self.VERBS[self.verb] + parsed_args.verb = self.verb + + self.remove_config_file_domains_for_renewal(parsed_args) + + if self.detect_defaults: + return parsed_args + + self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) + for key in vars(parsed_args)) + + # Do any post-parsing homework here + + if self.verb == "renew": + if parsed_args.force_interactive: + raise errors.Error( + "{0} cannot be used with renew".format( + constants.FORCE_INTERACTIVE_FLAG)) + parsed_args.noninteractive_mode = True + + if parsed_args.force_interactive and parsed_args.noninteractive_mode: + raise errors.Error( + "Flag for non-interactive mode and {0} conflict".format( + constants.FORCE_INTERACTIVE_FLAG)) + + if parsed_args.staging or parsed_args.dry_run: + self.set_test_server(parsed_args) + + if parsed_args.csr: + self.handle_csr(parsed_args) + + if parsed_args.must_staple: + parsed_args.staple = True + + if parsed_args.validate_hooks: + hooks.validate_hooks(parsed_args) + + if parsed_args.allow_subset_of_names: + if any(util.is_wildcard_domain(d) for d in parsed_args.domains): + raise errors.Error("Using --allow-subset-of-names with a" + " wildcard domain is not supported.") + + if parsed_args.hsts and parsed_args.auto_hsts: + raise errors.Error( + "Parameters --hsts and --auto-hsts cannot be used simultaneously.") + + return parsed_args + + def set_test_server(self, parsed_args): + """We have --staging/--dry-run; perform sanity check and set config.server""" + + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server | Conflict error | Use | + + default_servers = (flag_default("server"), constants.STAGING_URI) + + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI + + if parsed_args.dry_run: + if self.verb not in ["certonly", "renew"]: + raise errors.Error("--dry-run currently only works with the " + "'certonly' or 'renew' subcommands (%r)" % self.verb) + parsed_args.break_my_certs = parsed_args.staging = True + if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): + # The user has a prod account, but might not have a staging + # one; we don't want to start trying to perform interactive registration + parsed_args.tos = True + parsed_args.register_unsafely_without_email = True + + def handle_csr(self, parsed_args): + """Process a --csr flag.""" + if parsed_args.verb != "certonly": + raise errors.Error("Currently, a CSR file may only be specified " + "when obtaining a new or replacement " + "via the certonly command. Please try the " + "certonly command instead.") + if parsed_args.allow_subset_of_names: + raise errors.Error("--allow-subset-of-names cannot be used with --csr") + + csrfile, contents = parsed_args.csr[0:2] + typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) + + # This is not necessary for webroot to work, however, + # obtain_certificate_from_csr requires parsed_args.domains to be set + for domain in domains: + add_domains(parsed_args, domain) + + if not domains: + # TODO: add CN to domains instead: + raise errors.Error( + "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" + % parsed_args.csr[0]) + + parsed_args.actual_csr = (csr, typ) + + csr_domains = set([d.lower() for d in domains]) + config_domains = set(parsed_args.domains) + if csr_domains != config_domains: + raise errors.ConfigurationError( + "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" + .format(", ".join(csr_domains), ", ".join(config_domains))) + + + def determine_verb(self): + """Determines the verb/subcommand provided by the user. + + This function works around some of the limitations of argparse. + + """ + if "-h" in self.args or "--help" in self.args: + # all verbs double as help arguments; don't get them confused + self.verb = "help" + return + + for i, token in enumerate(self.args): + if token in self.VERBS: + verb = token + if verb == "auth": + verb = "certonly" + if verb == "everything": + verb = "run" + self.verb = verb + self.args.pop(i) + return + + self.verb = "run" + + def prescan_for_flag(self, flag, possible_arguments): + """Checks cli input for flags. + + Check for a flag, which accepts a fixed set of possible arguments, in + the command line; we will use this information to configure argparse's + help correctly. Return the flag's argument, if it has one that matches + the sequence @possible_arguments; otherwise return whether the flag is + present. + + """ + if flag not in self.args: + return False + pos = self.args.index(flag) + try: + nxt = self.args[pos + 1] + if nxt in possible_arguments: + return nxt + except IndexError: + pass + return True + + def add(self, topics, *args, **kwargs): + """Add a new command line argument. + + :param topics: str or [str] help topic(s) this should be listed under, + or None for options that don't fit under a specific + topic which will only be shown in "--help all" output. + The first entry determines where the flag lives in the + "--help all" output (None -> "optional arguments"). + :param list *args: the names of this argument flag + :param dict **kwargs: various argparse settings for this argument + + """ + + if isinstance(topics, list): + # if this flag can be listed in multiple sections, try to pick the one + # that the user has asked for help about + topic = self.help_arg if self.help_arg in topics else topics[0] + else: + topic = topics # there's only one + + if self.detect_defaults: + kwargs = self.modify_kwargs_for_default_detection(**kwargs) + + if self.visible_topics[topic]: + if topic in self.groups: + group = self.groups[topic] + group.add_argument(*args, **kwargs) + else: + self.parser.add_argument(*args, **kwargs) + else: + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) + + def modify_kwargs_for_default_detection(self, **kwargs): + """Modify an arg so we can check if it was set by the user. + + Changes the parameters given to argparse when adding an argument + so we can properly detect if the value was set by the user. + + :param dict kwargs: various argparse settings for this argument + + :returns: a modified versions of kwargs + :rtype: dict + + """ + action = kwargs.get("action", None) + if action not in EXIT_ACTIONS: + kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else + "store") + kwargs["default"] = _Default() + for param in ARGPARSE_PARAMS_TO_REMOVE: + kwargs.pop(param, None) + + return kwargs + + def add_deprecated_argument(self, argument_name, num_args): + """Adds a deprecated argument with the name argument_name. + + Deprecated arguments are not shown in the help. If they are used + on the command line, a warning is shown stating that the + argument is deprecated and no other action is taken. + + :param str argument_name: Name of deprecated argument. + :param int nargs: Number of arguments the option takes. + + """ + util.add_deprecated_argument( + self.parser.add_argument, argument_name, num_args) + + def add_group(self, topic, verbs=(), **kwargs): + """Create a new argument group. + + This method must be called once for every topic, however, calls + to this function are left next to the argument definitions for + clarity. + + :param str topic: Name of the new argument group. + :param str verbs: List of subcommands that should be documented as part of + this help group / topic + + :returns: The new argument group. + :rtype: `HelpfulArgumentGroup` + + """ + if self.visible_topics[topic]: + self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) + if self.help_arg: + for v in verbs: + self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) + return HelpfulArgumentGroup(self, topic) + + def add_plugin_args(self, plugins): + """ + + Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics. + + """ + for name, plugin_ep in six.iteritems(plugins): + parser_or_group = self.add_group(name, + description=plugin_ep.long_description) + plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) + + def determine_help_topics(self, chosen_topic): + """ + + The user may have requested help on a topic, return a dict of which + topics to display. @chosen_topic has prescan_for_flag's return type + + :returns: dict + + """ + # topics maps each topic to whether it should be documented by + # argparse on the command line + if chosen_topic == "auth": + chosen_topic = "certonly" + if chosen_topic == "everything": + chosen_topic = "run" + if chosen_topic == "all": + # 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]) + return dict([(t, t == chosen_topic) for t in self.help_topics]) + +def _add_all_groups(helpful): + helpful.add_group("automation", description="Flags for automating execution & other tweaks") + helpful.add_group("security", description="Security parameters & server settings") + helpful.add_group("testing", + description="The following flags are meant for testing and integration purposes only.") + helpful.add_group("paths", description="Flags for changing execution paths & servers") + helpful.add_group("manage", + description="Various subcommands and flags are available for managing your certificates:", + verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) + + # VERBS + for verb, docs in VERB_HELP: + name = docs.get("realname", verb) + helpful.add_group(name, description=docs["opts"]) + + +def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: disable=too-many-statements + """Returns parsed command line arguments. + + :param .PluginsRegistry plugins: available plugins + :param list args: command line arguments with the program name removed + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + + # pylint: disable=too-many-statements + + helpful = HelpfulArgumentParser(args, plugins, detect_defaults) + _add_all_groups(helpful) + + # --help is automatically provided by argparse + helpful.add( + None, "-v", "--verbose", dest="verbose_count", action="count", + default=flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") + helpful.add( + None, "-t", "--text", dest="text_mode", action="store_true", + default=flag_default("text_mode"), help=argparse.SUPPRESS) + helpful.add( + None, "--max-log-backups", type=nonnegative_int, + default=flag_default("max_log_backups"), + help="Specifies the maximum number of backup logs that should " + "be kept by Certbot's built in log rotation. Setting this " + "flag to 0 disables log rotation entirely, causing " + "Certbot to always append to the same log file.") + helpful.add( + [None, "automation", "run", "certonly", "enhance"], + "-n", "--non-interactive", "--noninteractive", + dest="noninteractive_mode", action="store_true", + default=flag_default("noninteractive_mode"), + help="Run without ever asking for user input. This may require " + "additional command line flags; the client will try to explain " + "which ones are required if it finds one missing") + helpful.add( + [None, "register", "run", "certonly", "enhance"], + constants.FORCE_INTERACTIVE_FLAG, action="store_true", + default=flag_default("force_interactive"), + help="Force Certbot to be interactive even if it detects it's not " + "being run in a terminal. This flag cannot be used with the " + "renew subcommand.") + helpful.add( + [None, "run", "certonly", "certificates", "enhance"], + "-d", "--domains", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction, + default=flag_default("domains"), + help="Domain names to apply. For multiple domains you can use " + "multiple -d flags or enter a comma separated list of domains " + "as a parameter. The first domain provided will be the " + "subject CN of the certificate, and all domains will be " + "Subject Alternative Names on the certificate. " + "The first domain will also be used in " + "some software user interfaces and as the file paths for the " + "certificate and related material unless otherwise " + "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", + metavar="CERTNAME", default=flag_default("certname"), + help="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 certificate names, run 'certbot certificates'. " + "When creating a new certificate, specifies the new certificate's name. " + "(default: the first provided domain or the name of an existing " + "certificate on your system for the same domains)") + helpful.add( + [None, "testing", "renew", "certonly"], + "--dry-run", action="store_true", dest="dry_run", + default=flag_default("dry_run"), + help="Perform a test run of the client, obtaining test (invalid) certificates" + " but not saving them to disk. This can currently only be used" + " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" + " tries to avoid making any persistent changes on a system, it " + " is not completely side-effect free: if used with webserver authenticator plugins" + " like apache and nginx, it makes and then reverts temporary config changes" + " in order to obtain test certificates, and reloads webservers to deploy and then" + " roll back those changes. It also calls --pre-hook and --post-hook commands" + " if they are defined because they may be necessary to accurately simulate" + " renewal. --deploy-hook commands are not called.") + helpful.add( + ["register", "automation"], "--register-unsafely-without-email", action="store_true", + default=flag_default("register_unsafely_without_email"), + help="Specifying this flag enables registering an account with no " + "email address. This is strongly discouraged, because in the " + "event of key loss or account compromise you will irrevocably " + "lose access to your account. You will also be unable to receive " + "notice about impending expiration or revocation of your " + "certificates. Updates to the Subscriber Agreement will still " + "affect you, and will be effective 14 days after posting an " + "update to the web site.") + helpful.add( + ["register", "update_account", "unregister", "automation"], "-m", "--email", + default=flag_default("email"), + help=config_help("email")) + 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", "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"], + "--keep-until-expiring", "--keep", "--reinstall", + dest="reinstall", action="store_true", default=flag_default("reinstall"), + help="If the requested certificate matches an existing certificate, always keep the " + "existing one until it is due for renewal (for the " + "'run' subcommand this means reinstall the existing certificate). (default: Ask)") + helpful.add( + "automation", "--expand", action="store_true", default=flag_default("expand"), + help="If an existing certificate is a strict subset of the requested names, " + "always expand and replace it with the additional names. (default: Ask)") + helpful.add( + "automation", "--version", action="version", + version="%(prog)s {0}".format(certbot.__version__), + help="show program's version number and exit") + helpful.add( + ["automation", "renew"], + "--force-renewal", "--renew-by-default", dest="renew_by_default", + action="store_true", default=flag_default("renew_by_default"), + help="If a certificate " + "already exists for the requested domains, renew it now, " + "regardless of whether it is near expiry. (Often " + "--keep-until-expiring is more appropriate). Also implies " + "--expand.") + helpful.add( + "automation", "--renew-with-new-domains", dest="renew_with_new_domains", + action="store_true", default=flag_default("renew_with_new_domains"), + help="If a " + "certificate already exists for the requested certificate name " + "but does not match the requested domains, renew it now, " + "regardless of whether it is near expiry.") + helpful.add( + "automation", "--reuse-key", dest="reuse_key", + action="store_true", default=flag_default("reuse_key"), + help="When renewing, use the same private key as the existing " + "certificate.") + + helpful.add( + ["automation", "renew", "certonly"], + "--allow-subset-of-names", action="store_true", + default=flag_default("allow_subset_of_names"), + help="When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This option cannot be used with --csr.") + helpful.add( + "automation", "--agree-tos", dest="tos", action="store_true", + default=flag_default("tos"), + help="Agree to the ACME Subscriber Agreement (default: Ask)") + helpful.add( + ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", + default=flag_default("account"), + help="Account ID to use") + helpful.add( + "automation", "--duplicate", dest="duplicate", action="store_true", + default=flag_default("duplicate"), + help="Allow making a certificate lineage that duplicates an existing one " + "(both can be renewed in parallel)") + helpful.add( + "automation", "--os-packages-only", action="store_true", + default=flag_default("os_packages_only"), + help="(certbot-auto only) install OS package dependencies and then stop") + helpful.add( + "automation", "--no-self-upgrade", action="store_true", + default=flag_default("no_self_upgrade"), + help="(certbot-auto only) prevent the certbot-auto script from" + " upgrading itself to newer released versions (default: Upgrade" + " automatically)") + helpful.add( + "automation", "--no-bootstrap", action="store_true", + default=flag_default("no_bootstrap"), + help="(certbot-auto only) prevent the certbot-auto script from" + " installing OS-level dependencies (default: Prompt to install " + " OS-wide dependencies, but exit if the user says 'No')") + helpful.add( + "automation", "--no-permissions-check", action="store_true", + default=flag_default("no_permissions_check"), + help="(certbot-auto only) skip the check on the file system" + " permissions of the certbot-auto script") + helpful.add( + ["automation", "renew", "certonly", "run"], + "-q", "--quiet", dest="quiet", action="store_true", + default=flag_default("quiet"), + help="Silence all output except errors. Useful for automation via cron." + " Implies --non-interactive.") + # overwrites server, handled in HelpfulArgumentParser.parse_args() + helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", + dest="staging", action="store_true", default=flag_default("staging"), + help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" + " to --server " + constants.STAGING_URI) + helpful.add( + "testing", "--debug", action="store_true", default=flag_default("debug"), + help="Show tracebacks in case of errors, and allow certbot-auto " + "execution on experimental platforms") + helpful.add( + [None, "certonly", "run"], "--debug-challenges", action="store_true", + default=flag_default("debug_challenges"), + help="After setting up challenges, wait for user input before " + "submitting to CA") + helpful.add( + "testing", "--no-verify-ssl", action="store_true", + help=config_help("no_verify_ssl"), + default=flag_default("no_verify_ssl")) + helpful.add( + ["testing", "standalone", "manual"], "--http-01-port", type=int, + dest="http01_port", + default=flag_default("http01_port"), help=config_help("http01_port")) + helpful.add( + ["testing", "standalone"], "--http-01-address", + dest="http01_address", + default=flag_default("http01_address"), help=config_help("http01_address")) + helpful.add( + ["testing", "nginx"], "--https-port", type=int, + default=flag_default("https_port"), + help=config_help("https_port")) + helpful.add( + "testing", "--break-my-certs", action="store_true", + default=flag_default("break_my_certs"), + help="Be willing to replace or renew valid certificates with invalid " + "(testing/staging) certificates") + helpful.add( + "security", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) + helpful.add( + "security", "--must-staple", action="store_true", + dest="must_staple", default=flag_default("must_staple"), + help=config_help("must_staple")) + helpful.add( + ["security", "enhance"], + "--redirect", action="store_true", dest="redirect", + default=flag_default("redirect"), + help="Automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + "security", "--no-redirect", action="store_false", dest="redirect", + default=flag_default("redirect"), + help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + ["security", "enhance"], + "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), + help="Add the Strict-Transport-Security header to every HTTP response." + " Forcing browser to always use SSL for the domain." + " Defends against SSL Stripping.") + helpful.add( + "security", "--no-hsts", action="store_false", dest="hsts", + default=flag_default("hsts"), help=argparse.SUPPRESS) + helpful.add( + ["security", "enhance"], + "--uir", action="store_true", dest="uir", default=flag_default("uir"), + help='Add the "Content-Security-Policy: upgrade-insecure-requests"' + ' header to every HTTP response. Forcing the browser to use' + ' https:// for every http:// resource.') + helpful.add( + "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), + help=argparse.SUPPRESS) + helpful.add( + "security", "--staple-ocsp", action="store_true", dest="staple", + default=flag_default("staple"), + help="Enables OCSP Stapling. A valid OCSP response is stapled to" + " the certificate that the server offers during TLS.") + helpful.add( + "security", "--no-staple-ocsp", action="store_false", dest="staple", + default=flag_default("staple"), help=argparse.SUPPRESS) + helpful.add( + "security", "--strict-permissions", action="store_true", + default=flag_default("strict_permissions"), + help="Require that all configuration files are owned by the current " + "user; only needed if your config is somewhere unsafe like /tmp/") + helpful.add( + ["manual", "standalone", "certonly", "renew"], + "--preferred-challenges", dest="pref_challs", + action=_PrefChallAction, default=flag_default("pref_challs"), + help='A sorted, comma delimited list of the preferred challenge to ' + 'use during authorization with the most preferred challenge ' + 'listed first (Eg, "dns" or "http,dns"). ' + 'Not all plugins support all challenges. See ' + 'https://certbot.eff.org/docs/using.html#plugins for details. ' + 'ACME Challenges are versioned, but if you pick "http" rather ' + 'than "http-01", Certbot will select the latest version ' + 'automatically.') + helpful.add( + "renew", "--pre-hook", + help="Command to be run in a shell before obtaining any certificates." + " Intended primarily for renewal, where it can be used to temporarily" + " shut down a webserver that might conflict with the standalone" + " plugin. This will only be called if a certificate is actually to be" + " obtained/renewed. When renewing several certificates that have" + " identical pre-hooks, only the first will be executed.") + helpful.add( + "renew", "--post-hook", + help="Command to be run in a shell after attempting to obtain/renew" + " certificates. Can be used to deploy renewed certificates, or to" + " restart any servers that were stopped by --pre-hook. This is only" + " run if an attempt was made to obtain/renew a certificate. If" + " multiple renewed certificates have identical post-hooks, only" + " 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' + ' issued certificate. For this command, the shell variable' + ' $RENEWED_LINEAGE will point to the config live subdirectory' + ' (for example, "/etc/letsencrypt/live/example.com") containing' + ' the new certificates and keys; the shell variable' + ' $RENEWED_DOMAINS will contain a space-delimited list of' + ' renewed certificate domains (for example, "example.com' + ' www.example.com"') + helpful.add( + "renew", "--disable-hook-validation", + action="store_false", dest="validate_hooks", + default=flag_default("validate_hooks"), + help="Ordinarily the commands specified for" + " --pre-hook/--post-hook/--deploy-hook will be checked for" + " validity, to see if the programs being run are in the $PATH," + " so that mistakes can be caught early, even when the hooks" + " aren't being run just yet. The validation is rather" + " simplistic and fails if you use more advanced shell" + " constructs, so you can use this switch to disable it." + " (default: False)") + helpful.add( + "renew", "--no-directory-hooks", action="store_false", + default=flag_default("directory_hooks"), dest="directory_hooks", + help="Disable running executables found in Certbot's hook directories" + " during renewal. (default: False)") + helpful.add( + "renew", "--disable-renew-updates", action="store_true", + default=flag_default("disable_renew_updates"), dest="disable_renew_updates", + help="Disable automatic updates to your server configuration that" + " would otherwise be done by the selected installer plugin, and triggered" + " when the user executes \"certbot renew\", regardless of if the certificate" + " is renewed. This setting does not apply to important TLS configuration" + " updates.") + helpful.add( + "renew", "--no-autorenew", action="store_false", + default=flag_default("autorenew"), dest="autorenew", + help="Disable auto renewal of certificates.") + + # Populate the command line parameters for new style enhancements + enhancements.populate_cli(helpful.add) + + _create_subparsers(helpful) + _paths_parser(helpful) + # _plugins_parsing should be the last thing to act upon the main + # parser (--help should display plugin-specific options last) + _plugins_parsing(helpful, plugins) + + if not detect_defaults: + global helpful_parser # pylint: disable=global-statement + helpful_parser = helpful + return helpful.parse_args() + + +def _create_subparsers(helpful): + from certbot._internal.client import sample_user_agent # avoid import loops + helpful.add( + None, "--user-agent", default=flag_default("user_agent"), + help='Set a custom user agent string for the client. User agent strings allow ' + 'the CA to collect high level statistics about success rates by OS, ' + 'plugin and use 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: {0}). The flags encoded in the user agent are: ' + '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' + 'whether any hooks are set.'.format(sample_user_agent())) + helpful.add( + None, "--user-agent-comment", default=flag_default("user_agent_comment"), + type=_user_agent_comment_type, + help="Add a comment to the default user agent string. May be used when repackaging Certbot " + "or calling it from another tool to allow additional statistical data to be collected." + " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") + helpful.add("certonly", + "--csr", default=flag_default("csr"), type=read_file, + help="Path to a Certificate Signing Request (CSR) in DER or PEM format." + " Currently --csr only works with the 'certonly' subcommand.") + helpful.add("revoke", + "--reason", dest="reason", + choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, + key=constants.REVOCATION_REASONS.get)), + action=_EncodeReasonAction, default=flag_default("reason"), + help="Specify reason for revoking certificate. (default: unspecified)") + helpful.add("revoke", + "--delete-after-revoke", action="store_true", + default=flag_default("delete_after_revoke"), + help="Delete certificates after revoking them, along with all previous and later " + "versions of those certificates.") + helpful.add("revoke", + "--no-delete-after-revoke", action="store_false", + dest="delete_after_revoke", + default=flag_default("delete_after_revoke"), + help="Do not delete certificates after revoking them. This " + "option should be used with caution because the 'renew' " + "subcommand will attempt to renew undeleted revoked " + "certificates.") + helpful.add("rollback", + "--checkpoints", type=int, metavar="N", + default=flag_default("rollback_checkpoints"), + help="Revert configuration N number of checkpoints.") + helpful.add("plugins", + "--init", action="store_true", default=flag_default("init"), + help="Initialize plugins.") + helpful.add("plugins", + "--prepare", action="store_true", default=flag_default("prepare"), + help="Initialize and prepare plugins.") + helpful.add("plugins", + "--authenticators", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") + helpful.add("plugins", + "--installers", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IInstaller, help="Limit to installer plugins only.") + + +class CaseInsensitiveList(list): + """A list that will ignore case when searching. + + This class is passed to the `choices` argument of `argparse.add_arguments` + through the `helpful` wrapper. It is necessary due to special handling of + command line arguments by `set_by_cli` in which the `type_func` is not applied.""" + def __contains__(self, element): + return super(CaseInsensitiveList, self).__contains__(element.lower()) + + +def _paths_parser(helpful): + add = helpful.add + verb = helpful.verb + if verb == "help": + verb = helpful.help_arg + + cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." + sections = ["paths", "install", "revoke", "certonly", "manage"] + if verb == "certonly": + add(sections, "--cert-path", type=os.path.abspath, + default=flag_default("auth_cert_path"), help=cph) + elif verb == "revoke": + add(sections, "--cert-path", type=read_file, required=False, help=cph) + else: + add(sections, "--cert-path", type=os.path.abspath, help=cph) + + section = "paths" + if verb in ("install", "revoke"): + section = verb + # revoke --key-path reads a file, install --key-path takes a string + add(section, "--key-path", + type=((verb == "revoke" and read_file) or os.path.abspath), + help="Path to private key for certificate installation " + "or revocation (if account key is missing)") + + default_cp = None + if verb == "certonly": + default_cp = flag_default("auth_chain_path") + add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a full certificate chain (certificate plus chain).") + add("paths", "--chain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a certificate chain.") + add("paths", "--config-dir", default=flag_default("config_dir"), + help=config_help("config_dir")) + add("paths", "--work-dir", default=flag_default("work_dir"), + help=config_help("work_dir")) + add("paths", "--logs-dir", default=flag_default("logs_dir"), + help="Logs directory.") + add("paths", "--server", default=flag_default("server"), + help=config_help("server")) + + +def _plugins_parsing(helpful, plugins): + # It's nuts, but there are two "plugins" topics. Somehow this works + helpful.add_group( + "plugins", description="Plugin Selection: Certbot client supports an " + "extensible plugins architecture. See '%(prog)s plugins' for a " + "list of all installed plugins and their names. You can force " + "a particular plugin by setting options provided below. Running " + "--help will list flags specific to that plugin.") + + helpful.add("plugins", "--configurator", default=flag_default("configurator"), + help="Name of the plugin that is both an authenticator and an installer." + " Should not be used together with --authenticator or --installer. " + "(default: Ask)") + helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), + help="Authenticator plugin name.") + helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), + help="Installer plugin name (also used to find domains).") + helpful.add(["plugins", "certonly", "run", "install"], + "--apache", action="store_true", default=flag_default("apache"), + help="Obtain and install certificates using Apache") + helpful.add(["plugins", "certonly", "run", "install"], + "--nginx", action="store_true", default=flag_default("nginx"), + help="Obtain and install certificates using Nginx") + helpful.add(["plugins", "certonly"], "--standalone", action="store_true", + default=flag_default("standalone"), + help='Obtain certificates using a "standalone" webserver.') + helpful.add(["plugins", "certonly"], "--manual", action="store_true", + default=flag_default("manual"), + help="Provide laborious manual instructions for obtaining a certificate") + helpful.add(["plugins", "certonly"], "--webroot", action="store_true", + default=flag_default("webroot"), + help="Obtain certificates by placing files in a webroot directory.") + helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", + default=flag_default("dns_cloudflare"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Cloudflare for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", + default=flag_default("dns_cloudxns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using CloudXNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", + default=flag_default("dns_digitalocean"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DigitalOcean for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", + default=flag_default("dns_dnsimple"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNSimple for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", + default=flag_default("dns_dnsmadeeasy"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNS Made Easy for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", + default=flag_default("dns_gehirn"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Gehirn Infrastracture Service for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", + default=flag_default("dns_google"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Google Cloud DNS).")) + helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", + default=flag_default("dns_linode"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Linode for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", + default=flag_default("dns_luadns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using LuaDNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", + default=flag_default("dns_nsone"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using NS1 for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", + default=flag_default("dns_ovh"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using OVH for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", + default=flag_default("dns_rfc2136"), + help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") + helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", + default=flag_default("dns_route53"), + help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " + "DNS).")) + helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", + default=flag_default("dns_sakuracloud"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Sakura Cloud for DNS).")) + + # things should not be reorder past/pre this comment: + # plugins_group should be displayed in --help before plugin + # specific groups (so that plugins_group.description makes sense) + + helpful.add_plugin_args(plugins) + + +class _EncodeReasonAction(argparse.Action): + """Action class for parsing revocation reason.""" + + def __call__(self, parser, namespace, reason, option_string=None): + """Encodes the reason for certificate revocation.""" + code = constants.REVOCATION_REASONS[reason.lower()] + setattr(namespace, self.dest, code) + + +class _DomainsAction(argparse.Action): + """Action class for parsing domains.""" + + def __call__(self, parser, namespace, domain, option_string=None): + """Just wrap add_domains in argparseese.""" + add_domains(namespace, domain) + +def add_domains(args_or_config, domains): + """Registers new domains to be used during the current client run. + + Domains are not added to the list of requested domains if they have + already been registered. + + :param args_or_config: parsed command line arguments + :type args_or_config: argparse.Namespace or + configuration.NamespaceConfig + :param str domain: one or more comma separated domains + + :returns: domains after they have been normalized and validated + :rtype: `list` of `str` + + """ + validated_domains = [] + for domain in domains.split(","): + domain = util.enforce_domain_sanity(domain.strip()) + validated_domains.append(domain) + if domain not in args_or_config.domains: + args_or_config.domains.append(domain) + + return validated_domains + +class _PrefChallAction(argparse.Action): + """Action class for parsing preferred challenges.""" + + def __call__(self, parser, namespace, pref_challs, option_string=None): + try: + challs = parse_preferred_challenges(pref_challs.split(",")) + except errors.Error as error: + raise argparse.ArgumentError(self, str(error)) + namespace.pref_challs.extend(challs) + + +def parse_preferred_challenges(pref_challs): + """Translate and validate preferred challenges. + + :param pref_challs: list of preferred challenge types + :type pref_challs: `list` of `str` + + :returns: validated list of preferred challenge types + :rtype: `list` of `str` + + :raises errors.Error: if pref_challs is invalid + + """ + aliases = {"dns": "dns-01", "http": "http-01"} + challs = [c.strip() for c in pref_challs] + challs = [aliases.get(c, c) for c in challs] + + unrecognized = ", ".join(name for name in challs + if name not in challenges.Challenge.TYPES) + if unrecognized: + raise errors.Error( + "Unrecognized challenges: {0}".format(unrecognized)) + return challs + + +def _user_agent_comment_type(value): + if "(" in value or ")" in value: + raise argparse.ArgumentTypeError("may not contain parentheses") + return value + + +class _DeployHookAction(argparse.Action): + """Action class for parsing deploy hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + renew_hook_set = namespace.deploy_hook != namespace.renew_hook + if renew_hook_set and namespace.renew_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --renew-hook value") + namespace.deploy_hook = namespace.renew_hook = values + + +class _RenewHookAction(argparse.Action): + """Action class for parsing renew hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + deploy_hook_set = namespace.deploy_hook is not None + if deploy_hook_set and namespace.deploy_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --deploy-hook value") + namespace.renew_hook = values + + +def nonnegative_int(value): + """Converts value to an int and checks that it is not negative. + + This function should used as the type parameter for argparse + arguments. + + :param str value: value provided on the command line + + :returns: integer representation of value + :rtype: int + + :raises argparse.ArgumentTypeError: if value isn't a non-negative integer + + """ + try: + int_value = int(value) + except ValueError: + raise argparse.ArgumentTypeError("value must be an integer") + + if int_value < 0: + raise argparse.ArgumentTypeError("value must be non-negative") + return int_value diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py index f694a0ae7..2a9a52e73 100644 --- a/certbot/_internal/client.py +++ b/certbot/_internal/client.py @@ -20,7 +20,7 @@ from acme.magic_typing import Optional, List # pylint: disable=unused-import,no import certbot from certbot._internal import account from certbot._internal import auth_handler -from certbot import cli +from certbot._internal import cli from certbot._internal import constants from certbot import crypto_util from certbot._internal import eff diff --git a/certbot/_internal/hooks.py b/certbot/_internal/hooks.py new file mode 100644 index 000000000..1bb3a2eab --- /dev/null +++ b/certbot/_internal/hooks.py @@ -0,0 +1,272 @@ +"""Facilities for implementing hooks that call shell commands.""" +from __future__ import print_function + +import logging +from subprocess import Popen, PIPE + +from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot import util +from certbot.compat import filesystem +from certbot.compat import os +from certbot.plugins import util as plug_util + +logger = logging.getLogger(__name__) + + +def validate_hooks(config): + """Check hook commands are executable.""" + validate_hook(config.pre_hook, "pre") + validate_hook(config.post_hook, "post") + validate_hook(config.deploy_hook, "deploy") + validate_hook(config.renew_hook, "renew") + + +def _prog(shell_cmd): + """Extract the program run by a shell command. + + :param str shell_cmd: command to be executed + + :returns: basename of command or None if the command isn't found + :rtype: str or None + + """ + if not util.exe_exists(shell_cmd): + plug_util.path_surgery(shell_cmd) + if not util.exe_exists(shell_cmd): + return None + return os.path.basename(shell_cmd) + + +def validate_hook(shell_cmd, hook_name): + """Check that a command provided as a hook is plausibly executable. + + :raises .errors.HookCommandNotFound: if the command is not found + """ + if shell_cmd: + cmd = shell_cmd.split(None, 1)[0] + if not _prog(cmd): + path = os.environ["PATH"] + if os.path.exists(cmd): + msg = "{1}-hook command {0} exists, but is not executable.".format(cmd, hook_name) + else: + msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format( + cmd, path, hook_name) + + raise errors.HookCommandNotFound(msg) + + +def pre_hook(config): + """Run pre-hooks if they exist and haven't already been run. + + When Certbot is running with the renew subcommand, this function + runs any hooks found in the config.renewal_pre_hooks_dir (if they + have not already been run) followed by any pre-hook in the config. + If hooks in config.renewal_pre_hooks_dir are run and the pre-hook in + the config is a path to one of these scripts, it is not run twice. + + :param configuration.NamespaceConfig config: Certbot settings + + """ + if config.verb == "renew" and config.directory_hooks: + for hook in list_hooks(config.renewal_pre_hooks_dir): + _run_pre_hook_if_necessary(hook) + + cmd = config.pre_hook + if cmd: + _run_pre_hook_if_necessary(cmd) + + +executed_pre_hooks = set() # type: Set[str] + + +def _run_pre_hook_if_necessary(command): + """Run the specified pre-hook if we haven't already. + + If we've already run this exact command before, a message is logged + saying the pre-hook was skipped. + + :param str command: pre-hook to be run + + """ + if command in executed_pre_hooks: + logger.info("Pre-hook command already run, skipping: %s", command) + else: + _run_hook("pre-hook", command) + executed_pre_hooks.add(command) + + +def post_hook(config): + """Run post-hooks if defined. + + This function also registers any executables found in + config.renewal_post_hooks_dir to be run when Certbot is used with + the renew subcommand. + + If the verb is renew, we delay executing any post-hooks until + :func:`run_saved_post_hooks` is called. In this case, this function + registers all hooks found in config.renewal_post_hooks_dir to be + called followed by any post-hook in the config. If the post-hook in + the config is a path to an executable in the post-hook directory, it + is not scheduled to be run twice. + + :param configuration.NamespaceConfig config: Certbot settings + + """ + + cmd = config.post_hook + # In the "renew" case, we save these up to run at the end + if config.verb == "renew": + if config.directory_hooks: + for hook in list_hooks(config.renewal_post_hooks_dir): + _run_eventually(hook) + if cmd: + _run_eventually(cmd) + # certonly / run + elif cmd: + _run_hook("post-hook", cmd) + + +post_hooks = [] # type: List[str] + + +def _run_eventually(command): + """Registers a post-hook to be run eventually. + + All commands given to this function will be run exactly once in the + order they were given when :func:`run_saved_post_hooks` is called. + + :param str command: post-hook to register to be run + + """ + if command not in post_hooks: + post_hooks.append(command) + + +def run_saved_post_hooks(): + """Run any post hooks that were saved up in the course of the 'renew' verb""" + for cmd in post_hooks: + _run_hook("post-hook", cmd) + + +def deploy_hook(config, domains, lineage_path): + """Run post-issuance hook if defined. + + :param configuration.NamespaceConfig config: Certbot settings + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + + """ + if config.deploy_hook: + _run_deploy_hook(config.deploy_hook, domains, + lineage_path, config.dry_run) + + +def renew_hook(config, domains, lineage_path): + """Run post-renewal hooks. + + This function runs any hooks found in + config.renewal_deploy_hooks_dir followed by any renew-hook in the + config. If the renew-hook in the config is a path to a script in + config.renewal_deploy_hooks_dir, it is not run twice. + + If Certbot is doing a dry run, no hooks are run and messages are + logged saying that they were skipped. + + :param configuration.NamespaceConfig config: Certbot settings + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + + """ + executed_dir_hooks = set() + if config.directory_hooks: + for hook in list_hooks(config.renewal_deploy_hooks_dir): + _run_deploy_hook(hook, domains, lineage_path, config.dry_run) + executed_dir_hooks.add(hook) + + if config.renew_hook: + if config.renew_hook in executed_dir_hooks: + logger.info("Skipping deploy-hook '%s' as it was already run.", + config.renew_hook) + else: + _run_deploy_hook(config.renew_hook, domains, + lineage_path, config.dry_run) + + +def _run_deploy_hook(command, domains, lineage_path, dry_run): + """Run the specified deploy-hook (if not doing a dry run). + + If dry_run is True, command is not run and a message is logged + saying that it was skipped. If dry_run is False, the hook is run + after setting the appropriate environment variables. + + :param str command: command to run as a deploy-hook + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + :param bool dry_run: True iff Certbot is doing a dry run + + """ + if dry_run: + logger.warning("Dry run: skipping deploy hook command: %s", + command) + return + + os.environ["RENEWED_DOMAINS"] = " ".join(domains) + os.environ["RENEWED_LINEAGE"] = lineage_path + _run_hook("deploy-hook", command) + + +def _run_hook(cmd_name, shell_cmd): + """Run a hook command. + + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` + + :returns: stderr if there was any""" + err, _ = execute(cmd_name, shell_cmd) + return err + + +def execute(cmd_name, shell_cmd): + """Run a command. + + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` + + :returns: `tuple` (`str` stderr, `str` stdout)""" + logger.info("Running %s command: %s", cmd_name, shell_cmd) + + # universal_newlines causes Popen.communicate() + # to return str objects instead of bytes in Python 3 + cmd = Popen(shell_cmd, shell=True, stdout=PIPE, + stderr=PIPE, universal_newlines=True) + out, err = cmd.communicate() + base_cmd = os.path.basename(shell_cmd.split(None, 1)[0]) + if out: + logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out) + if cmd.returncode != 0: + logger.error('%s command "%s" returned error code %d', + cmd_name, shell_cmd, cmd.returncode) + if err: + logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) + return err, out + + +def list_hooks(dir_path): + """List paths to all hooks found in dir_path in sorted order. + + :param str dir_path: directory to search + + :returns: `list` of `str` + :rtype: sorted list of absolute paths to executables in dir_path + + """ + allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) + hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] + return sorted(hooks) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index bf6fd2b11..e58c5285f 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -16,14 +16,14 @@ from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in import certbot from certbot._internal import account from certbot._internal import cert_manager -from certbot import cli +from certbot._internal import cli from certbot._internal import client from certbot._internal import configuration from certbot._internal import constants from certbot import crypto_util from certbot._internal import eff from certbot import errors -from certbot import hooks +from certbot._internal import hooks from certbot import interfaces from certbot._internal import log from certbot._internal import renewal diff --git a/certbot/_internal/plugins/manual.py b/certbot/_internal/plugins/manual.py index 4bb11de3f..43f70d650 100644 --- a/certbot/_internal/plugins/manual.py +++ b/certbot/_internal/plugins/manual.py @@ -7,7 +7,7 @@ from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in- from certbot import achallenges # pylint: disable=unused-import from certbot import errors -from certbot import hooks +from certbot._internal import hooks from certbot import interfaces from certbot import reverter from certbot.compat import os diff --git a/certbot/_internal/plugins/selection.py b/certbot/_internal/plugins/selection.py index ae3d8448b..1ac3456d3 100644 --- a/certbot/_internal/plugins/selection.py +++ b/certbot/_internal/plugins/selection.py @@ -197,7 +197,7 @@ def choose_configurator_plugins(config, plugins, verb): # Which plugins do we need? if verb == "run": need_inst = need_auth = True - from certbot.cli import cli_command + from certbot._internal.cli import cli_command if req_auth in noninstaller_plugins and not req_inst: msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' '{1} {2} certonly --{0}{1}{1}' @@ -328,7 +328,7 @@ def diagnose_configurator_problem(cfg_type, requested, plugins): "your existing configuration.\nThe error was: {1!r}" .format(requested, plugins[requested].problem)) elif cfg_type == "installer": - from certbot.cli import cli_command + from certbot._internal.cli import cli_command msg = ('Certbot doesn\'t know how to automatically configure the web ' 'server on this system. However, it can still get a certificate for ' 'you. Please run "{0} certonly" to do so. You\'ll need to ' diff --git a/certbot/_internal/plugins/webroot.py b/certbot/_internal/plugins/webroot.py index 33e6530a1..b87b3092a 100644 --- a/certbot/_internal/plugins/webroot.py +++ b/certbot/_internal/plugins/webroot.py @@ -15,7 +15,7 @@ from acme.magic_typing import Dict, Set, DefaultDict, List # pylint: enable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import -from certbot import cli +from certbot._internal import cli from certbot import errors from certbot import interfaces from certbot.compat import os diff --git a/certbot/_internal/renewal.py b/certbot/_internal/renewal.py index 31833c806..5f2f90d63 100644 --- a/certbot/_internal/renewal.py +++ b/certbot/_internal/renewal.py @@ -15,10 +15,10 @@ import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from certbot import cli +from certbot._internal import cli from certbot import crypto_util from certbot import errors -from certbot import hooks +from certbot._internal import hooks from certbot import interfaces from certbot._internal import storage from certbot._internal import updater diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py index b7b878dab..f7e7d5e7f 100644 --- a/certbot/_internal/storage.py +++ b/certbot/_internal/storage.py @@ -12,7 +12,7 @@ import pytz import six import certbot -from certbot import cli +from certbot._internal import cli from certbot._internal import constants from certbot import crypto_util from certbot._internal import error_handler diff --git a/certbot/cli.py b/certbot/cli.py deleted file mode 100644 index f13dedbaa..000000000 --- a/certbot/cli.py +++ /dev/null @@ -1,1584 +0,0 @@ -"""Certbot command line argument & config processing.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import argparse -import copy -import glob -import logging.handlers -import sys - -import configargparse -import six -import zope.component -import zope.interface -from zope.interface import interfaces as zope_interfaces - -from acme import challenges -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Dict, Optional -# pylint: enable=unused-import, no-name-in-module - -import certbot -import certbot.plugins.enhancements as enhancements -import certbot._internal.plugins.selection as plugin_selection -from certbot._internal import constants -from certbot import crypto_util -from certbot import errors -from certbot import hooks -from certbot import interfaces -from certbot import util -from certbot.compat import os -from certbot.display import util as display_util -from certbot._internal.plugins import disco as plugins_disco - -logger = logging.getLogger(__name__) - -# Global, to save us from a lot of argument passing within the scope of this module -helpful_parser = None # type: Optional[HelpfulArgumentParser] - -# For help strings, figure out how the user ran us. -# When invoked from letsencrypt-auto, sys.argv[0] is something like: -# "/home/user/.local/share/certbot/bin/certbot" -# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before -# running letsencrypt-auto (and sudo stops us from seeing if they did), so it -# should only be used for purposes where inability to detect letsencrypt-auto -# fails safely - -LEAUTO = "letsencrypt-auto" -if "CERTBOT_AUTO" in os.environ: - # if we're here, this is probably going to be certbot-auto, unless the - # user saved the script under a different name - LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) - -old_path_fragment = os.path.join(".local", "share", "letsencrypt") -new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", - "eff.org", "certbot", "venv")) -if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): - cli_command = LEAUTO -else: - cli_command = "certbot" - -# Argparse's help formatting has a lot of unhelpful peculiarities, so we want -# to replace as much of it as we can... - -# This is the stub to include in help generated by argparse -SHORT_USAGE = """ - {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... - -Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, -it will attempt to use a webserver both for obtaining and installing the -certificate. """.format(cli_command) - -# This section is used for --help and --help all ; it needs information -# about installed plugins to be fully formatted -COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: - -obtain, install, and renew certificates: - (default) run Obtain & install a certificate in your current webserver - certonly Obtain or renew a certificate, but do not install it - renew Renew all previously obtained certificates that are near expiry - enhance Add security enhancements to your existing configuration - -d DOMAINS Comma-separated list of domains to obtain a certificate for - - %s - --standalone Run a standalone webserver for authentication - %s - --webroot Place files in a server's webroot folder for authentication - --manual Obtain certificates interactively, or using shell script hooks - - -n Run non-interactively - --test-cert Obtain a test certificate from a staging server - --dry-run Test "renew" or "certonly" without saving any certificates to disk - -manage certificates: - certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate - -manage your account: - register Create an ACME account - unregister Deactivate an ACME account - update_account Update an ACME account - --agree-tos Agree to the ACME server's Subscriber Agreement - -m EMAIL Email address for important account notifications -""" - -# This is the short help for certbot --help, where we disable argparse -# altogether -HELP_AND_VERSION_USAGE = """ -More detailed help: - - -h, --help [TOPIC] print this message, or detailed help on a topic; - the available TOPICS are: - - all, automation, commands, paths, security, testing, or any of the - subcommands or plugins (certonly, renew, install, register, nginx, - apache, standalone, webroot, etc.) - -h all print a detailed help page including all topics - --version print the version number -""" - - -# These argparse parameters should be removed when detecting defaults. -ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) - - -# These sets are used when to help detect options set by the user. -EXIT_ACTIONS = set(("help", "version",)) - - -ZERO_ARG_ACTIONS = set(("store_const", "store_true", - "store_false", "append_const", "count",)) - - -# Maps a config option to a set of config options that may have modified it. -# This dictionary is used recursively, so if A modifies B and B modifies C, -# it is determined that C was modified by the user if A was modified. -VAR_MODIFIERS = {"account": set(("server",)), - "renew_hook": set(("deploy_hook",)), - "server": set(("dry_run", "staging",)), - "webroot_map": set(("webroot_path",))} - - -def report_config_interaction(modified, modifiers): - """Registers config option interaction to be checked by set_by_cli. - - This function can be called by during the __init__ or - add_parser_arguments methods of plugins to register interactions - between config options. - - :param modified: config options that can be modified by modifiers - :type modified: iterable or str (string_types) - :param modifiers: config options that modify modified - :type modifiers: iterable or str (string_types) - - """ - if isinstance(modified, six.string_types): - modified = (modified,) - if isinstance(modifiers, six.string_types): - modifiers = (modifiers,) - - for var in modified: - VAR_MODIFIERS.setdefault(var, set()).update(modifiers) - - -class _Default(object): - """A class to use as a default to detect if a value is set by a user""" - - def __bool__(self): - return False - - def __eq__(self, other): - return isinstance(other, _Default) - - def __hash__(self): - return id(_Default) - - def __nonzero__(self): - return self.__bool__() - - -def set_by_cli(var): - """ - Return True if a particular config variable has been set by the user - (CLI or config file) including if the user explicitly set it to the - default. Returns False if the variable was assigned a default value. - """ - detector = set_by_cli.detector # type: ignore - if detector is None and helpful_parser is not None: - # Setup on first run: `detector` is a weird version of config in which - # the default value of every attribute is wrangled to be boolean-false - plugins = plugins_disco.PluginsRegistry.find_all() - # reconstructed_args == sys.argv[1:], or whatever was passed to main() - reconstructed_args = helpful_parser.args + [helpful_parser.verb] - detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore - plugins, reconstructed_args, detect_defaults=True) - # propagate plugin requests: eg --standalone modifies config.authenticator - detector.authenticator, detector.installer = ( # type: ignore - plugin_selection.cli_plugin_requests(detector)) - - if not isinstance(getattr(detector, var), _Default): - logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) - return True - - for modifier in VAR_MODIFIERS.get(var, []): - if set_by_cli(modifier): - logger.debug("Var %s=%s (set by user).", - var, VAR_MODIFIERS.get(var, [])) - return True - - return False - -# static housekeeping var -# functions attributed are not supported by mypy -# https://github.com/python/mypy/issues/2087 -set_by_cli.detector = None # type: ignore - - -def has_default_value(option, value): - """Does option have the default value? - - If the default value of option is not known, False is returned. - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if option has the default value, otherwise, False - :rtype: bool - - """ - if helpful_parser is not None: - return (option in helpful_parser.defaults and - helpful_parser.defaults[option] == value) - return False - - -def option_was_set(option, value): - """Was option set by the user or does it differ from the default? - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if the option was set, otherwise, False - :rtype: bool - - """ - return set_by_cli(option) or not has_default_value(option, value) - - -def argparse_type(variable): - """Return our argparse type function for a config variable (default: str)""" - # pylint: disable=protected-access - if helpful_parser is not None: - for action in helpful_parser.parser._actions: - if action.type is not None and action.dest == variable: - return action.type - return str - -def read_file(filename, mode="rb"): - """Returns the given file's contents. - - :param str filename: path to file - :param str mode: open mode (see `open`) - - :returns: absolute path of filename and its contents - :rtype: tuple - - :raises argparse.ArgumentTypeError: File does not exist or is not readable. - - """ - try: - filename = os.path.abspath(filename) - with open(filename, mode) as the_file: - contents = the_file.read() - return filename, contents - except IOError as exc: - raise argparse.ArgumentTypeError(exc.strerror) - - -def flag_default(name): - """Default value for CLI flag.""" - # XXX: this is an internal housekeeping notion of defaults before - # argparse has been set up; it is not accurate for all flags. Call it - # with caution. Plugin defaults are missing, and some things are using - # defaults defined in this file, not in constants.py :( - return copy.deepcopy(constants.CLI_DEFAULTS[name]) - - -def config_help(name, hidden=False): - """Extract the help message for an `.IConfig` attribute.""" - # pylint: disable=no-member - if hidden: - return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter - return field.__doc__ - - -class HelpfulArgumentGroup(object): - """Emulates an argparse group for use with HelpfulArgumentParser. - - This class is used in the add_group method of HelpfulArgumentParser. - Command line arguments can be added to the group, but help - suppression and default detection is applied by - HelpfulArgumentParser when necessary. - - """ - def __init__(self, helpful_arg_parser, topic): - self._parser = helpful_arg_parser - self._topic = topic - - def add_argument(self, *args, **kwargs): - """Add a new command line argument to the argument group.""" - self._parser.add(self._topic, *args, **kwargs) - -class CustomHelpFormatter(argparse.HelpFormatter): - """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. - - In particular we fix https://bugs.python.org/issue28742 - """ - - def _get_help_string(self, action): - helpstr = action.help - if '%(default)' not in action.help and '(default:' not in action.help: - if action.default != argparse.SUPPRESS: - defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - helpstr += ' (default: %(default)s)' - return helpstr - -# The attributes here are: -# short: a string that will be displayed by "certbot -h commands" -# opts: a string that heads the section of flags with which this command is documented, -# both for "certbot -h SUBCOMMAND" and "certbot -h all" -# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" -VERB_HELP = [ - ("run (default)", { - "short": "Obtain/renew a certificate, and install it", - "opts": "Options for obtaining & installing certificates", - "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), - "realname": "run" - }), - ("certonly", { - "short": "Obtain or renew a certificate, but do not install it", - "opts": "Options for modifying how a certificate is obtained", - "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" - "This command obtains a TLS/SSL certificate without installing it anywhere.") - }), - ("renew", { - "short": "Renew all certificates (or one specified with --cert-name)", - "opts": ("The 'renew' subcommand will attempt to renew all" - " certificates (or more precisely, certificate lineages) you have" - " previously obtained if they are close to expiry, and print a" - " summary of the results. By default, 'renew' will reuse the options" - " used to create obtain or most recently successfully renew each" - " certificate lineage. You can try it with `--dry-run` first. For" - " more fine-grained control, you can renew individual lineages with" - " the `certonly` subcommand. Hooks are available to run commands" - " before and after renewal; see" - " https://certbot.eff.org/docs/using.html#renewal for more" - " information on these."), - "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" - }), - ("certificates", { - "short": "List certificates managed by Certbot", - "opts": "List certificates managed by Certbot", - "usage": ("\n\n certbot certificates [options] ...\n\n" - "Print information about the status of certificates managed by Certbot.") - }), - ("delete", { - "short": "Clean up all files related to a certificate", - "opts": "Options for deleting a certificate", - "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" - }), - ("revoke", { - "short": "Revoke a certificate specified with --cert-path or --cert-name", - "opts": "Options for revocation of certificates", - "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " - "--cert-name example.com] [options]\n\n" - }), - ("register", { - "short": "Register for account with Let's Encrypt / other ACME server", - "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.", - "usage": "\n\n certbot unregister [options]\n\n" - }), - ("install", { - "short": "Install an arbitrary certificate in a server", - "opts": "Options for modifying how a certificate is deployed", - "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " - " --key-path /path/to/private-key [options]\n\n" - }), - ("rollback", { - "short": "Roll back server conf changes made during certificate installation", - "opts": "Options for rolling back server configuration changes", - "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" - }), - ("plugins", { - "short": "List plugins that are installed and available on your system", - "opts": 'Options for the "plugins" subcommand', - "usage": "\n\n certbot plugins [options]\n\n" - }), - ("update_symlinks", { - "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", - "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " - "or edited a renewal configuration file".format( - os.path.join(flag_default("config_dir"), "live"))), - "usage": "\n\n certbot update_symlinks [options]\n\n" - }), - ("enhance", { - "short": "Add security enhancements to your existing configuration", - "opts": ("Helps to harden the TLS configuration by adding security enhancements " - "to already existing configuration."), - "usage": "\n\n certbot enhance [options]\n\n" - }), - -] -# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful -VERB_HELP_MAP = dict(VERB_HELP) - - -class HelpfulArgumentParser(object): - """Argparse Wrapper. - - This class wraps argparse, adding the ability to make --help less - verbose, and request help on specific subcategories at a time, eg - 'certbot --help security' for security options. - - """ - - - def __init__(self, args, plugins, detect_defaults=False): - from certbot._internal import main - self.VERBS = { - "auth": main.certonly, - "certonly": main.certonly, - "run": main.run, - "install": main.install, - "plugins": main.plugins_cmd, - "register": main.register, - "update_account": main.update_account, - "unregister": main.unregister, - "renew": main.renew, - "revoke": main.revoke, - "rollback": main.rollback, - "everything": main.run, - "update_symlinks": main.update_symlinks, - "certificates": main.certificates, - "delete": main.delete, - "enhance": main.enhance, - } - - # Get notification function for printing - try: - self.notify = zope.component.getUtility( - interfaces.IDisplay).notification - except zope_interfaces.ComponentLookupError: - self.notify = display_util.NoninteractiveDisplay( - sys.stdout).notification - - - # List of topics for which additional help can be provided - HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] - HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] - - plugin_names = list(plugins) - self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore - - self.detect_defaults = detect_defaults - self.args = args - - if self.args and self.args[0] == 'help': - self.args[0] = '--help' - - self.determine_verb() - help1 = self.prescan_for_flag("-h", self.help_topics) - help2 = self.prescan_for_flag("--help", self.help_topics) - if isinstance(help1, bool) and isinstance(help2, bool): - self.help_arg = help1 or help2 - else: - self.help_arg = help1 if isinstance(help1, six.string_types) else help2 - - short_usage = self._usage_string(plugins, self.help_arg) - - self.visible_topics = self.determine_help_topics(self.help_arg) - - # elements are added by .add_group() - self.groups = {} # type: Dict[str, argparse._ArgumentGroup] - # elements are added by .parse_args() - self.defaults = {} # type: Dict[str, Any] - - self.parser = configargparse.ArgParser( - prog="certbot", - usage=short_usage, - formatter_class=CustomHelpFormatter, - args_for_setting_config_path=["-c", "--config"], - default_config_files=flag_default("config_files"), - config_arg_help_message="path to config file (default: {0})".format( - " and ".join(flag_default("config_files")))) - - # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False # pylint: disable=protected-access - - # Help that are synonyms for --help subcommands - COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] - def _list_subcommands(self): - longest = max(len(v) for v in VERB_HELP_MAP) - - text = "The full list of available SUBCOMMANDS is:\n\n" - for verb, props in sorted(VERB_HELP): - doc = props.get("short", "") - text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) - - text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" - return text - - def _usage_string(self, plugins, help_arg): - """Make usage strings late so that plugins can be initialised late - - :param plugins: all discovered plugins - :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC - :rtype: str - :returns: a short usage string for the top of --help TOPIC) - """ - if "nginx" in plugins: - nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" - else: - nginx_doc = "(the certbot nginx plugin is not installed)" - if "apache" in plugins: - apache_doc = "--apache Use the Apache plugin for authentication & installation" - else: - apache_doc = "(the certbot apache plugin is not installed)" - - usage = SHORT_USAGE - if help_arg is True: - self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) - sys.exit(0) - elif help_arg in self.COMMANDS_TOPICS: - self.notify(usage + self._list_subcommands()) - sys.exit(0) - elif help_arg == "all": - # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at - # the top; if we're doing --help someothertopic, it's OT so it's not - usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) - else: - custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) - usage = custom if custom else usage - - return usage - - def remove_config_file_domains_for_renewal(self, parsed_args): - """Make "certbot renew" safe if domains are set in cli.ini.""" - # Works around https://github.com/certbot/certbot/issues/4096 - if self.verb == "renew": - for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access - if source.startswith("config_file") and "domains" in flags: - parsed_args.domains = _Default() if self.detect_defaults else [] - - def parse_args(self): - """Parses command line arguments and returns the result. - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - parsed_args = self.parser.parse_args(self.args) - parsed_args.func = self.VERBS[self.verb] - parsed_args.verb = self.verb - - self.remove_config_file_domains_for_renewal(parsed_args) - - if self.detect_defaults: - return parsed_args - - self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) - for key in vars(parsed_args)) - - # Do any post-parsing homework here - - if self.verb == "renew": - if parsed_args.force_interactive: - raise errors.Error( - "{0} cannot be used with renew".format( - constants.FORCE_INTERACTIVE_FLAG)) - parsed_args.noninteractive_mode = True - - if parsed_args.force_interactive and parsed_args.noninteractive_mode: - raise errors.Error( - "Flag for non-interactive mode and {0} conflict".format( - constants.FORCE_INTERACTIVE_FLAG)) - - if parsed_args.staging or parsed_args.dry_run: - self.set_test_server(parsed_args) - - if parsed_args.csr: - self.handle_csr(parsed_args) - - if parsed_args.must_staple: - parsed_args.staple = True - - if parsed_args.validate_hooks: - hooks.validate_hooks(parsed_args) - - if parsed_args.allow_subset_of_names: - if any(util.is_wildcard_domain(d) for d in parsed_args.domains): - raise errors.Error("Using --allow-subset-of-names with a" - " wildcard domain is not supported.") - - if parsed_args.hsts and parsed_args.auto_hsts: - raise errors.Error( - "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - - return parsed_args - - def set_test_server(self, parsed_args): - """We have --staging/--dry-run; perform sanity check and set config.server""" - - # Flag combinations should produce these results: - # | --staging | --dry-run | - # ------------------------------------------------------------ - # | --server acme-v02 | Use staging | Use staging | - # | --server acme-staging-v02 | Use staging | Use staging | - # | --server | Conflict error | Use | - - default_servers = (flag_default("server"), constants.STAGING_URI) - - if parsed_args.staging and parsed_args.server not in default_servers: - raise errors.Error("--server value conflicts with --staging") - - if parsed_args.server in default_servers: - parsed_args.server = constants.STAGING_URI - - if parsed_args.dry_run: - if self.verb not in ["certonly", "renew"]: - raise errors.Error("--dry-run currently only works with the " - "'certonly' or 'renew' subcommands (%r)" % self.verb) - parsed_args.break_my_certs = parsed_args.staging = True - if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): - # The user has a prod account, but might not have a staging - # one; we don't want to start trying to perform interactive registration - parsed_args.tos = True - parsed_args.register_unsafely_without_email = True - - def handle_csr(self, parsed_args): - """Process a --csr flag.""" - if parsed_args.verb != "certonly": - raise errors.Error("Currently, a CSR file may only be specified " - "when obtaining a new or replacement " - "via the certonly command. Please try the " - "certonly command instead.") - if parsed_args.allow_subset_of_names: - raise errors.Error("--allow-subset-of-names cannot be used with --csr") - - csrfile, contents = parsed_args.csr[0:2] - typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) - - # This is not necessary for webroot to work, however, - # obtain_certificate_from_csr requires parsed_args.domains to be set - for domain in domains: - add_domains(parsed_args, domain) - - if not domains: - # TODO: add CN to domains instead: - raise errors.Error( - "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" - % parsed_args.csr[0]) - - parsed_args.actual_csr = (csr, typ) - - csr_domains = set([d.lower() for d in domains]) - config_domains = set(parsed_args.domains) - if csr_domains != config_domains: - raise errors.ConfigurationError( - "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" - .format(", ".join(csr_domains), ", ".join(config_domains))) - - - def determine_verb(self): - """Determines the verb/subcommand provided by the user. - - This function works around some of the limitations of argparse. - - """ - if "-h" in self.args or "--help" in self.args: - # all verbs double as help arguments; don't get them confused - self.verb = "help" - return - - for i, token in enumerate(self.args): - if token in self.VERBS: - verb = token - if verb == "auth": - verb = "certonly" - if verb == "everything": - verb = "run" - self.verb = verb - self.args.pop(i) - return - - self.verb = "run" - - def prescan_for_flag(self, flag, possible_arguments): - """Checks cli input for flags. - - Check for a flag, which accepts a fixed set of possible arguments, in - the command line; we will use this information to configure argparse's - help correctly. Return the flag's argument, if it has one that matches - the sequence @possible_arguments; otherwise return whether the flag is - present. - - """ - if flag not in self.args: - return False - pos = self.args.index(flag) - try: - nxt = self.args[pos + 1] - if nxt in possible_arguments: - return nxt - except IndexError: - pass - return True - - def add(self, topics, *args, **kwargs): - """Add a new command line argument. - - :param topics: str or [str] help topic(s) this should be listed under, - or None for options that don't fit under a specific - topic which will only be shown in "--help all" output. - The first entry determines where the flag lives in the - "--help all" output (None -> "optional arguments"). - :param list *args: the names of this argument flag - :param dict **kwargs: various argparse settings for this argument - - """ - - if isinstance(topics, list): - # if this flag can be listed in multiple sections, try to pick the one - # that the user has asked for help about - topic = self.help_arg if self.help_arg in topics else topics[0] - else: - topic = topics # there's only one - - if self.detect_defaults: - kwargs = self.modify_kwargs_for_default_detection(**kwargs) - - if self.visible_topics[topic]: - if topic in self.groups: - group = self.groups[topic] - group.add_argument(*args, **kwargs) - else: - self.parser.add_argument(*args, **kwargs) - else: - kwargs["help"] = argparse.SUPPRESS - self.parser.add_argument(*args, **kwargs) - - def modify_kwargs_for_default_detection(self, **kwargs): - """Modify an arg so we can check if it was set by the user. - - Changes the parameters given to argparse when adding an argument - so we can properly detect if the value was set by the user. - - :param dict kwargs: various argparse settings for this argument - - :returns: a modified versions of kwargs - :rtype: dict - - """ - action = kwargs.get("action", None) - if action not in EXIT_ACTIONS: - kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else - "store") - kwargs["default"] = _Default() - for param in ARGPARSE_PARAMS_TO_REMOVE: - kwargs.pop(param, None) - - return kwargs - - def add_deprecated_argument(self, argument_name, num_args): - """Adds a deprecated argument with the name argument_name. - - Deprecated arguments are not shown in the help. If they are used - on the command line, a warning is shown stating that the - argument is deprecated and no other action is taken. - - :param str argument_name: Name of deprecated argument. - :param int nargs: Number of arguments the option takes. - - """ - util.add_deprecated_argument( - self.parser.add_argument, argument_name, num_args) - - def add_group(self, topic, verbs=(), **kwargs): - """Create a new argument group. - - This method must be called once for every topic, however, calls - to this function are left next to the argument definitions for - clarity. - - :param str topic: Name of the new argument group. - :param str verbs: List of subcommands that should be documented as part of - this help group / topic - - :returns: The new argument group. - :rtype: `HelpfulArgumentGroup` - - """ - if self.visible_topics[topic]: - self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) - if self.help_arg: - for v in verbs: - self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) - return HelpfulArgumentGroup(self, topic) - - def add_plugin_args(self, plugins): - """ - - Let each of the plugins add its own command line arguments, which - may or may not be displayed as help topics. - - """ - for name, plugin_ep in six.iteritems(plugins): - parser_or_group = self.add_group(name, - description=plugin_ep.long_description) - plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) - - def determine_help_topics(self, chosen_topic): - """ - - The user may have requested help on a topic, return a dict of which - topics to display. @chosen_topic has prescan_for_flag's return type - - :returns: dict - - """ - # topics maps each topic to whether it should be documented by - # argparse on the command line - if chosen_topic == "auth": - chosen_topic = "certonly" - if chosen_topic == "everything": - chosen_topic = "run" - if chosen_topic == "all": - # 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]) - return dict([(t, t == chosen_topic) for t in self.help_topics]) - -def _add_all_groups(helpful): - helpful.add_group("automation", description="Flags for automating execution & other tweaks") - helpful.add_group("security", description="Security parameters & server settings") - helpful.add_group("testing", - description="The following flags are meant for testing and integration purposes only.") - helpful.add_group("paths", description="Flags for changing execution paths & servers") - helpful.add_group("manage", - description="Various subcommands and flags are available for managing your certificates:", - verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) - - # VERBS - for verb, docs in VERB_HELP: - name = docs.get("realname", verb) - helpful.add_group(name, description=docs["opts"]) - - -def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: disable=too-many-statements - """Returns parsed command line arguments. - - :param .PluginsRegistry plugins: available plugins - :param list args: command line arguments with the program name removed - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - - # pylint: disable=too-many-statements - - helpful = HelpfulArgumentParser(args, plugins, detect_defaults) - _add_all_groups(helpful) - - # --help is automatically provided by argparse - helpful.add( - None, "-v", "--verbose", dest="verbose_count", action="count", - default=flag_default("verbose_count"), help="This flag can be used " - "multiple times to incrementally increase the verbosity of output, " - "e.g. -vvv.") - helpful.add( - None, "-t", "--text", dest="text_mode", action="store_true", - default=flag_default("text_mode"), help=argparse.SUPPRESS) - helpful.add( - None, "--max-log-backups", type=nonnegative_int, - default=flag_default("max_log_backups"), - help="Specifies the maximum number of backup logs that should " - "be kept by Certbot's built in log rotation. Setting this " - "flag to 0 disables log rotation entirely, causing " - "Certbot to always append to the same log file.") - helpful.add( - [None, "automation", "run", "certonly", "enhance"], - "-n", "--non-interactive", "--noninteractive", - dest="noninteractive_mode", action="store_true", - default=flag_default("noninteractive_mode"), - help="Run without ever asking for user input. This may require " - "additional command line flags; the client will try to explain " - "which ones are required if it finds one missing") - helpful.add( - [None, "register", "run", "certonly", "enhance"], - constants.FORCE_INTERACTIVE_FLAG, action="store_true", - default=flag_default("force_interactive"), - help="Force Certbot to be interactive even if it detects it's not " - "being run in a terminal. This flag cannot be used with the " - "renew subcommand.") - helpful.add( - [None, "run", "certonly", "certificates", "enhance"], - "-d", "--domains", "--domain", dest="domains", - metavar="DOMAIN", action=_DomainsAction, - default=flag_default("domains"), - help="Domain names to apply. For multiple domains you can use " - "multiple -d flags or enter a comma separated list of domains " - "as a parameter. The first domain provided will be the " - "subject CN of the certificate, and all domains will be " - "Subject Alternative Names on the certificate. " - "The first domain will also be used in " - "some software user interfaces and as the file paths for the " - "certificate and related material unless otherwise " - "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", - metavar="CERTNAME", default=flag_default("certname"), - help="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 certificate names, run 'certbot certificates'. " - "When creating a new certificate, specifies the new certificate's name. " - "(default: the first provided domain or the name of an existing " - "certificate on your system for the same domains)") - helpful.add( - [None, "testing", "renew", "certonly"], - "--dry-run", action="store_true", dest="dry_run", - default=flag_default("dry_run"), - help="Perform a test run of the client, obtaining test (invalid) certificates" - " but not saving them to disk. This can currently only be used" - " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" - " tries to avoid making any persistent changes on a system, it " - " is not completely side-effect free: if used with webserver authenticator plugins" - " like apache and nginx, it makes and then reverts temporary config changes" - " in order to obtain test certificates, and reloads webservers to deploy and then" - " roll back those changes. It also calls --pre-hook and --post-hook commands" - " if they are defined because they may be necessary to accurately simulate" - " renewal. --deploy-hook commands are not called.") - helpful.add( - ["register", "automation"], "--register-unsafely-without-email", action="store_true", - default=flag_default("register_unsafely_without_email"), - help="Specifying this flag enables registering an account with no " - "email address. This is strongly discouraged, because in the " - "event of key loss or account compromise you will irrevocably " - "lose access to your account. You will also be unable to receive " - "notice about impending expiration or revocation of your " - "certificates. Updates to the Subscriber Agreement will still " - "affect you, and will be effective 14 days after posting an " - "update to the web site.") - helpful.add( - ["register", "update_account", "unregister", "automation"], "-m", "--email", - default=flag_default("email"), - help=config_help("email")) - 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", "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"], - "--keep-until-expiring", "--keep", "--reinstall", - dest="reinstall", action="store_true", default=flag_default("reinstall"), - help="If the requested certificate matches an existing certificate, always keep the " - "existing one until it is due for renewal (for the " - "'run' subcommand this means reinstall the existing certificate). (default: Ask)") - helpful.add( - "automation", "--expand", action="store_true", default=flag_default("expand"), - help="If an existing certificate is a strict subset of the requested names, " - "always expand and replace it with the additional names. (default: Ask)") - helpful.add( - "automation", "--version", action="version", - version="%(prog)s {0}".format(certbot.__version__), - help="show program's version number and exit") - helpful.add( - ["automation", "renew"], - "--force-renewal", "--renew-by-default", dest="renew_by_default", - action="store_true", default=flag_default("renew_by_default"), - help="If a certificate " - "already exists for the requested domains, renew it now, " - "regardless of whether it is near expiry. (Often " - "--keep-until-expiring is more appropriate). Also implies " - "--expand.") - helpful.add( - "automation", "--renew-with-new-domains", dest="renew_with_new_domains", - action="store_true", default=flag_default("renew_with_new_domains"), - help="If a " - "certificate already exists for the requested certificate name " - "but does not match the requested domains, renew it now, " - "regardless of whether it is near expiry.") - helpful.add( - "automation", "--reuse-key", dest="reuse_key", - action="store_true", default=flag_default("reuse_key"), - help="When renewing, use the same private key as the existing " - "certificate.") - - helpful.add( - ["automation", "renew", "certonly"], - "--allow-subset-of-names", action="store_true", - default=flag_default("allow_subset_of_names"), - help="When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This may be useful for allowing renewals for " - "multiple domains to succeed even if some domains no longer point " - "at this system. This option cannot be used with --csr.") - helpful.add( - "automation", "--agree-tos", dest="tos", action="store_true", - default=flag_default("tos"), - help="Agree to the ACME Subscriber Agreement (default: Ask)") - helpful.add( - ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", - default=flag_default("account"), - help="Account ID to use") - helpful.add( - "automation", "--duplicate", dest="duplicate", action="store_true", - default=flag_default("duplicate"), - help="Allow making a certificate lineage that duplicates an existing one " - "(both can be renewed in parallel)") - helpful.add( - "automation", "--os-packages-only", action="store_true", - default=flag_default("os_packages_only"), - help="(certbot-auto only) install OS package dependencies and then stop") - helpful.add( - "automation", "--no-self-upgrade", action="store_true", - default=flag_default("no_self_upgrade"), - help="(certbot-auto only) prevent the certbot-auto script from" - " upgrading itself to newer released versions (default: Upgrade" - " automatically)") - helpful.add( - "automation", "--no-bootstrap", action="store_true", - default=flag_default("no_bootstrap"), - help="(certbot-auto only) prevent the certbot-auto script from" - " installing OS-level dependencies (default: Prompt to install " - " OS-wide dependencies, but exit if the user says 'No')") - helpful.add( - "automation", "--no-permissions-check", action="store_true", - default=flag_default("no_permissions_check"), - help="(certbot-auto only) skip the check on the file system" - " permissions of the certbot-auto script") - helpful.add( - ["automation", "renew", "certonly", "run"], - "-q", "--quiet", dest="quiet", action="store_true", - default=flag_default("quiet"), - help="Silence all output except errors. Useful for automation via cron." - " Implies --non-interactive.") - # overwrites server, handled in HelpfulArgumentParser.parse_args() - helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", - dest="staging", action="store_true", default=flag_default("staging"), - help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" - " to --server " + constants.STAGING_URI) - helpful.add( - "testing", "--debug", action="store_true", default=flag_default("debug"), - help="Show tracebacks in case of errors, and allow certbot-auto " - "execution on experimental platforms") - helpful.add( - [None, "certonly", "run"], "--debug-challenges", action="store_true", - default=flag_default("debug_challenges"), - help="After setting up challenges, wait for user input before " - "submitting to CA") - helpful.add( - "testing", "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl"), - default=flag_default("no_verify_ssl")) - helpful.add( - ["testing", "standalone", "manual"], "--http-01-port", type=int, - dest="http01_port", - default=flag_default("http01_port"), help=config_help("http01_port")) - helpful.add( - ["testing", "standalone"], "--http-01-address", - dest="http01_address", - default=flag_default("http01_address"), help=config_help("http01_address")) - helpful.add( - ["testing", "nginx"], "--https-port", type=int, - default=flag_default("https_port"), - help=config_help("https_port")) - helpful.add( - "testing", "--break-my-certs", action="store_true", - default=flag_default("break_my_certs"), - help="Be willing to replace or renew valid certificates with invalid " - "(testing/staging) certificates") - helpful.add( - "security", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) - helpful.add( - "security", "--must-staple", action="store_true", - dest="must_staple", default=flag_default("must_staple"), - help=config_help("must_staple")) - helpful.add( - ["security", "enhance"], - "--redirect", action="store_true", dest="redirect", - default=flag_default("redirect"), - help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - "security", "--no-redirect", action="store_false", dest="redirect", - default=flag_default("redirect"), - help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - ["security", "enhance"], - "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), - help="Add the Strict-Transport-Security header to every HTTP response." - " Forcing browser to always use SSL for the domain." - " Defends against SSL Stripping.") - helpful.add( - "security", "--no-hsts", action="store_false", dest="hsts", - default=flag_default("hsts"), help=argparse.SUPPRESS) - helpful.add( - ["security", "enhance"], - "--uir", action="store_true", dest="uir", default=flag_default("uir"), - help='Add the "Content-Security-Policy: upgrade-insecure-requests"' - ' header to every HTTP response. Forcing the browser to use' - ' https:// for every http:// resource.') - helpful.add( - "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), - help=argparse.SUPPRESS) - helpful.add( - "security", "--staple-ocsp", action="store_true", dest="staple", - default=flag_default("staple"), - help="Enables OCSP Stapling. A valid OCSP response is stapled to" - " the certificate that the server offers during TLS.") - helpful.add( - "security", "--no-staple-ocsp", action="store_false", dest="staple", - default=flag_default("staple"), help=argparse.SUPPRESS) - helpful.add( - "security", "--strict-permissions", action="store_true", - default=flag_default("strict_permissions"), - help="Require that all configuration files are owned by the current " - "user; only needed if your config is somewhere unsafe like /tmp/") - helpful.add( - ["manual", "standalone", "certonly", "renew"], - "--preferred-challenges", dest="pref_challs", - action=_PrefChallAction, default=flag_default("pref_challs"), - help='A sorted, comma delimited list of the preferred challenge to ' - 'use during authorization with the most preferred challenge ' - 'listed first (Eg, "dns" or "http,dns"). ' - 'Not all plugins support all challenges. See ' - 'https://certbot.eff.org/docs/using.html#plugins for details. ' - 'ACME Challenges are versioned, but if you pick "http" rather ' - 'than "http-01", Certbot will select the latest version ' - 'automatically.') - helpful.add( - "renew", "--pre-hook", - help="Command to be run in a shell before obtaining any certificates." - " Intended primarily for renewal, where it can be used to temporarily" - " shut down a webserver that might conflict with the standalone" - " plugin. This will only be called if a certificate is actually to be" - " obtained/renewed. When renewing several certificates that have" - " identical pre-hooks, only the first will be executed.") - helpful.add( - "renew", "--post-hook", - help="Command to be run in a shell after attempting to obtain/renew" - " certificates. Can be used to deploy renewed certificates, or to" - " restart any servers that were stopped by --pre-hook. This is only" - " run if an attempt was made to obtain/renew a certificate. If" - " multiple renewed certificates have identical post-hooks, only" - " 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' - ' issued certificate. For this command, the shell variable' - ' $RENEWED_LINEAGE will point to the config live subdirectory' - ' (for example, "/etc/letsencrypt/live/example.com") containing' - ' the new certificates and keys; the shell variable' - ' $RENEWED_DOMAINS will contain a space-delimited list of' - ' renewed certificate domains (for example, "example.com' - ' www.example.com"') - helpful.add( - "renew", "--disable-hook-validation", - action="store_false", dest="validate_hooks", - default=flag_default("validate_hooks"), - help="Ordinarily the commands specified for" - " --pre-hook/--post-hook/--deploy-hook will be checked for" - " validity, to see if the programs being run are in the $PATH," - " so that mistakes can be caught early, even when the hooks" - " aren't being run just yet. The validation is rather" - " simplistic and fails if you use more advanced shell" - " constructs, so you can use this switch to disable it." - " (default: False)") - helpful.add( - "renew", "--no-directory-hooks", action="store_false", - default=flag_default("directory_hooks"), dest="directory_hooks", - help="Disable running executables found in Certbot's hook directories" - " during renewal. (default: False)") - helpful.add( - "renew", "--disable-renew-updates", action="store_true", - default=flag_default("disable_renew_updates"), dest="disable_renew_updates", - help="Disable automatic updates to your server configuration that" - " would otherwise be done by the selected installer plugin, and triggered" - " when the user executes \"certbot renew\", regardless of if the certificate" - " is renewed. This setting does not apply to important TLS configuration" - " updates.") - helpful.add( - "renew", "--no-autorenew", action="store_false", - default=flag_default("autorenew"), dest="autorenew", - help="Disable auto renewal of certificates.") - - # Populate the command line parameters for new style enhancements - enhancements.populate_cli(helpful.add) - - _create_subparsers(helpful) - _paths_parser(helpful) - # _plugins_parsing should be the last thing to act upon the main - # parser (--help should display plugin-specific options last) - _plugins_parsing(helpful, plugins) - - if not detect_defaults: - global helpful_parser # pylint: disable=global-statement - helpful_parser = helpful - return helpful.parse_args() - - -def _create_subparsers(helpful): - from certbot._internal.client import sample_user_agent # avoid import loops - helpful.add( - None, "--user-agent", default=flag_default("user_agent"), - help='Set a custom user agent string for the client. User agent strings allow ' - 'the CA to collect high level statistics about success rates by OS, ' - 'plugin and use 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: {0}). The flags encoded in the user agent are: ' - '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' - 'whether any hooks are set.'.format(sample_user_agent())) - helpful.add( - None, "--user-agent-comment", default=flag_default("user_agent_comment"), - type=_user_agent_comment_type, - help="Add a comment to the default user agent string. May be used when repackaging Certbot " - "or calling it from another tool to allow additional statistical data to be collected." - " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") - helpful.add("certonly", - "--csr", default=flag_default("csr"), type=read_file, - help="Path to a Certificate Signing Request (CSR) in DER or PEM format." - " Currently --csr only works with the 'certonly' subcommand.") - helpful.add("revoke", - "--reason", dest="reason", - choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, - key=constants.REVOCATION_REASONS.get)), - action=_EncodeReasonAction, default=flag_default("reason"), - help="Specify reason for revoking certificate. (default: unspecified)") - helpful.add("revoke", - "--delete-after-revoke", action="store_true", - default=flag_default("delete_after_revoke"), - help="Delete certificates after revoking them, along with all previous and later " - "versions of those certificates.") - helpful.add("revoke", - "--no-delete-after-revoke", action="store_false", - dest="delete_after_revoke", - default=flag_default("delete_after_revoke"), - help="Do not delete certificates after revoking them. This " - "option should be used with caution because the 'renew' " - "subcommand will attempt to renew undeleted revoked " - "certificates.") - helpful.add("rollback", - "--checkpoints", type=int, metavar="N", - default=flag_default("rollback_checkpoints"), - help="Revert configuration N number of checkpoints.") - helpful.add("plugins", - "--init", action="store_true", default=flag_default("init"), - help="Initialize plugins.") - helpful.add("plugins", - "--prepare", action="store_true", default=flag_default("prepare"), - help="Initialize and prepare plugins.") - helpful.add("plugins", - "--authenticators", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") - helpful.add("plugins", - "--installers", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IInstaller, help="Limit to installer plugins only.") - - -class CaseInsensitiveList(list): - """A list that will ignore case when searching. - - This class is passed to the `choices` argument of `argparse.add_arguments` - through the `helpful` wrapper. It is necessary due to special handling of - command line arguments by `set_by_cli` in which the `type_func` is not applied.""" - def __contains__(self, element): - return super(CaseInsensitiveList, self).__contains__(element.lower()) - - -def _paths_parser(helpful): - add = helpful.add - verb = helpful.verb - if verb == "help": - verb = helpful.help_arg - - cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." - sections = ["paths", "install", "revoke", "certonly", "manage"] - if verb == "certonly": - add(sections, "--cert-path", type=os.path.abspath, - default=flag_default("auth_cert_path"), help=cph) - elif verb == "revoke": - add(sections, "--cert-path", type=read_file, required=False, help=cph) - else: - add(sections, "--cert-path", type=os.path.abspath, help=cph) - - section = "paths" - if verb in ("install", "revoke"): - section = verb - # revoke --key-path reads a file, install --key-path takes a string - add(section, "--key-path", - type=((verb == "revoke" and read_file) or os.path.abspath), - help="Path to private key for certificate installation " - "or revocation (if account key is missing)") - - default_cp = None - if verb == "certonly": - default_cp = flag_default("auth_chain_path") - add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a full certificate chain (certificate plus chain).") - add("paths", "--chain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a certificate chain.") - add("paths", "--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir")) - add("paths", "--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir")) - add("paths", "--logs-dir", default=flag_default("logs_dir"), - help="Logs directory.") - add("paths", "--server", default=flag_default("server"), - help=config_help("server")) - - -def _plugins_parsing(helpful, plugins): - # It's nuts, but there are two "plugins" topics. Somehow this works - helpful.add_group( - "plugins", description="Plugin Selection: Certbot client supports an " - "extensible plugins architecture. See '%(prog)s plugins' for a " - "list of all installed plugins and their names. You can force " - "a particular plugin by setting options provided below. Running " - "--help will list flags specific to that plugin.") - - helpful.add("plugins", "--configurator", default=flag_default("configurator"), - help="Name of the plugin that is both an authenticator and an installer." - " Should not be used together with --authenticator or --installer. " - "(default: Ask)") - helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), - help="Authenticator plugin name.") - helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), - help="Installer plugin name (also used to find domains).") - helpful.add(["plugins", "certonly", "run", "install"], - "--apache", action="store_true", default=flag_default("apache"), - help="Obtain and install certificates using Apache") - helpful.add(["plugins", "certonly", "run", "install"], - "--nginx", action="store_true", default=flag_default("nginx"), - help="Obtain and install certificates using Nginx") - helpful.add(["plugins", "certonly"], "--standalone", action="store_true", - default=flag_default("standalone"), - help='Obtain certificates using a "standalone" webserver.') - helpful.add(["plugins", "certonly"], "--manual", action="store_true", - default=flag_default("manual"), - help="Provide laborious manual instructions for obtaining a certificate") - helpful.add(["plugins", "certonly"], "--webroot", action="store_true", - default=flag_default("webroot"), - help="Obtain certificates by placing files in a webroot directory.") - helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", - default=flag_default("dns_cloudflare"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Cloudflare for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", - default=flag_default("dns_cloudxns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using CloudXNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", - default=flag_default("dns_digitalocean"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DigitalOcean for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", - default=flag_default("dns_dnsimple"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNSimple for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", - default=flag_default("dns_dnsmadeeasy"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNS Made Easy for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", - default=flag_default("dns_gehirn"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastracture Service for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", - default=flag_default("dns_google"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Google Cloud DNS).")) - helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", - default=flag_default("dns_linode"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Linode for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", - default=flag_default("dns_luadns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using LuaDNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", - default=flag_default("dns_nsone"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using NS1 for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", - default=flag_default("dns_ovh"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using OVH for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", - default=flag_default("dns_rfc2136"), - help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") - helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", - default=flag_default("dns_route53"), - help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " - "DNS).")) - helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", - default=flag_default("dns_sakuracloud"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Sakura Cloud for DNS).")) - - # things should not be reorder past/pre this comment: - # plugins_group should be displayed in --help before plugin - # specific groups (so that plugins_group.description makes sense) - - helpful.add_plugin_args(plugins) - - -class _EncodeReasonAction(argparse.Action): - """Action class for parsing revocation reason.""" - - def __call__(self, parser, namespace, reason, option_string=None): - """Encodes the reason for certificate revocation.""" - code = constants.REVOCATION_REASONS[reason.lower()] - setattr(namespace, self.dest, code) - - -class _DomainsAction(argparse.Action): - """Action class for parsing domains.""" - - def __call__(self, parser, namespace, domain, option_string=None): - """Just wrap add_domains in argparseese.""" - add_domains(namespace, domain) - -def add_domains(args_or_config, domains): - """Registers new domains to be used during the current client run. - - Domains are not added to the list of requested domains if they have - already been registered. - - :param args_or_config: parsed command line arguments - :type args_or_config: argparse.Namespace or - configuration.NamespaceConfig - :param str domain: one or more comma separated domains - - :returns: domains after they have been normalized and validated - :rtype: `list` of `str` - - """ - validated_domains = [] - for domain in domains.split(","): - domain = util.enforce_domain_sanity(domain.strip()) - validated_domains.append(domain) - if domain not in args_or_config.domains: - args_or_config.domains.append(domain) - - return validated_domains - -class _PrefChallAction(argparse.Action): - """Action class for parsing preferred challenges.""" - - def __call__(self, parser, namespace, pref_challs, option_string=None): - try: - challs = parse_preferred_challenges(pref_challs.split(",")) - except errors.Error as error: - raise argparse.ArgumentError(self, str(error)) - namespace.pref_challs.extend(challs) - - -def parse_preferred_challenges(pref_challs): - """Translate and validate preferred challenges. - - :param pref_challs: list of preferred challenge types - :type pref_challs: `list` of `str` - - :returns: validated list of preferred challenge types - :rtype: `list` of `str` - - :raises errors.Error: if pref_challs is invalid - - """ - aliases = {"dns": "dns-01", "http": "http-01"} - challs = [c.strip() for c in pref_challs] - challs = [aliases.get(c, c) for c in challs] - - unrecognized = ", ".join(name for name in challs - if name not in challenges.Challenge.TYPES) - if unrecognized: - raise errors.Error( - "Unrecognized challenges: {0}".format(unrecognized)) - return challs - - -def _user_agent_comment_type(value): - if "(" in value or ")" in value: - raise argparse.ArgumentTypeError("may not contain parentheses") - return value - - -class _DeployHookAction(argparse.Action): - """Action class for parsing deploy hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - renew_hook_set = namespace.deploy_hook != namespace.renew_hook - if renew_hook_set and namespace.renew_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --renew-hook value") - namespace.deploy_hook = namespace.renew_hook = values - - -class _RenewHookAction(argparse.Action): - """Action class for parsing renew hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - deploy_hook_set = namespace.deploy_hook is not None - if deploy_hook_set and namespace.deploy_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --deploy-hook value") - namespace.renew_hook = values - - -def nonnegative_int(value): - """Converts value to an int and checks that it is not negative. - - This function should used as the type parameter for argparse - arguments. - - :param str value: value provided on the command line - - :returns: integer representation of value - :rtype: int - - :raises argparse.ArgumentTypeError: if value isn't a non-negative integer - - """ - try: - int_value = int(value) - except ValueError: - raise argparse.ArgumentTypeError("value must be an integer") - - if int_value < 0: - raise argparse.ArgumentTypeError("value must be non-negative") - return int_value diff --git a/certbot/hooks.py b/certbot/hooks.py deleted file mode 100644 index 1bb3a2eab..000000000 --- a/certbot/hooks.py +++ /dev/null @@ -1,272 +0,0 @@ -"""Facilities for implementing hooks that call shell commands.""" -from __future__ import print_function - -import logging -from subprocess import Popen, PIPE - -from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot import util -from certbot.compat import filesystem -from certbot.compat import os -from certbot.plugins import util as plug_util - -logger = logging.getLogger(__name__) - - -def validate_hooks(config): - """Check hook commands are executable.""" - validate_hook(config.pre_hook, "pre") - validate_hook(config.post_hook, "post") - validate_hook(config.deploy_hook, "deploy") - validate_hook(config.renew_hook, "renew") - - -def _prog(shell_cmd): - """Extract the program run by a shell command. - - :param str shell_cmd: command to be executed - - :returns: basename of command or None if the command isn't found - :rtype: str or None - - """ - if not util.exe_exists(shell_cmd): - plug_util.path_surgery(shell_cmd) - if not util.exe_exists(shell_cmd): - return None - return os.path.basename(shell_cmd) - - -def validate_hook(shell_cmd, hook_name): - """Check that a command provided as a hook is plausibly executable. - - :raises .errors.HookCommandNotFound: if the command is not found - """ - if shell_cmd: - cmd = shell_cmd.split(None, 1)[0] - if not _prog(cmd): - path = os.environ["PATH"] - if os.path.exists(cmd): - msg = "{1}-hook command {0} exists, but is not executable.".format(cmd, hook_name) - else: - msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format( - cmd, path, hook_name) - - raise errors.HookCommandNotFound(msg) - - -def pre_hook(config): - """Run pre-hooks if they exist and haven't already been run. - - When Certbot is running with the renew subcommand, this function - runs any hooks found in the config.renewal_pre_hooks_dir (if they - have not already been run) followed by any pre-hook in the config. - If hooks in config.renewal_pre_hooks_dir are run and the pre-hook in - the config is a path to one of these scripts, it is not run twice. - - :param configuration.NamespaceConfig config: Certbot settings - - """ - if config.verb == "renew" and config.directory_hooks: - for hook in list_hooks(config.renewal_pre_hooks_dir): - _run_pre_hook_if_necessary(hook) - - cmd = config.pre_hook - if cmd: - _run_pre_hook_if_necessary(cmd) - - -executed_pre_hooks = set() # type: Set[str] - - -def _run_pre_hook_if_necessary(command): - """Run the specified pre-hook if we haven't already. - - If we've already run this exact command before, a message is logged - saying the pre-hook was skipped. - - :param str command: pre-hook to be run - - """ - if command in executed_pre_hooks: - logger.info("Pre-hook command already run, skipping: %s", command) - else: - _run_hook("pre-hook", command) - executed_pre_hooks.add(command) - - -def post_hook(config): - """Run post-hooks if defined. - - This function also registers any executables found in - config.renewal_post_hooks_dir to be run when Certbot is used with - the renew subcommand. - - If the verb is renew, we delay executing any post-hooks until - :func:`run_saved_post_hooks` is called. In this case, this function - registers all hooks found in config.renewal_post_hooks_dir to be - called followed by any post-hook in the config. If the post-hook in - the config is a path to an executable in the post-hook directory, it - is not scheduled to be run twice. - - :param configuration.NamespaceConfig config: Certbot settings - - """ - - cmd = config.post_hook - # In the "renew" case, we save these up to run at the end - if config.verb == "renew": - if config.directory_hooks: - for hook in list_hooks(config.renewal_post_hooks_dir): - _run_eventually(hook) - if cmd: - _run_eventually(cmd) - # certonly / run - elif cmd: - _run_hook("post-hook", cmd) - - -post_hooks = [] # type: List[str] - - -def _run_eventually(command): - """Registers a post-hook to be run eventually. - - All commands given to this function will be run exactly once in the - order they were given when :func:`run_saved_post_hooks` is called. - - :param str command: post-hook to register to be run - - """ - if command not in post_hooks: - post_hooks.append(command) - - -def run_saved_post_hooks(): - """Run any post hooks that were saved up in the course of the 'renew' verb""" - for cmd in post_hooks: - _run_hook("post-hook", cmd) - - -def deploy_hook(config, domains, lineage_path): - """Run post-issuance hook if defined. - - :param configuration.NamespaceConfig config: Certbot settings - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - - """ - if config.deploy_hook: - _run_deploy_hook(config.deploy_hook, domains, - lineage_path, config.dry_run) - - -def renew_hook(config, domains, lineage_path): - """Run post-renewal hooks. - - This function runs any hooks found in - config.renewal_deploy_hooks_dir followed by any renew-hook in the - config. If the renew-hook in the config is a path to a script in - config.renewal_deploy_hooks_dir, it is not run twice. - - If Certbot is doing a dry run, no hooks are run and messages are - logged saying that they were skipped. - - :param configuration.NamespaceConfig config: Certbot settings - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - - """ - executed_dir_hooks = set() - if config.directory_hooks: - for hook in list_hooks(config.renewal_deploy_hooks_dir): - _run_deploy_hook(hook, domains, lineage_path, config.dry_run) - executed_dir_hooks.add(hook) - - if config.renew_hook: - if config.renew_hook in executed_dir_hooks: - logger.info("Skipping deploy-hook '%s' as it was already run.", - config.renew_hook) - else: - _run_deploy_hook(config.renew_hook, domains, - lineage_path, config.dry_run) - - -def _run_deploy_hook(command, domains, lineage_path, dry_run): - """Run the specified deploy-hook (if not doing a dry run). - - If dry_run is True, command is not run and a message is logged - saying that it was skipped. If dry_run is False, the hook is run - after setting the appropriate environment variables. - - :param str command: command to run as a deploy-hook - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - :param bool dry_run: True iff Certbot is doing a dry run - - """ - if dry_run: - logger.warning("Dry run: skipping deploy hook command: %s", - command) - return - - os.environ["RENEWED_DOMAINS"] = " ".join(domains) - os.environ["RENEWED_LINEAGE"] = lineage_path - _run_hook("deploy-hook", command) - - -def _run_hook(cmd_name, shell_cmd): - """Run a hook command. - - :param str cmd_name: the user facing name of the hook being run - :param shell_cmd: shell command to execute - :type shell_cmd: `list` of `str` or `str` - - :returns: stderr if there was any""" - err, _ = execute(cmd_name, shell_cmd) - return err - - -def execute(cmd_name, shell_cmd): - """Run a command. - - :param str cmd_name: the user facing name of the hook being run - :param shell_cmd: shell command to execute - :type shell_cmd: `list` of `str` or `str` - - :returns: `tuple` (`str` stderr, `str` stdout)""" - logger.info("Running %s command: %s", cmd_name, shell_cmd) - - # universal_newlines causes Popen.communicate() - # to return str objects instead of bytes in Python 3 - cmd = Popen(shell_cmd, shell=True, stdout=PIPE, - stderr=PIPE, universal_newlines=True) - out, err = cmd.communicate() - base_cmd = os.path.basename(shell_cmd.split(None, 1)[0]) - if out: - logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out) - if cmd.returncode != 0: - logger.error('%s command "%s" returned error code %d', - cmd_name, shell_cmd, cmd.returncode) - if err: - logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) - return err, out - - -def list_hooks(dir_path): - """List paths to all hooks found in dir_path in sorted order. - - :param str dir_path: directory to search - - :returns: `list` of `str` - :rtype: sorted list of absolute paths to executables in dir_path - - """ - allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) - hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] - return sorted(hooks) diff --git a/certbot/plugins/enhancements.py b/certbot/plugins/enhancements.py index 8896c1a98..d917b0ea4 100644 --- a/certbot/plugins/enhancements.py +++ b/certbot/plugins/enhancements.py @@ -78,9 +78,9 @@ def enable(lineage, domains, installer, config): def populate_cli(add): """ - Populates the command line flags for certbot.cli.HelpfulParser + Populates the command line flags for certbot._internal.cli.HelpfulParser - :param add: Add function of certbot.cli.HelpfulParser + :param add: Add function of certbot._internal.cli.HelpfulParser :type add: func """ for enh in _INDEX: diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 8a398188c..a38a6fc4f 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.cli.""" +"""Tests for certbot._internal.cli.""" import argparse import copy import tempfile @@ -11,7 +11,7 @@ from six.moves import reload_module # pylint: disable=import-error from acme import challenges import certbot.tests.util as test_util -from certbot import cli +from certbot._internal import cli from certbot._internal import constants from certbot import errors from certbot.compat import os @@ -94,7 +94,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods return output.getvalue() - @mock.patch("certbot.cli.flag_default") + @mock.patch("certbot._internal.cli.flag_default") def test_cli_ini_domains(self, mock_flag_default): with tempfile.NamedTemporaryFile() as tmp_config: tmp_config.close() # close now because of compatibility issues on Windows @@ -366,7 +366,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods errors.Error, self.parse, "-n --force-interactive".split()) def test_deploy_hook_conflict(self): - with mock.patch("certbot.cli.sys.stderr"): + with mock.patch("certbot._internal.cli.sys.stderr"): self.assertRaises(SystemExit, self.parse, "--renew-hook foo --deploy-hook bar".split()) @@ -386,7 +386,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertEqual(namespace.renew_hook, value) def test_renew_hook_conflict(self): - with mock.patch("certbot.cli.sys.stderr"): + with mock.patch("certbot._internal.cli.sys.stderr"): self.assertRaises(SystemExit, self.parse, "--deploy-hook foo --renew-hook bar".split()) @@ -406,7 +406,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertEqual(namespace.renew_hook, value) def test_max_log_backups_error(self): - with mock.patch('certbot.cli.sys.stderr'): + with mock.patch('certbot._internal.cli.sys.stderr'): self.assertRaises( SystemExit, self.parse, "--max-log-backups foo".split()) self.assertRaises( @@ -462,7 +462,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods class DefaultTest(unittest.TestCase): - """Tests for certbot.cli._Default.""" + """Tests for certbot._internal.cli._Default.""" def setUp(self): @@ -536,7 +536,7 @@ class SetByCliTest(unittest.TestCase): def _call_set_by_cli(var, args, verb): - with mock.patch('certbot.cli.helpful_parser') as mock_parser: + with mock.patch('certbot._internal.cli.helpful_parser') as mock_parser: with test_util.patch_get_utility(): mock_parser.args = args mock_parser.verb = verb diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index e7cf84c3c..1ebd5f7b5 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -462,7 +462,7 @@ class ClientTest(ClientTestCommon): names = [call[0][0] for call in mock_storage.call_args_list] self.assertEqual(names, ["example_cert", "example.com", "example.com"]) - @mock.patch("certbot.cli.helpful_parser") + @mock.patch("certbot._internal.cli.helpful_parser") def test_save_certificate(self, mock_parser): # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 017edc560..2e403d8f3 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.hooks.""" +"""Tests for certbot._internal.hooks.""" import unittest import mock @@ -12,14 +12,14 @@ from certbot.tests import util as test_util class ValidateHooksTest(unittest.TestCase): - """Tests for certbot.hooks.validate_hooks.""" + """Tests for certbot._internal.hooks.validate_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import validate_hooks + from certbot._internal.hooks import validate_hooks return validate_hooks(*args, **kwargs) - @mock.patch("certbot.hooks.validate_hook") + @mock.patch("certbot._internal.hooks.validate_hook") def test_it(self, mock_validate_hook): config = mock.MagicMock() self._call(config) @@ -31,30 +31,30 @@ class ValidateHooksTest(unittest.TestCase): class ValidateHookTest(test_util.TempDirTestCase): - """Tests for certbot.hooks.validate_hook.""" + """Tests for certbot._internal.hooks.validate_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import validate_hook + from certbot._internal.hooks import validate_hook return validate_hook(*args, **kwargs) def test_hook_not_executable(self): # prevent unnecessary modifications to PATH - with mock.patch("certbot.hooks.plug_util.path_surgery"): + with mock.patch("certbot._internal.hooks.plug_util.path_surgery"): # We just mock out filesystem.is_executable since on Windows, it is difficult # to get a fully working test around executable permissions. See # certbot.tests.compat.filesystem::NotExecutableTest for more in-depth tests. - with mock.patch("certbot.hooks.filesystem.is_executable", return_value=False): + with mock.patch("certbot._internal.hooks.filesystem.is_executable", return_value=False): self.assertRaises(errors.HookCommandNotFound, self._call, 'dummy', "foo") - @mock.patch("certbot.hooks.util.exe_exists") + @mock.patch("certbot._internal.hooks.util.exe_exists") def test_not_found(self, mock_exe_exists): mock_exe_exists.return_value = False - with mock.patch("certbot.hooks.plug_util.path_surgery") as mock_ps: + with mock.patch("certbot._internal.hooks.plug_util.path_surgery") as mock_ps: self.assertRaises(errors.HookCommandNotFound, self._call, "foo", "bar") self.assertTrue(mock_ps.called) - @mock.patch("certbot.hooks._prog") + @mock.patch("certbot._internal.hooks._prog") def test_unset(self, mock_prog): self._call(None, "foo") self.assertFalse(mock_prog.called) @@ -70,24 +70,24 @@ class HookTest(test_util.ConfigTestCase): @classmethod def _call_with_mock_execute(cls, *args, **kwargs): - """Calls self._call after mocking out certbot.hooks.execute. + """Calls self._call after mocking out certbot._internal.hooks.execute. The mock execute object is returned rather than the return value of self._call. """ - with mock.patch("certbot.hooks.execute") as mock_execute: + with mock.patch("certbot._internal.hooks.execute") as mock_execute: mock_execute.return_value = ("", "") cls._call(*args, **kwargs) return mock_execute class PreHookTest(HookTest): - """Tests for certbot.hooks.pre_hook.""" + """Tests for certbot._internal.hooks.pre_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import pre_hook + from certbot._internal.hooks import pre_hook return pre_hook(*args, **kwargs) def setUp(self): @@ -107,7 +107,7 @@ class PreHookTest(HookTest): super(PreHookTest, self).tearDown() def _reset_pre_hook_already(self): - from certbot.hooks import executed_pre_hooks + from certbot._internal.hooks import executed_pre_hooks executed_pre_hooks.clear() def test_certonly(self): @@ -128,7 +128,7 @@ class PreHookTest(HookTest): self.config.verb = "renew" os.remove(self.dir_hook) - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) self.assertFalse(mock_execute.called) self.assertFalse(mock_logger.info.called) @@ -154,18 +154,18 @@ class PreHookTest(HookTest): self._test_no_executions_common() def _test_no_executions_common(self): - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) self.assertFalse(mock_execute.called) self.assertTrue(mock_logger.info.called) class PostHookTest(HookTest): - """Tests for certbot.hooks.post_hook.""" + """Tests for certbot._internal.hooks.post_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import post_hook + from certbot._internal.hooks import post_hook return post_hook(*args, **kwargs) def setUp(self): @@ -185,7 +185,7 @@ class PostHookTest(HookTest): super(PostHookTest, self).tearDown() def _reset_post_hook_eventually(self): - from certbot.hooks import post_hooks + from certbot._internal.hooks import post_hooks del post_hooks[:] def test_certonly_and_run_with_hook(self): @@ -239,27 +239,27 @@ class PostHookTest(HookTest): self.assertEqual(self._get_eventually(), expected) def _get_eventually(self): - from certbot.hooks import post_hooks + from certbot._internal.hooks import post_hooks return post_hooks class RunSavedPostHooksTest(HookTest): - """Tests for certbot.hooks.run_saved_post_hooks.""" + """Tests for certbot._internal.hooks.run_saved_post_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import run_saved_post_hooks + from certbot._internal.hooks import run_saved_post_hooks return run_saved_post_hooks() def _call_with_mock_execute_and_eventually(self, *args, **kwargs): """Call run_saved_post_hooks but mock out execute and eventually - certbot.hooks.post_hooks is replaced with + certbot._internal.hooks.post_hooks is replaced with self.eventually. The mock execute object is returned rather than the return value of run_saved_post_hooks. """ - eventually_path = "certbot.hooks.post_hooks" + eventually_path = "certbot._internal.hooks.post_hooks" with mock.patch(eventually_path, new=self.eventually): return self._call_with_mock_execute(*args, **kwargs) @@ -290,7 +290,7 @@ class RenewalHookTest(HookTest): # pylint: disable=abstract-method def _call_with_mock_execute(self, *args, **kwargs): - """Calls self._call after mocking out certbot.hooks.execute. + """Calls self._call after mocking out certbot._internal.hooks.execute. The mock execute object is returned rather than the return value of self._call. The mock execute object asserts that environment @@ -311,7 +311,7 @@ class RenewalHookTest(HookTest): self.assertEqual(os.environ["RENEWED_LINEAGE"], lineage) return ("", "") - with mock.patch("certbot.hooks.execute") as mock_execute: + with mock.patch("certbot._internal.hooks.execute") as mock_execute: mock_execute.side_effect = execute_side_effect self._call(*args, **kwargs) return mock_execute @@ -329,14 +329,14 @@ class RenewalHookTest(HookTest): class DeployHookTest(RenewalHookTest): - """Tests for certbot.hooks.deploy_hook.""" + """Tests for certbot._internal.hooks.deploy_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import deploy_hook + from certbot._internal.hooks import deploy_hook return deploy_hook(*args, **kwargs) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_dry_run(self, mock_logger): self.config.deploy_hook = "foo" self.config.dry_run = True @@ -345,7 +345,7 @@ class DeployHookTest(RenewalHookTest): self.assertFalse(mock_execute.called) self.assertTrue(mock_logger.warning.called) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_no_hook(self, mock_logger): self.config.deploy_hook = None mock_execute = self._call_with_mock_execute( @@ -363,11 +363,11 @@ class DeployHookTest(RenewalHookTest): class RenewHookTest(RenewalHookTest): - """Tests for certbot.hooks.renew_hook""" + """Tests for certbot._internal.hooks.renew_hook""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import renew_hook + from certbot._internal.hooks import renew_hook return renew_hook(*args, **kwargs) def setUp(self): @@ -385,7 +385,7 @@ class RenewHookTest(RenewalHookTest): self.config, ["example.org"], "/foo/bar") mock_execute.assert_called_once_with("deploy-hook", self.config.renew_hook) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_dry_run(self, mock_logger): self.config.dry_run = True mock_execute = self._call_with_mock_execute( @@ -397,7 +397,7 @@ class RenewHookTest(RenewalHookTest): self.config.renew_hook = None os.remove(self.dir_hook) - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") self.assertFalse(mock_execute.called) @@ -417,11 +417,11 @@ class RenewHookTest(RenewalHookTest): class ExecuteTest(unittest.TestCase): - """Tests for certbot.hooks.execute.""" + """Tests for certbot._internal.hooks.execute.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import execute + from certbot._internal.hooks import execute return execute(*args, **kwargs) def test_it(self): @@ -433,10 +433,10 @@ class ExecuteTest(unittest.TestCase): def _test_common(self, returncode, stdout, stderr): given_command = "foo" given_name = "foo-hook" - with mock.patch("certbot.hooks.Popen") as mock_popen: + with mock.patch("certbot._internal.hooks.Popen") as mock_popen: mock_popen.return_value.communicate.return_value = (stdout, stderr) mock_popen.return_value.returncode = returncode - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: self.assertEqual(self._call(given_name, given_command), (stderr, stdout)) executed_command = mock_popen.call_args[1].get( @@ -453,11 +453,11 @@ class ExecuteTest(unittest.TestCase): class ListHooksTest(test_util.TempDirTestCase): - """Tests for certbot.hooks.list_hooks.""" + """Tests for certbot._internal.hooks.list_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import list_hooks + from certbot._internal.hooks import list_hooks return list_hooks(*args, **kwargs) def test_empty(self): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 789c0db42..ee9fe8558 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -22,7 +22,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- import certbot.tests.util as test_util from certbot._internal import account -from certbot import cli +from certbot._internal import cli from certbot._internal import configuration from certbot._internal import constants from certbot import crypto_util @@ -715,7 +715,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met # This needed two calls to find_all(), which we're avoiding for now # because of possible side effects: # https://github.com/letsencrypt/letsencrypt/commit/51ed2b681f87b1eb29088dd48718a54f401e4855 - #with mock.patch('certbot.cli.plugins_testable') as plugins: + #with mock.patch('certbot._internal.cli.plugins_testable') as plugins: # plugins.return_value = {"apache": True, "nginx": True} # ret, _, _, _ = self._call(args) # self.assertTrue("Too many flags setting" in ret) @@ -1145,7 +1145,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run", "--post-hook=no-such-command", "--disable-hook-validation"] - with mock.patch("certbot.hooks.post_hook"): + with mock.patch("certbot._internal.hooks.post_hook"): self._test_renewal_common(True, [], args=args, should_renew=True, error_expected=False) diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index dcbaed6a2..9b36c8b83 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -12,7 +12,7 @@ import certbot.tests.util as test_util class RenewalTest(test_util.ConfigTestCase): - @mock.patch('certbot.cli.set_by_cli') + @mock.patch('certbot._internal.cli.set_by_cli') def test_ancient_webroot_renewal_conf(self, mock_set_by_cli): mock_set_by_cli.return_value = False rc_path = test_util.make_lineage( diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 0c525fcc6..72394c2a8 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -43,7 +43,7 @@ class RelevantValuesTest(unittest.TestCase): from certbot._internal.storage import relevant_values return relevant_values(*args, **kwargs) - @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot._internal.cli.option_was_set") @mock.patch("certbot._internal.plugins.disco.PluginsRegistry.find_all") def test_namespace(self, mock_find_all, mock_option_was_set): mock_find_all.return_value = ["certbot-foo:bar"] @@ -53,7 +53,7 @@ class RelevantValuesTest(unittest.TestCase): self.assertEqual( self._call(self.values.copy()), self.values) - @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot._internal.cli.option_was_set") def test_option_set(self, mock_option_was_set): mock_option_was_set.return_value = True @@ -65,7 +65,7 @@ class RelevantValuesTest(unittest.TestCase): self.assertEqual(self._call(self.values), expected_relevant_values) - @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot._internal.cli.option_was_set") def test_option_unset(self, mock_option_was_set): mock_option_was_set.return_value = False diff --git a/docs/api/cli.rst b/docs/api/cli.rst deleted file mode 100644 index 91be86758..000000000 --- a/docs/api/cli.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.cli` ----------------------- - -.. automodule:: certbot.cli - :members: diff --git a/docs/api/hooks.rst b/docs/api/hooks.rst deleted file mode 100644 index 140fbf284..000000000 --- a/docs/api/hooks.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.hooks` ------------------------- - -.. automodule:: certbot.hooks - :members: -- cgit v1.2.3 From 2692b862d288fbdd1ce2193533940321260d2a5d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 13 Nov 2019 13:44:36 -0800 Subject: Update pinned dependencies (#7558) Fixes #7184. I updated #7358 to track the issue of unpinning all of these dependencies. * pin back configargparse * Pin back zope packages. * update deps * Add changelog entry. * run build.py --- CHANGELOG.md | 2 + letsencrypt-auto-source/letsencrypt-auto | 202 ++++++++++++--------- .../pieces/dependency-requirements.txt | 202 ++++++++++++--------- letsencrypt-auto-source/rebuild_dependencies.py | 7 +- 4 files changed, 243 insertions(+), 170 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1eaed7ba..a6b1685b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed +* certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are + not running on x86-64. This primarily affects RHEL 6 based systems. * Certbot's `config_changes` subcommand has been removed * `certbot.plugins.common.TLSSNI01` has been removed. * Deprecated attributes related to the TLS-SNI-01 challenge in diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 386d19531..09a7c998e 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1145,63 +1145,70 @@ if [ "$1" = "--le-auto-phase2" ]; then # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.6.16 \ - --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ - --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 -cffi==1.12.3 \ - --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ - --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ - --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ - --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ - --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ - --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ - --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ - --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ - --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ - --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ - --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ - --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ - --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ - --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ - --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ - --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ - --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ - --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ - --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ - --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ - --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ - --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ - --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ - --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ - --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ - --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ - --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ - --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.7 \ - --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ - --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ - --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ - --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ - --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ - --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ - --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ - --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ - --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ - --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ - --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ - --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ - --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ - --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ - --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ - --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 distro==1.4.0 \ --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 @@ -1213,14 +1220,14 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 josepy==1.2.0 \ --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 @@ -1230,9 +1237,9 @@ mock==1.3.0 \ parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.2 \ - --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ - --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1241,29 +1248,28 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.2 \ - --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ - --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2019.2 \ - --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ - --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 urllib3==1.24.3 \ --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a @@ -1314,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.2 \ - --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ - --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ - --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ - --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ - --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ - --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ - --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ - --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ - --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ - --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ - --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 2d683eb48..034fae46d 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -11,63 +11,70 @@ # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.6.16 \ - --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ - --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 -cffi==1.12.3 \ - --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ - --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ - --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ - --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ - --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ - --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ - --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ - --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ - --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ - --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ - --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ - --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ - --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ - --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ - --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ - --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ - --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ - --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ - --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ - --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ - --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ - --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ - --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ - --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ - --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ - --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ - --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ - --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.7 \ - --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ - --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ - --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ - --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ - --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ - --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ - --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ - --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ - --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ - --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ - --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ - --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ - --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ - --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ - --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ - --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 distro==1.4.0 \ --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 @@ -79,14 +86,14 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 josepy==1.2.0 \ --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 @@ -96,9 +103,9 @@ mock==1.3.0 \ parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.2 \ - --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ - --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -107,29 +114,28 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.2 \ - --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ - --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2019.2 \ - --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ - --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 urllib3==1.24.3 \ --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a @@ -180,15 +186,43 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.2 \ - --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ - --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ - --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ - --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ - --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ - --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ - --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ - --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ - --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ - --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ - --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index d98ae8706..e660568c3 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -46,9 +46,12 @@ AUTHORITATIVE_CONSTRAINTS = { # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. 'enum34': '1.1.6 ; python_version < \'3.4\'', - # Newer versions of requests dropped support for python 3.4. Once Certbot does as well, - # we should unpin the dependency. + # Newer versions of the packages below dropped support for python 3.4. Once + # Certbot does as well, we should unpin these dependencies. 'requests': '2.21.0', + 'ConfigArgParse': '0.14.0', + 'zope.hookable': '4.2.0', + 'zope.interface': '4.6.0', } -- cgit v1.2.3 From 4f3010ef3f245e66933d19595038535934376c30 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 14 Nov 2019 14:26:01 -0800 Subject: fixes #7553 (#7560) --- .pylintrc | 36 +--------------------- acme/acme/challenges.py | 6 ++-- acme/acme/challenges_test.py | 3 -- acme/acme/client.py | 8 ++--- acme/acme/client_test.py | 5 +-- acme/acme/crypto_util.py | 4 +-- acme/acme/crypto_util_test.py | 1 - acme/acme/jws.py | 2 +- acme/acme/standalone.py | 2 +- certbot-apache/certbot_apache/configurator.py | 3 +- certbot-apache/certbot_apache/obj.py | 3 +- certbot-apache/certbot_apache/parser.py | 1 - .../certbot_apache/tests/autohsts_test.py | 2 +- .../certbot_apache/tests/configurator_test.py | 2 +- certbot-apache/certbot_apache/tests/parser_test.py | 1 - certbot-apache/certbot_apache/tests/util.py | 4 +-- .../configurators/apache/common.py | 1 - .../configurators/common.py | 1 - .../configurators/nginx/common.py | 1 - certbot-nginx/certbot_nginx/configurator.py | 1 - certbot-nginx/certbot_nginx/nginxparser.py | 1 - certbot-nginx/certbot_nginx/obj.py | 4 +-- .../certbot_nginx/tests/configurator_test.py | 1 - certbot-nginx/certbot_nginx/tests/parser_test.py | 2 +- certbot-nginx/certbot_nginx/tests/util.py | 3 +- certbot/_internal/account.py | 2 +- certbot/_internal/cli.py | 3 +- certbot/_internal/main.py | 4 +-- certbot/_internal/plugins/selection.py | 3 +- certbot/_internal/renewal.py | 2 +- certbot/_internal/storage.py | 2 -- certbot/achallenges.py | 1 - certbot/display/util.py | 3 +- certbot/interfaces.py | 2 -- certbot/tests/account_test.py | 1 - certbot/tests/auth_handler_test.py | 2 +- certbot/tests/cert_manager_test.py | 3 +- certbot/tests/cli_test.py | 2 +- certbot/tests/client_test.py | 1 - certbot/tests/crypto_util_test.py | 2 +- certbot/tests/display/ops_test.py | 3 -- certbot/tests/display/util_test.py | 1 - certbot/tests/main_test.py | 7 +---- certbot/tests/reverter_test.py | 2 -- certbot/tests/storage_test.py | 4 +-- 45 files changed, 33 insertions(+), 115 deletions(-) diff --git a/.pylintrc b/.pylintrc index bd4eab11a..c2d8ff85e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -41,7 +41,7 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,locally-enabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import,duplicate-code +disable=fixme,locally-disabled,locally-enabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design # abstract-class-not-used cannot be disabled locally (at least in # pylint 1.4.1), same for abstract-class-little-used @@ -297,40 +297,6 @@ valid-classmethod-first-arg=cls valid-metaclass-classmethod-first-arg=mcs -[DESIGN] - -# Maximum number of arguments for function / method -max-args=6 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=(unused)?_.*|dummy - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=12 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index f417bc47c..d9d529810 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -231,7 +231,7 @@ class DNS01Response(KeyAuthorizationChallengeResponse): return verified -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class DNS01(KeyAuthorizationChallenge): """ACME dns-01 challenge.""" response_cls = DNS01Response @@ -321,7 +321,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse): return True -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class HTTP01(KeyAuthorizationChallenge): """ACME http-01 challenge.""" response_cls = HTTP01Response @@ -372,7 +372,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse): typ = "tls-alpn-01" -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class TLSALPN01(KeyAuthorizationChallenge): """ACME tls-alpn-01 challenge. diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 94641eaac..4f728e4a4 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -75,7 +75,6 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase): class DNS01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import DNS01Response @@ -147,7 +146,6 @@ class DNS01Test(unittest.TestCase): class HTTP01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import HTTP01Response @@ -258,7 +256,6 @@ class HTTP01Test(unittest.TestCase): class TLSALPN01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import TLSALPN01Response diff --git a/acme/acme/client.py b/acme/acme/client.py index d2002576e..4f5be0176 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -44,7 +44,7 @@ DEFAULT_NETWORK_TIMEOUT = 45 DER_CONTENT_TYPE = 'application/pkix-cert' -class ClientBase(object): # pylint: disable=too-many-instance-attributes +class ClientBase(object): """ACME client base object. :ivar messages.Directory directory: @@ -254,7 +254,6 @@ class Client(ClientBase): URI from which the resource will be downloaded. """ - # pylint: disable=too-many-arguments self.key = key if net is None: net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) @@ -435,7 +434,6 @@ class Client(ClientBase): was marked by the CA as invalid """ - # pylint: disable=too-many-locals assert max_attempts > 0 attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int] exhausted = set() @@ -947,7 +945,7 @@ class BackwardsCompatibleClientV2(object): return self.client.external_account_required() -class ClientNetwork(object): # pylint: disable=too-many-instance-attributes +class ClientNetwork(object): """Wrapper around requests that signs POSTs for authentication. Also adds user agent, and handles Content-Type. @@ -973,7 +971,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes def __init__(self, key, account=None, alg=jose.RS256, verify_ssl=True, user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT, source_address=None): - # pylint: disable=too-many-arguments self.key = key self.account = account self.alg = alg @@ -1081,7 +1078,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes return response def _send_request(self, method, url, *args, **kwargs): - # pylint: disable=too-many-locals """Send HTTP request. Makes sure that `verify_ssl` is respected. Logs request and diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index 4c762031b..a53ce799b 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -318,7 +318,6 @@ class BackwardsCompatibleClientV2Test(ClientTestBase): class ClientTest(ClientTestBase): """Tests for acme.client.Client.""" - # pylint: disable=too-many-instance-attributes,too-many-public-methods def setUp(self): super(ClientTest, self).setUp() @@ -888,7 +887,7 @@ class ClientV2Test(ClientTestBase): 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 + class FakeError(messages.Error): """Fake error to reproduce a malformed request ACME error""" def __init__(self): # pylint: disable=super-init-not-called pass @@ -917,7 +916,6 @@ class MockJSONDeSerializable(jose.JSONDeSerializable): class ClientNetworkTest(unittest.TestCase): """Tests for acme.client.ClientNetwork.""" - # pylint: disable=too-many-public-methods def setUp(self): self.verify_ssl = mock.MagicMock() @@ -1123,7 +1121,6 @@ class ClientNetworkTest(unittest.TestCase): class ClientNetworkWithMockedResponseTest(unittest.TestCase): """Tests for acme.client.ClientNetwork which mock out response.""" - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.client import ClientNetwork diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 6a319d94e..0be3cc896 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) _DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore -class SSLSocket(object): # pylint: disable=too-few-public-methods +class SSLSocket(object): """SSL wrapper for sockets. :ivar socket sock: Original wrapped socket. @@ -74,7 +74,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods class FakeConnection(object): """Fake OpenSSL.SSL.Connection.""" - # pylint: disable=too-few-public-methods,missing-docstring + # pylint: disable=missing-docstring def __init__(self, connection): self._wrapped = connection diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 44b245bbe..6477f6501 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -30,7 +30,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): class _TestServer(socketserver.TCPServer): - # pylint: disable=too-few-public-methods # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init def server_bind(self): # pylint: disable=missing-docstring diff --git a/acme/acme/jws.py b/acme/acme/jws.py index c92d226d4..ff8070d0b 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -43,7 +43,7 @@ class JWS(jose.JWS): __slots__ = jose.JWS._orig_slots # pylint: disable=no-member @classmethod - # pylint: disable=arguments-differ,too-many-arguments + # pylint: disable=arguments-differ def sign(cls, payload, key, alg, nonce, url=None, kid=None): # Per ACME spec, jwk and kid are mutually exclusive, so only include a # jwk field if kid is not provided. diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index b23b9b843..e355dca38 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -17,7 +17,7 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-m logger = logging.getLogger(__name__) # six.moves.* | pylint: disable=no-member,attribute-defined-outside-init -# pylint: disable=too-few-public-methods,no-init +# pylint: disable=no-init class TLSServer(socketserver.TCPServer): diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index 57cd4a9b4..d77fbc1af 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -71,7 +71,6 @@ logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) class ApacheConfigurator(common.Installer): - # pylint: disable=too-many-instance-attributes,too-many-public-methods """Apache configurator. :ivar config: Configuration. @@ -1116,7 +1115,7 @@ class ApacheConfigurator(common.Installer): if "ssl_module" not in self.parser.modules: self.enable_mod("ssl", temp=temp) - def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals + def make_vhost_ssl(self, nonssl_vhost): """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options diff --git a/certbot-apache/certbot_apache/obj.py b/certbot-apache/certbot_apache/obj.py index 22abc85cd..9cea1380a 100644 --- a/certbot-apache/certbot_apache/obj.py +++ b/certbot-apache/certbot_apache/obj.py @@ -98,7 +98,7 @@ class Addr(common.Addr): return self.get_addr_obj(port) -class VirtualHost(object): # pylint: disable=too-few-public-methods +class VirtualHost(object): """Represents an Apache Virtualhost. :ivar str filep: file path of VH @@ -126,7 +126,6 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods def __init__(self, filep, path, addrs, ssl, enabled, name=None, aliases=None, modmacro=False, ancestor=None): - # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.path = path diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py index b5f0cd81a..ceb09548f 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/parser.py @@ -19,7 +19,6 @@ logger = logging.getLogger(__name__) class ApacheParser(object): - # pylint: disable=too-many-public-methods """Class handles the fine details of parsing the Apache Configuration. .. todo:: Make parsing general... remove sites-available etc... diff --git a/certbot-apache/certbot_apache/tests/autohsts_test.py b/certbot-apache/certbot_apache/tests/autohsts_test.py index 2d22df289..28fb172ed 100644 --- a/certbot-apache/certbot_apache/tests/autohsts_test.py +++ b/certbot-apache/certbot_apache/tests/autohsts_test.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-public-methods,too-many-lines +# pylint: disable=too-many-lines """Test for certbot_apache.configurator AutoHSTS functionality""" import re import unittest diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index 52c5b437c..c12dd81bc 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-public-methods,too-many-lines +# pylint: disable=too-many-lines """Test for certbot_apache.configurator.""" import copy import shutil diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index 27d66f680..0b92a0b92 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -1,4 +1,3 @@ -# pylint: disable=too-many-public-methods """Tests for certbot_apache.parser.""" import shutil import unittest diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index 8e3de04be..aa94a943f 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -18,7 +18,7 @@ from certbot_apache import entrypoint from certbot_apache import obj -class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods +class ApacheTest(unittest.TestCase): def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", config_root="debian_apache_2_4/multiple_vhosts/apache2", @@ -81,7 +81,7 @@ class ParserTest(ApacheTest): self.config_path, self.vhost_path, configurator=self.config) -def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals +def get_apache_configurator( config_path, vhost_path, config_dir, work_dir, version=(2, 4, 7), os_info="generic", diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 050a3bdb3..68978ec81 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -18,7 +18,6 @@ from certbot_compatibility_test.configurators import common as configurators_com @zope.interface.implementer(interfaces.IConfiguratorProxy) class Proxy(configurators_common.Proxy): - # pylint: disable=too-many-instance-attributes """A common base for Apache test configurators""" def __init__(self, args): diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index f8d106f21..47ebac741 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) class Proxy(object): - # pylint: disable=too-many-instance-attributes """A common base for compatibility test configurators""" @classmethod diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 39f69da19..3dcf834c0 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -18,7 +18,6 @@ from certbot_compatibility_test.configurators import common as configurators_com @zope.interface.implementer(interfaces.IConfiguratorProxy) class Proxy(configurators_common.Proxy): - # pylint: disable=too-many-instance-attributes """A common base for Nginx test configurators""" def load_config(self): diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py index 177c0ea40..6ec53430c 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/configurator.py @@ -44,7 +44,6 @@ logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) class NginxConfigurator(common.Installer): - # pylint: disable=too-many-instance-attributes,too-many-public-methods """Nginx configurator. .. todo:: Add proper support for comments in the config. Currently, diff --git a/certbot-nginx/certbot_nginx/nginxparser.py b/certbot-nginx/certbot_nginx/nginxparser.py index bfb75adcc..f4603dcde 100644 --- a/certbot-nginx/certbot_nginx/nginxparser.py +++ b/certbot-nginx/certbot_nginx/nginxparser.py @@ -63,7 +63,6 @@ class RawNginxParser(object): return self.parse().asList() class RawNginxDumper(object): - # pylint: disable=too-few-public-methods """A class that dumps nginx configuration from the provided tree.""" def __init__(self, blocks): self.blocks = blocks diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/obj.py index b14be81ea..dff48ce02 100644 --- a/certbot-nginx/certbot_nginx/obj.py +++ b/certbot-nginx/certbot_nginx/obj.py @@ -37,7 +37,6 @@ class Addr(common.Addr): CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] def __init__(self, host, port, ssl, default, ipv6, ipv6only): - # pylint: disable=too-many-arguments super(Addr, self).__init__((host, port)) self.ssl = ssl self.default = default @@ -145,7 +144,7 @@ class Addr(common.Addr): return False -class VirtualHost(object): # pylint: disable=too-few-public-methods +class VirtualHost(object): """Represents an Nginx Virtualhost. :ivar str filep: file path of VH @@ -162,7 +161,6 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods """ def __init__(self, filep, addrs, ssl, enabled, names, raw, path): - # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.addrs = addrs diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 4a7f7cafb..6b3438d89 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -1,4 +1,3 @@ -# pylint: disable=too-many-public-methods """Test for certbot_nginx.configurator.""" import unittest diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index 396f996bf..323830013 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -15,7 +15,7 @@ from certbot_nginx import parser from certbot_nginx.tests import util -class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods +class NginxParserTest(util.NginxTest): """Nginx Parser Test.""" def tearDown(self): diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index c1d7e9d34..8be285050 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -17,7 +17,7 @@ from certbot_nginx import configurator from certbot_nginx import nginxparser -class NginxTest(test_util.ConfigTestCase): # pylint: disable=too-few-public-methods +class NginxTest(test_util.ConfigTestCase): def setUp(self): super(NginxTest, self).setUp() @@ -46,7 +46,6 @@ class NginxTest(test_util.ConfigTestCase): # pylint: disable=too-few-public-met shutil.rmtree(self.work_dir) shutil.rmtree(self.logs_dir) - # pylint: disable=too-many-arguments def get_nginx_configurator(self, config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2), openssl_version="1.0.2g"): """Create an Nginx Configurator with the specified options.""" diff --git a/certbot/_internal/account.py b/certbot/_internal/account.py index 12f6a3e8a..6060cbd71 100644 --- a/certbot/_internal/account.py +++ b/certbot/_internal/account.py @@ -25,7 +25,7 @@ from certbot.compat import os logger = logging.getLogger(__name__) -class Account(object): # pylint: disable=too-few-public-methods +class Account(object): """ACME protocol registration. :ivar .RegistrationResource regr: Registration Resource diff --git a/certbot/_internal/cli.py b/certbot/_internal/cli.py index ad20f6494..7eabeeee6 100644 --- a/certbot/_internal/cli.py +++ b/certbot/_internal/cli.py @@ -870,7 +870,7 @@ def _add_all_groups(helpful): helpful.add_group(name, description=docs["opts"]) -def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: disable=too-many-statements +def prepare_and_parse_args(plugins, args, detect_defaults=False): """Returns parsed command line arguments. :param .PluginsRegistry plugins: available plugins @@ -881,7 +881,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis """ - # pylint: disable=too-many-statements helpful = HelpfulArgumentParser(args, plugins, detect_defaults) _add_all_groups(helpful) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py index e58c5285f..d697dc5b7 100644 --- a/certbot/_internal/main.py +++ b/certbot/_internal/main.py @@ -532,7 +532,7 @@ def _determine_account(config): return acc, acme -def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-branches +def _delete_if_appropriate(config): """Does the user want to delete their now-revoked certs? If run in non-interactive mode, deleting happens automatically. @@ -1068,7 +1068,7 @@ def revoke(config, unused_plugins): return None -def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals +def run(config, plugins): """Obtain a certificate and install. :param config: Configuration object diff --git a/certbot/_internal/plugins/selection.py b/certbot/_internal/plugins/selection.py index 1ac3456d3..de1d27227 100644 --- a/certbot/_internal/plugins/selection.py +++ b/certbot/_internal/plugins/selection.py @@ -175,7 +175,6 @@ def record_chosen_plugins(config, plugins, auth, inst): def choose_configurator_plugins(config, plugins, verb): - # pylint: disable=too-many-branches """ Figure out which configurator we're going to use, modifies config.authenticator and config.installer strings to reflect that choice if @@ -254,7 +253,7 @@ def set_configurator(previously, now): return now -def cli_plugin_requests(config): # pylint: disable=too-many-branches +def cli_plugin_requests(config): """ Figure out which plugins the user requested with CLI and config options diff --git a/certbot/_internal/renewal.py b/certbot/_internal/renewal.py index 5f2f90d63..f96cd004f 100644 --- a/certbot/_internal/renewal.py +++ b/certbot/_internal/renewal.py @@ -372,7 +372,7 @@ def _renew_describe_results(config, renew_successes, renew_failures, disp.notification("\n".join(out), wrap=False) -def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many-branches,too-many-statements +def handle_renewal_request(config): """Examine each lineage; renew if due and report results""" # This is trivially False if config.domains is empty diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py index f7e7d5e7f..bb36f462a 100644 --- a/certbot/_internal/storage.py +++ b/certbot/_internal/storage.py @@ -377,7 +377,6 @@ def delete_files(config, certname): class RenewableCert(object): - # pylint: disable=too-many-instance-attributes,too-many-public-methods """Renewable certificate. Represents a lineage of certificates that is under the management of @@ -952,7 +951,6 @@ class RenewableCert(object): @classmethod def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): - # pylint: disable=too-many-locals """Create a new certificate lineage. Attempts to create a certificate lineage -- enrolled for diff --git a/certbot/achallenges.py b/certbot/achallenges.py index 6535a6b63..2f2e1f3bd 100644 --- a/certbot/achallenges.py +++ b/certbot/achallenges.py @@ -27,7 +27,6 @@ from acme import challenges logger = logging.getLogger(__name__) -# pylint: disable=too-few-public-methods class AnnotatedChallenge(jose.ImmutableMap): """Client annotated challenge. diff --git a/certbot/display/util.py b/certbot/display/util.py index 6cf9f9c05..b79ba338f 100644 --- a/certbot/display/util.py +++ b/certbot/display/util.py @@ -89,7 +89,6 @@ def input_with_timeout(prompt=None, timeout=36000.0): @zope.interface.implementer(interfaces.IDisplay) class FileDisplay(object): """File-based display.""" - # pylint: disable=too-many-arguments # see https://github.com/certbot/certbot/issues/3915 def __init__(self, outfile, force_interactive): @@ -482,7 +481,7 @@ class NoninteractiveDisplay(object): def menu(self, message, choices, ok_label=None, cancel_label=None, help_label=None, default=None, cli_flag=None, **unused_kwargs): - # pylint: disable=unused-argument,too-many-arguments + # pylint: disable=unused-argument """Avoid displaying a menu. :param str message: title of menu diff --git a/certbot/interfaces.py b/certbot/interfaces.py index 8cdd247d8..edf71e63f 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -4,7 +4,6 @@ import six import zope.interface # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class -# pylint: disable=too-few-public-methods @six.add_metaclass(abc.ABCMeta) @@ -372,7 +371,6 @@ class IInstaller(IPlugin): class IDisplay(zope.interface.Interface): """Generic display.""" - # pylint: disable=too-many-arguments # see https://github.com/certbot/certbot/issues/3915 def notification(message, pause, wrap=True, force_interactive=False): diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 0f0b47ec4..80f381028 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -93,7 +93,6 @@ class AccountMemoryStorageTest(unittest.TestCase): class AccountFileStorageTest(test_util.ConfigTestCase): """Tests for certbot._internal.account.AccountFileStorage.""" - #pylint: disable=too-many-public-methods def setUp(self): super(AccountFileStorageTest, self).setUp() diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 75bd9b45b..308154aad 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -56,7 +56,7 @@ class ChallengeFactoryTest(unittest.TestCase): errors.Error, self.handler._challenge_factory, authzr, [0]) -class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-public-methods +class HandleAuthorizationsTest(unittest.TestCase): """handle_authorizations test. This tests everything except for all functions under _poll_challenges. diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 25dd0f45a..582cf09b8 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -68,7 +68,6 @@ class UpdateLiveSymlinksTest(BaseCertManagerTest): """ def test_update_live_symlinks(self): """Test update_live_symlinks""" - # pylint: disable=too-many-statements # create files with incorrect symlinks from certbot._internal import cert_manager archive_paths = {} @@ -202,7 +201,7 @@ class CertificatesTest(BaseCertManagerTest): shutil.rmtree(empty_tempdir) @mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked') - def test_report_human_readable(self, mock_revoked): #pylint: disable=too-many-statements + def test_report_human_readable(self, mock_revoked): mock_revoked.return_value = None from certbot._internal import cert_manager import datetime diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index a38a6fc4f..fbfaea333 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -60,7 +60,7 @@ class FlagDefaultTest(unittest.TestCase): self.assertEqual(cli.flag_default('logs_dir'), 'C:\\Certbot\\log') -class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods +class ParseTest(unittest.TestCase): '''Test the cli args entrypoint''' diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 1ebd5f7b5..cac716854 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -464,7 +464,6 @@ class ClientTest(ClientTestCommon): @mock.patch("certbot._internal.cli.helpful_parser") def test_save_certificate(self, mock_parser): - # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() filesystem.chmod(tmp_path, 0o755) # TODO: really?? diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 2673f4219..666e4c082 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -164,7 +164,7 @@ class ImportCSRFileTest(unittest.TestCase): test_util.load_vector('cert_512.pem')) -class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods +class MakeKeyTest(unittest.TestCase): """Tests for certbot.crypto_util.make_key.""" def test_it(self): # pylint: disable=no-self-use diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index 26d4eade2..c19941a4b 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -354,7 +354,6 @@ class ChooseNamesTest(unittest.TestCase): class SuccessInstallationTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success installation message.""" @classmethod def _call(cls, names): @@ -376,7 +375,6 @@ class SuccessInstallationTest(unittest.TestCase): class SuccessRenewalTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success renewal message.""" @classmethod def _call(cls, names): @@ -397,7 +395,6 @@ class SuccessRenewalTest(unittest.TestCase): self.assertTrue(name in arg) class SuccessRevocationTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success revocation message.""" @classmethod def _call(cls, path): diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 138bfe628..ca9f1e382 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -58,7 +58,6 @@ class FileOutputDisplayTest(unittest.TestCase): functions look to a user, uncomment the test_visual function. """ - # pylint:disable=too-many-public-methods def setUp(self): super(FileOutputDisplayTest, self).setUp() self.mock_stdout = mock.MagicMock() diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index ee9fe8558..c60a79292 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -356,7 +356,6 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): util_mock.yesno.return_value = False self._test_delete_opt_out_common(mock_get_utility) - # pylint: disable=too-many-arguments @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.delete') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @@ -376,7 +375,6 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) mock_delete.assert_not_called() - # pylint: disable=too-many-arguments @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @mock.patch('certbot._internal.storage.full_archive_path') @@ -395,7 +393,6 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) self.assertEqual(mock_delete.call_count, 1) - # pylint: disable=too-many-arguments @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @mock.patch('certbot._internal.storage.full_archive_path') @@ -416,7 +413,6 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) self.assertEqual(mock_delete.call_count, 1) - # pylint: disable=too-many-arguments @mock.patch('certbot._internal.storage.renewal_file_for_certname') @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') @mock.patch('certbot._internal.storage.full_archive_path') @@ -507,7 +503,7 @@ class DetermineAccountTest(test_util.ConfigTestCase): self.assertEqual('other email', self.config.email) -class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-methods +class MainTest(test_util.ConfigTestCase): """Tests for different commands.""" def setUp(self): @@ -970,7 +966,6 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met args=None, should_renew=True, error_expected=False, quiet_mode=False, expiry_date=datetime.datetime.now(), 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 = os.path.normpath(os.path.join(self.config.config_dir, 'live/foo.bar/fullchain.pem')) diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 3ba628475..fc9133c5f 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -14,7 +14,6 @@ from certbot.tests import util as test_util class ReverterCheckpointLocalTest(test_util.ConfigTestCase): - # pylint: disable=too-many-instance-attributes, too-many-public-methods """Test the Reverter Class.""" def setUp(self): super(ReverterCheckpointLocalTest, self).setUp() @@ -277,7 +276,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): class TestFullCheckpointsReverter(test_util.ConfigTestCase): - # pylint: disable=too-many-instance-attributes """Tests functions having to deal with full checkpoints.""" def setUp(self): super(TestFullCheckpointsReverter, self).setUp() diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 72394c2a8..35f10656e 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -140,7 +140,6 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): class RenewableCertTests(BaseRenewableCertTest): - # pylint: disable=too-many-public-methods """Tests for certbot._internal.storage.""" def test_initialization(self): @@ -202,7 +201,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue("version" in mock_logger.info.call_args[0][0]) def test_consistent(self): - # pylint: disable=too-many-statements,protected-access + # pylint: disable=protected-access oldcert = self.test_rc.cert self.test_rc.cert = "relative/path" # Absolute path for item requirement @@ -483,7 +482,6 @@ class RenewableCertTests(BaseRenewableCertTest): def test_should_autorenew(self, mock_ocsp, mock_cli): """Test should_autorenew on the basis of reasons other than expiry time window.""" - # pylint: disable=too-many-statements mock_cli.set_by_cli.return_value = False # Autorenewal turned off self.test_rc.configuration["renewalparams"] = {"autorenew": "False"} -- cgit v1.2.3 From 70e4cb785332ecacd918df86aafe3958ecf90e6a Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 22 Nov 2019 12:50:01 -0800 Subject: Remove unused apache docs (#7575) Part of #5775. We don't use these docs anywhere, so delete them. Removes: - `certbot-apache/readthedocs.org.requirements.txt` - `certbot-apache/docs/` folder - docs include in `MANIFEST.in` - docs dependencies in `setup.py` --- certbot-apache/MANIFEST.in | 1 - certbot-apache/docs/.gitignore | 1 - certbot-apache/docs/Makefile | 192 -------------- certbot-apache/docs/_static/.gitignore | 0 certbot-apache/docs/_templates/.gitignore | 0 certbot-apache/docs/api.rst | 8 - certbot-apache/docs/api/augeas_configurator.rst | 5 - certbot-apache/docs/api/configurator.rst | 5 - certbot-apache/docs/api/display_ops.rst | 5 - certbot-apache/docs/api/obj.rst | 5 - certbot-apache/docs/api/parser.rst | 5 - certbot-apache/docs/conf.py | 318 ------------------------ certbot-apache/docs/index.rst | 31 --- certbot-apache/docs/make.bat | 263 -------------------- certbot-apache/readthedocs.org.requirements.txt | 12 - certbot-apache/setup.py | 8 - 16 files changed, 859 deletions(-) delete mode 100644 certbot-apache/docs/.gitignore delete mode 100644 certbot-apache/docs/Makefile delete mode 100644 certbot-apache/docs/_static/.gitignore delete mode 100644 certbot-apache/docs/_templates/.gitignore delete mode 100644 certbot-apache/docs/api.rst delete mode 100644 certbot-apache/docs/api/augeas_configurator.rst delete mode 100644 certbot-apache/docs/api/configurator.rst delete mode 100644 certbot-apache/docs/api/display_ops.rst delete mode 100644 certbot-apache/docs/api/obj.rst delete mode 100644 certbot-apache/docs/api/parser.rst delete mode 100644 certbot-apache/docs/conf.py delete mode 100644 certbot-apache/docs/index.rst delete mode 100644 certbot-apache/docs/make.bat delete mode 100644 certbot-apache/readthedocs.org.requirements.txt diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index 3e594a953..031cb5980 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,6 +1,5 @@ include LICENSE.txt include README.rst -recursive-include docs * recursive-include certbot_apache/tests/testdata * include certbot_apache/centos-options-ssl-apache.conf include certbot_apache/options-ssl-apache.conf diff --git a/certbot-apache/docs/.gitignore b/certbot-apache/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-apache/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-apache/docs/Makefile b/certbot-apache/docs/Makefile deleted file mode 100644 index 0e611ecec..000000000 --- a/certbot-apache/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-apache.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-apache.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-apache" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-apache" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/certbot-apache/docs/_static/.gitignore b/certbot-apache/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-apache/docs/_templates/.gitignore b/certbot-apache/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-apache/docs/api.rst b/certbot-apache/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-apache/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-apache/docs/api/augeas_configurator.rst b/certbot-apache/docs/api/augeas_configurator.rst deleted file mode 100644 index b47ffbc6b..000000000 --- a/certbot-apache/docs/api/augeas_configurator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.augeas_configurator` ---------------------------------------------- - -.. automodule:: certbot_apache.augeas_configurator - :members: diff --git a/certbot-apache/docs/api/configurator.rst b/certbot-apache/docs/api/configurator.rst deleted file mode 100644 index 8ec266d1a..000000000 --- a/certbot-apache/docs/api/configurator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.configurator` --------------------------------------- - -.. automodule:: certbot_apache.configurator - :members: diff --git a/certbot-apache/docs/api/display_ops.rst b/certbot-apache/docs/api/display_ops.rst deleted file mode 100644 index 26d3ed3dc..000000000 --- a/certbot-apache/docs/api/display_ops.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.display_ops` -------------------------------------- - -.. automodule:: certbot_apache.display_ops - :members: diff --git a/certbot-apache/docs/api/obj.rst b/certbot-apache/docs/api/obj.rst deleted file mode 100644 index 82e58df3f..000000000 --- a/certbot-apache/docs/api/obj.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.obj` ------------------------------ - -.. automodule:: certbot_apache.obj - :members: diff --git a/certbot-apache/docs/api/parser.rst b/certbot-apache/docs/api/parser.rst deleted file mode 100644 index 3427735be..000000000 --- a/certbot-apache/docs/api/parser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.parser` --------------------------------- - -.. automodule:: certbot_apache.parser - :members: diff --git a/certbot-apache/docs/conf.py b/certbot-apache/docs/conf.py deleted file mode 100644 index 5375fd2b8..000000000 --- a/certbot-apache/docs/conf.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-apache documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:39:26 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -from certbot.compat import os -import shlex - -import mock - - -# http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules -# c.f. #262 -sys.modules.update( - (mod_name, mock.MagicMock()) for mod_name in ['augeas']) - -here = os.path.abspath(os.path.dirname(__file__)) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -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' - -# 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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'certbot-apache' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Certbot Project' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0' -# The full version, including alpha/beta/rc tags. -release = '0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'certbot-apachedoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'certbot-apache.tex', u'certbot-apache Documentation', - u'Certbot Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'certbot-apache', u'certbot-apache Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'certbot-apache', u'certbot-apache Documentation', - author, 'certbot-apache', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), -} diff --git a/certbot-apache/docs/index.rst b/certbot-apache/docs/index.rst deleted file mode 100644 index bfe4d245c..000000000 --- a/certbot-apache/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. certbot-apache documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:39:26 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-apache's documentation! -============================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -.. automodule:: certbot_apache - :members: - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-apache/docs/make.bat b/certbot-apache/docs/make.bat deleted file mode 100644 index 3a7818940..000000000 --- a/certbot-apache/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-apache.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-apache.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/certbot-apache/readthedocs.org.requirements.txt b/certbot-apache/readthedocs.org.requirements.txt deleted file mode 100644 index fe30ab1dc..000000000 --- a/certbot-apache/readthedocs.org.requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead - --e acme --e . --e certbot-apache[docs] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 5895ec810..c8e5e7073 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -18,11 +18,6 @@ install_requires = [ 'zope.interface', ] -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - class PyTest(TestCommand): user_options = [] @@ -74,9 +69,6 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'certbot.plugins': [ 'apache = certbot_apache.entrypoint:ENTRYPOINT', -- cgit v1.2.3 From bd35e71b5c927515938ef8cfd9109212a5e8995e Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 22 Nov 2019 12:54:18 -0800 Subject: Remove unused certbot-compatibility-test docs (#7577) Part of #5775. We don't use these docs anywhere, so delete them. Removes: - `certbot-compatibility-test/readthedocs.org.requirements.txt` - `certbot-compatibility-test/docs/` folder - docs include in `MANIFEST.in` - docs dependencies in `setup.py` --- certbot-compatibility-test/MANIFEST.in | 1 - certbot-compatibility-test/docs/.gitignore | 1 - certbot-compatibility-test/docs/Makefile | 192 ------------- certbot-compatibility-test/docs/_static/.gitignore | 0 .../docs/_templates/.gitignore | 0 certbot-compatibility-test/docs/api.rst | 8 - certbot-compatibility-test/docs/api/index.rst | 53 ---- certbot-compatibility-test/docs/conf.py | 319 --------------------- certbot-compatibility-test/docs/index.rst | 27 -- certbot-compatibility-test/docs/make.bat | 263 ----------------- .../readthedocs.org.requirements.txt | 13 - certbot-compatibility-test/setup.py | 8 - 12 files changed, 885 deletions(-) delete mode 100644 certbot-compatibility-test/docs/.gitignore delete mode 100644 certbot-compatibility-test/docs/Makefile delete mode 100644 certbot-compatibility-test/docs/_static/.gitignore delete mode 100644 certbot-compatibility-test/docs/_templates/.gitignore delete mode 100644 certbot-compatibility-test/docs/api.rst delete mode 100644 certbot-compatibility-test/docs/api/index.rst delete mode 100644 certbot-compatibility-test/docs/conf.py delete mode 100644 certbot-compatibility-test/docs/index.rst delete mode 100644 certbot-compatibility-test/docs/make.bat delete mode 100644 certbot-compatibility-test/readthedocs.org.requirements.txt diff --git a/certbot-compatibility-test/MANIFEST.in b/certbot-compatibility-test/MANIFEST.in index 11762538a..a9d4f5ce7 100644 --- a/certbot-compatibility-test/MANIFEST.in +++ b/certbot-compatibility-test/MANIFEST.in @@ -1,6 +1,5 @@ include LICENSE.txt include README.rst -recursive-include docs * include certbot_compatibility_test/configurators/apache/a2enmod.sh include certbot_compatibility_test/configurators/apache/a2dismod.sh include certbot_compatibility_test/configurators/apache/Dockerfile diff --git a/certbot-compatibility-test/docs/.gitignore b/certbot-compatibility-test/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-compatibility-test/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-compatibility-test/docs/Makefile b/certbot-compatibility-test/docs/Makefile deleted file mode 100644 index 0c9cf40aa..000000000 --- a/certbot-compatibility-test/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-compatibility-test.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-compatibility-test.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-compatibility-test" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-compatibility-test" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/certbot-compatibility-test/docs/_static/.gitignore b/certbot-compatibility-test/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-compatibility-test/docs/_templates/.gitignore b/certbot-compatibility-test/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-compatibility-test/docs/api.rst b/certbot-compatibility-test/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-compatibility-test/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-compatibility-test/docs/api/index.rst b/certbot-compatibility-test/docs/api/index.rst deleted file mode 100644 index fea92d2e5..000000000 --- a/certbot-compatibility-test/docs/api/index.rst +++ /dev/null @@ -1,53 +0,0 @@ -:mod:`certbot_compatibility_test` -------------------------------------- - -.. automodule:: certbot_compatibility_test - :members: - -:mod:`certbot_compatibility_test.errors` -============================================ - -.. automodule:: certbot_compatibility_test.errors - :members: - -:mod:`certbot_compatibility_test.interfaces` -================================================ - -.. automodule:: certbot_compatibility_test.interfaces - :members: - -:mod:`certbot_compatibility_test.test_driver` -================================================= - -.. automodule:: certbot_compatibility_test.test_driver - :members: - -:mod:`certbot_compatibility_test.util` -========================================== - -.. automodule:: certbot_compatibility_test.util - :members: - -:mod:`certbot_compatibility_test.configurators` -=================================================== - -.. automodule:: certbot_compatibility_test.configurators - :members: - -:mod:`certbot_compatibility_test.configurators.apache` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: certbot_compatibility_test.configurators.apache - :members: - -:mod:`certbot_compatibility_test.configurators.apache.apache24` -------------------------------------------------------------------- - -.. automodule:: certbot_compatibility_test.configurators.apache.apache24 - :members: - -:mod:`certbot_compatibility_test.configurators.apache.common` -------------------------------------------------------------------- - -.. automodule:: certbot_compatibility_test.configurators.apache.common - :members: diff --git a/certbot-compatibility-test/docs/conf.py b/certbot-compatibility-test/docs/conf.py deleted file mode 100644 index f89f4b368..000000000 --- a/certbot-compatibility-test/docs/conf.py +++ /dev/null @@ -1,319 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-compatibility-test documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:40:53 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os -import shlex - - -here = os.path.abspath(os.path.dirname(__file__)) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -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' - -# 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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'repoze.sphinx.autointerface', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'certbot-compatibility-test' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Certbot Project' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0' -# The full version, including alpha/beta/rc tags. -release = '0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'certbot-compatibility-testdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'certbot-compatibility-test.tex', - u'certbot-compatibility-test Documentation', - u'Certbot Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'certbot-compatibility-test', - u'certbot-compatibility-test Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'certbot-compatibility-test', - u'certbot-compatibility-test Documentation', - author, 'certbot-compatibility-test', - 'One line description of project.', 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), - 'certbot-apache': ( - 'https://letsencrypt-apache.readthedocs.org/en/latest/', None), - 'certbot-nginx': ( - 'https://letsencrypt-nginx.readthedocs.org/en/latest/', None), -} diff --git a/certbot-compatibility-test/docs/index.rst b/certbot-compatibility-test/docs/index.rst deleted file mode 100644 index a5e71e844..000000000 --- a/certbot-compatibility-test/docs/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. certbot-compatibility-test documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:40:53 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-compatibility-test's documentation! -========================================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-compatibility-test/docs/make.bat b/certbot-compatibility-test/docs/make.bat deleted file mode 100644 index b6c0360f4..000000000 --- a/certbot-compatibility-test/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-compatibility-test.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-compatibility-test.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/certbot-compatibility-test/readthedocs.org.requirements.txt b/certbot-compatibility-test/readthedocs.org.requirements.txt deleted file mode 100644 index c2a0c1110..000000000 --- a/certbot-compatibility-test/readthedocs.org.requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead - --e acme --e . --e certbot-apache --e certbot-compatibility-test[docs] diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 40cde4352..3e5c24600 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -20,11 +20,6 @@ if sys.version_info < (2, 7, 9): install_requires.append('ndg-httpsclient') install_requires.append('pyasn1') -docs_extras = [ - 'repoze.sphinx.autointerface', - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] setup( name='certbot-compatibility-test', @@ -55,9 +50,6 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'console_scripts': [ 'certbot-compatibility-test = certbot_compatibility_test.test_driver:main', -- cgit v1.2.3 From d8ca555eed11cf0af91d5c3b88e9c7b8e47aa352 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 22 Nov 2019 12:58:06 -0800 Subject: Remove DNS plugin API docs. (#7578) Replace DNS plugins' API documentation with a note that plugins adhere to certbot's plugin interface. --- certbot-dns-cloudflare/docs/api.rst | 5 +---- certbot-dns-cloudflare/docs/api/dns_cloudflare.rst | 5 ----- certbot-dns-cloudxns/docs/api.rst | 5 +---- certbot-dns-cloudxns/docs/api/dns_cloudxns.rst | 5 ----- certbot-dns-digitalocean/docs/api.rst | 5 +---- certbot-dns-digitalocean/docs/api/dns_digitalocean.rst | 5 ----- certbot-dns-dnsimple/docs/api.rst | 5 +---- certbot-dns-dnsimple/docs/api/dns_dnsimple.rst | 5 ----- certbot-dns-dnsmadeeasy/docs/api.rst | 5 +---- certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst | 5 ----- certbot-dns-gehirn/docs/api.rst | 5 +---- certbot-dns-gehirn/docs/api/dns_gehirn.rst | 5 ----- certbot-dns-google/docs/api.rst | 5 +---- certbot-dns-google/docs/api/dns_google.rst | 5 ----- certbot-dns-linode/docs/api.rst | 5 +---- certbot-dns-linode/docs/api/dns_linode.rst | 5 ----- certbot-dns-luadns/docs/api.rst | 5 +---- certbot-dns-luadns/docs/api/dns_luadns.rst | 5 ----- certbot-dns-nsone/docs/api.rst | 5 +---- certbot-dns-nsone/docs/api/dns_nsone.rst | 5 ----- certbot-dns-ovh/docs/api.rst | 5 +---- certbot-dns-ovh/docs/api/dns_ovh.rst | 5 ----- certbot-dns-rfc2136/docs/api.rst | 5 +---- certbot-dns-rfc2136/docs/api/dns_rfc2136.rst | 5 ----- certbot-dns-route53/docs/api.rst | 5 +---- certbot-dns-route53/docs/api/authenticator.rst | 5 ----- certbot-dns-route53/docs/api/dns_route53.rst | 5 ----- certbot-dns-sakuracloud/docs/api.rst | 5 +---- certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst | 5 ----- 29 files changed, 14 insertions(+), 131 deletions(-) delete mode 100644 certbot-dns-cloudflare/docs/api/dns_cloudflare.rst delete mode 100644 certbot-dns-cloudxns/docs/api/dns_cloudxns.rst delete mode 100644 certbot-dns-digitalocean/docs/api/dns_digitalocean.rst delete mode 100644 certbot-dns-dnsimple/docs/api/dns_dnsimple.rst delete mode 100644 certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst delete mode 100644 certbot-dns-gehirn/docs/api/dns_gehirn.rst delete mode 100644 certbot-dns-google/docs/api/dns_google.rst delete mode 100644 certbot-dns-linode/docs/api/dns_linode.rst delete mode 100644 certbot-dns-luadns/docs/api/dns_luadns.rst delete mode 100644 certbot-dns-nsone/docs/api/dns_nsone.rst delete mode 100644 certbot-dns-ovh/docs/api/dns_ovh.rst delete mode 100644 certbot-dns-rfc2136/docs/api/dns_rfc2136.rst delete mode 100644 certbot-dns-route53/docs/api/authenticator.rst delete mode 100644 certbot-dns-route53/docs/api/dns_route53.rst delete mode 100644 certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst diff --git a/certbot-dns-cloudflare/docs/api.rst b/certbot-dns-cloudflare/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-cloudflare/docs/api.rst +++ b/certbot-dns-cloudflare/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst b/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst deleted file mode 100644 index 35f525201..000000000 --- a/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_cloudflare.dns_cloudflare` --------------------------------------------- - -.. automodule:: certbot_dns_cloudflare.dns_cloudflare - :members: diff --git a/certbot-dns-cloudxns/docs/api.rst b/certbot-dns-cloudxns/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-cloudxns/docs/api.rst +++ b/certbot-dns-cloudxns/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst b/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst deleted file mode 100644 index be794d1a0..000000000 --- a/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_cloudxns.dns_cloudxns` ----------------------------------------- - -.. automodule:: certbot_dns_cloudxns.dns_cloudxns - :members: diff --git a/certbot-dns-digitalocean/docs/api.rst b/certbot-dns-digitalocean/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-digitalocean/docs/api.rst +++ b/certbot-dns-digitalocean/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst b/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst deleted file mode 100644 index 8a787987e..000000000 --- a/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_digitalocean.dns_digitalocean` ------------------------------------------------- - -.. automodule:: certbot_dns_digitalocean.dns_digitalocean - :members: diff --git a/certbot-dns-dnsimple/docs/api.rst b/certbot-dns-dnsimple/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-dnsimple/docs/api.rst +++ b/certbot-dns-dnsimple/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst b/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst deleted file mode 100644 index b0544107b..000000000 --- a/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_dnsimple.dns_dnsimple` ----------------------------------------- - -.. automodule:: certbot_dns_dnsimple.dns_dnsimple - :members: diff --git a/certbot-dns-dnsmadeeasy/docs/api.rst b/certbot-dns-dnsmadeeasy/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-dnsmadeeasy/docs/api.rst +++ b/certbot-dns-dnsmadeeasy/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst b/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst deleted file mode 100644 index 81948a77f..000000000 --- a/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_dnsmadeeasy.dns_dnsmadeeasy` ------------------------------------- - -.. automodule:: certbot_dns_dnsmadeeasy.dns_dnsmadeeasy - :members: diff --git a/certbot-dns-gehirn/docs/api.rst b/certbot-dns-gehirn/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-gehirn/docs/api.rst +++ b/certbot-dns-gehirn/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-gehirn/docs/api/dns_gehirn.rst b/certbot-dns-gehirn/docs/api/dns_gehirn.rst deleted file mode 100644 index 35a13e9c1..000000000 --- a/certbot-dns-gehirn/docs/api/dns_gehirn.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_gehirn.dns_gehirn` ------------------------------------- - -.. automodule:: certbot_dns_gehirn.dns_gehirn - :members: diff --git a/certbot-dns-google/docs/api.rst b/certbot-dns-google/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-google/docs/api.rst +++ b/certbot-dns-google/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-google/docs/api/dns_google.rst b/certbot-dns-google/docs/api/dns_google.rst deleted file mode 100644 index 6f5459934..000000000 --- a/certbot-dns-google/docs/api/dns_google.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_google.dns_google` ------------------------------------- - -.. automodule:: certbot_dns_google.dns_google - :members: diff --git a/certbot-dns-linode/docs/api.rst b/certbot-dns-linode/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-linode/docs/api.rst +++ b/certbot-dns-linode/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-linode/docs/api/dns_linode.rst b/certbot-dns-linode/docs/api/dns_linode.rst deleted file mode 100644 index 6380b3eba..000000000 --- a/certbot-dns-linode/docs/api/dns_linode.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_linode.dns_linode` ------------------------------------------------- - -.. automodule:: certbot_dns_linode.dns_linode - :members: diff --git a/certbot-dns-luadns/docs/api.rst b/certbot-dns-luadns/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-luadns/docs/api.rst +++ b/certbot-dns-luadns/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-luadns/docs/api/dns_luadns.rst b/certbot-dns-luadns/docs/api/dns_luadns.rst deleted file mode 100644 index 9aecbaf05..000000000 --- a/certbot-dns-luadns/docs/api/dns_luadns.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_luadns.dns_luadns` ----------------------------------- - -.. automodule:: certbot_dns_luadns.dns_luadns - :members: diff --git a/certbot-dns-nsone/docs/api.rst b/certbot-dns-nsone/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-nsone/docs/api.rst +++ b/certbot-dns-nsone/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-nsone/docs/api/dns_nsone.rst b/certbot-dns-nsone/docs/api/dns_nsone.rst deleted file mode 100644 index 788ce732a..000000000 --- a/certbot-dns-nsone/docs/api/dns_nsone.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_nsone.dns_nsone` ----------------------------------- - -.. automodule:: certbot_dns_nsone.dns_nsone - :members: diff --git a/certbot-dns-ovh/docs/api.rst b/certbot-dns-ovh/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-ovh/docs/api.rst +++ b/certbot-dns-ovh/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-ovh/docs/api/dns_ovh.rst b/certbot-dns-ovh/docs/api/dns_ovh.rst deleted file mode 100644 index 79863d05f..000000000 --- a/certbot-dns-ovh/docs/api/dns_ovh.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_ovh.dns_ovh` ------------------------------- - -.. automodule:: certbot_dns_ovh.dns_ovh - :members: diff --git a/certbot-dns-rfc2136/docs/api.rst b/certbot-dns-rfc2136/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-rfc2136/docs/api.rst +++ b/certbot-dns-rfc2136/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst b/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst deleted file mode 100644 index f5e98454a..000000000 --- a/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_rfc2136.dns_rfc2136` --------------------------------------- - -.. automodule:: certbot_dns_rfc2136.dns_rfc2136 - :members: diff --git a/certbot-dns-route53/docs/api.rst b/certbot-dns-route53/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-route53/docs/api.rst +++ b/certbot-dns-route53/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-route53/docs/api/authenticator.rst b/certbot-dns-route53/docs/api/authenticator.rst deleted file mode 100644 index 2d96a419b..000000000 --- a/certbot-dns-route53/docs/api/authenticator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_route53.authenticator` ----------------------------------------- - -.. automodule:: certbot_dns_route53.authenticator - :members: diff --git a/certbot-dns-route53/docs/api/dns_route53.rst b/certbot-dns-route53/docs/api/dns_route53.rst deleted file mode 100644 index 7573f2e19..000000000 --- a/certbot-dns-route53/docs/api/dns_route53.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_route53.dns_route53` --------------------------------------- - -.. automodule:: certbot_dns_route53.dns_route53 - :members: diff --git a/certbot-dns-sakuracloud/docs/api.rst b/certbot-dns-sakuracloud/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-sakuracloud/docs/api.rst +++ b/certbot-dns-sakuracloud/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst b/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst deleted file mode 100644 index 74692e15b..000000000 --- a/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_sakuracloud.dns_sakuracloud` ----------------------------------------------- - -.. automodule:: certbot_dns_sakuracloud.dns_sakuracloud - :members: -- cgit v1.2.3 From 5809aa6a2caaaa228f5cbcb0967078b023d62fef Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 22 Nov 2019 13:24:51 -0800 Subject: remove unused route53 tools (#7586) --- certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100755 certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh diff --git a/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh b/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh deleted file mode 100755 index dbbaa2251..000000000 --- a/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# I just wanted a place to dump the incantations I use for testing. -set -e - -brew install openssl libffi - -rm -rf scratch; mkdir scratch - -virtualenv scratch/venv -p /usr/local/bin/python2.7 -scratch/venv/bin/pip install -U pip setuptools - -CPPFLAGS=-I/usr/local/opt/openssl/include LDFLAGS=-L/usr/local/opt/openssl/lib scratch/venv/bin/pip install -e . - -scratch/venv/bin/certbot certonly -n --agree-tos --test-cert --email pkoch@lifeonmars.pt -a certbot-route53:auth -d pkoch.lifeonmars.pt --work-dir scratch --config-dir scratch --logs-dir scratch - -rm -rf scratch -- cgit v1.2.3 From a27b1137a55db1fdf09116e7849e464a2f9e0c28 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 09:18:12 -0800 Subject: Remove unused nginx docs (#7576) Part of #5775. We don't use these docs anywhere, so delete them. Removes: - `certbot-nginx/readthedocs.org.requirements.txt` - `certbot-nginx/docs/` folder - docs include in `MANIFEST.in` - docs dependencies in `setup.py` * Remove unused nginx docs * Add changelog entry about the removal --- CHANGELOG.md | 7 +- certbot-nginx/MANIFEST.in | 1 - certbot-nginx/docs/.gitignore | 1 - certbot-nginx/docs/Makefile | 192 --------------- certbot-nginx/docs/_static/.gitignore | 0 certbot-nginx/docs/_templates/.gitignore | 0 certbot-nginx/docs/api.rst | 8 - certbot-nginx/docs/api/nginxparser.rst | 5 - certbot-nginx/docs/api/obj.rst | 5 - certbot-nginx/docs/api/parser.rst | 5 - certbot-nginx/docs/conf.py | 311 ------------------------- certbot-nginx/docs/index.rst | 31 --- certbot-nginx/docs/make.bat | 263 --------------------- certbot-nginx/readthedocs.org.requirements.txt | 12 - certbot-nginx/setup.py | 8 - 15 files changed, 6 insertions(+), 843 deletions(-) delete mode 100644 certbot-nginx/docs/.gitignore delete mode 100644 certbot-nginx/docs/Makefile delete mode 100644 certbot-nginx/docs/_static/.gitignore delete mode 100644 certbot-nginx/docs/_templates/.gitignore delete mode 100644 certbot-nginx/docs/api.rst delete mode 100644 certbot-nginx/docs/api/nginxparser.rst delete mode 100644 certbot-nginx/docs/api/obj.rst delete mode 100644 certbot-nginx/docs/api/parser.rst delete mode 100644 certbot-nginx/docs/conf.py delete mode 100644 certbot-nginx/docs/index.rst delete mode 100644 certbot-nginx/docs/make.bat delete mode 100644 certbot-nginx/readthedocs.org.requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b1685b9..dfb6acde8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * +### Removed + +* The `docs` extras for the `certbot-apache` and `certbot-nginx` packages + have been removed. + ### Changed * certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are @@ -87,7 +92,7 @@ More details about these changes can be found on our GitHub repo. ### Changed * Don't send OCSP requests for expired certificates -* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 +* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 * Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11. ### Fixed diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index 3a22c4873..fff59467b 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -1,5 +1,4 @@ include LICENSE.txt include README.rst -recursive-include docs * recursive-include certbot_nginx/tests/testdata * recursive-include certbot_nginx/tls_configs *.conf diff --git a/certbot-nginx/docs/.gitignore b/certbot-nginx/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-nginx/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-nginx/docs/Makefile b/certbot-nginx/docs/Makefile deleted file mode 100644 index 0bd88a347..000000000 --- a/certbot-nginx/docs/Makefile +++ /dev/null @@ -1,192 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certbot-nginx.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-nginx.qhc" - -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/certbot-nginx" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-nginx" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/certbot-nginx/docs/_static/.gitignore b/certbot-nginx/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-nginx/docs/_templates/.gitignore b/certbot-nginx/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-nginx/docs/api.rst b/certbot-nginx/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-nginx/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-nginx/docs/api/nginxparser.rst b/certbot-nginx/docs/api/nginxparser.rst deleted file mode 100644 index 6a3be5247..000000000 --- a/certbot-nginx/docs/api/nginxparser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.nginxparser` ------------------------------------- - -.. automodule:: certbot_nginx.nginxparser - :members: diff --git a/certbot-nginx/docs/api/obj.rst b/certbot-nginx/docs/api/obj.rst deleted file mode 100644 index a2c94037b..000000000 --- a/certbot-nginx/docs/api/obj.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.obj` ----------------------------- - -.. automodule:: certbot_nginx.obj - :members: diff --git a/certbot-nginx/docs/api/parser.rst b/certbot-nginx/docs/api/parser.rst deleted file mode 100644 index 0149f99cb..000000000 --- a/certbot-nginx/docs/api/parser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.parser` -------------------------------- - -.. automodule:: certbot_nginx.parser - :members: diff --git a/certbot-nginx/docs/conf.py b/certbot-nginx/docs/conf.py deleted file mode 100644 index d606b6292..000000000 --- a/certbot-nginx/docs/conf.py +++ /dev/null @@ -1,311 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-nginx documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:39:39 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -from certbot.compat import os -import shlex - - -here = os.path.abspath(os.path.dirname(__file__)) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -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' - -# 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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'certbot-nginx' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Let\'s Encrypt Project' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0' -# The full version, including alpha/beta/rc tags. -release = '0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'certbot-nginxdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'certbot-nginx.tex', u'certbot-nginx Documentation', - u'Let\'s Encrypt Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'certbot-nginx', u'certbot-nginx Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'certbot-nginx', u'certbot-nginx Documentation', - author, 'certbot-nginx', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), - 'certbot': ('https://certbot.eff.org/docs/', None), -} diff --git a/certbot-nginx/docs/index.rst b/certbot-nginx/docs/index.rst deleted file mode 100644 index 488a7ab9c..000000000 --- a/certbot-nginx/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. certbot-nginx documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:39:39 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-nginx's documentation! -============================================= - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -.. automodule:: certbot_nginx - :members: - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-nginx/docs/make.bat b/certbot-nginx/docs/make.bat deleted file mode 100644 index b12255d4c..000000000 --- a/certbot-nginx/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certbot-nginx.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-nginx.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/certbot-nginx/readthedocs.org.requirements.txt b/certbot-nginx/readthedocs.org.requirements.txt deleted file mode 100644 index ca5f33363..000000000 --- a/certbot-nginx/readthedocs.org.requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead - --e acme --e . --e certbot-nginx[docs] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 50febc33a..671fcef9c 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -18,11 +18,6 @@ install_requires = [ 'zope.interface', ] -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - class PyTest(TestCommand): user_options = [] @@ -74,9 +69,6 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'certbot.plugins': [ 'nginx = certbot_nginx.configurator:NginxConfigurator', -- cgit v1.2.3 From 8139689d4c404fab84009397f2368ba21133a6e7 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 09:44:40 -0800 Subject: Make the contents of the apache plugin private (#7579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #5775. Tree: ``` certbot-apache/certbot_apache ├── __init__.py ├── _internal │   ├── apache_util.py │   ├── augeas_lens │   │   ├── httpd.aug │   │   └── README │   ├── centos-options-ssl-apache.conf │   ├── configurator.py │   ├── constants.py │   ├── display_ops.py │   ├── entrypoint.py │   ├── http_01.py │   ├── __init__.py │   ├── obj.py │   ├── options-ssl-apache.conf │   ├── override_arch.py │   ├── override_centos.py │   ├── override_darwin.py │   ├── override_debian.py │   ├── override_fedora.py │   ├── override_gentoo.py │   ├── override_suse.py │   └── parser.py └── tests ├── ... ``` * Create _internal folder for certbot_apache * Move apache_util.py to _internal * Move display_ops.py to _internal * Move override_centos.py to _internal * Move override_gentoo.py to _internal * Move override_darwin.py to _internal * Move override_suse.py to _internal * Move override_debian.py to _internal * Move override_fedora.py to _internal * Move override_arch.py to _internal * Move parser.py to _internal * Move obj.py to _internal * Move http_01.py to _internal * Move entrypoint.py to _internal * Move constants.py to _internal * Move configurator.py to _internal * Move augeas_lens to _internal * Move options-ssl-apache.conf files to _internal * move augeas_lens in MANIFEST * Clean up some stray references to certbot_apache that could use _internal * Correct imports and lint --- certbot-apache/MANIFEST.in | 6 +- .../certbot_apache/_internal/__init__.py | 1 + .../certbot_apache/_internal/apache_util.py | 107 + .../certbot_apache/_internal/augeas_lens/README | 2 + .../certbot_apache/_internal/augeas_lens/httpd.aug | 206 ++ .../_internal/centos-options-ssl-apache.conf | 25 + .../certbot_apache/_internal/configurator.py | 2517 ++++++++++++++++++++ .../certbot_apache/_internal/constants.py | 70 + .../certbot_apache/_internal/display_ops.py | 125 + .../certbot_apache/_internal/entrypoint.py | 69 + certbot-apache/certbot_apache/_internal/http_01.py | 210 ++ certbot-apache/certbot_apache/_internal/obj.py | 269 +++ .../_internal/options-ssl-apache.conf | 26 + .../certbot_apache/_internal/override_arch.py | 32 + .../certbot_apache/_internal/override_centos.py | 218 ++ .../certbot_apache/_internal/override_darwin.py | 32 + .../certbot_apache/_internal/override_debian.py | 146 ++ .../certbot_apache/_internal/override_fedora.py | 99 + .../certbot_apache/_internal/override_gentoo.py | 76 + .../certbot_apache/_internal/override_suse.py | 32 + certbot-apache/certbot_apache/_internal/parser.py | 1009 ++++++++ certbot-apache/certbot_apache/apache_util.py | 107 - certbot-apache/certbot_apache/augeas_lens/README | 2 - .../certbot_apache/augeas_lens/httpd.aug | 206 -- .../certbot_apache/centos-options-ssl-apache.conf | 25 - certbot-apache/certbot_apache/configurator.py | 2517 -------------------- certbot-apache/certbot_apache/constants.py | 68 - certbot-apache/certbot_apache/display_ops.py | 125 - certbot-apache/certbot_apache/entrypoint.py | 69 - certbot-apache/certbot_apache/http_01.py | 210 -- certbot-apache/certbot_apache/obj.py | 269 --- .../certbot_apache/options-ssl-apache.conf | 26 - certbot-apache/certbot_apache/override_arch.py | 31 - certbot-apache/certbot_apache/override_centos.py | 217 -- certbot-apache/certbot_apache/override_darwin.py | 31 - certbot-apache/certbot_apache/override_debian.py | 146 -- certbot-apache/certbot_apache/override_fedora.py | 98 - certbot-apache/certbot_apache/override_gentoo.py | 75 - certbot-apache/certbot_apache/override_suse.py | 31 - certbot-apache/certbot_apache/parser.py | 1009 -------- .../certbot_apache/tests/autohsts_test.py | 38 +- .../certbot_apache/tests/centos6_test.py | 8 +- certbot-apache/certbot_apache/tests/centos_test.py | 20 +- .../certbot_apache/tests/complex_parsing_test.py | 4 +- .../tests/configurator_reverter_test.py | 2 +- .../certbot_apache/tests/configurator_test.py | 122 +- certbot-apache/certbot_apache/tests/debian_test.py | 14 +- .../certbot_apache/tests/display_ops_test.py | 16 +- .../certbot_apache/tests/entrypoint_test.py | 6 +- certbot-apache/certbot_apache/tests/fedora_test.py | 20 +- certbot-apache/certbot_apache/tests/gentoo_test.py | 19 +- .../certbot_apache/tests/http_01_test.py | 12 +- certbot-apache/certbot_apache/tests/obj_test.py | 19 +- certbot-apache/certbot_apache/tests/parser_test.py | 62 +- certbot-apache/certbot_apache/tests/util.py | 16 +- certbot-apache/setup.py | 2 +- .../configurators/apache/common.py | 4 +- certbot-nginx/certbot_nginx/obj.py | 2 +- .../certbot_nginx/tests/display_ops_test.py | 2 +- docs/install.rst | 2 +- docs/using.rst | 10 +- 61 files changed, 5475 insertions(+), 5464 deletions(-) create mode 100644 certbot-apache/certbot_apache/_internal/__init__.py create mode 100644 certbot-apache/certbot_apache/_internal/apache_util.py create mode 100644 certbot-apache/certbot_apache/_internal/augeas_lens/README create mode 100644 certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug create mode 100644 certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/_internal/configurator.py create mode 100644 certbot-apache/certbot_apache/_internal/constants.py create mode 100644 certbot-apache/certbot_apache/_internal/display_ops.py create mode 100644 certbot-apache/certbot_apache/_internal/entrypoint.py create mode 100644 certbot-apache/certbot_apache/_internal/http_01.py create mode 100644 certbot-apache/certbot_apache/_internal/obj.py create mode 100644 certbot-apache/certbot_apache/_internal/options-ssl-apache.conf create mode 100644 certbot-apache/certbot_apache/_internal/override_arch.py create mode 100644 certbot-apache/certbot_apache/_internal/override_centos.py create mode 100644 certbot-apache/certbot_apache/_internal/override_darwin.py create mode 100644 certbot-apache/certbot_apache/_internal/override_debian.py create mode 100644 certbot-apache/certbot_apache/_internal/override_fedora.py create mode 100644 certbot-apache/certbot_apache/_internal/override_gentoo.py create mode 100644 certbot-apache/certbot_apache/_internal/override_suse.py create mode 100644 certbot-apache/certbot_apache/_internal/parser.py delete mode 100644 certbot-apache/certbot_apache/apache_util.py delete mode 100644 certbot-apache/certbot_apache/augeas_lens/README delete mode 100644 certbot-apache/certbot_apache/augeas_lens/httpd.aug delete mode 100644 certbot-apache/certbot_apache/centos-options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/configurator.py delete mode 100644 certbot-apache/certbot_apache/constants.py delete mode 100644 certbot-apache/certbot_apache/display_ops.py delete mode 100644 certbot-apache/certbot_apache/entrypoint.py delete mode 100644 certbot-apache/certbot_apache/http_01.py delete mode 100644 certbot-apache/certbot_apache/obj.py delete mode 100644 certbot-apache/certbot_apache/options-ssl-apache.conf delete mode 100644 certbot-apache/certbot_apache/override_arch.py delete mode 100644 certbot-apache/certbot_apache/override_centos.py delete mode 100644 certbot-apache/certbot_apache/override_darwin.py delete mode 100644 certbot-apache/certbot_apache/override_debian.py delete mode 100644 certbot-apache/certbot_apache/override_fedora.py delete mode 100644 certbot-apache/certbot_apache/override_gentoo.py delete mode 100644 certbot-apache/certbot_apache/override_suse.py delete mode 100644 certbot-apache/certbot_apache/parser.py diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index 031cb5980..5f8396a8d 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,6 +1,6 @@ include LICENSE.txt include README.rst recursive-include certbot_apache/tests/testdata * -include certbot_apache/centos-options-ssl-apache.conf -include certbot_apache/options-ssl-apache.conf -recursive-include certbot_apache/augeas_lens *.aug +include certbot_apache/_internal/centos-options-ssl-apache.conf +include certbot_apache/_internal/options-ssl-apache.conf +recursive-include certbot_apache/_internal/augeas_lens *.aug diff --git a/certbot-apache/certbot_apache/_internal/__init__.py b/certbot-apache/certbot_apache/_internal/__init__.py new file mode 100644 index 000000000..9c195ccc7 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/__init__.py @@ -0,0 +1 @@ +"""Certbot Apache plugin.""" diff --git a/certbot-apache/certbot_apache/_internal/apache_util.py b/certbot-apache/certbot_apache/_internal/apache_util.py new file mode 100644 index 000000000..7a2ecf49b --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/apache_util.py @@ -0,0 +1,107 @@ +""" Utility functions for certbot-apache plugin """ +import binascii + +from certbot import util +from certbot.compat import os + + +def get_mod_deps(mod_name): + """Get known module dependencies. + + .. note:: This does not need to be accurate in order for the client to + run. This simply keeps things clean if the user decides to revert + changes. + .. warning:: If all deps are not included, it may cause incorrect parsing + behavior, due to enable_mod's shortcut for updating the parser's + currently defined modules (`.ApacheParser.add_mod`) + This would only present a major problem in extremely atypical + configs that use ifmod for the missing deps. + + """ + deps = { + "ssl": ["setenvif", "mime"] + } + return deps.get(mod_name, []) + + +def get_file_path(vhost_path): + """Get file path from augeas_vhost_path. + + Takes in Augeas path and returns the file name + + :param str vhost_path: Augeas virtual host path + + :returns: filename of vhost + :rtype: str + + """ + if not vhost_path or not vhost_path.startswith("/files/"): + return None + + return _split_aug_path(vhost_path)[0] + + +def get_internal_aug_path(vhost_path): + """Get the Augeas path for a vhost with the file path removed. + + :param str vhost_path: Augeas virtual host path + + :returns: Augeas path to vhost relative to the containing file + :rtype: str + + """ + return _split_aug_path(vhost_path)[1] + + +def _split_aug_path(vhost_path): + """Splits an Augeas path into a file path and an internal path. + + After removing "/files", this function splits vhost_path into the + file path and the remaining Augeas path. + + :param str vhost_path: Augeas virtual host path + + :returns: file path and internal Augeas path + :rtype: `tuple` of `str` + + """ + # Strip off /files + file_path = vhost_path[6:] + internal_path = [] + + # Remove components from the end of file_path until it becomes valid + while not os.path.exists(file_path): + file_path, _, internal_path_part = file_path.rpartition("/") + internal_path.append(internal_path_part) + + return file_path, "/".join(reversed(internal_path)) + + +def parse_define_file(filepath, varname): + """ Parses Defines from a variable in configuration file + + :param str filepath: Path of file to parse + :param str varname: Name of the variable + + :returns: Dict of Define:Value pairs + :rtype: `dict` + + """ + return_vars = {} + # Get list of words in the variable + a_opts = util.get_var_from_file(varname, filepath).split() + for i, v in enumerate(a_opts): + # Handle Define statements and make sure it has an argument + if v == "-D" and len(a_opts) >= i+2: + var_parts = a_opts[i+1].partition("=") + return_vars[var_parts[0]] = var_parts[2] + elif len(v) > 2 and v.startswith("-D"): + # Found var with no whitespace separator + var_parts = v[2:].partition("=") + return_vars[var_parts[0]] = var_parts[2] + return return_vars + + +def unique_id(): + """ Returns an unique id to be used as a VirtualHost identifier""" + return binascii.hexlify(os.urandom(16)).decode("utf-8") diff --git a/certbot-apache/certbot_apache/_internal/augeas_lens/README b/certbot-apache/certbot_apache/_internal/augeas_lens/README new file mode 100644 index 000000000..bf9161f93 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/augeas_lens/README @@ -0,0 +1,2 @@ +Certbot includes the very latest Augeas lenses in order to ship bug fixes +to Apache configuration handling bugs as quickly as possible diff --git a/certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug b/certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug new file mode 100644 index 000000000..5600088cf --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug @@ -0,0 +1,206 @@ +(* Apache HTTPD lens for Augeas + +Authors: + David Lutterkort + Francis Giraldeau + Raphael Pinson + +About: Reference + Online Apache configuration manual: http://httpd.apache.org/docs/trunk/ + +About: License + This file is licensed under the LGPL v2+. + +About: Lens Usage + Sample usage of this lens in augtool + + Apache configuration is represented by two main structures, nested sections + and directives. Sections are used as labels, while directives are kept as a + value. Sections and directives can have positional arguments inside values + of "arg" nodes. Arguments of sections must be the firsts child of the + section node. + + This lens doesn't support automatic string quoting. Hence, the string must + be quoted when containing a space. + + Create a new VirtualHost section with one directive: + > clear /files/etc/apache2/sites-available/foo/VirtualHost + > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80" + > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin" + > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com" + +About: Configuration files + This lens applies to files in /etc/httpd and /etc/apache2. See . + +*) + + +module Httpd = + +autoload xfm + +(****************************************************************** + * Utilities lens + *****************************************************************) +let dels (s:string) = del s s + +(* The continuation sequence that indicates that we should consider the + * next line part of the current line *) +let cont = /\\\\\r?\n/ + +(* Whitespace within a line: space, tab, and the continuation sequence *) +let ws = /[ \t]/ | cont + +(* Any possible character - '.' does not match \n *) +let any = /(.|\n)/ + +(* Any character preceded by a backslash *) +let esc_any = /\\\\(.|\n)/ + +(* Newline sequence - both for Unix and DOS newlines *) +let nl = /\r?\n/ + +(* Whitespace at the end of a line *) +let eol = del (ws* . nl) "\n" + +(* deal with continuation lines *) +let sep_spc = del ws+ " " +let sep_osp = del ws* "" +let sep_eq = del (ws* . "=" . ws*) "=" + +let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/ +let word = /[a-z][a-z0-9._-]*/i + +(* A complete line that is either just whitespace or a comment that only + * contains whitespace *) +let empty = [ del (ws* . /#?/ . ws* . nl) "\n" ] + +let indent = Util.indent + +(* A comment that is not just whitespace. We define it in terms of the + * things that are not allowed as part of such a comment: + * 1) Starts with whitespace + * 2) Ends with whitespace, a backslash or \r + * 3) Unescaped newlines + *) +let comment = + let comment_start = del (ws* . "#" . ws* ) "# " in + let unesc_eol = /[^\]?/ . nl in + let w = /[^\t\n\r \\]/ in + let r = /[\r\\]/ in + let s = /[\t\r ]/ in + (* + * we'd like to write + * let b = /\\\\/ in + * let t = /[\t\n\r ]/ in + * let x = b . (t? . (s|w)* ) in + * but the definition of b depends on commit 244c0edd in 1.9.0 and + * would make the lens unusable with versions before 1.9.0. So we write + * x out which works in older versions, too + *) + let x = /\\\\[\t\n\r ]?[^\n\\]*/ in + let line = ((r . s* . w|w|r) . (s|w)* . x*|(r.s* )?).w.(s*.w)* in + [ label "#comment" . comment_start . store line . eol ] + +(* borrowed from shellvars.aug *) +let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ / +let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/ + +let dquot = + let no_dquot = /[^"\\\r\n]/ + in /"/ . (no_dquot|esc_any)* . /"/ +let dquot_msg = + let no_dquot = /([^ \t"\\\r\n]|[^"\\\r\n]+[^ \t"\\\r\n])/ + in /"/ . (no_dquot|esc_any)* . no_dquot + +let squot = + let no_squot = /[^'\\\r\n]/ + in /'/ . (no_squot|esc_any)* . /'/ +let comp = /[<>=]?=/ + +(****************************************************************** + * Attributes + *****************************************************************) + +(* The arguments for a directive come in two flavors: quoted with single or + * double quotes, or bare. Bare arguments may not start with a single or + * double quote; since we also treat "word lists" special, i.e. lists + * enclosed in curly braces, bare arguments may not start with those, + * either. + * + * Bare arguments may not contain unescaped spaces, but we allow escaping + * with '\\'. Quoted arguments can contain anything, though the quote must + * be escaped with '\\'. + *) +let bare = /([^{"' \t\n\r]|\\\\.)([^ \t\n\r]|\\\\.)*[^ \t\n\r\\]|[^{"' \t\n\r\\]/ + +let arg_quoted = [ label "arg" . store (dquot|squot) ] +let arg_bare = [ label "arg" . store bare ] + +(* message argument starts with " but ends at EOL *) +let arg_dir_msg = [ label "arg" . store dquot_msg ] +let arg_wl = [ label "arg" . store (char_arg_wl+|dquot|squot) ] + +(* comma-separated wordlist as permitted in the SSLRequire directive *) +let arg_wordlist = + let wl_start = dels "{" in + let wl_end = dels "}" in + let wl_sep = del /[ \t]*,[ \t]*/ ", " + in [ label "wordlist" . wl_start . arg_wl . (wl_sep . arg_wl)* . wl_end ] + +let argv (l:lens) = l . (sep_spc . l)* + +(* the arguments of a directive. We use this once we have parsed the name + * of the directive, and the space right after it. When dir_args is used, + * we also know that we have at least one argument. We need to be careful + * with the spacing between arguments: quoted arguments and word lists do + * not need to have space between them, but bare arguments do. + * + * Apache apparently is also happy if the last argument starts with a double + * quote, but has no corresponding closing duoble quote, which is what + * arg_dir_msg handles + *) +let dir_args = + let arg_nospc = arg_quoted|arg_wordlist in + (arg_bare . sep_spc | arg_nospc . sep_osp)* . (arg_bare|arg_nospc|arg_dir_msg) + +let directive = + [ indent . label "directive" . store word . (sep_spc . dir_args)? . eol ] + +let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ] + +let section (body:lens) = + (* opt_eol includes empty lines *) + let opt_eol = del /([ \t]*#?[ \t]*\r?\n)*/ "\n" in + let inner = (sep_spc . argv arg_sec)? . sep_osp . + dels ">" . opt_eol . ((body|comment) . (body|empty|comment)*)? . + indent . dels "[ \t\n\r]*/ ">\n" ] + +let perl_section = [ indent . label "Perl" . del //i "" + . store /[^<]*/ + . del /<\/perl>/i "" . eol ] + + +let rec content = section (content|directive) + | perl_section + +let lns = (content|directive|comment|empty)* + +let filter = (incl "/etc/apache2/apache2.conf") . + (incl "/etc/apache2/httpd.conf") . + (incl "/etc/apache2/ports.conf") . + (incl "/etc/apache2/conf.d/*") . + (incl "/etc/apache2/conf-available/*.conf") . + (incl "/etc/apache2/mods-available/*") . + (incl "/etc/apache2/sites-available/*") . + (incl "/etc/apache2/vhosts.d/*.conf") . + (incl "/etc/httpd/conf.d/*.conf") . + (incl "/etc/httpd/httpd.conf") . + (incl "/etc/httpd/conf/httpd.conf") . + (incl "/etc/httpd/conf.modules.d/*.conf") . + Util.stdexcl + +let xfm = transform lns filter diff --git a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf new file mode 100644 index 000000000..56c946a4e --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf @@ -0,0 +1,25 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py new file mode 100644 index 000000000..5df61ecdc --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -0,0 +1,2517 @@ +"""Apache Configurator.""" +# pylint: disable=too-many-lines +import copy +import fnmatch +import logging +import re +import socket +import time + +from collections import defaultdict + +import pkg_resources +import six + +import zope.component +import zope.interface + +from acme import challenges +from acme.magic_typing import DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot import interfaces +from certbot import util + +from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import +from certbot.compat import filesystem +from certbot.compat import os +from certbot.plugins import common +from certbot.plugins.util import path_surgery +from certbot.plugins.enhancements import AutoHSTSEnhancement + +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants +from certbot_apache._internal import display_ops +from certbot_apache._internal import http_01 +from certbot_apache._internal import obj +from certbot_apache._internal import parser + +logger = logging.getLogger(__name__) + + +# TODO: Augeas sections ie. , beginning and closing +# tags need to be the same case, otherwise Augeas doesn't recognize them. +# This is not able to be completely remedied by regular expressions because +# Augeas views as an error. This will just +# require another check_parsing_errors() after all files are included... +# (after a find_directive search is executed currently). It can be a one +# time check however because all of LE's transactions will ensure +# only properly formed sections are added. + +# Note: This protocol works for filenames with spaces in it, the sites are +# properly set up and directives are changed appropriately, but Apache won't +# recognize names in sites-enabled that have spaces. These are not added to the +# Apache configuration. It may be wise to warn the user if they are trying +# to use vhost filenames that contain spaces and offer to change ' ' to '_' + +# Note: FILEPATHS and changes to files are transactional. They are copied +# over before the updates are made to the existing files. NEW_FILES is +# transactional due to the use of register_file_creation() + + +# TODO: Verify permissions on configuration root... it is easier than +# checking permissions on each of the relative directories and less error +# prone. +# TODO: Write a server protocol finder. Listen or +# Protocol . This can verify partial setups are correct +# TODO: Add directives to sites-enabled... not sites-available. +# sites-available doesn't allow immediate find_dir search even with save() +# and load() + +@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class ApacheConfigurator(common.Installer): + """Apache configurator. + + :ivar config: Configuration. + :type config: :class:`~certbot.interfaces.IConfig` + + :ivar parser: Handles low level parsing + :type parser: :class:`~certbot_apache._internal.parser` + + :ivar tup version: version of Apache + :ivar list vhosts: All vhosts found in the configuration + (:class:`list` of :class:`~certbot_apache._internal.obj.VirtualHost`) + + :ivar dict assoc: Mapping between domains and vhosts + + """ + + description = "Apache Web Server plugin" + if os.environ.get("CERTBOT_DOCS") == "1": + description += ( # pragma: no cover + " (Please note that the default values of the Apache plugin options" + " change depending on the operating system Certbot is run on.)" + ) + + OS_DEFAULTS = dict( + server_root="/etc/apache2", + vhost_root="/etc/apache2/sites-available", + vhost_files="*", + logs_root="/var/log/apache2", + ctl="apache2ctl", + version_cmd=['apache2ctl', '-v'], + restart_cmd=['apache2ctl', 'graceful'], + conftest_cmd=['apache2ctl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) + + def option(self, key): + """Get a value from options""" + return self.options.get(key) + + def _prepare_options(self): + """ + Set the values possibly changed by command line parameters to + OS_DEFAULTS constant dictionary + """ + opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root", + "logs_root", "challenge_location", "handle_modules", "handle_sites", + "ctl"] + for o in opts: + # Config options use dashes instead of underscores + if self.conf(o.replace("_", "-")) is not None: + self.options[o] = self.conf(o.replace("_", "-")) + else: + self.options[o] = self.OS_DEFAULTS[o] + + # Special cases + self.options["version_cmd"][0] = self.option("ctl") + self.options["restart_cmd"][0] = self.option("ctl") + self.options["conftest_cmd"][0] = self.option("ctl") + + @classmethod + def add_parser_arguments(cls, add): + # When adding, modifying or deleting command line arguments, be sure to + # include the changes in the list used in method _prepare_options() to + # ensure consistent behavior. + + # Respect CERTBOT_DOCS environment variable and use default values from + # base class regardless of the underlying distribution (overrides). + if os.environ.get("CERTBOT_DOCS") == "1": + DEFAULTS = ApacheConfigurator.OS_DEFAULTS + else: + # cls.OS_DEFAULTS can be distribution specific, see override classes + DEFAULTS = cls.OS_DEFAULTS + add("enmod", default=DEFAULTS["enmod"], + help="Path to the Apache 'a2enmod' binary") + add("dismod", default=DEFAULTS["dismod"], + help="Path to the Apache 'a2dismod' binary") + add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"], + help="SSL vhost configuration extension") + add("server-root", default=DEFAULTS["server_root"], + help="Apache server root directory") + add("vhost-root", default=None, + help="Apache server VirtualHost configuration root") + add("logs-root", default=DEFAULTS["logs_root"], + help="Apache server logs directory") + add("challenge-location", + default=DEFAULTS["challenge_location"], + help="Directory path for challenge configuration") + add("handle-modules", default=DEFAULTS["handle_modules"], + help="Let installer handle enabling required modules for you " + + "(Only Ubuntu/Debian currently)") + add("handle-sites", default=DEFAULTS["handle_sites"], + help="Let installer handle enabling sites for you " + + "(Only Ubuntu/Debian currently)") + add("ctl", default=DEFAULTS["ctl"], + help="Full path to Apache control script") + + def __init__(self, *args, **kwargs): + """Initialize an Apache Configurator. + + :param tup version: version of Apache as a tuple (2, 4, 7) + (used mostly for unittesting) + + """ + version = kwargs.pop("version", None) + super(ApacheConfigurator, self).__init__(*args, **kwargs) + + # Add name_server association dict + self.assoc = dict() # type: Dict[str, obj.VirtualHost] + # Outstanding challenges + self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge] + # List of vhosts configured per wildcard domain on this run. + # used by deploy_cert() and enhance() + self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]] + # Maps enhancements to vhosts we've enabled the enhancement for + self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]] + # Temporary state for AutoHSTS enhancement + self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]] + # Reverter save notes + self.save_notes = "" + + # These will be set in the prepare function + self._prepared = False + self.parser = None + self.version = version + self.vhosts = None + self.options = copy.deepcopy(self.OS_DEFAULTS) + self._enhance_func = {"redirect": self._enable_redirect, + "ensure-http-header": self._set_http_header, + "staple-ocsp": self._enable_ocsp_stapling} + + @property + def mod_ssl_conf(self): + """Full absolute path to SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) + + @property + def updated_mod_ssl_conf_digest(self): + """Full absolute path to digest of updated SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) + + def prepare(self): + """Prepare the authenticator/installer. + + :raises .errors.NoInstallationError: If Apache configs cannot be found + :raises .errors.MisconfigurationError: If Apache is misconfigured + :raises .errors.NotSupportedError: If Apache version is not supported + :raises .errors.PluginError: If there is any other error + + """ + self._prepare_options() + + # Verify Apache is installed + self._verify_exe_availability(self.option("ctl")) + + # Make sure configuration is valid + self.config_test() + + # Set Version + if self.version is None: + self.version = self.get_version() + logger.debug('Apache version is %s', + '.'.join(str(i) for i in self.version)) + if self.version < (2, 2): + raise errors.NotSupportedError( + "Apache Version {0} not supported.".format(str(self.version))) + + # Recover from previous crash before Augeas initialization to have the + # correct parse tree from the get go. + self.recovery_routine() + # Perform the actual Augeas initialization to be able to react + self.parser = self.get_parser() + + # Check for errors in parsing files with Augeas + self.parser.check_parsing_errors("httpd.aug") + + # Get all of the available vhosts + self.vhosts = self.get_virtual_hosts() + + self.install_ssl_options_conf(self.mod_ssl_conf, + self.updated_mod_ssl_conf_digest) + + # Prevent two Apache plugins from modifying a config at once + try: + util.lock_dir_until_exit(self.option("server_root")) + except (OSError, errors.LockError): + logger.debug("Encountered error:", exc_info=True) + raise errors.PluginError( + "Unable to create a lock file in {0}. Are you running" + " Certbot with sufficient privileges to modify your" + " Apache configuration?".format(self.option("server_root"))) + self._prepared = True + + def save(self, title=None, temporary=False): + """Saves all changes to the configuration files. + + This function first checks for save errors, if none are found, + all configuration changes made will be saved. According to the + function parameters. If an exception is raised, a new checkpoint + was not created. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (ie. challenges) + + """ + save_files = self.parser.unsaved_files() + if save_files: + self.add_to_checkpoint(save_files, + self.save_notes, temporary=temporary) + # Handle the parser specific tasks + self.parser.save(save_files) + if title and not temporary: + self.finalize_checkpoint(title) + + def recovery_routine(self): + """Revert all previously modified files. + + Reverts all modified files that have not been saved as a checkpoint + + :raises .errors.PluginError: If unable to recover the configuration + + """ + super(ApacheConfigurator, self).recovery_routine() + # Reload configuration after these changes take effect if needed + # ie. ApacheParser has been initialized. + if self.parser: + # TODO: wrap into non-implementation specific parser interface + self.parser.aug.load() + + def revert_challenge_config(self): + """Used to cleanup challenge configurations. + + :raises .errors.PluginError: If unable to revert the challenge config. + + """ + self.revert_temporary_config() + self.parser.aug.load() + + def rollback_checkpoints(self, rollback=1): + """Rollback saved checkpoints. + + :param int rollback: Number of checkpoints to revert + + :raises .errors.PluginError: If there is a problem with the input or + the function is unable to correctly revert the configuration + + """ + super(ApacheConfigurator, self).rollback_checkpoints(rollback) + self.parser.aug.load() + + def _verify_exe_availability(self, exe): + """Checks availability of Apache executable""" + if not util.exe_exists(exe): + if not path_surgery(exe): + raise errors.NoInstallationError( + 'Cannot find Apache executable {0}'.format(exe)) + + def get_parser(self): + """Initializes the ApacheParser""" + # If user provided vhost_root value in command line, use it + return parser.ApacheParser( + self.option("server_root"), self.conf("vhost-root"), + self.version, configurator=self) + + def _wildcard_domain(self, domain): + """ + Checks if domain is a wildcard domain + + :param str domain: Domain to check + + :returns: If the domain is wildcard domain + :rtype: bool + """ + if isinstance(domain, six.text_type): + wildcard_marker = u"*." + else: + wildcard_marker = b"*." + return domain.startswith(wildcard_marker) + + def deploy_cert(self, domain, cert_path, key_path, + chain_path=None, fullchain_path=None): + """Deploys certificate to specified virtual host. + + Currently tries to find the last directives to deploy the certificate + in the VHost associated with the given domain. If it can't find the + directives, it searches the "included" confs. The function verifies + that it has located the three directives and finally modifies them + to point to the correct destination. After the certificate is + installed, the VirtualHost is enabled if it isn't already. + + .. todo:: Might be nice to remove chain directive if none exists + This shouldn't happen within certbot though + + :raises errors.PluginError: When unable to deploy certificate due to + a lack of directives + + """ + vhosts = self.choose_vhosts(domain) + for vhost in vhosts: + self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) + + def choose_vhosts(self, domain, create_if_no_ssl=True): + """ + Finds VirtualHosts that can be used with the provided domain + + :param str domain: Domain name to match VirtualHosts to + :param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS + counterpart, should one get created + + :returns: List of VirtualHosts or None + :rtype: `list` of :class:`~certbot_apache._internal.obj.VirtualHost` + """ + + if self._wildcard_domain(domain): + if domain in self._wildcard_vhosts: + # Vhosts for a wildcard domain were already selected + return self._wildcard_vhosts[domain] + # Ask user which VHosts to support. + # Returned objects are guaranteed to be ssl vhosts + return self._choose_vhosts_wildcard(domain, create_if_no_ssl) + else: + return [self.choose_vhost(domain, create_if_no_ssl)] + + def _vhosts_for_wildcard(self, domain): + """ + Get VHost objects for every VirtualHost that the user wants to handle + with the wildcard certificate. + """ + + # Collect all vhosts that match the name + matched = set() + for vhost in self.vhosts: + for name in vhost.get_names(): + if self._in_wildcard_scope(name, domain): + matched.add(vhost) + + return list(matched) + + def _in_wildcard_scope(self, name, domain): + """ + Helper method for _vhosts_for_wildcard() that makes sure that the domain + is in the scope of wildcard domain. + + eg. in scope: domain = *.wild.card, name = 1.wild.card + not in scope: domain = *.wild.card, name = 1.2.wild.card + """ + if len(name.split(".")) == len(domain.split(".")): + return fnmatch.fnmatch(name, domain) + return None + + def _choose_vhosts_wildcard(self, domain, create_ssl=True): + """Prompts user to choose vhosts to install a wildcard certificate for""" + + # Get all vhosts that are covered by the wildcard domain + vhosts = self._vhosts_for_wildcard(domain) + + # Go through the vhosts, making sure that we cover all the names + # present, but preferring the SSL vhosts + filtered_vhosts = dict() + for vhost in vhosts: + for name in vhost.get_names(): + if vhost.ssl: + # Always prefer SSL vhosts + filtered_vhosts[name] = vhost + elif name not in filtered_vhosts and create_ssl: + # Add if not in list previously + filtered_vhosts[name] = vhost + + # Only unique VHost objects + dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + + # Ask the user which of names to enable, expect list of names back + dialog_output = display_ops.select_vhost_multiple(list(dialog_input)) + + if not dialog_output: + logger.error( + "No vhost exists with servername or alias for domain %s. " + "No vhost was selected. Please specify ServerName or ServerAlias " + "in the Apache config.", + domain) + raise errors.PluginError("No vhost selected") + + # Make sure we create SSL vhosts for the ones that are HTTP only + # if requested. + return_vhosts = list() + for vhost in dialog_output: + if not vhost.ssl: + return_vhosts.append(self.make_vhost_ssl(vhost)) + else: + return_vhosts.append(vhost) + + self._wildcard_vhosts[domain] = return_vhosts + return return_vhosts + + def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): + """ + Helper function for deploy_cert() that handles the actual deployment + this exists because we might want to do multiple deployments per + domain originally passed for deploy_cert(). This is especially true + with wildcard certificates + """ + # This is done first so that ssl module is enabled and cert_path, + # cert_key... can all be parsed appropriately + self.prepare_server_https("443") + + # Add directives and remove duplicates + self._add_dummy_ssl_directives(vhost.path) + self._clean_vhost(vhost) + + path = {"cert_path": self.parser.find_dir("SSLCertificateFile", + None, vhost.path), + "cert_key": self.parser.find_dir("SSLCertificateKeyFile", + None, vhost.path)} + + # Only include if a certificate chain is specified + if chain_path is not None: + path["chain_path"] = self.parser.find_dir( + "SSLCertificateChainFile", None, vhost.path) + + # Handle errors when certificate/key directives cannot be found + if not path["cert_path"]: + logger.warning( + "Cannot find an SSLCertificateFile directive in %s. " + "VirtualHost was not modified", vhost.path) + raise errors.PluginError( + "Unable to find an SSLCertificateFile directive") + elif not path["cert_key"]: + logger.warning( + "Cannot find an SSLCertificateKeyFile directive for " + "certificate in %s. VirtualHost was not modified", vhost.path) + raise errors.PluginError( + "Unable to find an SSLCertificateKeyFile directive for " + "certificate") + + logger.info("Deploying Certificate to VirtualHost %s", vhost.filep) + + if self.version < (2, 4, 8) or (chain_path and not fullchain_path): + # install SSLCertificateFile, SSLCertificateKeyFile, + # and SSLCertificateChainFile directives + set_cert_path = cert_path + self.parser.aug.set(path["cert_path"][-1], cert_path) + self.parser.aug.set(path["cert_key"][-1], key_path) + if chain_path is not None: + self.parser.add_dir(vhost.path, + "SSLCertificateChainFile", chain_path) + else: + raise errors.PluginError("--chain-path is required for your " + "version of Apache") + else: + if not fullchain_path: + raise errors.PluginError("Please provide the --fullchain-path " + "option pointing to your full chain file") + set_cert_path = fullchain_path + self.parser.aug.set(path["cert_path"][-1], fullchain_path) + self.parser.aug.set(path["cert_key"][-1], key_path) + + # Enable the new vhost if needed + if not vhost.enabled: + self.enable_site(vhost) + + # Save notes about the transaction that took place + self.save_notes += ("Changed vhost at %s with addresses of %s\n" + "\tSSLCertificateFile %s\n" + "\tSSLCertificateKeyFile %s\n" % + (vhost.filep, + ", ".join(str(addr) for addr in vhost.addrs), + set_cert_path, key_path)) + if chain_path is not None: + self.save_notes += "\tSSLCertificateChainFile %s\n" % chain_path + + def choose_vhost(self, target_name, create_if_no_ssl=True): + """Chooses a virtual host based on the given domain name. + + If there is no clear virtual host to be selected, the user is prompted + with all available choices. + + The returned vhost is guaranteed to have TLS enabled unless + create_if_no_ssl is set to False, in which case there is no such guarantee + and the result is not cached. + + :param str target_name: domain name + :param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS + counterpart, should one get created + + :returns: vhost associated with name + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` + + :raises .errors.PluginError: If no vhost is available or chosen + + """ + # Allows for domain names to be associated with a virtual host + if target_name in self.assoc: + return self.assoc[target_name] + + # Try to find a reasonable vhost + vhost = self._find_best_vhost(target_name) + if vhost is not None: + if not create_if_no_ssl: + return vhost + if not vhost.ssl: + vhost = self.make_vhost_ssl(vhost) + + self._add_servername_alias(target_name, vhost) + self.assoc[target_name] = vhost + return vhost + + # Negate create_if_no_ssl value to indicate if we want a SSL vhost + # to get created if a non-ssl vhost is selected. + return self._choose_vhost_from_list(target_name, temp=not create_if_no_ssl) + + def _choose_vhost_from_list(self, target_name, temp=False): + # Select a vhost from a list + vhost = display_ops.select_vhost(target_name, self.vhosts) + if vhost is None: + logger.error( + "No vhost exists with servername or alias of %s. " + "No vhost was selected. Please specify ServerName or ServerAlias " + "in the Apache config.", + target_name) + raise errors.PluginError("No vhost selected") + elif temp: + return vhost + elif not vhost.ssl: + addrs = self._get_proposed_addrs(vhost, "443") + # TODO: Conflicts is too conservative + if not any(vhost.enabled and vhost.conflicts(addrs) for + vhost in self.vhosts): + vhost = self.make_vhost_ssl(vhost) + else: + logger.error( + "The selected vhost would conflict with other HTTPS " + "VirtualHosts within Apache. Please select another " + "vhost or add ServerNames to your configuration.") + raise errors.PluginError( + "VirtualHost not able to be selected.") + + self._add_servername_alias(target_name, vhost) + self.assoc[target_name] = vhost + return vhost + + def domain_in_names(self, names, target_name): + """Checks if target domain is covered by one or more of the provided + names. The target name is matched by wildcard as well as exact match. + + :param names: server aliases + :type names: `collections.Iterable` of `str` + :param str target_name: name to compare with wildcards + + :returns: True if target_name is covered by a wildcard, + otherwise, False + :rtype: bool + + """ + # use lowercase strings because fnmatch can be case sensitive + target_name = target_name.lower() + for name in names: + name = name.lower() + # fnmatch treats "[seq]" specially and [ or ] characters aren't + # valid in Apache but Apache doesn't error out if they are present + if "[" not in name and fnmatch.fnmatch(target_name, name): + return True + return False + + def find_best_http_vhost(self, target, filter_defaults, port="80"): + """Returns non-HTTPS vhost objects found from the Apache config + + :param str target: Domain name of the desired VirtualHost + :param bool filter_defaults: whether _default_ vhosts should be + included if it is the best match + :param str port: port number the vhost should be listening on + + :returns: VirtualHost object that's the best match for target name + :rtype: `obj.VirtualHost` or None + """ + filtered_vhosts = [] + for vhost in self.vhosts: + if any(a.is_wildcard() or a.get_port() == port for a in vhost.addrs) and not vhost.ssl: + filtered_vhosts.append(vhost) + return self._find_best_vhost(target, filtered_vhosts, filter_defaults) + + def _find_best_vhost(self, target_name, vhosts=None, filter_defaults=True): + """Finds the best vhost for a target_name. + + This does not upgrade a vhost to HTTPS... it only finds the most + appropriate vhost for the given target_name. + + :param str target_name: domain handled by the desired vhost + :param vhosts: vhosts to consider + :type vhosts: `collections.Iterable` of :class:`~certbot_apache._internal.obj.VirtualHost` + :param bool filter_defaults: whether a vhost with a _default_ + addr is acceptable + + :returns: VHost or None + + """ + # Points 6 - Servername SSL + # Points 5 - Wildcard SSL + # Points 4 - Address name with SSL + # Points 3 - Servername no SSL + # Points 2 - Wildcard no SSL + # Points 1 - Address name with no SSL + best_candidate = None + best_points = 0 + + if vhosts is None: + vhosts = self.vhosts + + for vhost in vhosts: + if vhost.modmacro is True: + continue + names = vhost.get_names() + if target_name in names: + points = 3 + elif self.domain_in_names(names, target_name): + points = 2 + elif any(addr.get_addr() == target_name for addr in vhost.addrs): + points = 1 + else: + # No points given if names can't be found. + # This gets hit but doesn't register + continue # pragma: no cover + + if vhost.ssl: + points += 3 + + if points > best_points: + best_points = points + best_candidate = vhost + + # No winners here... is there only one reasonable vhost? + if best_candidate is None: + if filter_defaults: + vhosts = self._non_default_vhosts(vhosts) + # remove mod_macro hosts from reasonable vhosts + reasonable_vhosts = [vh for vh + in vhosts if vh.modmacro is False] + if len(reasonable_vhosts) == 1: + best_candidate = reasonable_vhosts[0] + + return best_candidate + + def _non_default_vhosts(self, vhosts): + """Return all non _default_ only vhosts.""" + return [vh for vh in vhosts if not all( + addr.get_addr() == "_default_" for addr in vh.addrs + )] + + def get_all_names(self): + """Returns all names found in the Apache Configuration. + + :returns: All ServerNames, ServerAliases, and reverse DNS entries for + virtual host addresses + :rtype: set + + """ + all_names = set() # type: Set[str] + + vhost_macro = [] + + for vhost in self.vhosts: + all_names.update(vhost.get_names()) + if vhost.modmacro: + vhost_macro.append(vhost.filep) + + for addr in vhost.addrs: + if common.hostname_regex.match(addr.get_addr()): + all_names.add(addr.get_addr()) + else: + name = self.get_name_from_ip(addr) + if name: + all_names.add(name) + + if vhost_macro: + zope.component.getUtility(interfaces.IDisplay).notification( + "Apache mod_macro seems to be in use in file(s):\n{0}" + "\n\nUnfortunately mod_macro is not yet supported".format( + "\n ".join(vhost_macro)), force_interactive=True) + + return util.get_filtered_names(all_names) + + def get_name_from_ip(self, addr): # pylint: disable=no-self-use + """Returns a reverse dns name if available. + + :param addr: IP Address + :type addr: ~.common.Addr + + :returns: name or empty string if name cannot be determined + :rtype: str + + """ + # If it isn't a private IP, do a reverse DNS lookup + if not common.private_ips_regex.match(addr.get_addr()): + try: + socket.inet_aton(addr.get_addr()) + return socket.gethostbyaddr(addr.get_addr())[0] + except (socket.error, socket.herror, socket.timeout): + pass + + return "" + + def _get_vhost_names(self, path): + """Helper method for getting the ServerName and + ServerAlias values from vhost in path + + :param path: Path to read ServerName and ServerAliases from + + :returns: Tuple including ServerName and `list` of ServerAlias strings + """ + + servername_match = self.parser.find_dir( + "ServerName", None, start=path, exclude=False) + serveralias_match = self.parser.find_dir( + "ServerAlias", None, start=path, exclude=False) + + serveraliases = [] + for alias in serveralias_match: + serveralias = self.parser.get_arg(alias) + serveraliases.append(serveralias) + + servername = None + if servername_match: + # Get last ServerName as each overwrites the previous + servername = self.parser.get_arg(servername_match[-1]) + + return (servername, serveraliases) + + def _add_servernames(self, host): + """Helper function for get_virtual_hosts(). + + :param host: In progress vhost whose names will be added + :type host: :class:`~certbot_apache._internal.obj.VirtualHost` + + """ + + servername, serveraliases = self._get_vhost_names(host.path) + + for alias in serveraliases: + if not host.modmacro: + host.aliases.add(alias) + + if not host.modmacro: + host.name = servername + + def _create_vhost(self, path): + """Used by get_virtual_hosts to create vhost objects + + :param str path: Augeas path to virtual host + + :returns: newly created vhost + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` + + """ + addrs = set() + try: + args = self.parser.aug.match(path + "/arg") + except RuntimeError: + logger.warning("Encountered a problem while parsing file: %s, skipping", path) + return None + for arg in args: + addrs.add(obj.Addr.fromstring(self.parser.get_arg(arg))) + is_ssl = False + + if self.parser.find_dir("SSLEngine", "on", start=path, exclude=False): + is_ssl = True + + # "SSLEngine on" might be set outside of + # Treat vhosts with port 443 as ssl vhosts + for addr in addrs: + if addr.get_port() == "443": + is_ssl = True + + filename = apache_util.get_file_path( + self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) + if filename is None: + return None + + macro = False + if "/macro/" in path.lower(): + macro = True + + vhost_enabled = self.parser.parsed_in_original(filename) + + vhost = obj.VirtualHost(filename, path, addrs, is_ssl, + vhost_enabled, modmacro=macro) + self._add_servernames(vhost) + return vhost + + def get_virtual_hosts(self): + """Returns list of virtual hosts found in the Apache configuration. + + :returns: List of :class:`~certbot_apache._internal.obj.VirtualHost` + objects found in configuration + :rtype: list + + """ + # Search base config, and all included paths for VirtualHosts + file_paths = {} # type: Dict[str, str] + internal_paths = defaultdict(set) # type: DefaultDict[str, Set[str]] + vhs = [] + # Make a list of parser paths because the parser_paths + # dictionary may be modified during the loop. + for vhost_path in list(self.parser.parser_paths): + paths = self.parser.aug.match( + ("/files%s//*[label()=~regexp('%s')]" % + (vhost_path, parser.case_i("VirtualHost")))) + paths = [path for path in paths if + "virtualhost" in os.path.basename(path).lower()] + for path in paths: + new_vhost = self._create_vhost(path) + if not new_vhost: + continue + internal_path = apache_util.get_internal_aug_path(new_vhost.path) + realpath = filesystem.realpath(new_vhost.filep) + if realpath not in file_paths: + file_paths[realpath] = new_vhost.filep + internal_paths[realpath].add(internal_path) + vhs.append(new_vhost) + elif (realpath == new_vhost.filep and + realpath != file_paths[realpath]): + # Prefer "real" vhost paths instead of symlinked ones + # ex: sites-enabled/vh.conf -> sites-available/vh.conf + + # remove old (most likely) symlinked one + new_vhs = [] + for v in vhs: + if v.filep == file_paths[realpath]: + internal_paths[realpath].remove( + apache_util.get_internal_aug_path(v.path)) + else: + new_vhs.append(v) + vhs = new_vhs + + file_paths[realpath] = realpath + internal_paths[realpath].add(internal_path) + vhs.append(new_vhost) + elif internal_path not in internal_paths[realpath]: + internal_paths[realpath].add(internal_path) + vhs.append(new_vhost) + return vhs + + def is_name_vhost(self, target_addr): + """Returns if vhost is a name based vhost + + NameVirtualHost was deprecated in Apache 2.4 as all VirtualHosts are + now NameVirtualHosts. If version is earlier than 2.4, check if addr + has a NameVirtualHost directive in the Apache config + + :param certbot_apache._internal.obj.Addr target_addr: vhost address + + :returns: Success + :rtype: bool + + """ + # Mixed and matched wildcard NameVirtualHost with VirtualHost + # behavior is undefined. Make sure that an exact match exists + + # search for NameVirtualHost directive for ip_addr + # note ip_addr can be FQDN although Apache does not recommend it + return (self.version >= (2, 4) or + self.parser.find_dir("NameVirtualHost", str(target_addr))) + + def add_name_vhost(self, addr): + """Adds NameVirtualHost directive for given address. + + :param addr: Address that will be added as NameVirtualHost directive + :type addr: :class:`~certbot_apache._internal.obj.Addr` + + """ + + loc = parser.get_aug_path(self.parser.loc["name"]) + if addr.get_port() == "443": + path = self.parser.add_dir_to_ifmodssl( + loc, "NameVirtualHost", [str(addr)]) + else: + path = self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) + + msg = ("Setting %s to be NameBasedVirtualHost\n" + "\tDirective added to %s\n" % (addr, path)) + logger.debug(msg) + self.save_notes += msg + + def prepare_server_https(self, port, temp=False): + """Prepare the server for HTTPS. + + Make sure that the ssl_module is loaded and that the server + is appropriately listening on port. + + :param str port: Port to listen on + + """ + + self.prepare_https_modules(temp) + self.ensure_listen(port, https=True) + + def ensure_listen(self, port, https=False): + """Make sure that Apache is listening on the port. Checks if the + Listen statement for the port already exists, and adds it to the + configuration if necessary. + + :param str port: Port number to check and add Listen for if not in + place already + :param bool https: If the port will be used for HTTPS + + """ + + # If HTTPS requested for nonstandard port, add service definition + if https and port != "443": + port_service = "%s %s" % (port, "https") + else: + port_service = port + + # Check for Listen + # Note: This could be made to also look for ip:443 combo + listens = [self.parser.get_arg(x).split()[0] for + x in self.parser.find_dir("Listen")] + + # Listen already in place + if self._has_port_already(listens, port): + return + + listen_dirs = set(listens) + + if not listens: + listen_dirs.add(port_service) + + for listen in listens: + # For any listen statement, check if the machine also listens on + # the given port. If not, add such a listen statement. + if len(listen.split(":")) == 1: + # Its listening to all interfaces + if port not in listen_dirs and port_service not in listen_dirs: + listen_dirs.add(port_service) + else: + # The Listen statement specifies an ip + _, ip = listen[::-1].split(":", 1) + ip = ip[::-1] + if "%s:%s" % (ip, port_service) not in listen_dirs and ( + "%s:%s" % (ip, port_service) not in listen_dirs): + listen_dirs.add("%s:%s" % (ip, port_service)) + if https: + self._add_listens_https(listen_dirs, listens, port) + else: + self._add_listens_http(listen_dirs, listens, port) + + def _add_listens_http(self, listens, listens_orig, port): + """Helper method for ensure_listen to figure out which new + listen statements need adding for listening HTTP on port + + :param set listens: Set of all needed Listen statements + :param list listens_orig: List of existing listen statements + :param string port: Port number we're adding + """ + + new_listens = listens.difference(listens_orig) + + if port in new_listens: + # We have wildcard, skip the rest + self.parser.add_dir(parser.get_aug_path(self.parser.loc["listen"]), + "Listen", port) + self.save_notes += "Added Listen %s directive to %s\n" % ( + port, self.parser.loc["listen"]) + else: + for listen in new_listens: + self.parser.add_dir(parser.get_aug_path( + self.parser.loc["listen"]), "Listen", listen.split(" ")) + self.save_notes += ("Added Listen %s directive to " + "%s\n") % (listen, + self.parser.loc["listen"]) + + def _add_listens_https(self, listens, listens_orig, port): + """Helper method for ensure_listen to figure out which new + listen statements need adding for listening HTTPS on port + + :param set listens: Set of all needed Listen statements + :param list listens_orig: List of existing listen statements + :param string port: Port number we're adding + """ + + # Add service definition for non-standard ports + if port != "443": + port_service = "%s %s" % (port, "https") + else: + port_service = port + + new_listens = listens.difference(listens_orig) + + if port in new_listens or port_service in new_listens: + # We have wildcard, skip the rest + self.parser.add_dir_to_ifmodssl( + parser.get_aug_path(self.parser.loc["listen"]), + "Listen", port_service.split(" ")) + self.save_notes += "Added Listen %s directive to %s\n" % ( + port_service, self.parser.loc["listen"]) + else: + for listen in new_listens: + self.parser.add_dir_to_ifmodssl( + parser.get_aug_path(self.parser.loc["listen"]), + "Listen", listen.split(" ")) + self.save_notes += ("Added Listen %s directive to " + "%s\n") % (listen, + self.parser.loc["listen"]) + + def _has_port_already(self, listens, port): + """Helper method for prepare_server_https to find out if user + already has an active Listen statement for the port we need + + :param list listens: List of listen variables + :param string port: Port in question + """ + + if port in listens: + return True + # Check if Apache is already listening on a specific IP + for listen in listens: + if len(listen.split(":")) > 1: + # Ugly but takes care of protocol def, eg: 1.1.1.1:443 https + if listen.split(":")[-1].split(" ")[0] == port: + return True + return None + + def prepare_https_modules(self, temp): + """Helper method for prepare_server_https, taking care of enabling + needed modules + + :param boolean temp: If the change is temporary + """ + + if self.option("handle_modules"): + if self.version >= (2, 4) and ("socache_shmcb_module" not in + self.parser.modules): + self.enable_mod("socache_shmcb", temp=temp) + if "ssl_module" not in self.parser.modules: + self.enable_mod("ssl", temp=temp) + + def make_vhost_ssl(self, nonssl_vhost): + """Makes an ssl_vhost version of a nonssl_vhost. + + Duplicates vhost and adds default ssl options + New vhost will reside as (nonssl_vhost.path) + + ``self.option("le_vhost_ext")`` + + .. note:: This function saves the configuration + + :param nonssl_vhost: Valid VH that doesn't have SSLEngine on + :type nonssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :returns: SSL vhost + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` + + :raises .errors.PluginError: If more than one virtual host is in + the file or if plugin is unable to write/read vhost files. + + """ + avail_fp = nonssl_vhost.filep + ssl_fp = self._get_ssl_vhost_path(avail_fp) + + orig_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % + (self._escape(ssl_fp), + parser.case_i("VirtualHost"))) + + self._copy_create_ssl_vhost_skeleton(nonssl_vhost, ssl_fp) + + # Reload augeas to take into account the new vhost + self.parser.aug.load() + # Get Vhost augeas path for new vhost + new_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % + (self._escape(ssl_fp), + parser.case_i("VirtualHost"))) + + vh_p = self._get_new_vh_path(orig_matches, new_matches) + + if not vh_p: + # The vhost was not found on the currently parsed paths + # Make Augeas aware of the new vhost + self.parser.parse_file(ssl_fp) + # Try to search again + new_matches = self.parser.aug.match( + "/files%s//* [label()=~regexp('%s')]" % + (self._escape(ssl_fp), + parser.case_i("VirtualHost"))) + vh_p = self._get_new_vh_path(orig_matches, new_matches) + if not vh_p: + raise errors.PluginError( + "Could not reverse map the HTTPS VirtualHost to the original") + + + # Update Addresses + self._update_ssl_vhosts_addrs(vh_p) + + # Log actions and create save notes + logger.info("Created an SSL vhost at %s", ssl_fp) + self.save_notes += "Created ssl vhost at %s\n" % ssl_fp + self.save() + + # We know the length is one because of the assertion above + # Create the Vhost object + ssl_vhost = self._create_vhost(vh_p) + ssl_vhost.ancestor = nonssl_vhost + + self.vhosts.append(ssl_vhost) + + # NOTE: Searches through Augeas seem to ruin changes to directives + # The configuration must also be saved before being searched + # for the new directives; For these reasons... this is tacked + # on after fully creating the new vhost + + # Now check if addresses need to be added as NameBasedVhost addrs + # This is for compliance with versions of Apache < 2.4 + self._add_name_vhost_if_necessary(ssl_vhost) + + return ssl_vhost + + def _get_new_vh_path(self, orig_matches, new_matches): + """ Helper method for make_vhost_ssl for matching augeas paths. Returns + VirtualHost path from new_matches that's not present in orig_matches. + + Paths are normalized, because augeas leaves indices out for paths + with only single directive with a similar key """ + + orig_matches = [i.replace("[1]", "") for i in orig_matches] + for match in new_matches: + if match.replace("[1]", "") not in orig_matches: + # Return the unmodified path + return match + return None + + def _get_ssl_vhost_path(self, non_ssl_vh_fp): + """ Get a file path for SSL vhost, uses user defined path as priority, + but if the value is invalid or not defined, will fall back to non-ssl + vhost filepath. + + :param str non_ssl_vh_fp: Filepath of non-SSL vhost + + :returns: Filepath for SSL vhost + :rtype: str + """ + + if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")): + fp = os.path.join(filesystem.realpath(self.option("vhost_root")), + os.path.basename(non_ssl_vh_fp)) + else: + # Use non-ssl filepath + fp = filesystem.realpath(non_ssl_vh_fp) + + if fp.endswith(".conf"): + return fp[:-(len(".conf"))] + self.option("le_vhost_ext") + return fp + self.option("le_vhost_ext") + + def _sift_rewrite_rule(self, line): + """Decides whether a line should be copied to a SSL vhost. + + A canonical example of when sifting a line is required: + When the http vhost contains a RewriteRule that unconditionally + redirects any request to the https version of the same site. + e.g: + RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,QSA,R=permanent] + Copying the above line to the ssl vhost would cause a + redirection loop. + + :param str line: a line extracted from the http vhost. + + :returns: True - don't copy line from http vhost to SSL vhost. + :rtype: bool + + """ + if not line.lower().lstrip().startswith("rewriterule"): + return False + + # According to: http://httpd.apache.org/docs/2.4/rewrite/flags.html + # The syntax of a RewriteRule is: + # RewriteRule pattern target [Flag1,Flag2,Flag3] + # i.e. target is required, so it must exist. + target = line.split()[2].strip() + + # target may be surrounded with quotes + if target[0] in ("'", '"') and target[0] == target[-1]: + target = target[1:-1] + + # Sift line if it redirects the request to a HTTPS site + return target.startswith("https://") + + def _copy_create_ssl_vhost_skeleton(self, vhost, ssl_fp): + """Copies over existing Vhost with IfModule mod_ssl.c> skeleton. + + :param obj.VirtualHost vhost: Original VirtualHost object + :param str ssl_fp: Full path where the new ssl_vhost will reside. + + A new file is created on the filesystem. + + """ + # First register the creation so that it is properly removed if + # configuration is rolled back + if os.path.exists(ssl_fp): + notes = "Appended new VirtualHost directive to file %s" % ssl_fp + files = set() + files.add(ssl_fp) + self.reverter.add_to_checkpoint(files, notes) + else: + self.reverter.register_file_creation(False, ssl_fp) + sift = False + + try: + orig_contents = self._get_vhost_block(vhost) + ssl_vh_contents, sift = self._sift_rewrite_rules(orig_contents) + + with open(ssl_fp, "a") as new_file: + new_file.write("\n") + new_file.write("\n".join(ssl_vh_contents)) + # The content does not include the closing tag, so add it + new_file.write("\n") + new_file.write("\n") + # Add new file to augeas paths if we're supposed to handle + # activation (it's not included as default) + if not self.parser.parsed_in_current(ssl_fp): + self.parser.parse_file(ssl_fp) + except IOError: + logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True) + raise errors.PluginError("Unable to write/read in make_vhost_ssl") + + if sift: + reporter = zope.component.getUtility(interfaces.IReporter) + reporter.add_message( + "Some rewrite rules copied from {0} were disabled in the " + "vhost for your HTTPS site located at {1} because they have " + "the potential to create redirection loops.".format( + vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY) + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") + + def _sift_rewrite_rules(self, contents): + """ Helper function for _copy_create_ssl_vhost_skeleton to prepare the + new HTTPS VirtualHost contents. Currently disabling the rewrites """ + + result = [] + sift = False + contents = iter(contents) + + comment = ("# Some rewrite rules in this file were " + "disabled on your HTTPS site,\n" + "# because they have the potential to create " + "redirection loops.\n") + + for line in contents: + A = line.lower().lstrip().startswith("rewritecond") + B = line.lower().lstrip().startswith("rewriterule") + + if not (A or B): + result.append(line) + continue + + # A RewriteRule that doesn't need filtering + if B and not self._sift_rewrite_rule(line): + result.append(line) + continue + + # A RewriteRule that does need filtering + if B and self._sift_rewrite_rule(line): + if not sift: + result.append(comment) + sift = True + result.append("# " + line) + continue + + # We save RewriteCond(s) and their corresponding + # RewriteRule in 'chunk'. + # We then decide whether we comment out the entire + # chunk based on its RewriteRule. + chunk = [] + if A: + chunk.append(line) + line = next(contents) + + # RewriteCond(s) must be followed by one RewriteRule + while not line.lower().lstrip().startswith("rewriterule"): + chunk.append(line) + line = next(contents) + + # Now, current line must start with a RewriteRule + chunk.append(line) + + if self._sift_rewrite_rule(line): + if not sift: + result.append(comment) + sift = True + + result.append('\n'.join( + ['# ' + l for l in chunk])) + continue + else: + result.append('\n'.join(chunk)) + continue + return result, sift + + def _get_vhost_block(self, vhost): + """ Helper method to get VirtualHost contents from the original file. + This is done with help of augeas span, which returns the span start and + end positions + + :returns: `list` of VirtualHost block content lines without closing tag + """ + + try: + span_val = self.parser.aug.span(vhost.path) + except ValueError: + logger.critical("Error while reading the VirtualHost %s from " + "file %s", vhost.name, vhost.filep, exc_info=True) + raise errors.PluginError("Unable to read VirtualHost from file") + span_filep = span_val[0] + span_start = span_val[5] + span_end = span_val[6] + with open(span_filep, 'r') as fh: + fh.seek(span_start) + vh_contents = fh.read(span_end-span_start).split("\n") + self._remove_closing_vhost_tag(vh_contents) + return vh_contents + + def _remove_closing_vhost_tag(self, vh_contents): + """Removes the closing VirtualHost tag if it exists. + + This method modifies vh_contents directly to remove the closing + tag. If the closing vhost tag is found, everything on the line + after it is also removed. Whether or not this tag is included + in the result of span depends on the Augeas version. + + :param list vh_contents: VirtualHost block contents to check + + """ + for offset, line in enumerate(reversed(vh_contents)): + if line: + line_index = line.lower().find("") + if line_index != -1: + content_index = len(vh_contents) - offset - 1 + vh_contents[content_index] = line[:line_index] + break + + def _update_ssl_vhosts_addrs(self, vh_path): + ssl_addrs = set() + ssl_addr_p = self.parser.aug.match(vh_path + "/arg") + + for addr in ssl_addr_p: + old_addr = obj.Addr.fromstring( + str(self.parser.get_arg(addr))) + ssl_addr = old_addr.get_addr_obj("443") + self.parser.aug.set(addr, str(ssl_addr)) + ssl_addrs.add(ssl_addr) + + return ssl_addrs + + def _clean_vhost(self, vhost): + # remove duplicated or conflicting ssl directives + self._deduplicate_directives(vhost.path, + ["SSLCertificateFile", + "SSLCertificateKeyFile"]) + # remove all problematic directives + self._remove_directives(vhost.path, ["SSLCertificateChainFile"]) + + def _deduplicate_directives(self, vh_path, directives): + for directive in directives: + while len(self.parser.find_dir(directive, None, + vh_path, False)) > 1: + directive_path = self.parser.find_dir(directive, None, + vh_path, False) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + + def _remove_directives(self, vh_path, directives): + for directive in directives: + while self.parser.find_dir(directive, None, vh_path, False): + directive_path = self.parser.find_dir(directive, None, + vh_path, False) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + + def _add_dummy_ssl_directives(self, vh_path): + self.parser.add_dir(vh_path, "SSLCertificateFile", + "insert_cert_file_path") + self.parser.add_dir(vh_path, "SSLCertificateKeyFile", + "insert_key_file_path") + # Only include the TLS configuration if not already included + existing_inc = self.parser.find_dir("Include", self.mod_ssl_conf, vh_path) + if not existing_inc: + self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) + + def _add_servername_alias(self, target_name, vhost): + vh_path = vhost.path + sname, saliases = self._get_vhost_names(vh_path) + if target_name == sname or target_name in saliases: + return + if self._has_matching_wildcard(vh_path, target_name): + return + if not self.parser.find_dir("ServerName", None, + start=vh_path, exclude=False): + self.parser.add_dir(vh_path, "ServerName", target_name) + else: + self.parser.add_dir(vh_path, "ServerAlias", target_name) + self._add_servernames(vhost) + + def _has_matching_wildcard(self, vh_path, target_name): + """Is target_name already included in a wildcard in the vhost? + + :param str vh_path: Augeas path to the vhost + :param str target_name: name to compare with wildcards + + :returns: True if there is a wildcard covering target_name in + the vhost in vhost_path, otherwise, False + :rtype: bool + + """ + matches = self.parser.find_dir( + "ServerAlias", start=vh_path, exclude=False) + aliases = (self.parser.aug.get(match) for match in matches) + return self.domain_in_names(aliases, target_name) + + def _add_name_vhost_if_necessary(self, vhost): + """Add NameVirtualHost Directives if necessary for new vhost. + + NameVirtualHosts was a directive in Apache < 2.4 + https://httpd.apache.org/docs/2.2/mod/core.html#namevirtualhost + + :param vhost: New virtual host that was recently created. + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + """ + need_to_save = False + + # See if the exact address appears in any other vhost + # Remember 1.1.1.1:* == 1.1.1.1 -> hence any() + for addr in vhost.addrs: + # In Apache 2.2, when a NameVirtualHost directive is not + # set, "*" and "_default_" will conflict when sharing a port + addrs = set((addr,)) + if addr.get_addr() in ("*", "_default_"): + addrs.update(obj.Addr((a, addr.get_port(),)) + for a in ("*", "_default_")) + + for test_vh in self.vhosts: + if (vhost.filep != test_vh.filep and + any(test_addr in addrs for + test_addr in test_vh.addrs) and + not self.is_name_vhost(addr)): + self.add_name_vhost(addr) + logger.info("Enabling NameVirtualHosts on %s", addr) + need_to_save = True + break + + if need_to_save: + self.save() + + def find_vhost_by_id(self, id_str): + """ + Searches through VirtualHosts and tries to match the id in a comment + + :param str id_str: Id string for matching + + :returns: The matched VirtualHost or None + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` or None + + :raises .errors.PluginError: If no VirtualHost is found + """ + + for vh in self.vhosts: + if self._find_vhost_id(vh) == id_str: + return vh + msg = "No VirtualHost with ID {} was found.".format(id_str) + logger.warning(msg) + raise errors.PluginError(msg) + + def _find_vhost_id(self, vhost): + """Tries to find the unique ID from the VirtualHost comments. This is + used for keeping track of VirtualHost directive over time. + + :param vhost: Virtual host to add the id + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :returns: The unique ID or None + :rtype: str or None + """ + + # Strip the {} off from the format string + search_comment = constants.MANAGED_COMMENT_ID.format("") + + id_comment = self.parser.find_comments(search_comment, vhost.path) + if id_comment: + # Use the first value, multiple ones shouldn't exist + comment = self.parser.get_arg(id_comment[0]) + return comment.split(" ")[-1] + return None + + def add_vhost_id(self, vhost): + """Adds an unique ID to the VirtualHost as a comment for mapping back + to it on later invocations, as the config file order might have changed. + If ID already exists, returns that instead. + + :param vhost: Virtual host to add or find the id + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :returns: The unique ID for vhost + :rtype: str or None + """ + + vh_id = self._find_vhost_id(vhost) + if vh_id: + return vh_id + + id_string = apache_util.unique_id() + comment = constants.MANAGED_COMMENT_ID.format(id_string) + self.parser.add_comment(vhost.path, comment) + return id_string + + def _escape(self, fp): + fp = fp.replace(",", "\\,") + fp = fp.replace("[", "\\[") + fp = fp.replace("]", "\\]") + fp = fp.replace("|", "\\|") + fp = fp.replace("=", "\\=") + fp = fp.replace("(", "\\(") + fp = fp.replace(")", "\\)") + fp = fp.replace("!", "\\!") + return fp + + ###################################################################### + # Enhancements + ###################################################################### + def supported_enhancements(self): # pylint: disable=no-self-use + """Returns currently supported enhancements.""" + return ["redirect", "ensure-http-header", "staple-ocsp"] + + def enhance(self, domain, enhancement, options=None): + """Enhance configuration. + + :param str domain: domain to enhance + :param str enhancement: enhancement type defined in + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + :param options: options for the enhancement + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + documentation for appropriate parameter. + + :raises .errors.PluginError: If Enhancement is not supported, or if + there is any other problem with the enhancement. + + """ + try: + func = self._enhance_func[enhancement] + except KeyError: + raise errors.PluginError( + "Unsupported enhancement: {0}".format(enhancement)) + + matched_vhosts = self.choose_vhosts(domain, create_if_no_ssl=False) + # We should be handling only SSL vhosts for enhancements + vhosts = [vhost for vhost in matched_vhosts if vhost.ssl] + + if not vhosts: + msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a " + "domain {0} for enabling enhancement \"{1}\". The requested " + "enhancement was not configured.") + msg_enhancement = enhancement + if options: + msg_enhancement += ": " + options + msg = msg_tmpl.format(domain, msg_enhancement) + logger.warning(msg) + raise errors.PluginError(msg) + try: + for vhost in vhosts: + func(vhost, options) + except errors.PluginError: + logger.warning("Failed %s for %s", enhancement, domain) + raise + + def _autohsts_increase(self, vhost, id_str, nextstep): + """Increase the AutoHSTS max-age value + + :param vhost: Virtual host object to modify + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :param str id_str: The unique ID string of VirtualHost + + :param int nextstep: Next AutoHSTS max-age value index + + """ + nextstep_value = constants.AUTOHSTS_STEPS[nextstep] + self._autohsts_write(vhost, nextstep_value) + self._autohsts[id_str] = {"laststep": nextstep, "timestamp": time.time()} + + def _autohsts_write(self, vhost, nextstep_value): + """ + Write the new HSTS max-age value to the VirtualHost file + """ + + hsts_dirpath = None + header_path = self.parser.find_dir("Header", None, vhost.path) + if header_path: + pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' + for match in header_path: + if re.search(pat, self.parser.aug.get(match).lower()): + hsts_dirpath = match + if not hsts_dirpath: + err_msg = ("Certbot was unable to find the existing HSTS header " + "from the VirtualHost at path {0}.").format(vhost.filep) + raise errors.PluginError(err_msg) + + # Prepare the HSTS header value + hsts_maxage = "\"max-age={0}\"".format(nextstep_value) + + # Update the header + # Our match statement was for string strict-transport-security, but + # we need to update the value instead. The next index is for the value + hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]") + self.parser.aug.set(hsts_dirpath, hsts_maxage) + note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost " + "in {1}\n".format(nextstep_value, vhost.filep)) + logger.debug(note_msg) + self.save_notes += note_msg + self.save(note_msg) + + def _autohsts_fetch_state(self): + """ + Populates the AutoHSTS state from the pluginstorage + """ + try: + self._autohsts = self.storage.fetch("autohsts") + except KeyError: + self._autohsts = dict() + + def _autohsts_save_state(self): + """ + Saves the state of AutoHSTS object to pluginstorage + """ + self.storage.put("autohsts", self._autohsts) + self.storage.save() + + def _autohsts_vhost_in_lineage(self, vhost, lineage): + """ + Searches AutoHSTS managed VirtualHosts that belong to the lineage. + Matches the private key path. + """ + + return bool( + self.parser.find_dir("SSLCertificateKeyFile", + lineage.key_path, vhost.path)) + + def _enable_ocsp_stapling(self, ssl_vhost, unused_options): + """Enables OCSP Stapling + + In OCSP, each client (e.g. browser) would have to query the + OCSP Responder to validate that the site certificate was not revoked. + + Enabling OCSP Stapling, would allow the web-server to query the OCSP + Responder, and staple its response to the offered certificate during + TLS. i.e. clients would not have to query the OCSP responder. + + OCSP Stapling enablement on Apache implicitly depends on + SSLCertificateChainFile being set by other code. + + .. note:: This function saves the configuration + + :param ssl_vhost: Destination of traffic, an ssl enabled vhost + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :param unused_options: Not currently used + :type unused_options: Not Available + + :returns: Success, general_vhost (HTTP vhost) + :rtype: (bool, :class:`~certbot_apache._internal.obj.VirtualHost`) + + """ + min_apache_ver = (2, 3, 3) + if self.get_version() < min_apache_ver: + raise errors.PluginError( + "Unable to set OCSP directives.\n" + "Apache version is below 2.3.3.") + + if "socache_shmcb_module" not in self.parser.modules: + self.enable_mod("socache_shmcb") + + # Check if there's an existing SSLUseStapling directive on. + use_stapling_aug_path = self.parser.find_dir("SSLUseStapling", + "on", start=ssl_vhost.path) + if not use_stapling_aug_path: + self.parser.add_dir(ssl_vhost.path, "SSLUseStapling", "on") + + ssl_vhost_aug_path = self._escape(parser.get_aug_path(ssl_vhost.filep)) + + # Check if there's an existing SSLStaplingCache directive. + stapling_cache_aug_path = self.parser.find_dir('SSLStaplingCache', + None, ssl_vhost_aug_path) + + # We'll simply delete the directive, so that we'll have a + # consistent OCSP cache path. + if stapling_cache_aug_path: + self.parser.aug.remove( + re.sub(r"/\w*$", "", stapling_cache_aug_path[0])) + + self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path, + "SSLStaplingCache", + ["shmcb:/var/run/apache2/stapling_cache(128000)"]) + + msg = "OCSP Stapling was enabled on SSL Vhost: %s.\n"%( + ssl_vhost.filep) + self.save_notes += msg + self.save() + logger.info(msg) + + def _set_http_header(self, ssl_vhost, header_substring): + """Enables header that is identified by header_substring on ssl_vhost. + + If the header identified by header_substring is not already set, + a new Header directive is placed in ssl_vhost's configuration with + arguments from: constants.HTTP_HEADER[header_substring] + + .. note:: This function saves the configuration + + :param ssl_vhost: Destination of traffic, an ssl enabled vhost + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :param header_substring: string that uniquely identifies a header. + e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. + :type str + + :returns: Success, general_vhost (HTTP vhost) + :rtype: (bool, :class:`~certbot_apache._internal.obj.VirtualHost`) + + :raises .errors.PluginError: If no viable HTTP host can be created or + set with header header_substring. + + """ + if "headers_module" not in self.parser.modules: + self.enable_mod("headers") + + # Check if selected header is already set + self._verify_no_matching_http_header(ssl_vhost, header_substring) + + # Add directives to server + self.parser.add_dir(ssl_vhost.path, "Header", + constants.HEADER_ARGS[header_substring]) + + self.save_notes += ("Adding %s header to ssl vhost in %s\n" % + (header_substring, ssl_vhost.filep)) + + self.save() + logger.info("Adding %s header to ssl vhost in %s", header_substring, + ssl_vhost.filep) + + def _verify_no_matching_http_header(self, ssl_vhost, header_substring): + """Checks to see if an there is an existing Header directive that + contains the string header_substring. + + :param ssl_vhost: vhost to check + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :param header_substring: string that uniquely identifies a header. + e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. + :type str + + :returns: boolean + :rtype: (bool) + + :raises errors.PluginEnhancementAlreadyPresent When header + header_substring exists + + """ + header_path = self.parser.find_dir("Header", None, + start=ssl_vhost.path) + if header_path: + # "Existing Header directive for virtualhost" + pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower()) + for match in header_path: + if re.search(pat, self.parser.aug.get(match).lower()): + raise errors.PluginEnhancementAlreadyPresent( + "Existing %s header" % (header_substring)) + + def _enable_redirect(self, ssl_vhost, unused_options): + """Redirect all equivalent HTTP traffic to ssl_vhost. + + .. todo:: This enhancement should be rewritten and will + unfortunately require lots of debugging by hand. + + Adds Redirect directive to the port 80 equivalent of ssl_vhost + First the function attempts to find the vhost with equivalent + ip addresses that serves on non-ssl ports + The function then adds the directive + + .. note:: This function saves the configuration + + :param ssl_vhost: Destination of traffic, an ssl enabled vhost + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :param unused_options: Not currently used + :type unused_options: Not Available + + :raises .errors.PluginError: If no viable HTTP host can be created or + used for the redirect. + + """ + if "rewrite_module" not in self.parser.modules: + self.enable_mod("rewrite") + general_vh = self._get_http_vhost(ssl_vhost) + + if general_vh is None: + # Add virtual_server with redirect + logger.debug("Did not find http version of ssl virtual host " + "attempting to create") + redirect_addrs = self._get_proposed_addrs(ssl_vhost) + for vhost in self.vhosts: + if vhost.enabled and vhost.conflicts(redirect_addrs): + raise errors.PluginError( + "Unable to find corresponding HTTP vhost; " + "Unable to create one as intended addresses conflict; " + "Current configuration does not support automated " + "redirection") + self._create_redirect_vhost(ssl_vhost) + else: + if general_vh in self._enhanced_vhosts["redirect"]: + logger.debug("Already enabled redirect for this vhost") + return + + # Check if Certbot redirection already exists + self._verify_no_certbot_redirect(general_vh) + + # Note: if code flow gets here it means we didn't find the exact + # certbot RewriteRule config for redirection. Finding + # another RewriteRule is likely to be fine in most or all cases, + # but redirect loops are possible in very obscure cases; see #1620 + # for reasoning. + if self._is_rewrite_exists(general_vh): + logger.warning("Added an HTTP->HTTPS rewrite in addition to " + "other RewriteRules; you may wish to check for " + "overall consistency.") + + # Add directives to server + # Note: These are not immediately searchable in sites-enabled + # even with save() and load() + if not self._is_rewrite_engine_on(general_vh): + self.parser.add_dir(general_vh.path, "RewriteEngine", "on") + + names = ssl_vhost.get_names() + for idx, name in enumerate(names): + args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"] + if idx == len(names) - 1: + args.pop() + self.parser.add_dir(general_vh.path, "RewriteCond", args) + + self._set_https_redirection_rewrite_rule(general_vh) + + self.save_notes += ("Redirecting host in %s to ssl vhost in %s\n" % + (general_vh.filep, ssl_vhost.filep)) + self.save() + + self._enhanced_vhosts["redirect"].add(general_vh) + logger.info("Redirecting vhost in %s to ssl vhost in %s", + general_vh.filep, ssl_vhost.filep) + + def _set_https_redirection_rewrite_rule(self, vhost): + if self.get_version() >= (2, 3, 9): + self.parser.add_dir(vhost.path, "RewriteRule", + constants.REWRITE_HTTPS_ARGS_WITH_END) + else: + self.parser.add_dir(vhost.path, "RewriteRule", + constants.REWRITE_HTTPS_ARGS) + + def _verify_no_certbot_redirect(self, vhost): + """Checks to see if a redirect was already installed by certbot. + + Checks to see if virtualhost already contains a rewrite rule that is + identical to Certbot's redirection rewrite rule. + + For graceful transition to new rewrite rules for HTTPS redireciton we + delete certbot's old rewrite rules and set the new one instead. + + :param vhost: vhost to check + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :raises errors.PluginEnhancementAlreadyPresent: When the exact + certbot redirection WriteRule exists in virtual host. + """ + rewrite_path = self.parser.find_dir( + "RewriteRule", None, start=vhost.path) + + # There can be other RewriteRule directive lines in vhost config. + # rewrite_args_dict keys are directive ids and the corresponding value + # for each is a list of arguments to that directive. + rewrite_args_dict = defaultdict(list) # type: DefaultDict[str, List[str]] + pat = r'(.*directive\[\d+\]).*' + for match in rewrite_path: + m = re.match(pat, match) + if m: + dir_path = m.group(1) + rewrite_args_dict[dir_path].append(match) + + if rewrite_args_dict: + redirect_args = [constants.REWRITE_HTTPS_ARGS, + constants.REWRITE_HTTPS_ARGS_WITH_END] + + for dir_path, args_paths in rewrite_args_dict.items(): + arg_vals = [self.parser.aug.get(x) for x in args_paths] + + # Search for past redirection rule, delete it, set the new one + if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS: + self.parser.aug.remove(dir_path) + self._set_https_redirection_rewrite_rule(vhost) + self.save() + raise errors.PluginEnhancementAlreadyPresent( + "Certbot has already enabled redirection") + + if arg_vals in redirect_args: + raise errors.PluginEnhancementAlreadyPresent( + "Certbot has already enabled redirection") + + def _is_rewrite_exists(self, vhost): + """Checks if there exists a RewriteRule directive in vhost + + :param vhost: vhost to check + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :returns: True if a RewriteRule directive exists. + :rtype: bool + + """ + rewrite_path = self.parser.find_dir( + "RewriteRule", None, start=vhost.path) + return bool(rewrite_path) + + def _is_rewrite_engine_on(self, vhost): + """Checks if a RewriteEngine directive is on + + :param vhost: vhost to check + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + """ + rewrite_engine_path_list = self.parser.find_dir("RewriteEngine", "on", + start=vhost.path) + if rewrite_engine_path_list: + for re_path in rewrite_engine_path_list: + # A RewriteEngine directive may also be included in per + # directory .htaccess files. We only care about the VirtualHost. + if 'virtualhost' in re_path.lower(): + return self.parser.get_arg(re_path) + return False + + def _create_redirect_vhost(self, ssl_vhost): + """Creates an http_vhost specifically to redirect for the ssl_vhost. + + :param ssl_vhost: ssl vhost + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :returns: tuple of the form + (`success`, :class:`~certbot_apache._internal.obj.VirtualHost`) + :rtype: tuple + + """ + text = self._get_redirect_config_str(ssl_vhost) + + redirect_filepath = self._write_out_redirect(ssl_vhost, text) + + self.parser.aug.load() + # Make a new vhost data structure and add it to the lists + new_vhost = self._create_vhost(parser.get_aug_path(self._escape(redirect_filepath))) + self.vhosts.append(new_vhost) + self._enhanced_vhosts["redirect"].add(new_vhost) + + # Finally create documentation for the change + self.save_notes += ("Created a port 80 vhost, %s, for redirection to " + "ssl vhost %s\n" % + (new_vhost.filep, ssl_vhost.filep)) + + def _get_redirect_config_str(self, ssl_vhost): + # get servernames and serveraliases + serveralias = "" + servername = "" + + if ssl_vhost.name is not None: + servername = "ServerName " + ssl_vhost.name + if ssl_vhost.aliases: + serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases) + + rewrite_rule_args = [] # type: List[str] + if self.get_version() >= (2, 3, 9): + rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END + else: + rewrite_rule_args = constants.REWRITE_HTTPS_ARGS + + return ("\n" + "%s \n" + "%s \n" + "ServerSignature Off\n" + "\n" + "RewriteEngine On\n" + "RewriteRule %s\n" + "\n" + "ErrorLog %s/redirect.error.log\n" + "LogLevel warn\n" + "\n" + % (" ".join(str(addr) for + addr in self._get_proposed_addrs(ssl_vhost)), + servername, serveralias, + " ".join(rewrite_rule_args), + self.option("logs_root"))) + + def _write_out_redirect(self, ssl_vhost, text): + # This is the default name + redirect_filename = "le-redirect.conf" + + # See if a more appropriate name can be applied + if ssl_vhost.name is not None: + # make sure servername doesn't exceed filename length restriction + if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)): + redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name + + redirect_filepath = os.path.join(self.option("vhost_root"), + redirect_filename) + + # Register the new file that will be created + # Note: always register the creation before writing to ensure file will + # be removed in case of unexpected program exit + self.reverter.register_file_creation(False, redirect_filepath) + + # Write out file + with open(redirect_filepath, "w") as redirect_file: + redirect_file.write(text) + + # Add new include to configuration if it doesn't exist yet + if not self.parser.parsed_in_current(redirect_filepath): + self.parser.parse_file(redirect_filepath) + + logger.info("Created redirect file: %s", redirect_filename) + + return redirect_filepath + + def _get_http_vhost(self, ssl_vhost): + """Find appropriate HTTP vhost for ssl_vhost.""" + # First candidate vhosts filter + if ssl_vhost.ancestor: + return ssl_vhost.ancestor + candidate_http_vhs = [ + vhost for vhost in self.vhosts if not vhost.ssl + ] + + # Second filter - check addresses + for http_vh in candidate_http_vhs: + if http_vh.same_server(ssl_vhost): + return http_vh + # Third filter - if none with same names, return generic + for http_vh in candidate_http_vhs: + if http_vh.same_server(ssl_vhost, generic=True): + return http_vh + + return None + + def _get_proposed_addrs(self, vhost, port="80"): + """Return all addrs of vhost with the port replaced with the specified. + + :param obj.VirtualHost ssl_vhost: Original Vhost + :param str port: Desired port for new addresses + + :returns: `set` of :class:`~obj.Addr` + + """ + redirects = set() + for addr in vhost.addrs: + redirects.add(addr.get_addr_obj(port)) + + return redirects + + def enable_site(self, vhost): + """Enables an available site, Apache reload required. + + .. note:: Does not make sure that the site correctly works or that all + modules are enabled appropriately. + .. note:: The distribution specific override replaces functionality + of this method where available. + + :param vhost: vhost to enable + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :raises .errors.NotSupportedError: If filesystem layout is not + supported. + + """ + if vhost.enabled: + return + + if not self.parser.parsed_in_original(vhost.filep): + # Add direct include to root conf + logger.info("Enabling site %s by adding Include to root configuration", + vhost.filep) + self.save_notes += "Enabled site %s\n" % vhost.filep + self.parser.add_include(self.parser.loc["default"], vhost.filep) + vhost.enabled = True + return + + def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument + """Enables module in Apache. + + Both enables and reloads Apache so module is active. + + :param str mod_name: Name of the module to enable. (e.g. 'ssl') + :param bool temp: Whether or not this is a temporary action. + + .. note:: The distribution specific override replaces functionality + of this method where available. + + :raises .errors.MisconfigurationError: We cannot enable modules in + generic fashion. + + """ + mod_message = ("Apache needs to have module \"{0}\" active for the " + + "requested installation options. Unfortunately Certbot is unable " + + "to install or enable it for you. Please install the module, and " + + "run Certbot again.") + raise errors.MisconfigurationError(mod_message.format(mod_name)) + + def restart(self): + """Runs a config test and reloads the Apache server. + + :raises .errors.MisconfigurationError: If either the config test + or reload fails. + + """ + self.config_test() + self._reload() + + def _reload(self): + """Reloads the Apache server. + + :raises .errors.MisconfigurationError: If reload fails + + """ + try: + util.run_script(self.option("restart_cmd")) + except errors.SubprocessError as err: + logger.info("Unable to restart apache using %s", + self.option("restart_cmd")) + alt_restart = self.option("restart_cmd_alt") + if alt_restart: + logger.debug("Trying alternative restart command: %s", + alt_restart) + # There is an alternative restart command available + # This usually is "restart" verb while original is "graceful" + try: + util.run_script(self.option( + "restart_cmd_alt")) + return + except errors.SubprocessError as secerr: + error = str(secerr) + else: + error = str(err) + raise errors.MisconfigurationError(error) + + def config_test(self): # pylint: disable=no-self-use + """Check the configuration of Apache for errors. + + :raises .errors.MisconfigurationError: If config_test fails + + """ + try: + util.run_script(self.option("conftest_cmd")) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + def get_version(self): + """Return version of Apache Server. + + Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) + + :returns: version + :rtype: tuple + + :raises .PluginError: if unable to find Apache version + + """ + try: + stdout, _ = util.run_script(self.option("version_cmd")) + except errors.SubprocessError: + raise errors.PluginError( + "Unable to run %s -v" % + self.option("version_cmd")) + + regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE) + matches = regex.findall(stdout) + + if len(matches) != 1: + raise errors.PluginError("Unable to find Apache version") + + return tuple([int(i) for i in matches[0].split(".")]) + + def more_info(self): + """Human-readable string to help understand the module""" + return ( + "Configures Apache to authenticate and install HTTPS.{0}" + "Server root: {root}{0}" + "Version: {version}".format( + os.linesep, root=self.parser.loc["root"], + version=".".join(str(i) for i in self.version)) + ) + + ########################################################################### + # Challenges Section + ########################################################################### + def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + """Return list of challenge preferences.""" + return [challenges.HTTP01] + + def perform(self, achalls): + """Perform the configuration related challenge. + + This function currently assumes all challenges will be fulfilled. + If this turns out not to be the case in the future. Cleanup and + outstanding challenges will have to be designed better. + + """ + self._chall_out.update(achalls) + responses = [None] * len(achalls) + http_doer = http_01.ApacheHttp01(self) + + for i, achall in enumerate(achalls): + # Currently also have chall_doer hold associated index of the + # challenge. This helps to put all of the responses back together + # when they are all complete. + http_doer.add_chall(achall, i) + + http_response = http_doer.perform() + if http_response: + # Must reload in order to activate the challenges. + # Handled here because we may be able to load up other challenge + # types + self.restart() + + # TODO: Remove this dirty hack. We need to determine a reliable way + # of identifying when the new configuration is being used. + time.sleep(3) + + self._update_responses(responses, http_response, http_doer) + + return responses + + def _update_responses(self, responses, chall_response, chall_doer): + # Go through all of the challenges and assign them to the proper + # place in the responses return value. All responses must be in the + # same order as the original challenges. + for i, resp in enumerate(chall_response): + responses[chall_doer.indices[i]] = resp + + def cleanup(self, achalls): + """Revert all challenges.""" + self._chall_out.difference_update(achalls) + + # If all of the challenges have been finished, clean up everything + if not self._chall_out: + self.revert_challenge_config() + self.restart() + self.parser.reset_modules() + + def install_ssl_options_conf(self, options_ssl, options_ssl_digest): + """Copy Certbot's SSL options file into the system's config dir if required.""" + + # XXX if we ever try to enforce a local privilege boundary (eg, running + # certbot for unprivileged users via setuid), this function will need + # to be modified. + return common.install_version_controlled_file(options_ssl, options_ssl_digest, + self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES) + + def enable_autohsts(self, _unused_lineage, domains): + """ + Enable the AutoHSTS enhancement for defined domains + + :param _unused_lineage: Certificate lineage object, unused + :type _unused_lineage: certbot._internal.storage.RenewableCert + + :param domains: List of domains in certificate to enhance + :type domains: str + """ + + self._autohsts_fetch_state() + _enhanced_vhosts = [] + for d in domains: + matched_vhosts = self.choose_vhosts(d, create_if_no_ssl=False) + # We should be handling only SSL vhosts for AutoHSTS + vhosts = [vhost for vhost in matched_vhosts if vhost.ssl] + + if not vhosts: + msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a " + "domain {0} for enabling AutoHSTS enhancement.") + msg = msg_tmpl.format(d) + logger.warning(msg) + raise errors.PluginError(msg) + for vh in vhosts: + try: + self._enable_autohsts_domain(vh) + _enhanced_vhosts.append(vh) + except errors.PluginEnhancementAlreadyPresent: + if vh in _enhanced_vhosts: + continue + msg = ("VirtualHost for domain {0} in file {1} has a " + + "String-Transport-Security header present, exiting.") + raise errors.PluginEnhancementAlreadyPresent( + msg.format(d, vh.filep)) + if _enhanced_vhosts: + note_msg = "Enabling AutoHSTS" + self.save(note_msg) + logger.info(note_msg) + self.restart() + + # Save the current state to pluginstorage + self._autohsts_save_state() + + def _enable_autohsts_domain(self, ssl_vhost): + """Do the initial AutoHSTS deployment to a vhost + + :param ssl_vhost: The VirtualHost object to deploy the AutoHSTS + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` or None + + :raises errors.PluginEnhancementAlreadyPresent: When already enhanced + + """ + # This raises the exception + self._verify_no_matching_http_header(ssl_vhost, + "Strict-Transport-Security") + + if "headers_module" not in self.parser.modules: + self.enable_mod("headers") + # Prepare the HSTS header value + hsts_header = constants.HEADER_ARGS["Strict-Transport-Security"][:-1] + initial_maxage = constants.AUTOHSTS_STEPS[0] + hsts_header.append("\"max-age={0}\"".format(initial_maxage)) + + # Add ID to the VirtualHost for mapping back to it later + uniq_id = self.add_vhost_id(ssl_vhost) + self.save_notes += "Adding unique ID {0} to VirtualHost in {1}\n".format( + uniq_id, ssl_vhost.filep) + # Add the actual HSTS header + self.parser.add_dir(ssl_vhost.path, "Header", hsts_header) + note_msg = ("Adding gradually increasing HSTS header with initial value " + "of {0} to VirtualHost in {1}\n".format( + initial_maxage, ssl_vhost.filep)) + self.save_notes += note_msg + + # Save the current state to pluginstorage + self._autohsts[uniq_id] = {"laststep": 0, "timestamp": time.time()} + + def update_autohsts(self, _unused_domain): + """ + Increase the AutoHSTS values of VirtualHosts that the user has enabled + this enhancement for. + + :param _unused_domain: Not currently used + :type _unused_domain: Not Available + + """ + self._autohsts_fetch_state() + if not self._autohsts: + # No AutoHSTS enabled for any domain + return + curtime = time.time() + save_and_restart = False + for id_str, config in list(self._autohsts.items()): + if config["timestamp"] + constants.AUTOHSTS_FREQ > curtime: + # Skip if last increase was < AUTOHSTS_FREQ ago + continue + nextstep = config["laststep"] + 1 + if nextstep < len(constants.AUTOHSTS_STEPS): + # If installer hasn't been prepared yet, do it now + if not self._prepared: + self.prepare() + # Have not reached the max value yet + try: + vhost = self.find_vhost_by_id(id_str) + except errors.PluginError: + msg = ("Could not find VirtualHost with ID {0}, disabling " + "AutoHSTS for this VirtualHost").format(id_str) + logger.warning(msg) + # Remove the orphaned AutoHSTS entry from pluginstorage + self._autohsts.pop(id_str) + continue + self._autohsts_increase(vhost, id_str, nextstep) + msg = ("Increasing HSTS max-age value for VirtualHost with id " + "{0}").format(id_str) + self.save_notes += msg + save_and_restart = True + + if save_and_restart: + self.save("Increased HSTS max-age values") + self.restart() + + self._autohsts_save_state() + + def deploy_autohsts(self, lineage): + """ + Checks if autohsts vhost has reached maximum auto-increased value + and changes the HSTS max-age to a high value. + + :param lineage: Certificate lineage object + :type lineage: certbot._internal.storage.RenewableCert + """ + self._autohsts_fetch_state() + if not self._autohsts: + # No autohsts enabled for any vhost + return + + vhosts = [] + affected_ids = [] + # Copy, as we are removing from the dict inside the loop + for id_str, config in list(self._autohsts.items()): + if config["laststep"]+1 >= len(constants.AUTOHSTS_STEPS): + # max value reached, try to make permanent + try: + vhost = self.find_vhost_by_id(id_str) + except errors.PluginError: + msg = ("VirtualHost with id {} was not found, unable to " + "make HSTS max-age permanent.").format(id_str) + logger.warning(msg) + self._autohsts.pop(id_str) + continue + if self._autohsts_vhost_in_lineage(vhost, lineage): + vhosts.append(vhost) + affected_ids.append(id_str) + + save_and_restart = False + for vhost in vhosts: + self._autohsts_write(vhost, constants.AUTOHSTS_PERMANENT) + msg = ("Strict-Transport-Security max-age value for " + "VirtualHost in {0} was made permanent.").format(vhost.filep) + logger.debug(msg) + self.save_notes += msg+"\n" + save_and_restart = True + + if save_and_restart: + self.save("Made HSTS max-age permanent") + self.restart() + + for id_str in affected_ids: + self._autohsts.pop(id_str) + + # Update AutoHSTS storage (We potentially removed vhosts from managed) + self._autohsts_save_state() + + +AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member diff --git a/certbot-apache/certbot_apache/_internal/constants.py b/certbot-apache/certbot_apache/_internal/constants.py new file mode 100644 index 000000000..a0f455a02 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -0,0 +1,70 @@ +"""Apache plugin constants.""" +import pkg_resources + +from certbot.compat import os + + +MOD_SSL_CONF_DEST = "options-ssl-apache.conf" +"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" + + +UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt" +"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" + +# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING! +ALL_SSL_OPTIONS_HASHES = [ + '2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a', + '4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a', + '5a922826719981c0a234b1fbcd495f3213e49d2519e845ea0748ba513044b65b', + '4066b90268c03c9ba0201068eaa39abbc02acf9558bb45a788b630eb85dadf27', + 'f175e2e7c673bd88d0aff8220735f385f916142c44aa83b09f1df88dd4767a88', + 'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b', + '80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791', + 'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082', + '717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20', + '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', + '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', + '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', +] +"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" + +AUGEAS_LENS_DIR = pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "augeas_lens")) +"""Path to the Augeas lens directory""" + +REWRITE_HTTPS_ARGS = [ + "^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,NE,R=permanent]"] +"""Apache version<2.3.9 rewrite rule arguments used for redirections to +https vhost""" + +REWRITE_HTTPS_ARGS_WITH_END = [ + "^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,NE,R=permanent]"] +"""Apache version >= 2.3.9 rewrite rule arguments used for redirections to + https vhost""" + +OLD_REWRITE_HTTPS_ARGS = [ + ["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"], + ["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]] + +HSTS_ARGS = ["always", "set", "Strict-Transport-Security", + "\"max-age=31536000\""] +"""Apache header arguments for HSTS""" + +UIR_ARGS = ["always", "set", "Content-Security-Policy", + "upgrade-insecure-requests"] + +HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS, + "Upgrade-Insecure-Requests": UIR_ARGS} + +AUTOHSTS_STEPS = [60, 300, 900, 3600, 21600, 43200, 86400] +"""AutoHSTS increase steps: 1min, 5min, 15min, 1h, 6h, 12h, 24h""" + +AUTOHSTS_PERMANENT = 31536000 +"""Value for the last max-age of HSTS""" + +AUTOHSTS_FREQ = 172800 +"""Minimum time since last increase to perform a new one: 48h""" + +MANAGED_COMMENT = "DO NOT REMOVE - Managed by Certbot" +MANAGED_COMMENT_ID = MANAGED_COMMENT+", VirtualHost id: {0}" +"""Managed by Certbot comments and the VirtualHost identification template""" diff --git a/certbot-apache/certbot_apache/_internal/display_ops.py b/certbot-apache/certbot_apache/_internal/display_ops.py new file mode 100644 index 000000000..4e746f5b8 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/display_ops.py @@ -0,0 +1,125 @@ +"""Contains UI methods for Apache operations.""" +import logging + +import zope.component + +import certbot.display.util as display_util +from certbot import errors +from certbot import interfaces +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +def select_vhost_multiple(vhosts): + """Select multiple Vhosts to install the certificate for + + :param vhosts: Available Apache VirtualHosts + :type vhosts: :class:`list` of type `~obj.Vhost` + + :returns: List of VirtualHosts + :rtype: :class:`list`of type `~obj.Vhost` + """ + if not vhosts: + return list() + tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] + # Remove the extra newline from the last entry + if tags_list: + tags_list[-1] = tags_list[-1][:-1] + code, names = zope.component.getUtility(interfaces.IDisplay).checklist( + "Which VirtualHosts would you like to install the wildcard certificate for?", + tags=tags_list, force_interactive=True) + if code == display_util.OK: + return_vhosts = _reversemap_vhosts(names, vhosts) + return return_vhosts + return [] + +def _reversemap_vhosts(names, vhosts): + """Helper function for select_vhost_multiple for mapping string + representations back to actual vhost objects""" + return_vhosts = list() + + for selection in names: + for vhost in vhosts: + if vhost.display_repr().strip() == selection.strip(): + return_vhosts.append(vhost) + return return_vhosts + +def select_vhost(domain, vhosts): + """Select an appropriate Apache Vhost. + + :param vhosts: Available Apache VirtualHosts + :type vhosts: :class:`list` of type `~obj.Vhost` + + :returns: VirtualHost or `None` + :rtype: `~obj.Vhost` or `None` + + """ + if not vhosts: + return None + code, tag = _vhost_menu(domain, vhosts) + if code == display_util.OK: + return vhosts[tag] + return None + +def _vhost_menu(domain, vhosts): + """Select an appropriate Apache Vhost. + + :param vhosts: Available Apache Virtual Hosts + :type vhosts: :class:`list` of type `~obj.Vhost` + + :returns: Display tuple - ('code', tag') + :rtype: `tuple` + + """ + # Free characters in the line of display text (9 is for ' | ' formatting) + free_chars = display_util.WIDTH - len("HTTPS") - len("Enabled") - 9 + + if free_chars < 2: + logger.debug("Display size is too small for " + "certbot_apache._internal.display_ops._vhost_menu()") + # This runs the edge off the screen, but it doesn't cause an "error" + filename_size = 1 + disp_name_size = 1 + else: + # Filename is a bit more important and probably longer with 000-* + filename_size = int(free_chars * .6) + disp_name_size = free_chars - filename_size + + choices = [] + for vhost in vhosts: + if len(vhost.get_names()) == 1: + disp_name = next(iter(vhost.get_names())) + elif not vhost.get_names(): + disp_name = "" + else: + disp_name = "Multiple Names" + + choices.append( + "{fn:{fn_size}s} | {name:{name_size}s} | {https:5s} | " + "{active:7s}".format( + fn=os.path.basename(vhost.filep)[:filename_size], + name=disp_name[:disp_name_size], + https="HTTPS" if vhost.ssl else "", + active="Enabled" if vhost.enabled else "", + fn_size=filename_size, + name_size=disp_name_size) + ) + + try: + code, tag = zope.component.getUtility(interfaces.IDisplay).menu( + "We were unable to find a vhost with a ServerName " + "or Address of {0}.{1}Which virtual host would you " + "like to choose?".format(domain, os.linesep), + choices, force_interactive=True) + except errors.MissingCommandlineFlag: + msg = ( + "Encountered vhost ambiguity when trying to find a vhost for " + "{0} but was unable to ask for user " + "guidance in non-interactive mode. Certbot may need " + "vhosts to be explicitly labelled with ServerName or " + "ServerAlias directives.".format(domain)) + logger.warning(msg) + raise errors.MissingCommandlineFlag(msg) + + return code, tag diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py new file mode 100644 index 000000000..96ffee1b3 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -0,0 +1,69 @@ +""" Entry point for Apache Plugin """ +# Pylint does not like disutils.version when running inside a venv. +# See: https://github.com/PyCQA/pylint/issues/73 +from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error + +from certbot import util + +from certbot_apache._internal import configurator +from certbot_apache._internal import override_arch +from certbot_apache._internal import override_fedora +from certbot_apache._internal import override_darwin +from certbot_apache._internal import override_debian +from certbot_apache._internal import override_centos +from certbot_apache._internal import override_gentoo +from certbot_apache._internal import override_suse + +OVERRIDE_CLASSES = { + "arch": override_arch.ArchConfigurator, + "cloudlinux": override_centos.CentOSConfigurator, + "darwin": override_darwin.DarwinConfigurator, + "debian": override_debian.DebianConfigurator, + "ubuntu": override_debian.DebianConfigurator, + "centos": override_centos.CentOSConfigurator, + "centos linux": override_centos.CentOSConfigurator, + "fedora_old": override_centos.CentOSConfigurator, + "fedora": override_fedora.FedoraConfigurator, + "linuxmint": override_debian.DebianConfigurator, + "ol": override_centos.CentOSConfigurator, + "oracle": override_centos.CentOSConfigurator, + "redhatenterpriseserver": override_centos.CentOSConfigurator, + "red hat enterprise linux server": override_centos.CentOSConfigurator, + "rhel": override_centos.CentOSConfigurator, + "amazon": override_centos.CentOSConfigurator, + "gentoo": override_gentoo.GentooConfigurator, + "gentoo base system": override_gentoo.GentooConfigurator, + "opensuse": override_suse.OpenSUSEConfigurator, + "suse": override_suse.OpenSUSEConfigurator, + "sles": override_suse.OpenSUSEConfigurator, + "scientific": override_centos.CentOSConfigurator, + "scientific linux": override_centos.CentOSConfigurator, +} + + +def get_configurator(): + """ Get correct configurator class based on the OS fingerprint """ + os_name, os_version = util.get_os_info() + os_name = os_name.lower() + override_class = None + + # Special case for older Fedora versions + if os_name == 'fedora' and LooseVersion(os_version) < LooseVersion('29'): + os_name = 'fedora_old' + + try: + override_class = OVERRIDE_CLASSES[os_name] + except KeyError: + # OS not found in the list + os_like = util.get_systemd_os_like() + if os_like: + for os_name in os_like: + if os_name in OVERRIDE_CLASSES.keys(): + override_class = OVERRIDE_CLASSES[os_name] + if not override_class: + # No override class found, return the generic configurator + override_class = configurator.ApacheConfigurator + return override_class + + +ENTRYPOINT = get_configurator() diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py new file mode 100644 index 000000000..a4be4853e --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -0,0 +1,210 @@ +"""A class that performs HTTP-01 challenges for Apache""" +import logging + +from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem +from certbot.plugins import common + +from certbot_apache._internal.obj import VirtualHost # pylint: disable=unused-import +from certbot_apache._internal.parser import get_aug_path + +logger = logging.getLogger(__name__) + + +class ApacheHttp01(common.ChallengePerformer): + """Class that performs HTTP-01 challenges within the Apache configurator.""" + + CONFIG_TEMPLATE22_PRE = """\ + RewriteEngine on + RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [L] + + """ + CONFIG_TEMPLATE22_POST = """\ + + Order Allow,Deny + Allow from all + + + Order Allow,Deny + Allow from all + + """ + + CONFIG_TEMPLATE24_PRE = """\ + RewriteEngine on + RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [END] + """ + CONFIG_TEMPLATE24_POST = """\ + + Require all granted + + + Require all granted + + """ + + def __init__(self, *args, **kwargs): + super(ApacheHttp01, self).__init__(*args, **kwargs) + self.challenge_conf_pre = os.path.join( + self.configurator.conf("challenge-location"), + "le_http_01_challenge_pre.conf") + self.challenge_conf_post = os.path.join( + self.configurator.conf("challenge-location"), + "le_http_01_challenge_post.conf") + self.challenge_dir = os.path.join( + self.configurator.config.work_dir, + "http_challenges") + self.moded_vhosts = set() # type: Set[VirtualHost] + + def perform(self): + """Perform all HTTP-01 challenges.""" + if not self.achalls: + return [] + # Save any changes to the configuration as a precaution + # About to make temporary changes to the config + self.configurator.save("Changes before challenge setup", True) + + self.configurator.ensure_listen(str( + self.configurator.config.http01_port)) + self.prepare_http01_modules() + + responses = self._set_up_challenges() + + self._mod_config() + # Save reversible changes + self.configurator.save("HTTP Challenge", True) + + return responses + + def prepare_http01_modules(self): + """Make sure that we have the needed modules available for http01""" + + if self.configurator.conf("handle-modules"): + needed_modules = ["rewrite"] + if self.configurator.version < (2, 4): + needed_modules.append("authz_host") + else: + needed_modules.append("authz_core") + for mod in needed_modules: + if mod + "_module" not in self.configurator.parser.modules: + self.configurator.enable_mod(mod, temp=True) + + def _mod_config(self): + selected_vhosts = [] # type: List[VirtualHost] + http_port = str(self.configurator.config.http01_port) + for chall in self.achalls: + # Search for matching VirtualHosts + for vh in self._matching_vhosts(chall.domain): + selected_vhosts.append(vh) + + # Ensure that we have one or more VirtualHosts that we can continue + # with. (one that listens to port configured with --http-01-port) + found = False + for vhost in selected_vhosts: + if any(a.is_wildcard() or a.get_port() == http_port for a in vhost.addrs): + found = True + + if not found: + for vh in self._relevant_vhosts(): + selected_vhosts.append(vh) + + # Add the challenge configuration + for vh in selected_vhosts: + self._set_up_include_directives(vh) + + self.configurator.reverter.register_file_creation( + True, self.challenge_conf_pre) + self.configurator.reverter.register_file_creation( + True, self.challenge_conf_post) + + if self.configurator.version < (2, 4): + config_template_pre = self.CONFIG_TEMPLATE22_PRE + config_template_post = self.CONFIG_TEMPLATE22_POST + else: + config_template_pre = self.CONFIG_TEMPLATE24_PRE + config_template_post = self.CONFIG_TEMPLATE24_POST + + config_text_pre = config_template_pre.format(self.challenge_dir) + config_text_post = config_template_post.format(self.challenge_dir) + + logger.debug("writing a pre config file with text:\n %s", config_text_pre) + with open(self.challenge_conf_pre, "w") as new_conf: + new_conf.write(config_text_pre) + logger.debug("writing a post config file with text:\n %s", config_text_post) + with open(self.challenge_conf_post, "w") as new_conf: + new_conf.write(config_text_post) + + def _matching_vhosts(self, domain): + """Return all VirtualHost objects that have the requested domain name or + a wildcard name that would match the domain in ServerName or ServerAlias + directive. + """ + matching_vhosts = [] + for vhost in self.configurator.vhosts: + if self.configurator.domain_in_names(vhost.get_names(), domain): + # domain_in_names also matches the exact names, so no need + # to check "domain in vhost.get_names()" explicitly here + matching_vhosts.append(vhost) + + return matching_vhosts + + def _relevant_vhosts(self): + http01_port = str(self.configurator.config.http01_port) + relevant_vhosts = [] + for vhost in self.configurator.vhosts: + if any(a.is_wildcard() or a.get_port() == http01_port for a in vhost.addrs): + if not vhost.ssl: + relevant_vhosts.append(vhost) + if not relevant_vhosts: + raise errors.PluginError( + "Unable to find a virtual host listening on port {0} which is" + " currently needed for Certbot to prove to the CA that you" + " control your domain. Please add a virtual host for port" + " {0}.".format(http01_port)) + + return relevant_vhosts + + def _set_up_challenges(self): + if not os.path.isdir(self.challenge_dir): + filesystem.makedirs(self.challenge_dir, 0o755) + + responses = [] + for achall in self.achalls: + responses.append(self._set_up_challenge(achall)) + + return responses + + def _set_up_challenge(self, achall): + response, validation = achall.response_and_validation() + + name = os.path.join(self.challenge_dir, achall.chall.encode("token")) + + self.configurator.reverter.register_file_creation(True, name) + with open(name, 'wb') as f: + f.write(validation.encode()) + filesystem.chmod(name, 0o644) + + return response + + def _set_up_include_directives(self, vhost): + """Includes override configuration to the beginning and to the end of + VirtualHost. Note that this include isn't added to Augeas search tree""" + + if vhost not in self.moded_vhosts: + logger.debug( + "Adding a temporary challenge validation Include for name: %s " + + "in: %s", vhost.name, vhost.filep) + self.configurator.parser.add_dir_beginning( + vhost.path, "Include", self.challenge_conf_pre) + self.configurator.parser.add_dir( + vhost.path, "Include", self.challenge_conf_post) + + if not vhost.enabled: + self.configurator.parser.add_dir( + get_aug_path(self.configurator.parser.loc["default"]), + "Include", vhost.filep) + + self.moded_vhosts.add(vhost) diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py new file mode 100644 index 000000000..dd4018155 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -0,0 +1,269 @@ +"""Module contains classes used by the Apache Configurator.""" +import re + +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from certbot.plugins import common + + +class Addr(common.Addr): + """Represents an Apache address.""" + + def __eq__(self, other): + """This is defined as equivalent within Apache. + + ip_addr:* == ip_addr + + """ + if isinstance(other, self.__class__): + return ((self.tup == other.tup) or + (self.tup[0] == other.tup[0] and + self.is_wildcard() and other.is_wildcard())) + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "certbot_apache._internal.obj.Addr(" + repr(self.tup) + ")" + + def __hash__(self): # pylint: disable=useless-super-delegation + # Python 3 requires explicit overridden for __hash__ if __eq__ or + # __cmp__ is overridden. See https://bugs.python.org/issue2235 + return super(Addr, self).__hash__() + + def _addr_less_specific(self, addr): + """Returns if addr.get_addr() is more specific than self.get_addr().""" + # pylint: disable=protected-access + return addr._rank_specific_addr() > self._rank_specific_addr() + + def _rank_specific_addr(self): + """Returns numerical rank for get_addr() + + :returns: 2 - FQ, 1 - wildcard, 0 - _default_ + :rtype: int + + """ + if self.get_addr() == "_default_": + return 0 + elif self.get_addr() == "*": + return 1 + return 2 + + def conflicts(self, addr): + r"""Returns if address could conflict with correct function of self. + + Could addr take away service provided by self within Apache? + + .. note::IP Address is more important than wildcard. + Connection from 127.0.0.1:80 with choices of *:80 and 127.0.0.1:* + chooses 127.0.0.1:\* + + .. todo:: Handle domain name addrs... + + Examples: + + ========================================= ===== + ``127.0.0.1:\*.conflicts(127.0.0.1:443)`` True + ``127.0.0.1:443.conflicts(127.0.0.1:\*)`` False + ``\*:443.conflicts(\*:80)`` False + ``_default_:443.conflicts(\*:443)`` True + ========================================= ===== + + """ + if self._addr_less_specific(addr): + return True + elif self.get_addr() == addr.get_addr(): + if self.is_wildcard() or self.get_port() == addr.get_port(): + return True + return False + + def is_wildcard(self): + """Returns if address has a wildcard port.""" + return self.tup[1] == "*" or not self.tup[1] + + def get_sni_addr(self, port): + """Returns the least specific address that resolves on the port. + + Examples: + + - ``1.2.3.4:443`` -> ``1.2.3.4:`` + - ``1.2.3.4:*`` -> ``1.2.3.4:*`` + + :param str port: Desired port + + """ + if self.is_wildcard(): + return self + + return self.get_addr_obj(port) + + +class VirtualHost(object): + """Represents an Apache Virtualhost. + + :ivar str filep: file path of VH + :ivar str path: Augeas path to virtual host + :ivar set addrs: Virtual Host addresses (:class:`set` of + :class:`common.Addr`) + :ivar str name: ServerName of VHost + :ivar list aliases: Server aliases of vhost + (:class:`list` of :class:`str`) + + :ivar bool ssl: SSLEngine on in vhost + :ivar bool enabled: Virtual host is enabled + :ivar bool modmacro: VirtualHost is using mod_macro + :ivar VirtualHost ancestor: A non-SSL VirtualHost this is based on + + https://httpd.apache.org/docs/2.4/vhosts/details.html + + .. todo:: Any vhost that includes the magic _default_ wildcard is given the + same ServerName as the main server. + + """ + # ?: is used for not returning enclosed characters + strip_name = re.compile(r"^(?:.+://)?([^ :$]*)") + + def __init__(self, filep, path, addrs, ssl, enabled, name=None, + aliases=None, modmacro=False, ancestor=None): + + """Initialize a VH.""" + self.filep = filep + self.path = path + self.addrs = addrs + self.name = name + self.aliases = aliases if aliases is not None else set() + self.ssl = ssl + self.enabled = enabled + self.modmacro = modmacro + self.ancestor = ancestor + + def get_names(self): + """Return a set of all names.""" + all_names = set() # type: Set[str] + all_names.update(self.aliases) + # Strip out any scheme:// and field from servername + if self.name is not None: + all_names.add(VirtualHost.strip_name.findall(self.name)[0]) + + return all_names + + def __str__(self): + return ( + "File: {filename}\n" + "Vhost path: {vhpath}\n" + "Addresses: {addrs}\n" + "Name: {name}\n" + "Aliases: {aliases}\n" + "TLS Enabled: {tls}\n" + "Site Enabled: {active}\n" + "mod_macro Vhost: {modmacro}".format( + filename=self.filep, + vhpath=self.path, + addrs=", ".join(str(addr) for addr in self.addrs), + name=self.name if self.name is not None else "", + aliases=", ".join(name for name in self.aliases), + tls="Yes" if self.ssl else "No", + active="Yes" if self.enabled else "No", + modmacro="Yes" if self.modmacro else "No")) + + def display_repr(self): + """Return a representation of VHost to be used in dialog""" + return ( + "File: {filename}\n" + "Addresses: {addrs}\n" + "Names: {names}\n" + "HTTPS: {https}\n".format( + filename=self.filep, + addrs=", ".join(str(addr) for addr in self.addrs), + names=", ".join(self.get_names()), + https="Yes" if self.ssl else "No")) + + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.filep == other.filep and self.path == other.path and + self.addrs == other.addrs and + self.get_names() == other.get_names() and + self.ssl == other.ssl and + self.enabled == other.enabled and + self.modmacro == other.modmacro) + + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.filep, self.path, + tuple(self.addrs), tuple(self.get_names()), + self.ssl, self.enabled, self.modmacro)) + + def conflicts(self, addrs): + """See if vhost conflicts with any of the addrs. + + This determines whether or not these addresses would/could overwrite + the vhost addresses. + + :param addrs: Iterable Addresses + :type addrs: Iterable :class:~obj.Addr + + :returns: If addresses conflicts with vhost + :rtype: bool + + """ + for pot_addr in addrs: + for addr in self.addrs: + if addr.conflicts(pot_addr): + return True + return False + + def same_server(self, vhost, generic=False): + """Determines if the vhost is the same 'server'. + + Used in redirection - indicates whether or not the two virtual hosts + serve on the exact same IP combinations, but different ports. + The generic flag indicates that that we're trying to match to a + default or generic vhost + + .. todo:: Handle _default_ + + """ + + if not generic: + if vhost.get_names() != self.get_names(): + return False + + # If equal and set is not empty... assume same server + if self.name is not None or self.aliases: + return True + # If we're looking for a generic vhost, + # don't return one with a ServerName + elif self.name: + return False + + # Both sets of names are empty. + + # Make conservative educated guess... this is very restrictive + # Consider adding more safety checks. + if len(vhost.addrs) != len(self.addrs): + return False + + # already_found acts to keep everything very conservative. + # Don't allow multiple ip:ports in same set. + already_found = set() # type: Set[str] + + for addr in vhost.addrs: + for local_addr in self.addrs: + if (local_addr.get_addr() == addr.get_addr() and + local_addr != addr and + local_addr.get_addr() not in already_found): + + # This intends to make sure we aren't double counting... + # e.g. 127.0.0.1:* - We require same number of addrs + # currently + already_found.add(local_addr.get_addr()) + break + else: + return False + + return True diff --git a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf new file mode 100644 index 000000000..8113ee81e --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf @@ -0,0 +1,26 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +SSLEngine on + +# Intermediate configuration, tweak to your needs +SSLProtocol all -SSLv2 -SSLv3 +SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS +SSLHonorCipherOrder on +SSLCompression off + +SSLOptions +StrictRequire + +# Add vhost name to log entries: +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined +LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common + +#CustomLog /var/log/apache2/access.log vhost_combined +#LogLevel warn +#ErrorLog /var/log/apache2/error.log + +# Always ensure Cookies have "Secure" set (JAH 2012/1) +#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/_internal/override_arch.py b/certbot-apache/certbot_apache/_internal/override_arch.py new file mode 100644 index 000000000..c4cc1ce03 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_arch.py @@ -0,0 +1,32 @@ +""" Distribution specific override class for Arch Linux """ +import pkg_resources + +import zope.interface + +from certbot import interfaces +from certbot.compat import os + +from certbot_apache._internal import configurator + +@zope.interface.provider(interfaces.IPluginFactory) +class ArchConfigurator(configurator.ApacheConfigurator): + """Arch Linux specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/httpd", + vhost_root="/etc/httpd/conf", + vhost_files="*.conf", + logs_root="/var/log/httpd", + ctl="apachectl", + version_cmd=['apachectl', '-v'], + restart_cmd=['apachectl', 'graceful'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/httpd/conf", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py new file mode 100644 index 000000000..9de91e7a7 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -0,0 +1,218 @@ +""" Distribution specific override class for CentOS family (RHEL, Fedora) """ +import logging + +import pkg_resources +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot.errors import MisconfigurationError + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser + + +logger = logging.getLogger(__name__) + + +@zope.interface.provider(interfaces.IPluginFactory) +class CentOSConfigurator(configurator.ApacheConfigurator): + """CentOS specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/httpd", + vhost_root="/etc/httpd/conf.d", + vhost_files="*.conf", + logs_root="/var/log/httpd", + ctl="apachectl", + version_cmd=['apachectl', '-v'], + restart_cmd=['apachectl', 'graceful'], + restart_cmd_alt=['apachectl', 'restart'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + ) + + def config_test(self): + """ + Override config_test to mitigate configtest error in vanilla installation + of mod_ssl in Fedora. The error is caused by non-existent self-signed + certificates referenced by the configuration, that would be autogenerated + during the first (re)start of httpd. + """ + + os_info = util.get_os_info() + fedora = os_info[0].lower() == "fedora" + + try: + super(CentOSConfigurator, self).config_test() + except errors.MisconfigurationError: + if fedora: + self._try_restart_fedora() + else: + raise + + def _try_restart_fedora(self): + """ + Tries to restart httpd using systemctl to generate the self signed keypair. + """ + + try: + util.run_script(['systemctl', 'restart', 'httpd']) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + # Finish with actual config check to see if systemctl restart helped + super(CentOSConfigurator, self).config_test() + + def _prepare_options(self): + """ + Override the options dictionary initialization in order to support + alternative restart cmd used in CentOS. + """ + super(CentOSConfigurator, self)._prepare_options() + self.options["restart_cmd_alt"][0] = self.option("ctl") + + def get_parser(self): + """Initializes the ApacheParser""" + return CentOSParser( + self.option("server_root"), self.option("vhost_root"), + self.version, configurator=self) + + def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ + """ + Override _deploy_cert in order to ensure that the Apache configuration + has "LoadModule ssl_module..." before parsing the VirtualHost configuration + that was created by Certbot + """ + super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs) + if self.version < (2, 4, 0): + self._deploy_loadmodule_ssl_if_needed() + + def _deploy_loadmodule_ssl_if_needed(self): + """ + Add "LoadModule ssl_module " to main httpd.conf if + it doesn't exist there already. + """ + + loadmods = self.parser.find_dir("LoadModule", "ssl_module", exclude=False) + + correct_ifmods = [] # type: List[str] + loadmod_args = [] # type: List[str] + loadmod_paths = [] # type: List[str] + for m in loadmods: + noarg_path = m.rpartition("/")[0] + path_args = self.parser.get_all_args(noarg_path) + if loadmod_args: + if loadmod_args != path_args: + msg = ("Certbot encountered multiple LoadModule directives " + "for LoadModule ssl_module with differing library paths. " + "Please remove or comment out the one(s) that are not in " + "use, and run Certbot again.") + raise MisconfigurationError(msg) + else: + loadmod_args = path_args + + if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member + if self.parser.loc["default"] in noarg_path: + # LoadModule already in the main configuration file + if ("ifmodule/" in noarg_path.lower() or + "ifmodule[1]" in noarg_path.lower()): + # It's the first or only IfModule in the file + return + # Populate the list of known !mod_ssl.c IfModules + nodir_path = noarg_path.rpartition("/directive")[0] + correct_ifmods.append(nodir_path) + else: + loadmod_paths.append(noarg_path) + + if not loadmod_args: + # Do not try to enable mod_ssl + return + + # Force creation as the directive wasn't found from the beginning of + # httpd.conf + rootconf_ifmod = self.parser.create_ifmod( + parser.get_aug_path(self.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + # parser.get_ifmod returns a path postfixed with "/", remove that + self.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", loadmod_args) + correct_ifmods.append(rootconf_ifmod[:-1]) + self.save_notes += "Added LoadModule ssl_module to main configuration.\n" + + # Wrap LoadModule mod_ssl inside of if it's not + # configured like this already. + for loadmod_path in loadmod_paths: + nodir_path = loadmod_path.split("/directive")[0] + # Remove the old LoadModule directive + self.parser.aug.remove(loadmod_path) + + # Create a new IfModule !mod_ssl.c if not already found on path + ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", + beginning=True)[:-1] + if ssl_ifmod not in correct_ifmods: + self.parser.add_dir(ssl_ifmod, "LoadModule", loadmod_args) + correct_ifmods.append(ssl_ifmod) + self.save_notes += ("Wrapped pre-existing LoadModule ssl_module " + "inside of block.\n") + + +class CentOSParser(parser.ApacheParser): + """CentOS specific ApacheParser override class""" + def __init__(self, *args, **kwargs): + # CentOS specific configuration file for Apache + self.sysconfig_filep = "/etc/sysconfig/httpd" + super(CentOSParser, self).__init__(*args, **kwargs) + + def update_runtime_variables(self): + """ Override for update_runtime_variables for custom parsing """ + # Opportunistic, works if SELinux not enforced + super(CentOSParser, self).update_runtime_variables() + self.parse_sysconfig_var() + + def parse_sysconfig_var(self): + """ Parses Apache CLI options from CentOS configuration file """ + defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") + for k in defines: + self.variables[k] = defines[k] + + def not_modssl_ifmodule(self, path): + """Checks if the provided Augeas path has argument !mod_ssl""" + + if "ifmodule" not in path.lower(): + return False + + # Trim the path to the last ifmodule + workpath = path.lower() + while workpath: + # Get path to the last IfModule (ignore the tail) + parts = workpath.rpartition("ifmodule") + + if not parts[0]: + # IfModule not found + break + ifmod_path = parts[0] + parts[1] + # Check if ifmodule had an index + if parts[2].startswith("["): + # Append the index from tail + ifmod_path += parts[2].partition("/")[0] + # Get the original path trimmed to correct length + # This is required to preserve cases + ifmod_real_path = path[0:len(ifmod_path)] + if "!mod_ssl.c" in self.get_all_args(ifmod_real_path): + return True + # Set the workpath to the heading part + workpath = parts[0] + + return False diff --git a/certbot-apache/certbot_apache/_internal/override_darwin.py b/certbot-apache/certbot_apache/_internal/override_darwin.py new file mode 100644 index 000000000..254d738bb --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_darwin.py @@ -0,0 +1,32 @@ +""" Distribution specific override class for macOS """ +import pkg_resources + +import zope.interface + +from certbot import interfaces +from certbot.compat import os + +from certbot_apache._internal import configurator + +@zope.interface.provider(interfaces.IPluginFactory) +class DarwinConfigurator(configurator.ApacheConfigurator): + """macOS specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/apache2", + vhost_root="/etc/apache2/other", + vhost_files="*.conf", + logs_root="/var/log/apache2", + ctl="apachectl", + version_cmd=['apachectl', '-v'], + restart_cmd=['apachectl', 'graceful'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/apache2/other", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) diff --git a/certbot-apache/certbot_apache/_internal/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py new file mode 100644 index 000000000..37906808e --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -0,0 +1,146 @@ +""" Distribution specific override class for Debian family (Ubuntu/Debian) """ +import logging + +import pkg_resources +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import filesystem +from certbot.compat import os + +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator + +logger = logging.getLogger(__name__) + + +@zope.interface.provider(interfaces.IPluginFactory) +class DebianConfigurator(configurator.ApacheConfigurator): + """Debian specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/apache2", + vhost_root="/etc/apache2/sites-available", + vhost_files="*", + logs_root="/var/log/apache2", + ctl="apache2ctl", + version_cmd=['apache2ctl', '-v'], + restart_cmd=['apache2ctl', 'graceful'], + conftest_cmd=['apache2ctl', 'configtest'], + enmod="a2enmod", + dismod="a2dismod", + le_vhost_ext="-le-ssl.conf", + handle_modules=True, + handle_sites=True, + challenge_location="/etc/apache2", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) + + def enable_site(self, vhost): + """Enables an available site, Apache reload required. + + .. note:: Does not make sure that the site correctly works or that all + modules are enabled appropriately. + + :param vhost: vhost to enable + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` + + :raises .errors.NotSupportedError: If filesystem layout is not + supported. + + """ + if vhost.enabled: + return None + + enabled_path = ("%s/sites-enabled/%s" % + (self.parser.root, + os.path.basename(vhost.filep))) + if not os.path.isdir(os.path.dirname(enabled_path)): + # For some reason, sites-enabled / sites-available do not exist + # Call the parent method + return super(DebianConfigurator, self).enable_site(vhost) + self.reverter.register_file_creation(False, enabled_path) + try: + os.symlink(vhost.filep, enabled_path) + except OSError as err: + if os.path.islink(enabled_path) and filesystem.realpath( + enabled_path) == vhost.filep: + # Already in shape + vhost.enabled = True + return None + else: + logger.warning( + "Could not symlink %s to %s, got error: %s", enabled_path, + vhost.filep, err.strerror) + errstring = ("Encountered error while trying to enable a " + + "newly created VirtualHost located at {0} by " + + "linking to it from {1}") + raise errors.NotSupportedError(errstring.format(vhost.filep, + enabled_path)) + vhost.enabled = True + logger.info("Enabling available site: %s", vhost.filep) + self.save_notes += "Enabled site %s\n" % vhost.filep + return None + + def enable_mod(self, mod_name, temp=False): + """Enables module in Apache. + + Both enables and reloads Apache so module is active. + + :param str mod_name: Name of the module to enable. (e.g. 'ssl') + :param bool temp: Whether or not this is a temporary action. + + :raises .errors.NotSupportedError: If the filesystem layout is not + supported. + :raises .errors.MisconfigurationError: If a2enmod or a2dismod cannot be + run. + + """ + avail_path = os.path.join(self.parser.root, "mods-available") + enabled_path = os.path.join(self.parser.root, "mods-enabled") + if not os.path.isdir(avail_path) or not os.path.isdir(enabled_path): + raise errors.NotSupportedError( + "Unsupported directory layout. You may try to enable mod %s " + "and try again." % mod_name) + + deps = apache_util.get_mod_deps(mod_name) + + # Enable all dependencies + for dep in deps: + if (dep + "_module") not in self.parser.modules: + self._enable_mod_debian(dep, temp) + self.parser.add_mod(dep) + note = "Enabled dependency of %s module - %s" % (mod_name, dep) + if not temp: + self.save_notes += note + os.linesep + logger.debug(note) + + # Enable actual module + self._enable_mod_debian(mod_name, temp) + self.parser.add_mod(mod_name) + + if not temp: + self.save_notes += "Enabled %s module in Apache\n" % mod_name + logger.info("Enabled Apache %s module", mod_name) + + # Modules can enable additional config files. Variables may be defined + # within these new configuration sections. + # Reload is not necessary as DUMP_RUN_CFG uses latest config. + self.parser.update_runtime_variables() + + def _enable_mod_debian(self, mod_name, temp): + """Assumes mods-available, mods-enabled layout.""" + # Generate reversal command. + # Try to be safe here... check that we can probably reverse before + # applying enmod command + if not util.exe_exists(self.option("dismod")): + raise errors.MisconfigurationError( + "Unable to find a2dismod, please make sure a2enmod and " + "a2dismod are configured correctly for certbot.") + + self.reverter.register_undo_command( + temp, [self.option("dismod"), "-f", mod_name]) + util.run_script([self.option("enmod"), mod_name]) diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py new file mode 100644 index 000000000..e6045a634 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -0,0 +1,99 @@ +""" Distribution specific override class for Fedora 29+ """ +import pkg_resources +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os + +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser + + +@zope.interface.provider(interfaces.IPluginFactory) +class FedoraConfigurator(configurator.ApacheConfigurator): + """Fedora 29+ specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/httpd", + vhost_root="/etc/httpd/conf.d", + vhost_files="*.conf", + logs_root="/var/log/httpd", + ctl="httpd", + version_cmd=['httpd', '-v'], + restart_cmd=['apachectl', 'graceful'], + restart_cmd_alt=['apachectl', 'restart'], + conftest_cmd=['apachectl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/httpd/conf.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + # TODO: eventually newest version of Fedora will need their own config + "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + ) + + def config_test(self): + """ + Override config_test to mitigate configtest error in vanilla installation + of mod_ssl in Fedora. The error is caused by non-existent self-signed + certificates referenced by the configuration, that would be autogenerated + during the first (re)start of httpd. + """ + try: + super(FedoraConfigurator, self).config_test() + except errors.MisconfigurationError: + self._try_restart_fedora() + + def get_parser(self): + """Initializes the ApacheParser""" + return FedoraParser( + self.option("server_root"), self.option("vhost_root"), + self.version, configurator=self) + + def _try_restart_fedora(self): + """ + Tries to restart httpd using systemctl to generate the self signed keypair. + """ + try: + util.run_script(['systemctl', 'restart', 'httpd']) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + # Finish with actual config check to see if systemctl restart helped + super(FedoraConfigurator, self).config_test() + + def _prepare_options(self): + """ + Override the options dictionary initialization to keep using apachectl + instead of httpd and so take advantages of this new bash script in newer versions + of Fedora to restart httpd. + """ + super(FedoraConfigurator, self)._prepare_options() + self.options["restart_cmd"][0] = 'apachectl' + self.options["restart_cmd_alt"][0] = 'apachectl' + self.options["conftest_cmd"][0] = 'apachectl' + + +class FedoraParser(parser.ApacheParser): + """Fedora 29+ specific ApacheParser override class""" + def __init__(self, *args, **kwargs): + # Fedora 29+ specific configuration file for Apache + self.sysconfig_filep = "/etc/sysconfig/httpd" + super(FedoraParser, self).__init__(*args, **kwargs) + + def update_runtime_variables(self): + """ Override for update_runtime_variables for custom parsing """ + # Opportunistic, works if SELinux not enforced + super(FedoraParser, self).update_runtime_variables() + self._parse_sysconfig_var() + + def _parse_sysconfig_var(self): + """ Parses Apache CLI options from Fedora configuration file """ + defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") + for k in defines: + self.variables[k] = defines[k] diff --git a/certbot-apache/certbot_apache/_internal/override_gentoo.py b/certbot-apache/certbot_apache/_internal/override_gentoo.py new file mode 100644 index 000000000..845530b31 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_gentoo.py @@ -0,0 +1,76 @@ +""" Distribution specific override class for Gentoo Linux """ +import pkg_resources + +import zope.interface + +from certbot import interfaces +from certbot.compat import os + +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser + +@zope.interface.provider(interfaces.IPluginFactory) +class GentooConfigurator(configurator.ApacheConfigurator): + """Gentoo specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/apache2", + vhost_root="/etc/apache2/vhosts.d", + vhost_files="*.conf", + logs_root="/var/log/apache2", + ctl="apache2ctl", + version_cmd=['apache2ctl', '-v'], + restart_cmd=['apache2ctl', 'graceful'], + restart_cmd_alt=['apache2ctl', 'restart'], + conftest_cmd=['apache2ctl', 'configtest'], + enmod=None, + dismod=None, + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) + + def _prepare_options(self): + """ + Override the options dictionary initialization in order to support + alternative restart cmd used in Gentoo. + """ + super(GentooConfigurator, self)._prepare_options() + self.options["restart_cmd_alt"][0] = self.option("ctl") + + def get_parser(self): + """Initializes the ApacheParser""" + return GentooParser( + self.option("server_root"), self.option("vhost_root"), + self.version, configurator=self) + + +class GentooParser(parser.ApacheParser): + """Gentoo specific ApacheParser override class""" + def __init__(self, *args, **kwargs): + # Gentoo specific configuration file for Apache2 + self.apacheconfig_filep = "/etc/conf.d/apache2" + super(GentooParser, self).__init__(*args, **kwargs) + + def update_runtime_variables(self): + """ Override for update_runtime_variables for custom parsing """ + self.parse_sysconfig_var() + self.update_modules() + + def parse_sysconfig_var(self): + """ Parses Apache CLI options from Gentoo configuration file """ + defines = apache_util.parse_define_file(self.apacheconfig_filep, + "APACHE2_OPTS") + for k in defines: + self.variables[k] = defines[k] + + def update_modules(self): + """Get loaded modules from httpd process, and add them to DOM""" + mod_cmd = [self.configurator.option("ctl"), "modules"] + matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") + for mod in matches: + self.add_mod(mod.strip()) diff --git a/certbot-apache/certbot_apache/_internal/override_suse.py b/certbot-apache/certbot_apache/_internal/override_suse.py new file mode 100644 index 000000000..ab217bc0f --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/override_suse.py @@ -0,0 +1,32 @@ +""" Distribution specific override class for OpenSUSE """ +import pkg_resources + +import zope.interface + +from certbot import interfaces +from certbot.compat import os + +from certbot_apache._internal import configurator + +@zope.interface.provider(interfaces.IPluginFactory) +class OpenSUSEConfigurator(configurator.ApacheConfigurator): + """OpenSUSE specific ApacheConfigurator override class""" + + OS_DEFAULTS = dict( + server_root="/etc/apache2", + vhost_root="/etc/apache2/vhosts.d", + vhost_files="*.conf", + logs_root="/var/log/apache2", + ctl="apache2ctl", + version_cmd=['apache2ctl', '-v'], + restart_cmd=['apache2ctl', 'graceful'], + conftest_cmd=['apache2ctl', 'configtest'], + enmod="a2enmod", + dismod="a2dismod", + le_vhost_ext="-le-ssl.conf", + handle_modules=False, + handle_sites=False, + challenge_location="/etc/apache2/vhosts.d", + MOD_SSL_CONF_SRC=pkg_resources.resource_filename( + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) + ) diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py new file mode 100644 index 000000000..759518a2c --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -0,0 +1,1009 @@ +"""ApacheParser is a member object of the ApacheConfigurator class.""" +import copy +import fnmatch +import logging +import re +import subprocess +import sys + +import six + +from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os + +from certbot_apache._internal import constants + +logger = logging.getLogger(__name__) + + +class ApacheParser(object): + """Class handles the fine details of parsing the Apache Configuration. + + .. todo:: Make parsing general... remove sites-available etc... + + :ivar str root: Normalized absolute path to the server root + directory. Without trailing slash. + :ivar set modules: All module names that are currently enabled. + :ivar dict loc: Location to place directives, root - configuration origin, + default - user config file, name - NameVirtualHost, + + """ + arg_var_interpreter = re.compile(r"\$\{[^ \}]*}") + fnmatch_chars = set(["*", "?", "\\", "[", "]"]) + + def __init__(self, root, vhostroot=None, version=(2, 4), + configurator=None): + # Note: Order is important here. + + # Needed for calling save() with reverter functionality that resides in + # AugeasConfigurator superclass of ApacheConfigurator. This resolves + # issues with aug.load() after adding new files / defines to parse tree + self.configurator = configurator + + # Initialize augeas + self.aug = None + self.init_augeas() + + if not self.check_aug_version(): + raise errors.NotSupportedError( + "Apache plugin support requires libaugeas0 and augeas-lenses " + "version 1.2.0 or higher, please make sure you have you have " + "those installed.") + + self.modules = set() # type: Set[str] + self.parser_paths = {} # type: Dict[str, List[str]] + self.variables = {} # type: Dict[str, str] + + # Find configuration root and make sure augeas can parse it. + self.root = os.path.abspath(root) + self.loc = {"root": self._find_config_root()} + self.parse_file(self.loc["root"]) + + if version >= (2, 4): + # Look up variables from httpd and add to DOM if not already parsed + self.update_runtime_variables() + + # This problem has been fixed in Augeas 1.0 + self.standardize_excl() + + # Parse LoadModule directives from configuration files + self.parse_modules() + + # Set up rest of locations + self.loc.update(self._set_locations()) + + # list of the active include paths, before modifications + self.existing_paths = copy.deepcopy(self.parser_paths) + + # Must also attempt to parse additional virtual host root + if vhostroot: + self.parse_file(os.path.abspath(vhostroot) + "/" + + self.configurator.option("vhost_files")) + + # check to see if there were unparsed define statements + if version < (2, 4): + if self.find_dir("Define", exclude=False): + raise errors.PluginError("Error parsing runtime variables") + + def init_augeas(self): + """ Initialize the actual Augeas instance """ + + try: + import augeas + except ImportError: # pragma: no cover + raise errors.NoInstallationError("Problem in Augeas installation") + + self.aug = augeas.Augeas( + # specify a directory to load our preferred lens from + loadpath=constants.AUGEAS_LENS_DIR, + # Do not save backup (we do it ourselves), do not load + # anything by default + flags=(augeas.Augeas.NONE | + augeas.Augeas.NO_MODL_AUTOLOAD | + augeas.Augeas.ENABLE_SPAN)) + + def check_parsing_errors(self, lens): + """Verify Augeas can parse all of the lens files. + + :param str lens: lens to check for errors + + :raises .errors.PluginError: If there has been an error in parsing with + the specified lens. + + """ + error_files = self.aug.match("/augeas//error") + + for path in error_files: + # Check to see if it was an error resulting from the use of + # the httpd lens + lens_path = self.aug.get(path + "/lens") + # As aug.get may return null + if lens_path and lens in lens_path: + msg = ( + "There has been an error in parsing the file {0} on line {1}: " + "{2}".format( + # Strip off /augeas/files and /error + path[13:len(path) - 6], + self.aug.get(path + "/line"), + self.aug.get(path + "/message"))) + raise errors.PluginError(msg) + + def check_aug_version(self): + """ Checks that we have recent enough version of libaugeas. + If augeas version is recent enough, it will support case insensitive + regexp matching""" + + self.aug.set("/test/path/testing/arg", "aRgUMeNT") + try: + matches = self.aug.match( + "/test//*[self::arg=~regexp('argument', 'i')]") + except RuntimeError: + self.aug.remove("/test/path") + return False + self.aug.remove("/test/path") + return matches + + def unsaved_files(self): + """Lists files that have modified Augeas DOM but the changes have not + been written to the filesystem yet, used by `self.save()` and + ApacheConfigurator to check the file state. + + :raises .errors.PluginError: If there was an error in Augeas, in + an attempt to save the configuration, or an error creating a + checkpoint + + :returns: `set` of unsaved files + """ + save_state = self.aug.get("/augeas/save") + self.aug.set("/augeas/save", "noop") + # Existing Errors + ex_errs = self.aug.match("/augeas//error") + try: + # This is a noop save + self.aug.save() + except (RuntimeError, IOError): + self._log_save_errors(ex_errs) + # Erase Save Notes + self.configurator.save_notes = "" + raise errors.PluginError( + "Error saving files, check logs for more info.") + + # Return the original save method + self.aug.set("/augeas/save", save_state) + + # Retrieve list of modified files + # Note: Noop saves can cause the file to be listed twice, I used a + # set to remove this possibility. This is a known augeas 0.10 error. + save_paths = self.aug.match("/augeas/events/saved") + + save_files = set() + if save_paths: + for path in save_paths: + save_files.add(self.aug.get(path)[6:]) + return save_files + + def ensure_augeas_state(self): + """Makes sure that all Augeas dom changes are written to files to avoid + loss of configuration directives when doing additional augeas parsing, + causing a possible augeas.load() resulting dom reset + """ + + if self.unsaved_files(): + self.configurator.save_notes += "(autosave)" + self.configurator.save() + + def save(self, save_files): + """Saves all changes to the configuration files. + + save() is called from ApacheConfigurator to handle the parser specific + tasks of saving. + + :param list save_files: list of strings of file paths that we need to save. + + """ + self.configurator.save_notes = "" + self.aug.save() + + # Force reload if files were modified + # This is needed to recalculate augeas directive span + if save_files: + for sf in save_files: + self.aug.remove("/files/"+sf) + self.aug.load() + + def _log_save_errors(self, ex_errs): + """Log errors due to bad Augeas save. + + :param list ex_errs: Existing errors before save + + """ + # Check for the root of save problems + new_errs = self.aug.match("/augeas//error") + # logger.error("During Save - %s", mod_conf) + logger.error("Unable to save files: %s. Attempted Save Notes: %s", + ", ".join(err[13:len(err) - 6] for err in new_errs + # Only new errors caused by recent save + if err not in ex_errs), self.configurator.save_notes) + + def add_include(self, main_config, inc_path): + """Add Include for a new configuration file if one does not exist + + :param str main_config: file path to main Apache config file + :param str inc_path: path of file to include + + """ + if not self.find_dir(case_i("Include"), inc_path): + logger.debug("Adding Include %s to %s", + inc_path, get_aug_path(main_config)) + self.add_dir( + get_aug_path(main_config), + "Include", inc_path) + + # Add new path to parser paths + new_dir = os.path.dirname(inc_path) + new_file = os.path.basename(inc_path) + self.existing_paths.setdefault(new_dir, []).append(new_file) + + def add_mod(self, mod_name): + """Shortcut for updating parser modules.""" + if mod_name + "_module" not in self.modules: + self.modules.add(mod_name + "_module") + if "mod_" + mod_name + ".c" not in self.modules: + self.modules.add("mod_" + mod_name + ".c") + + def reset_modules(self): + """Reset the loaded modules list. This is called from cleanup to clear + temporarily loaded modules.""" + self.modules = set() + self.update_modules() + self.parse_modules() + + def parse_modules(self): + """Iterates on the configuration until no new modules are loaded. + + ..todo:: This should be attempted to be done with a binary to avoid + the iteration issue. Else... parse and enable mods at same time. + + """ + mods = set() # type: Set[str] + matches = self.find_dir("LoadModule") + iterator = iter(matches) + # Make sure prev_size != cur_size for do: while: iteration + prev_size = -1 + + while len(mods) != prev_size: + prev_size = len(mods) + + for match_name, match_filename in six.moves.zip( + iterator, iterator): + mod_name = self.get_arg(match_name) + mod_filename = self.get_arg(match_filename) + if mod_name and mod_filename: + mods.add(mod_name) + mods.add(os.path.basename(mod_filename)[:-2] + "c") + else: + logger.debug("Could not read LoadModule directive from " + + "Augeas path: %s", match_name[6:]) + self.modules.update(mods) + + def update_runtime_variables(self): + """Update Includes, Defines and Includes from httpd config dump data""" + self.update_defines() + self.update_includes() + self.update_modules() + + def update_defines(self): + """Get Defines from httpd process""" + + variables = dict() + define_cmd = [self.configurator.option("ctl"), "-t", "-D", + "DUMP_RUN_CFG"] + matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)") + try: + matches.remove("DUMP_RUN_CFG") + except ValueError: + return + + for match in matches: + if match.count("=") > 1: + logger.error("Unexpected number of equal signs in " + "runtime config dump.") + raise errors.PluginError( + "Error parsing Apache runtime variables") + parts = match.partition("=") + variables[parts[0]] = parts[2] + + self.variables = variables + + def update_includes(self): + """Get includes from httpd process, and add them to DOM if needed""" + + # Find_dir iterates over configuration for Include and IncludeOptional + # directives to make sure we see the full include tree present in the + # configuration files + _ = self.find_dir("Include") + + inc_cmd = [self.configurator.option("ctl"), "-t", "-D", + "DUMP_INCLUDES"] + matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)") + if matches: + for i in matches: + if not self.parsed_in_current(i): + self.parse_file(i) + + def update_modules(self): + """Get loaded modules from httpd process, and add them to DOM""" + + mod_cmd = [self.configurator.option("ctl"), "-t", "-D", + "DUMP_MODULES"] + matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") + for mod in matches: + self.add_mod(mod.strip()) + + def parse_from_subprocess(self, command, regexp): + """Get values from stdout of subprocess command + + :param list command: Command to run + :param str regexp: Regexp for parsing + + :returns: list parsed from command output + :rtype: list + + """ + stdout = self._get_runtime_cfg(command) + return re.compile(regexp).findall(stdout) + + def _get_runtime_cfg(self, command): # pylint: disable=no-self-use + """Get runtime configuration info. + :param command: Command to run + + :returns: stdout from command + + """ + try: + proc = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = proc.communicate() + + except (OSError, ValueError): + logger.error( + "Error running command %s for runtime parameters!%s", + command, os.linesep) + raise errors.MisconfigurationError( + "Error accessing loaded Apache parameters: {0}".format( + command)) + # Small errors that do not impede + if proc.returncode != 0: + logger.warning("Error in checking parameter list: %s", stderr) + raise errors.MisconfigurationError( + "Apache is unable to check whether or not the module is " + "loaded because Apache is misconfigured.") + + return stdout + + def filter_args_num(self, matches, args): # pylint: disable=no-self-use + """Filter out directives with specific number of arguments. + + This function makes the assumption that all related arguments are given + in order. Thus /files/apache/directive[5]/arg[2] must come immediately + after /files/apache/directive[5]/arg[1]. Runs in 1 linear pass. + + :param string matches: Matches of all directives with arg nodes + :param int args: Number of args you would like to filter + + :returns: List of directives that contain # of arguments. + (arg is stripped off) + + """ + filtered = [] + if args == 1: + for i, match in enumerate(matches): + if match.endswith("/arg"): + filtered.append(matches[i][:-4]) + else: + for i, match in enumerate(matches): + if match.endswith("/arg[%d]" % args): + # Make sure we don't cause an IndexError (end of list) + # Check to make sure arg + 1 doesn't exist + if (i == (len(matches) - 1) or + not matches[i + 1].endswith("/arg[%d]" % + (args + 1))): + filtered.append(matches[i][:-len("/arg[%d]" % args)]) + + return filtered + + def add_dir_to_ifmodssl(self, aug_conf_path, directive, args): + """Adds directive and value to IfMod ssl block. + + Adds given directive and value along configuration path within + an IfMod mod_ssl.c block. If the IfMod block does not exist in + the file, it is created. + + :param str aug_conf_path: Desired Augeas config path to add directive + :param str directive: Directive you would like to add, e.g. Listen + :param args: Values of the directive; str "443" or list of str + :type args: list + + """ + # TODO: Add error checking code... does the path given even exist? + # Does it throw exceptions? + if_mod_path = self.get_ifmod(aug_conf_path, "mod_ssl.c") + # IfModule can have only one valid argument, so append after + self.aug.insert(if_mod_path + "arg", "directive", False) + nvh_path = if_mod_path + "directive[1]" + self.aug.set(nvh_path, directive) + if len(args) == 1: + self.aug.set(nvh_path + "/arg", args[0]) + else: + for i, arg in enumerate(args): + self.aug.set("%s/arg[%d]" % (nvh_path, i + 1), arg) + + def get_ifmod(self, aug_conf_path, mod, beginning=False): + """Returns the path to and creates one if it doesn't exist. + + :param str aug_conf_path: Augeas configuration path + :param str mod: module ie. mod_ssl.c + :param bool beginning: If the IfModule should be created to the beginning + of augeas path DOM tree. + + :returns: Augeas path of the requested IfModule directive that pre-existed + or was created during the process. The path may be dynamic, + i.e. .../IfModule[last()] + :rtype: str + + """ + if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % + (aug_conf_path, mod))) + if not if_mods: + return self.create_ifmod(aug_conf_path, mod, beginning) + + # Strip off "arg" at end of first ifmod path + return if_mods[0].rpartition("arg")[0] + + def create_ifmod(self, aug_conf_path, mod, beginning=False): + """Creates a new and returns its path. + + :param str aug_conf_path: Augeas configuration path + :param str mod: module ie. mod_ssl.c + :param bool beginning: If the IfModule should be created to the beginning + of augeas path DOM tree. + + :returns: Augeas path of the newly created IfModule directive. + The path may be dynamic, i.e. .../IfModule[last()] + :rtype: str + + """ + if beginning: + c_path_arg = "{}/IfModule[1]/arg".format(aug_conf_path) + # Insert IfModule before the first directive + self.aug.insert("{}/directive[1]".format(aug_conf_path), + "IfModule", True) + retpath = "{}/IfModule[1]/".format(aug_conf_path) + else: + c_path = "{}/IfModule[last() + 1]".format(aug_conf_path) + c_path_arg = "{}/IfModule[last()]/arg".format(aug_conf_path) + self.aug.set(c_path, "") + retpath = "{}/IfModule[last()]/".format(aug_conf_path) + self.aug.set(c_path_arg, mod) + return retpath + + def add_dir(self, aug_conf_path, directive, args): + """Appends directive to the end fo the file given by aug_conf_path. + + .. note:: Not added to AugeasConfigurator because it may depend + on the lens + + :param str aug_conf_path: Augeas configuration path to add directive + :param str directive: Directive to add + :param args: Value of the directive. ie. Listen 443, 443 is arg + :type args: list or str + + """ + self.aug.set(aug_conf_path + "/directive[last() + 1]", directive) + if isinstance(args, list): + for i, value in enumerate(args, 1): + self.aug.set( + "%s/directive[last()]/arg[%d]" % (aug_conf_path, i), value) + else: + self.aug.set(aug_conf_path + "/directive[last()]/arg", args) + + def add_dir_beginning(self, aug_conf_path, dirname, args): + """Adds the directive to the beginning of defined aug_conf_path. + + :param str aug_conf_path: Augeas configuration path to add directive + :param str dirname: Directive to add + :param args: Value of the directive. ie. Listen 443, 443 is arg + :type args: list or str + """ + first_dir = aug_conf_path + "/directive[1]" + self.aug.insert(first_dir, "directive", True) + self.aug.set(first_dir, dirname) + if isinstance(args, list): + for i, value in enumerate(args, 1): + self.aug.set(first_dir + "/arg[%d]" % (i), value) + else: + self.aug.set(first_dir + "/arg", args) + + def add_comment(self, aug_conf_path, comment): + """Adds the comment to the augeas path + + :param str aug_conf_path: Augeas configuration path to add directive + :param str comment: Comment content + + """ + self.aug.set(aug_conf_path + "/#comment[last() + 1]", comment) + + def find_comments(self, arg, start=None): + """Finds a comment with specified content from the provided DOM path + + :param str arg: Comment content to search + :param str start: Beginning Augeas path to begin looking + + :returns: List of augeas paths containing the comment content + :rtype: list + + """ + if not start: + start = get_aug_path(self.root) + + comments = self.aug.match("%s//*[label() = '#comment']" % start) + + results = [] + for comment in comments: + c_content = self.aug.get(comment) + if c_content and arg in c_content: + results.append(comment) + return results + + def find_dir(self, directive, arg=None, start=None, exclude=True): + """Finds directive in the configuration. + + Recursively searches through config files to find directives + Directives should be in the form of a case insensitive regex currently + + .. todo:: arg should probably be a list + .. todo:: arg search currently only supports direct matching. It does + not handle the case of variables or quoted arguments. This should + be adapted to use a generic search for the directive and then do a + case-insensitive self.get_arg filter + + Note: Augeas is inherently case sensitive while Apache is case + insensitive. Augeas 1.0 allows case insensitive regexes like + regexp(/Listen/, "i"), however the version currently supported + by Ubuntu 0.10 does not. Thus I have included my own case insensitive + transformation by calling case_i() on everything to maintain + compatibility. + + :param str directive: Directive to look for + :param arg: Specific value directive must have, None if all should + be considered + :type arg: str or None + + :param str start: Beginning Augeas path to begin looking + :param bool exclude: Whether or not to exclude directives based on + variables and enabled modules + + """ + # Cannot place member variable in the definition of the function so... + if not start: + start = get_aug_path(self.loc["root"]) + + # No regexp code + # if arg is None: + # matches = self.aug.match(start + + # "//*[self::directive='" + directive + "']/arg") + # else: + # matches = self.aug.match(start + + # "//*[self::directive='" + directive + + # "']/* [self::arg='" + arg + "']") + + # includes = self.aug.match(start + + # "//* [self::directive='Include']/* [label()='arg']") + + regex = "(%s)|(%s)|(%s)" % (case_i(directive), + case_i("Include"), + case_i("IncludeOptional")) + matches = self.aug.match( + "%s//*[self::directive=~regexp('%s')]" % (start, regex)) + + if exclude: + matches = self._exclude_dirs(matches) + + if arg is None: + arg_suffix = "/arg" + else: + arg_suffix = "/*[self::arg=~regexp('%s')]" % case_i(arg) + + ordered_matches = [] # type: List[str] + + # TODO: Wildcards should be included in alphabetical order + # https://httpd.apache.org/docs/2.4/mod/core.html#include + for match in matches: + dir_ = self.aug.get(match).lower() + if dir_ == "include" or dir_ == "includeoptional": + ordered_matches.extend(self.find_dir( + directive, arg, + self._get_include_path(self.get_arg(match + "/arg")), + exclude)) + # This additionally allows Include + if dir_ == directive.lower(): + ordered_matches.extend(self.aug.match(match + arg_suffix)) + + return ordered_matches + + def get_all_args(self, match): + """ + Tries to fetch all arguments for a directive. See get_arg. + + Note that if match is an ancestor node, it returns all names of + child directives as well as the list of arguments. + + """ + + if match[-1] != "/": + match = match+"/" + allargs = self.aug.match(match + '*') + return [self.get_arg(arg) for arg in allargs] + + def get_arg(self, match): + """Uses augeas.get to get argument value and interprets result. + + This also converts all variables and parameters appropriately. + + """ + value = self.aug.get(match) + + # No need to strip quotes for variables, as apache2ctl already does + # this, but we do need to strip quotes for all normal arguments. + + # Note: normal argument may be a quoted variable + # e.g. strip now, not later + if not value: + return None + else: + value = value.strip("'\"") + + variables = ApacheParser.arg_var_interpreter.findall(value) + + for var in variables: + # Strip off ${ and } + try: + value = value.replace(var, self.variables[var[2:-1]]) + except KeyError: + raise errors.PluginError("Error Parsing variable: %s" % var) + + return value + + def _exclude_dirs(self, matches): + """Exclude directives that are not loaded into the configuration.""" + filters = [("ifmodule", self.modules), ("ifdefine", self.variables)] + + valid_matches = [] + + for match in matches: + for filter_ in filters: + if not self._pass_filter(match, filter_): + break + else: + valid_matches.append(match) + return valid_matches + + def _pass_filter(self, match, filter_): + """Determine if directive passes a filter. + + :param str match: Augeas path + :param list filter: list of tuples of form + [("lowercase if directive", set of relevant parameters)] + + """ + match_l = match.lower() + last_match_idx = match_l.find(filter_[0]) + + while last_match_idx != -1: + # Check args + end_of_if = match_l.find("/", last_match_idx) + # This should be aug.get (vars are not used e.g. parser.aug_get) + expression = self.aug.get(match[:end_of_if] + "/arg") + + if expression.startswith("!"): + # Strip off "!" + if expression[1:] in filter_[1]: + return False + else: + if expression not in filter_[1]: + return False + + last_match_idx = match_l.find(filter_[0], end_of_if) + + return True + + def _get_include_path(self, arg): + """Converts an Apache Include directive into Augeas path. + + Converts an Apache Include directive argument into an Augeas + searchable path + + .. todo:: convert to use os.path.join() + + :param str arg: Argument of Include directive + + :returns: Augeas path string + :rtype: str + + """ + # Check to make sure only expected characters are used <- maybe remove + # validChars = re.compile("[a-zA-Z0-9.*?_-/]*") + # matchObj = validChars.match(arg) + # if matchObj.group() != arg: + # logger.error("Error: Invalid regexp characters in %s", arg) + # return [] + + # Remove beginning and ending quotes + arg = arg.strip("'\"") + + # Standardize the include argument based on server root + if not arg.startswith("/"): + # Normpath will condense ../ + arg = os.path.normpath(os.path.join(self.root, arg)) + else: + arg = os.path.normpath(arg) + + # Attempts to add a transform to the file if one does not already exist + if os.path.isdir(arg): + self.parse_file(os.path.join(arg, "*")) + else: + self.parse_file(arg) + + # Argument represents an fnmatch regular expression, convert it + # Split up the path and convert each into an Augeas accepted regex + # then reassemble + split_arg = arg.split("/") + for idx, split in enumerate(split_arg): + if any(char in ApacheParser.fnmatch_chars for char in split): + # Turn it into a augeas regex + # TODO: Can this instead be an augeas glob instead of regex + split_arg[idx] = ("* [label()=~regexp('%s')]" % + self.fnmatch_to_re(split)) + # Reassemble the argument + # Note: This also normalizes the argument /serverroot/ -> /serverroot + arg = "/".join(split_arg) + + return get_aug_path(arg) + + def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use + """Method converts Apache's basic fnmatch to regular expression. + + Assumption - Configs are assumed to be well-formed and only writable by + privileged users. + + https://apr.apache.org/docs/apr/2.0/apr__fnmatch_8h_source.html + http://apache2.sourcearchive.com/documentation/2.2.16-6/apr__fnmatch_8h_source.html + + :param str clean_fn_match: Apache style filename match, like globs + + :returns: regex suitable for augeas + :rtype: str + + """ + if sys.version_info < (3, 6): + # This strips off final /Z(?ms) + return fnmatch.translate(clean_fn_match)[:-7] + # Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z + return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover + + def parse_file(self, filepath): + """Parse file with Augeas + + Checks to see if file_path is parsed by Augeas + If filepath isn't parsed, the file is added and Augeas is reloaded + + :param str filepath: Apache config file path + + """ + use_new, remove_old = self._check_path_actions(filepath) + # Ensure that we have the latest Augeas DOM state on disk before + # calling aug.load() which reloads the state from disk + self.ensure_augeas_state() + # Test if augeas included file for Httpd.lens + # Note: This works for augeas globs, ie. *.conf + if use_new: + inc_test = self.aug.match( + "/augeas/load/Httpd['%s' =~ glob(incl)]" % filepath) + if not inc_test: + # Load up files + # This doesn't seem to work on TravisCI + # self.aug.add_transform("Httpd.lns", [filepath]) + if remove_old: + self._remove_httpd_transform(filepath) + self._add_httpd_transform(filepath) + self.aug.load() + + def parsed_in_current(self, filep): + """Checks if the file path is parsed by current Augeas parser config + ie. returns True if the file is found on a path that's found in live + Augeas configuration. + + :param str filep: Path to match + + :returns: True if file is parsed in existing configuration tree + :rtype: bool + """ + return self._parsed_by_parser_paths(filep, self.parser_paths) + + def parsed_in_original(self, filep): + """Checks if the file path is parsed by existing Apache config. + ie. returns True if the file is found on a path that matches Include or + IncludeOptional statement in the Apache configuration. + + :param str filep: Path to match + + :returns: True if file is parsed in existing configuration tree + :rtype: bool + """ + return self._parsed_by_parser_paths(filep, self.existing_paths) + + def _parsed_by_parser_paths(self, filep, paths): + """Helper function that searches through provided paths and returns + True if file path is found in the set""" + for directory in paths.keys(): + for filename in paths[directory]: + if fnmatch.fnmatch(filep, os.path.join(directory, filename)): + return True + return False + + def _check_path_actions(self, filepath): + """Determine actions to take with a new augeas path + + This helper function will return a tuple that defines + if we should try to append the new filepath to augeas + parser paths, and / or remove the old one with more + narrow matching. + + :param str filepath: filepath to check the actions for + + """ + + try: + new_file_match = os.path.basename(filepath) + existing_matches = self.parser_paths[os.path.dirname(filepath)] + if "*" in existing_matches: + use_new = False + else: + use_new = True + remove_old = new_file_match == "*" + except KeyError: + use_new = True + remove_old = False + return use_new, remove_old + + def _remove_httpd_transform(self, filepath): + """Remove path from Augeas transform + + :param str filepath: filepath to remove + """ + + remove_basenames = self.parser_paths[os.path.dirname(filepath)] + remove_dirname = os.path.dirname(filepath) + for name in remove_basenames: + remove_path = remove_dirname + "/" + name + remove_inc = self.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % remove_path) + self.aug.remove(remove_inc[0]) + self.parser_paths.pop(remove_dirname) + + def _add_httpd_transform(self, incl): + """Add a transform to Augeas. + + This function will correctly add a transform to augeas + The existing augeas.add_transform in python doesn't seem to work for + Travis CI as it loads in libaugeas.so.0.10.0 + + :param str incl: filepath to include for transform + + """ + last_include = self.aug.match("/augeas/load/Httpd/incl [last()]") + if last_include: + # Insert a new node immediately after the last incl + self.aug.insert(last_include[0], "incl", False) + self.aug.set("/augeas/load/Httpd/incl[last()]", incl) + # On first use... must load lens and add file to incl + else: + # Augeas uses base 1 indexing... insert at beginning... + self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") + self.aug.set("/augeas/load/Httpd/incl", incl) + # Add included path to paths dictionary + try: + self.parser_paths[os.path.dirname(incl)].append( + os.path.basename(incl)) + except KeyError: + self.parser_paths[os.path.dirname(incl)] = [ + os.path.basename(incl)] + + def standardize_excl(self): + """Standardize the excl arguments for the Httpd lens in Augeas. + + Note: Hack! + Standardize the excl arguments for the Httpd lens in Augeas + Servers sometimes give incorrect defaults + Note: This problem should be fixed in Augeas 1.0. Unfortunately, + Augeas 0.10 appears to be the most popular version currently. + + """ + # attempt to protect against augeas error in 0.10.0 - ubuntu + # *.augsave -> /*.augsave upon augeas.load() + # Try to avoid bad httpd files + # There has to be a better way... but after a day and a half of testing + # I had no luck + # This is a hack... work around... submit to augeas if still not fixed + + excl = ["*.augnew", "*.augsave", "*.dpkg-dist", "*.dpkg-bak", + "*.dpkg-new", "*.dpkg-old", "*.rpmsave", "*.rpmnew", + "*~", + self.root + "/*.augsave", + self.root + "/*~", + self.root + "/*/*augsave", + self.root + "/*/*~", + self.root + "/*/*/*.augsave", + self.root + "/*/*/*~"] + + for i, excluded in enumerate(excl, 1): + self.aug.set("/augeas/load/Httpd/excl[%d]" % i, excluded) + + self.aug.load() + + def _set_locations(self): + """Set default location for directives. + + Locations are given as file_paths + .. todo:: Make sure that files are included + + """ + default = self.loc["root"] + + temp = os.path.join(self.root, "ports.conf") + if os.path.isfile(temp): + listen = temp + name = temp + else: + listen = default + name = default + + return {"default": default, "listen": listen, "name": name} + + def _find_config_root(self): + """Find the Apache Configuration Root file.""" + location = ["apache2.conf", "httpd.conf", "conf/httpd.conf"] + for name in location: + if os.path.isfile(os.path.join(self.root, name)): + return os.path.join(self.root, name) + raise errors.NoInstallationError("Could not find configuration root") + + +def case_i(string): + """Returns case insensitive regex. + + Returns a sloppy, but necessary version of a case insensitive regex. + Any string should be able to be submitted and the string is + escaped and then made case insensitive. + May be replaced by a more proper /i once augeas 1.0 is widely + supported. + + :param str string: string to make case i regex + + """ + return "".join(["[" + c.upper() + c.lower() + "]" + if c.isalpha() else c for c in re.escape(string)]) + + +def get_aug_path(file_path): + """Return augeas path for full filepath. + + :param str file_path: Full filepath + + """ + return "/files%s" % file_path diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/apache_util.py deleted file mode 100644 index 7a2ecf49b..000000000 --- a/certbot-apache/certbot_apache/apache_util.py +++ /dev/null @@ -1,107 +0,0 @@ -""" Utility functions for certbot-apache plugin """ -import binascii - -from certbot import util -from certbot.compat import os - - -def get_mod_deps(mod_name): - """Get known module dependencies. - - .. note:: This does not need to be accurate in order for the client to - run. This simply keeps things clean if the user decides to revert - changes. - .. warning:: If all deps are not included, it may cause incorrect parsing - behavior, due to enable_mod's shortcut for updating the parser's - currently defined modules (`.ApacheParser.add_mod`) - This would only present a major problem in extremely atypical - configs that use ifmod for the missing deps. - - """ - deps = { - "ssl": ["setenvif", "mime"] - } - return deps.get(mod_name, []) - - -def get_file_path(vhost_path): - """Get file path from augeas_vhost_path. - - Takes in Augeas path and returns the file name - - :param str vhost_path: Augeas virtual host path - - :returns: filename of vhost - :rtype: str - - """ - if not vhost_path or not vhost_path.startswith("/files/"): - return None - - return _split_aug_path(vhost_path)[0] - - -def get_internal_aug_path(vhost_path): - """Get the Augeas path for a vhost with the file path removed. - - :param str vhost_path: Augeas virtual host path - - :returns: Augeas path to vhost relative to the containing file - :rtype: str - - """ - return _split_aug_path(vhost_path)[1] - - -def _split_aug_path(vhost_path): - """Splits an Augeas path into a file path and an internal path. - - After removing "/files", this function splits vhost_path into the - file path and the remaining Augeas path. - - :param str vhost_path: Augeas virtual host path - - :returns: file path and internal Augeas path - :rtype: `tuple` of `str` - - """ - # Strip off /files - file_path = vhost_path[6:] - internal_path = [] - - # Remove components from the end of file_path until it becomes valid - while not os.path.exists(file_path): - file_path, _, internal_path_part = file_path.rpartition("/") - internal_path.append(internal_path_part) - - return file_path, "/".join(reversed(internal_path)) - - -def parse_define_file(filepath, varname): - """ Parses Defines from a variable in configuration file - - :param str filepath: Path of file to parse - :param str varname: Name of the variable - - :returns: Dict of Define:Value pairs - :rtype: `dict` - - """ - return_vars = {} - # Get list of words in the variable - a_opts = util.get_var_from_file(varname, filepath).split() - for i, v in enumerate(a_opts): - # Handle Define statements and make sure it has an argument - if v == "-D" and len(a_opts) >= i+2: - var_parts = a_opts[i+1].partition("=") - return_vars[var_parts[0]] = var_parts[2] - elif len(v) > 2 and v.startswith("-D"): - # Found var with no whitespace separator - var_parts = v[2:].partition("=") - return_vars[var_parts[0]] = var_parts[2] - return return_vars - - -def unique_id(): - """ Returns an unique id to be used as a VirtualHost identifier""" - return binascii.hexlify(os.urandom(16)).decode("utf-8") diff --git a/certbot-apache/certbot_apache/augeas_lens/README b/certbot-apache/certbot_apache/augeas_lens/README deleted file mode 100644 index bf9161f93..000000000 --- a/certbot-apache/certbot_apache/augeas_lens/README +++ /dev/null @@ -1,2 +0,0 @@ -Certbot includes the very latest Augeas lenses in order to ship bug fixes -to Apache configuration handling bugs as quickly as possible diff --git a/certbot-apache/certbot_apache/augeas_lens/httpd.aug b/certbot-apache/certbot_apache/augeas_lens/httpd.aug deleted file mode 100644 index 5600088cf..000000000 --- a/certbot-apache/certbot_apache/augeas_lens/httpd.aug +++ /dev/null @@ -1,206 +0,0 @@ -(* Apache HTTPD lens for Augeas - -Authors: - David Lutterkort - Francis Giraldeau - Raphael Pinson - -About: Reference - Online Apache configuration manual: http://httpd.apache.org/docs/trunk/ - -About: License - This file is licensed under the LGPL v2+. - -About: Lens Usage - Sample usage of this lens in augtool - - Apache configuration is represented by two main structures, nested sections - and directives. Sections are used as labels, while directives are kept as a - value. Sections and directives can have positional arguments inside values - of "arg" nodes. Arguments of sections must be the firsts child of the - section node. - - This lens doesn't support automatic string quoting. Hence, the string must - be quoted when containing a space. - - Create a new VirtualHost section with one directive: - > clear /files/etc/apache2/sites-available/foo/VirtualHost - > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80" - > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin" - > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com" - -About: Configuration files - This lens applies to files in /etc/httpd and /etc/apache2. See . - -*) - - -module Httpd = - -autoload xfm - -(****************************************************************** - * Utilities lens - *****************************************************************) -let dels (s:string) = del s s - -(* The continuation sequence that indicates that we should consider the - * next line part of the current line *) -let cont = /\\\\\r?\n/ - -(* Whitespace within a line: space, tab, and the continuation sequence *) -let ws = /[ \t]/ | cont - -(* Any possible character - '.' does not match \n *) -let any = /(.|\n)/ - -(* Any character preceded by a backslash *) -let esc_any = /\\\\(.|\n)/ - -(* Newline sequence - both for Unix and DOS newlines *) -let nl = /\r?\n/ - -(* Whitespace at the end of a line *) -let eol = del (ws* . nl) "\n" - -(* deal with continuation lines *) -let sep_spc = del ws+ " " -let sep_osp = del ws* "" -let sep_eq = del (ws* . "=" . ws*) "=" - -let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/ -let word = /[a-z][a-z0-9._-]*/i - -(* A complete line that is either just whitespace or a comment that only - * contains whitespace *) -let empty = [ del (ws* . /#?/ . ws* . nl) "\n" ] - -let indent = Util.indent - -(* A comment that is not just whitespace. We define it in terms of the - * things that are not allowed as part of such a comment: - * 1) Starts with whitespace - * 2) Ends with whitespace, a backslash or \r - * 3) Unescaped newlines - *) -let comment = - let comment_start = del (ws* . "#" . ws* ) "# " in - let unesc_eol = /[^\]?/ . nl in - let w = /[^\t\n\r \\]/ in - let r = /[\r\\]/ in - let s = /[\t\r ]/ in - (* - * we'd like to write - * let b = /\\\\/ in - * let t = /[\t\n\r ]/ in - * let x = b . (t? . (s|w)* ) in - * but the definition of b depends on commit 244c0edd in 1.9.0 and - * would make the lens unusable with versions before 1.9.0. So we write - * x out which works in older versions, too - *) - let x = /\\\\[\t\n\r ]?[^\n\\]*/ in - let line = ((r . s* . w|w|r) . (s|w)* . x*|(r.s* )?).w.(s*.w)* in - [ label "#comment" . comment_start . store line . eol ] - -(* borrowed from shellvars.aug *) -let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ / -let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/ - -let dquot = - let no_dquot = /[^"\\\r\n]/ - in /"/ . (no_dquot|esc_any)* . /"/ -let dquot_msg = - let no_dquot = /([^ \t"\\\r\n]|[^"\\\r\n]+[^ \t"\\\r\n])/ - in /"/ . (no_dquot|esc_any)* . no_dquot - -let squot = - let no_squot = /[^'\\\r\n]/ - in /'/ . (no_squot|esc_any)* . /'/ -let comp = /[<>=]?=/ - -(****************************************************************** - * Attributes - *****************************************************************) - -(* The arguments for a directive come in two flavors: quoted with single or - * double quotes, or bare. Bare arguments may not start with a single or - * double quote; since we also treat "word lists" special, i.e. lists - * enclosed in curly braces, bare arguments may not start with those, - * either. - * - * Bare arguments may not contain unescaped spaces, but we allow escaping - * with '\\'. Quoted arguments can contain anything, though the quote must - * be escaped with '\\'. - *) -let bare = /([^{"' \t\n\r]|\\\\.)([^ \t\n\r]|\\\\.)*[^ \t\n\r\\]|[^{"' \t\n\r\\]/ - -let arg_quoted = [ label "arg" . store (dquot|squot) ] -let arg_bare = [ label "arg" . store bare ] - -(* message argument starts with " but ends at EOL *) -let arg_dir_msg = [ label "arg" . store dquot_msg ] -let arg_wl = [ label "arg" . store (char_arg_wl+|dquot|squot) ] - -(* comma-separated wordlist as permitted in the SSLRequire directive *) -let arg_wordlist = - let wl_start = dels "{" in - let wl_end = dels "}" in - let wl_sep = del /[ \t]*,[ \t]*/ ", " - in [ label "wordlist" . wl_start . arg_wl . (wl_sep . arg_wl)* . wl_end ] - -let argv (l:lens) = l . (sep_spc . l)* - -(* the arguments of a directive. We use this once we have parsed the name - * of the directive, and the space right after it. When dir_args is used, - * we also know that we have at least one argument. We need to be careful - * with the spacing between arguments: quoted arguments and word lists do - * not need to have space between them, but bare arguments do. - * - * Apache apparently is also happy if the last argument starts with a double - * quote, but has no corresponding closing duoble quote, which is what - * arg_dir_msg handles - *) -let dir_args = - let arg_nospc = arg_quoted|arg_wordlist in - (arg_bare . sep_spc | arg_nospc . sep_osp)* . (arg_bare|arg_nospc|arg_dir_msg) - -let directive = - [ indent . label "directive" . store word . (sep_spc . dir_args)? . eol ] - -let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ] - -let section (body:lens) = - (* opt_eol includes empty lines *) - let opt_eol = del /([ \t]*#?[ \t]*\r?\n)*/ "\n" in - let inner = (sep_spc . argv arg_sec)? . sep_osp . - dels ">" . opt_eol . ((body|comment) . (body|empty|comment)*)? . - indent . dels "[ \t\n\r]*/ ">\n" ] - -let perl_section = [ indent . label "Perl" . del //i "" - . store /[^<]*/ - . del /<\/perl>/i "" . eol ] - - -let rec content = section (content|directive) - | perl_section - -let lns = (content|directive|comment|empty)* - -let filter = (incl "/etc/apache2/apache2.conf") . - (incl "/etc/apache2/httpd.conf") . - (incl "/etc/apache2/ports.conf") . - (incl "/etc/apache2/conf.d/*") . - (incl "/etc/apache2/conf-available/*.conf") . - (incl "/etc/apache2/mods-available/*") . - (incl "/etc/apache2/sites-available/*") . - (incl "/etc/apache2/vhosts.d/*.conf") . - (incl "/etc/httpd/conf.d/*.conf") . - (incl "/etc/httpd/httpd.conf") . - (incl "/etc/httpd/conf/httpd.conf") . - (incl "/etc/httpd/conf.modules.d/*.conf") . - Util.stdexcl - -let xfm = transform lns filter diff --git a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/centos-options-ssl-apache.conf deleted file mode 100644 index 56c946a4e..000000000 --- a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf +++ /dev/null @@ -1,25 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py deleted file mode 100644 index d77fbc1af..000000000 --- a/certbot-apache/certbot_apache/configurator.py +++ /dev/null @@ -1,2517 +0,0 @@ -"""Apache Configurator.""" -# pylint: disable=too-many-lines -import copy -import fnmatch -import logging -import re -import socket -import time - -from collections import defaultdict - -import pkg_resources -import six - -import zope.component -import zope.interface - -from acme import challenges -from acme.magic_typing import DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot import interfaces -from certbot import util - -from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import -from certbot.compat import filesystem -from certbot.compat import os -from certbot.plugins import common -from certbot.plugins.util import path_surgery -from certbot.plugins.enhancements import AutoHSTSEnhancement - -from certbot_apache import apache_util -from certbot_apache import constants -from certbot_apache import display_ops -from certbot_apache import http_01 -from certbot_apache import obj -from certbot_apache import parser - -logger = logging.getLogger(__name__) - - -# TODO: Augeas sections ie. , beginning and closing -# tags need to be the same case, otherwise Augeas doesn't recognize them. -# This is not able to be completely remedied by regular expressions because -# Augeas views as an error. This will just -# require another check_parsing_errors() after all files are included... -# (after a find_directive search is executed currently). It can be a one -# time check however because all of LE's transactions will ensure -# only properly formed sections are added. - -# Note: This protocol works for filenames with spaces in it, the sites are -# properly set up and directives are changed appropriately, but Apache won't -# recognize names in sites-enabled that have spaces. These are not added to the -# Apache configuration. It may be wise to warn the user if they are trying -# to use vhost filenames that contain spaces and offer to change ' ' to '_' - -# Note: FILEPATHS and changes to files are transactional. They are copied -# over before the updates are made to the existing files. NEW_FILES is -# transactional due to the use of register_file_creation() - - -# TODO: Verify permissions on configuration root... it is easier than -# checking permissions on each of the relative directories and less error -# prone. -# TODO: Write a server protocol finder. Listen or -# Protocol . This can verify partial setups are correct -# TODO: Add directives to sites-enabled... not sites-available. -# sites-available doesn't allow immediate find_dir search even with save() -# and load() - -@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class ApacheConfigurator(common.Installer): - """Apache configurator. - - :ivar config: Configuration. - :type config: :class:`~certbot.interfaces.IConfig` - - :ivar parser: Handles low level parsing - :type parser: :class:`~certbot_apache.parser` - - :ivar tup version: version of Apache - :ivar list vhosts: All vhosts found in the configuration - (:class:`list` of :class:`~certbot_apache.obj.VirtualHost`) - - :ivar dict assoc: Mapping between domains and vhosts - - """ - - description = "Apache Web Server plugin" - if os.environ.get("CERTBOT_DOCS") == "1": - description += ( # pragma: no cover - " (Please note that the default values of the Apache plugin options" - " change depending on the operating system Certbot is run on.)" - ) - - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/sites-available", - vhost_files="*", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/apache2", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) - - def option(self, key): - """Get a value from options""" - return self.options.get(key) - - def _prepare_options(self): - """ - Set the values possibly changed by command line parameters to - OS_DEFAULTS constant dictionary - """ - opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root", - "logs_root", "challenge_location", "handle_modules", "handle_sites", - "ctl"] - for o in opts: - # Config options use dashes instead of underscores - if self.conf(o.replace("_", "-")) is not None: - self.options[o] = self.conf(o.replace("_", "-")) - else: - self.options[o] = self.OS_DEFAULTS[o] - - # Special cases - self.options["version_cmd"][0] = self.option("ctl") - self.options["restart_cmd"][0] = self.option("ctl") - self.options["conftest_cmd"][0] = self.option("ctl") - - @classmethod - def add_parser_arguments(cls, add): - # When adding, modifying or deleting command line arguments, be sure to - # include the changes in the list used in method _prepare_options() to - # ensure consistent behavior. - - # Respect CERTBOT_DOCS environment variable and use default values from - # base class regardless of the underlying distribution (overrides). - if os.environ.get("CERTBOT_DOCS") == "1": - DEFAULTS = ApacheConfigurator.OS_DEFAULTS - else: - # cls.OS_DEFAULTS can be distribution specific, see override classes - DEFAULTS = cls.OS_DEFAULTS - add("enmod", default=DEFAULTS["enmod"], - help="Path to the Apache 'a2enmod' binary") - add("dismod", default=DEFAULTS["dismod"], - help="Path to the Apache 'a2dismod' binary") - add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"], - help="SSL vhost configuration extension") - add("server-root", default=DEFAULTS["server_root"], - help="Apache server root directory") - add("vhost-root", default=None, - help="Apache server VirtualHost configuration root") - add("logs-root", default=DEFAULTS["logs_root"], - help="Apache server logs directory") - add("challenge-location", - default=DEFAULTS["challenge_location"], - help="Directory path for challenge configuration") - add("handle-modules", default=DEFAULTS["handle_modules"], - help="Let installer handle enabling required modules for you " + - "(Only Ubuntu/Debian currently)") - add("handle-sites", default=DEFAULTS["handle_sites"], - help="Let installer handle enabling sites for you " + - "(Only Ubuntu/Debian currently)") - add("ctl", default=DEFAULTS["ctl"], - help="Full path to Apache control script") - - def __init__(self, *args, **kwargs): - """Initialize an Apache Configurator. - - :param tup version: version of Apache as a tuple (2, 4, 7) - (used mostly for unittesting) - - """ - version = kwargs.pop("version", None) - super(ApacheConfigurator, self).__init__(*args, **kwargs) - - # Add name_server association dict - self.assoc = dict() # type: Dict[str, obj.VirtualHost] - # Outstanding challenges - self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge] - # List of vhosts configured per wildcard domain on this run. - # used by deploy_cert() and enhance() - self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]] - # Maps enhancements to vhosts we've enabled the enhancement for - self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]] - # Temporary state for AutoHSTS enhancement - self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]] - # Reverter save notes - self.save_notes = "" - - # These will be set in the prepare function - self._prepared = False - self.parser = None - self.version = version - self.vhosts = None - self.options = copy.deepcopy(self.OS_DEFAULTS) - self._enhance_func = {"redirect": self._enable_redirect, - "ensure-http-header": self._set_http_header, - "staple-ocsp": self._enable_ocsp_stapling} - - @property - def mod_ssl_conf(self): - """Full absolute path to SSL configuration file.""" - return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) - - @property - def updated_mod_ssl_conf_digest(self): - """Full absolute path to digest of updated SSL configuration file.""" - return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) - - def prepare(self): - """Prepare the authenticator/installer. - - :raises .errors.NoInstallationError: If Apache configs cannot be found - :raises .errors.MisconfigurationError: If Apache is misconfigured - :raises .errors.NotSupportedError: If Apache version is not supported - :raises .errors.PluginError: If there is any other error - - """ - self._prepare_options() - - # Verify Apache is installed - self._verify_exe_availability(self.option("ctl")) - - # Make sure configuration is valid - self.config_test() - - # Set Version - if self.version is None: - self.version = self.get_version() - logger.debug('Apache version is %s', - '.'.join(str(i) for i in self.version)) - if self.version < (2, 2): - raise errors.NotSupportedError( - "Apache Version {0} not supported.".format(str(self.version))) - - # Recover from previous crash before Augeas initialization to have the - # correct parse tree from the get go. - self.recovery_routine() - # Perform the actual Augeas initialization to be able to react - self.parser = self.get_parser() - - # Check for errors in parsing files with Augeas - self.parser.check_parsing_errors("httpd.aug") - - # Get all of the available vhosts - self.vhosts = self.get_virtual_hosts() - - self.install_ssl_options_conf(self.mod_ssl_conf, - self.updated_mod_ssl_conf_digest) - - # Prevent two Apache plugins from modifying a config at once - try: - util.lock_dir_until_exit(self.option("server_root")) - except (OSError, errors.LockError): - logger.debug("Encountered error:", exc_info=True) - raise errors.PluginError( - "Unable to create a lock file in {0}. Are you running" - " Certbot with sufficient privileges to modify your" - " Apache configuration?".format(self.option("server_root"))) - self._prepared = True - - def save(self, title=None, temporary=False): - """Saves all changes to the configuration files. - - This function first checks for save errors, if none are found, - all configuration changes made will be saved. According to the - function parameters. If an exception is raised, a new checkpoint - was not created. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (ie. challenges) - - """ - save_files = self.parser.unsaved_files() - if save_files: - self.add_to_checkpoint(save_files, - self.save_notes, temporary=temporary) - # Handle the parser specific tasks - self.parser.save(save_files) - if title and not temporary: - self.finalize_checkpoint(title) - - def recovery_routine(self): - """Revert all previously modified files. - - Reverts all modified files that have not been saved as a checkpoint - - :raises .errors.PluginError: If unable to recover the configuration - - """ - super(ApacheConfigurator, self).recovery_routine() - # Reload configuration after these changes take effect if needed - # ie. ApacheParser has been initialized. - if self.parser: - # TODO: wrap into non-implementation specific parser interface - self.parser.aug.load() - - def revert_challenge_config(self): - """Used to cleanup challenge configurations. - - :raises .errors.PluginError: If unable to revert the challenge config. - - """ - self.revert_temporary_config() - self.parser.aug.load() - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - - """ - super(ApacheConfigurator, self).rollback_checkpoints(rollback) - self.parser.aug.load() - - def _verify_exe_availability(self, exe): - """Checks availability of Apache executable""" - if not util.exe_exists(exe): - if not path_surgery(exe): - raise errors.NoInstallationError( - 'Cannot find Apache executable {0}'.format(exe)) - - def get_parser(self): - """Initializes the ApacheParser""" - # If user provided vhost_root value in command line, use it - return parser.ApacheParser( - self.option("server_root"), self.conf("vhost-root"), - self.version, configurator=self) - - def _wildcard_domain(self, domain): - """ - Checks if domain is a wildcard domain - - :param str domain: Domain to check - - :returns: If the domain is wildcard domain - :rtype: bool - """ - if isinstance(domain, six.text_type): - wildcard_marker = u"*." - else: - wildcard_marker = b"*." - return domain.startswith(wildcard_marker) - - def deploy_cert(self, domain, cert_path, key_path, - chain_path=None, fullchain_path=None): - """Deploys certificate to specified virtual host. - - Currently tries to find the last directives to deploy the certificate - in the VHost associated with the given domain. If it can't find the - directives, it searches the "included" confs. The function verifies - that it has located the three directives and finally modifies them - to point to the correct destination. After the certificate is - installed, the VirtualHost is enabled if it isn't already. - - .. todo:: Might be nice to remove chain directive if none exists - This shouldn't happen within certbot though - - :raises errors.PluginError: When unable to deploy certificate due to - a lack of directives - - """ - vhosts = self.choose_vhosts(domain) - for vhost in vhosts: - self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) - - def choose_vhosts(self, domain, create_if_no_ssl=True): - """ - Finds VirtualHosts that can be used with the provided domain - - :param str domain: Domain name to match VirtualHosts to - :param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS - counterpart, should one get created - - :returns: List of VirtualHosts or None - :rtype: `list` of :class:`~certbot_apache.obj.VirtualHost` - """ - - if self._wildcard_domain(domain): - if domain in self._wildcard_vhosts: - # Vhosts for a wildcard domain were already selected - return self._wildcard_vhosts[domain] - # Ask user which VHosts to support. - # Returned objects are guaranteed to be ssl vhosts - return self._choose_vhosts_wildcard(domain, create_if_no_ssl) - else: - return [self.choose_vhost(domain, create_if_no_ssl)] - - def _vhosts_for_wildcard(self, domain): - """ - Get VHost objects for every VirtualHost that the user wants to handle - with the wildcard certificate. - """ - - # Collect all vhosts that match the name - matched = set() - for vhost in self.vhosts: - for name in vhost.get_names(): - if self._in_wildcard_scope(name, domain): - matched.add(vhost) - - return list(matched) - - def _in_wildcard_scope(self, name, domain): - """ - Helper method for _vhosts_for_wildcard() that makes sure that the domain - is in the scope of wildcard domain. - - eg. in scope: domain = *.wild.card, name = 1.wild.card - not in scope: domain = *.wild.card, name = 1.2.wild.card - """ - if len(name.split(".")) == len(domain.split(".")): - return fnmatch.fnmatch(name, domain) - return None - - def _choose_vhosts_wildcard(self, domain, create_ssl=True): - """Prompts user to choose vhosts to install a wildcard certificate for""" - - # Get all vhosts that are covered by the wildcard domain - vhosts = self._vhosts_for_wildcard(domain) - - # Go through the vhosts, making sure that we cover all the names - # present, but preferring the SSL vhosts - filtered_vhosts = dict() - for vhost in vhosts: - for name in vhost.get_names(): - if vhost.ssl: - # Always prefer SSL vhosts - filtered_vhosts[name] = vhost - elif name not in filtered_vhosts and create_ssl: - # Add if not in list previously - filtered_vhosts[name] = vhost - - # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) - - # Ask the user which of names to enable, expect list of names back - dialog_output = display_ops.select_vhost_multiple(list(dialog_input)) - - if not dialog_output: - logger.error( - "No vhost exists with servername or alias for domain %s. " - "No vhost was selected. Please specify ServerName or ServerAlias " - "in the Apache config.", - domain) - raise errors.PluginError("No vhost selected") - - # Make sure we create SSL vhosts for the ones that are HTTP only - # if requested. - return_vhosts = list() - for vhost in dialog_output: - if not vhost.ssl: - return_vhosts.append(self.make_vhost_ssl(vhost)) - else: - return_vhosts.append(vhost) - - self._wildcard_vhosts[domain] = return_vhosts - return return_vhosts - - def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): - """ - Helper function for deploy_cert() that handles the actual deployment - this exists because we might want to do multiple deployments per - domain originally passed for deploy_cert(). This is especially true - with wildcard certificates - """ - # This is done first so that ssl module is enabled and cert_path, - # cert_key... can all be parsed appropriately - self.prepare_server_https("443") - - # Add directives and remove duplicates - self._add_dummy_ssl_directives(vhost.path) - self._clean_vhost(vhost) - - path = {"cert_path": self.parser.find_dir("SSLCertificateFile", - None, vhost.path), - "cert_key": self.parser.find_dir("SSLCertificateKeyFile", - None, vhost.path)} - - # Only include if a certificate chain is specified - if chain_path is not None: - path["chain_path"] = self.parser.find_dir( - "SSLCertificateChainFile", None, vhost.path) - - # Handle errors when certificate/key directives cannot be found - if not path["cert_path"]: - logger.warning( - "Cannot find an SSLCertificateFile directive in %s. " - "VirtualHost was not modified", vhost.path) - raise errors.PluginError( - "Unable to find an SSLCertificateFile directive") - elif not path["cert_key"]: - logger.warning( - "Cannot find an SSLCertificateKeyFile directive for " - "certificate in %s. VirtualHost was not modified", vhost.path) - raise errors.PluginError( - "Unable to find an SSLCertificateKeyFile directive for " - "certificate") - - logger.info("Deploying Certificate to VirtualHost %s", vhost.filep) - - if self.version < (2, 4, 8) or (chain_path and not fullchain_path): - # install SSLCertificateFile, SSLCertificateKeyFile, - # and SSLCertificateChainFile directives - set_cert_path = cert_path - self.parser.aug.set(path["cert_path"][-1], cert_path) - self.parser.aug.set(path["cert_key"][-1], key_path) - if chain_path is not None: - self.parser.add_dir(vhost.path, - "SSLCertificateChainFile", chain_path) - else: - raise errors.PluginError("--chain-path is required for your " - "version of Apache") - else: - if not fullchain_path: - raise errors.PluginError("Please provide the --fullchain-path " - "option pointing to your full chain file") - set_cert_path = fullchain_path - self.parser.aug.set(path["cert_path"][-1], fullchain_path) - self.parser.aug.set(path["cert_key"][-1], key_path) - - # Enable the new vhost if needed - if not vhost.enabled: - self.enable_site(vhost) - - # Save notes about the transaction that took place - self.save_notes += ("Changed vhost at %s with addresses of %s\n" - "\tSSLCertificateFile %s\n" - "\tSSLCertificateKeyFile %s\n" % - (vhost.filep, - ", ".join(str(addr) for addr in vhost.addrs), - set_cert_path, key_path)) - if chain_path is not None: - self.save_notes += "\tSSLCertificateChainFile %s\n" % chain_path - - def choose_vhost(self, target_name, create_if_no_ssl=True): - """Chooses a virtual host based on the given domain name. - - If there is no clear virtual host to be selected, the user is prompted - with all available choices. - - The returned vhost is guaranteed to have TLS enabled unless - create_if_no_ssl is set to False, in which case there is no such guarantee - and the result is not cached. - - :param str target_name: domain name - :param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS - counterpart, should one get created - - :returns: vhost associated with name - :rtype: :class:`~certbot_apache.obj.VirtualHost` - - :raises .errors.PluginError: If no vhost is available or chosen - - """ - # Allows for domain names to be associated with a virtual host - if target_name in self.assoc: - return self.assoc[target_name] - - # Try to find a reasonable vhost - vhost = self._find_best_vhost(target_name) - if vhost is not None: - if not create_if_no_ssl: - return vhost - if not vhost.ssl: - vhost = self.make_vhost_ssl(vhost) - - self._add_servername_alias(target_name, vhost) - self.assoc[target_name] = vhost - return vhost - - # Negate create_if_no_ssl value to indicate if we want a SSL vhost - # to get created if a non-ssl vhost is selected. - return self._choose_vhost_from_list(target_name, temp=not create_if_no_ssl) - - def _choose_vhost_from_list(self, target_name, temp=False): - # Select a vhost from a list - vhost = display_ops.select_vhost(target_name, self.vhosts) - if vhost is None: - logger.error( - "No vhost exists with servername or alias of %s. " - "No vhost was selected. Please specify ServerName or ServerAlias " - "in the Apache config.", - target_name) - raise errors.PluginError("No vhost selected") - elif temp: - return vhost - elif not vhost.ssl: - addrs = self._get_proposed_addrs(vhost, "443") - # TODO: Conflicts is too conservative - if not any(vhost.enabled and vhost.conflicts(addrs) for - vhost in self.vhosts): - vhost = self.make_vhost_ssl(vhost) - else: - logger.error( - "The selected vhost would conflict with other HTTPS " - "VirtualHosts within Apache. Please select another " - "vhost or add ServerNames to your configuration.") - raise errors.PluginError( - "VirtualHost not able to be selected.") - - self._add_servername_alias(target_name, vhost) - self.assoc[target_name] = vhost - return vhost - - def domain_in_names(self, names, target_name): - """Checks if target domain is covered by one or more of the provided - names. The target name is matched by wildcard as well as exact match. - - :param names: server aliases - :type names: `collections.Iterable` of `str` - :param str target_name: name to compare with wildcards - - :returns: True if target_name is covered by a wildcard, - otherwise, False - :rtype: bool - - """ - # use lowercase strings because fnmatch can be case sensitive - target_name = target_name.lower() - for name in names: - name = name.lower() - # fnmatch treats "[seq]" specially and [ or ] characters aren't - # valid in Apache but Apache doesn't error out if they are present - if "[" not in name and fnmatch.fnmatch(target_name, name): - return True - return False - - def find_best_http_vhost(self, target, filter_defaults, port="80"): - """Returns non-HTTPS vhost objects found from the Apache config - - :param str target: Domain name of the desired VirtualHost - :param bool filter_defaults: whether _default_ vhosts should be - included if it is the best match - :param str port: port number the vhost should be listening on - - :returns: VirtualHost object that's the best match for target name - :rtype: `obj.VirtualHost` or None - """ - filtered_vhosts = [] - for vhost in self.vhosts: - if any(a.is_wildcard() or a.get_port() == port for a in vhost.addrs) and not vhost.ssl: - filtered_vhosts.append(vhost) - return self._find_best_vhost(target, filtered_vhosts, filter_defaults) - - def _find_best_vhost(self, target_name, vhosts=None, filter_defaults=True): - """Finds the best vhost for a target_name. - - This does not upgrade a vhost to HTTPS... it only finds the most - appropriate vhost for the given target_name. - - :param str target_name: domain handled by the desired vhost - :param vhosts: vhosts to consider - :type vhosts: `collections.Iterable` of :class:`~certbot_apache.obj.VirtualHost` - :param bool filter_defaults: whether a vhost with a _default_ - addr is acceptable - - :returns: VHost or None - - """ - # Points 6 - Servername SSL - # Points 5 - Wildcard SSL - # Points 4 - Address name with SSL - # Points 3 - Servername no SSL - # Points 2 - Wildcard no SSL - # Points 1 - Address name with no SSL - best_candidate = None - best_points = 0 - - if vhosts is None: - vhosts = self.vhosts - - for vhost in vhosts: - if vhost.modmacro is True: - continue - names = vhost.get_names() - if target_name in names: - points = 3 - elif self.domain_in_names(names, target_name): - points = 2 - elif any(addr.get_addr() == target_name for addr in vhost.addrs): - points = 1 - else: - # No points given if names can't be found. - # This gets hit but doesn't register - continue # pragma: no cover - - if vhost.ssl: - points += 3 - - if points > best_points: - best_points = points - best_candidate = vhost - - # No winners here... is there only one reasonable vhost? - if best_candidate is None: - if filter_defaults: - vhosts = self._non_default_vhosts(vhosts) - # remove mod_macro hosts from reasonable vhosts - reasonable_vhosts = [vh for vh - in vhosts if vh.modmacro is False] - if len(reasonable_vhosts) == 1: - best_candidate = reasonable_vhosts[0] - - return best_candidate - - def _non_default_vhosts(self, vhosts): - """Return all non _default_ only vhosts.""" - return [vh for vh in vhosts if not all( - addr.get_addr() == "_default_" for addr in vh.addrs - )] - - def get_all_names(self): - """Returns all names found in the Apache Configuration. - - :returns: All ServerNames, ServerAliases, and reverse DNS entries for - virtual host addresses - :rtype: set - - """ - all_names = set() # type: Set[str] - - vhost_macro = [] - - for vhost in self.vhosts: - all_names.update(vhost.get_names()) - if vhost.modmacro: - vhost_macro.append(vhost.filep) - - for addr in vhost.addrs: - if common.hostname_regex.match(addr.get_addr()): - all_names.add(addr.get_addr()) - else: - name = self.get_name_from_ip(addr) - if name: - all_names.add(name) - - if vhost_macro: - zope.component.getUtility(interfaces.IDisplay).notification( - "Apache mod_macro seems to be in use in file(s):\n{0}" - "\n\nUnfortunately mod_macro is not yet supported".format( - "\n ".join(vhost_macro)), force_interactive=True) - - return util.get_filtered_names(all_names) - - def get_name_from_ip(self, addr): # pylint: disable=no-self-use - """Returns a reverse dns name if available. - - :param addr: IP Address - :type addr: ~.common.Addr - - :returns: name or empty string if name cannot be determined - :rtype: str - - """ - # If it isn't a private IP, do a reverse DNS lookup - if not common.private_ips_regex.match(addr.get_addr()): - try: - socket.inet_aton(addr.get_addr()) - return socket.gethostbyaddr(addr.get_addr())[0] - except (socket.error, socket.herror, socket.timeout): - pass - - return "" - - def _get_vhost_names(self, path): - """Helper method for getting the ServerName and - ServerAlias values from vhost in path - - :param path: Path to read ServerName and ServerAliases from - - :returns: Tuple including ServerName and `list` of ServerAlias strings - """ - - servername_match = self.parser.find_dir( - "ServerName", None, start=path, exclude=False) - serveralias_match = self.parser.find_dir( - "ServerAlias", None, start=path, exclude=False) - - serveraliases = [] - for alias in serveralias_match: - serveralias = self.parser.get_arg(alias) - serveraliases.append(serveralias) - - servername = None - if servername_match: - # Get last ServerName as each overwrites the previous - servername = self.parser.get_arg(servername_match[-1]) - - return (servername, serveraliases) - - def _add_servernames(self, host): - """Helper function for get_virtual_hosts(). - - :param host: In progress vhost whose names will be added - :type host: :class:`~certbot_apache.obj.VirtualHost` - - """ - - servername, serveraliases = self._get_vhost_names(host.path) - - for alias in serveraliases: - if not host.modmacro: - host.aliases.add(alias) - - if not host.modmacro: - host.name = servername - - def _create_vhost(self, path): - """Used by get_virtual_hosts to create vhost objects - - :param str path: Augeas path to virtual host - - :returns: newly created vhost - :rtype: :class:`~certbot_apache.obj.VirtualHost` - - """ - addrs = set() - try: - args = self.parser.aug.match(path + "/arg") - except RuntimeError: - logger.warning("Encountered a problem while parsing file: %s, skipping", path) - return None - for arg in args: - addrs.add(obj.Addr.fromstring(self.parser.get_arg(arg))) - is_ssl = False - - if self.parser.find_dir("SSLEngine", "on", start=path, exclude=False): - is_ssl = True - - # "SSLEngine on" might be set outside of - # Treat vhosts with port 443 as ssl vhosts - for addr in addrs: - if addr.get_port() == "443": - is_ssl = True - - filename = apache_util.get_file_path( - self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) - if filename is None: - return None - - macro = False - if "/macro/" in path.lower(): - macro = True - - vhost_enabled = self.parser.parsed_in_original(filename) - - vhost = obj.VirtualHost(filename, path, addrs, is_ssl, - vhost_enabled, modmacro=macro) - self._add_servernames(vhost) - return vhost - - def get_virtual_hosts(self): - """Returns list of virtual hosts found in the Apache configuration. - - :returns: List of :class:`~certbot_apache.obj.VirtualHost` - objects found in configuration - :rtype: list - - """ - # Search base config, and all included paths for VirtualHosts - file_paths = {} # type: Dict[str, str] - internal_paths = defaultdict(set) # type: DefaultDict[str, Set[str]] - vhs = [] - # Make a list of parser paths because the parser_paths - # dictionary may be modified during the loop. - for vhost_path in list(self.parser.parser_paths): - paths = self.parser.aug.match( - ("/files%s//*[label()=~regexp('%s')]" % - (vhost_path, parser.case_i("VirtualHost")))) - paths = [path for path in paths if - "virtualhost" in os.path.basename(path).lower()] - for path in paths: - new_vhost = self._create_vhost(path) - if not new_vhost: - continue - internal_path = apache_util.get_internal_aug_path(new_vhost.path) - realpath = filesystem.realpath(new_vhost.filep) - if realpath not in file_paths: - file_paths[realpath] = new_vhost.filep - internal_paths[realpath].add(internal_path) - vhs.append(new_vhost) - elif (realpath == new_vhost.filep and - realpath != file_paths[realpath]): - # Prefer "real" vhost paths instead of symlinked ones - # ex: sites-enabled/vh.conf -> sites-available/vh.conf - - # remove old (most likely) symlinked one - new_vhs = [] - for v in vhs: - if v.filep == file_paths[realpath]: - internal_paths[realpath].remove( - apache_util.get_internal_aug_path(v.path)) - else: - new_vhs.append(v) - vhs = new_vhs - - file_paths[realpath] = realpath - internal_paths[realpath].add(internal_path) - vhs.append(new_vhost) - elif internal_path not in internal_paths[realpath]: - internal_paths[realpath].add(internal_path) - vhs.append(new_vhost) - return vhs - - def is_name_vhost(self, target_addr): - """Returns if vhost is a name based vhost - - NameVirtualHost was deprecated in Apache 2.4 as all VirtualHosts are - now NameVirtualHosts. If version is earlier than 2.4, check if addr - has a NameVirtualHost directive in the Apache config - - :param certbot_apache.obj.Addr target_addr: vhost address - - :returns: Success - :rtype: bool - - """ - # Mixed and matched wildcard NameVirtualHost with VirtualHost - # behavior is undefined. Make sure that an exact match exists - - # search for NameVirtualHost directive for ip_addr - # note ip_addr can be FQDN although Apache does not recommend it - return (self.version >= (2, 4) or - self.parser.find_dir("NameVirtualHost", str(target_addr))) - - def add_name_vhost(self, addr): - """Adds NameVirtualHost directive for given address. - - :param addr: Address that will be added as NameVirtualHost directive - :type addr: :class:`~certbot_apache.obj.Addr` - - """ - - loc = parser.get_aug_path(self.parser.loc["name"]) - if addr.get_port() == "443": - path = self.parser.add_dir_to_ifmodssl( - loc, "NameVirtualHost", [str(addr)]) - else: - path = self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) - - msg = ("Setting %s to be NameBasedVirtualHost\n" - "\tDirective added to %s\n" % (addr, path)) - logger.debug(msg) - self.save_notes += msg - - def prepare_server_https(self, port, temp=False): - """Prepare the server for HTTPS. - - Make sure that the ssl_module is loaded and that the server - is appropriately listening on port. - - :param str port: Port to listen on - - """ - - self.prepare_https_modules(temp) - self.ensure_listen(port, https=True) - - def ensure_listen(self, port, https=False): - """Make sure that Apache is listening on the port. Checks if the - Listen statement for the port already exists, and adds it to the - configuration if necessary. - - :param str port: Port number to check and add Listen for if not in - place already - :param bool https: If the port will be used for HTTPS - - """ - - # If HTTPS requested for nonstandard port, add service definition - if https and port != "443": - port_service = "%s %s" % (port, "https") - else: - port_service = port - - # Check for Listen - # Note: This could be made to also look for ip:443 combo - listens = [self.parser.get_arg(x).split()[0] for - x in self.parser.find_dir("Listen")] - - # Listen already in place - if self._has_port_already(listens, port): - return - - listen_dirs = set(listens) - - if not listens: - listen_dirs.add(port_service) - - for listen in listens: - # For any listen statement, check if the machine also listens on - # the given port. If not, add such a listen statement. - if len(listen.split(":")) == 1: - # Its listening to all interfaces - if port not in listen_dirs and port_service not in listen_dirs: - listen_dirs.add(port_service) - else: - # The Listen statement specifies an ip - _, ip = listen[::-1].split(":", 1) - ip = ip[::-1] - if "%s:%s" % (ip, port_service) not in listen_dirs and ( - "%s:%s" % (ip, port_service) not in listen_dirs): - listen_dirs.add("%s:%s" % (ip, port_service)) - if https: - self._add_listens_https(listen_dirs, listens, port) - else: - self._add_listens_http(listen_dirs, listens, port) - - def _add_listens_http(self, listens, listens_orig, port): - """Helper method for ensure_listen to figure out which new - listen statements need adding for listening HTTP on port - - :param set listens: Set of all needed Listen statements - :param list listens_orig: List of existing listen statements - :param string port: Port number we're adding - """ - - new_listens = listens.difference(listens_orig) - - if port in new_listens: - # We have wildcard, skip the rest - self.parser.add_dir(parser.get_aug_path(self.parser.loc["listen"]), - "Listen", port) - self.save_notes += "Added Listen %s directive to %s\n" % ( - port, self.parser.loc["listen"]) - else: - for listen in new_listens: - self.parser.add_dir(parser.get_aug_path( - self.parser.loc["listen"]), "Listen", listen.split(" ")) - self.save_notes += ("Added Listen %s directive to " - "%s\n") % (listen, - self.parser.loc["listen"]) - - def _add_listens_https(self, listens, listens_orig, port): - """Helper method for ensure_listen to figure out which new - listen statements need adding for listening HTTPS on port - - :param set listens: Set of all needed Listen statements - :param list listens_orig: List of existing listen statements - :param string port: Port number we're adding - """ - - # Add service definition for non-standard ports - if port != "443": - port_service = "%s %s" % (port, "https") - else: - port_service = port - - new_listens = listens.difference(listens_orig) - - if port in new_listens or port_service in new_listens: - # We have wildcard, skip the rest - self.parser.add_dir_to_ifmodssl( - parser.get_aug_path(self.parser.loc["listen"]), - "Listen", port_service.split(" ")) - self.save_notes += "Added Listen %s directive to %s\n" % ( - port_service, self.parser.loc["listen"]) - else: - for listen in new_listens: - self.parser.add_dir_to_ifmodssl( - parser.get_aug_path(self.parser.loc["listen"]), - "Listen", listen.split(" ")) - self.save_notes += ("Added Listen %s directive to " - "%s\n") % (listen, - self.parser.loc["listen"]) - - def _has_port_already(self, listens, port): - """Helper method for prepare_server_https to find out if user - already has an active Listen statement for the port we need - - :param list listens: List of listen variables - :param string port: Port in question - """ - - if port in listens: - return True - # Check if Apache is already listening on a specific IP - for listen in listens: - if len(listen.split(":")) > 1: - # Ugly but takes care of protocol def, eg: 1.1.1.1:443 https - if listen.split(":")[-1].split(" ")[0] == port: - return True - return None - - def prepare_https_modules(self, temp): - """Helper method for prepare_server_https, taking care of enabling - needed modules - - :param boolean temp: If the change is temporary - """ - - if self.option("handle_modules"): - if self.version >= (2, 4) and ("socache_shmcb_module" not in - self.parser.modules): - self.enable_mod("socache_shmcb", temp=temp) - if "ssl_module" not in self.parser.modules: - self.enable_mod("ssl", temp=temp) - - def make_vhost_ssl(self, nonssl_vhost): - """Makes an ssl_vhost version of a nonssl_vhost. - - Duplicates vhost and adds default ssl options - New vhost will reside as (nonssl_vhost.path) + - ``self.option("le_vhost_ext")`` - - .. note:: This function saves the configuration - - :param nonssl_vhost: Valid VH that doesn't have SSLEngine on - :type nonssl_vhost: :class:`~certbot_apache.obj.VirtualHost` - - :returns: SSL vhost - :rtype: :class:`~certbot_apache.obj.VirtualHost` - - :raises .errors.PluginError: If more than one virtual host is in - the file or if plugin is unable to write/read vhost files. - - """ - avail_fp = nonssl_vhost.filep - ssl_fp = self._get_ssl_vhost_path(avail_fp) - - orig_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % - (self._escape(ssl_fp), - parser.case_i("VirtualHost"))) - - self._copy_create_ssl_vhost_skeleton(nonssl_vhost, ssl_fp) - - # Reload augeas to take into account the new vhost - self.parser.aug.load() - # Get Vhost augeas path for new vhost - new_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % - (self._escape(ssl_fp), - parser.case_i("VirtualHost"))) - - vh_p = self._get_new_vh_path(orig_matches, new_matches) - - if not vh_p: - # The vhost was not found on the currently parsed paths - # Make Augeas aware of the new vhost - self.parser.parse_file(ssl_fp) - # Try to search again - new_matches = self.parser.aug.match( - "/files%s//* [label()=~regexp('%s')]" % - (self._escape(ssl_fp), - parser.case_i("VirtualHost"))) - vh_p = self._get_new_vh_path(orig_matches, new_matches) - if not vh_p: - raise errors.PluginError( - "Could not reverse map the HTTPS VirtualHost to the original") - - - # Update Addresses - self._update_ssl_vhosts_addrs(vh_p) - - # Log actions and create save notes - logger.info("Created an SSL vhost at %s", ssl_fp) - self.save_notes += "Created ssl vhost at %s\n" % ssl_fp - self.save() - - # We know the length is one because of the assertion above - # Create the Vhost object - ssl_vhost = self._create_vhost(vh_p) - ssl_vhost.ancestor = nonssl_vhost - - self.vhosts.append(ssl_vhost) - - # NOTE: Searches through Augeas seem to ruin changes to directives - # The configuration must also be saved before being searched - # for the new directives; For these reasons... this is tacked - # on after fully creating the new vhost - - # Now check if addresses need to be added as NameBasedVhost addrs - # This is for compliance with versions of Apache < 2.4 - self._add_name_vhost_if_necessary(ssl_vhost) - - return ssl_vhost - - def _get_new_vh_path(self, orig_matches, new_matches): - """ Helper method for make_vhost_ssl for matching augeas paths. Returns - VirtualHost path from new_matches that's not present in orig_matches. - - Paths are normalized, because augeas leaves indices out for paths - with only single directive with a similar key """ - - orig_matches = [i.replace("[1]", "") for i in orig_matches] - for match in new_matches: - if match.replace("[1]", "") not in orig_matches: - # Return the unmodified path - return match - return None - - def _get_ssl_vhost_path(self, non_ssl_vh_fp): - """ Get a file path for SSL vhost, uses user defined path as priority, - but if the value is invalid or not defined, will fall back to non-ssl - vhost filepath. - - :param str non_ssl_vh_fp: Filepath of non-SSL vhost - - :returns: Filepath for SSL vhost - :rtype: str - """ - - if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")): - fp = os.path.join(filesystem.realpath(self.option("vhost_root")), - os.path.basename(non_ssl_vh_fp)) - else: - # Use non-ssl filepath - fp = filesystem.realpath(non_ssl_vh_fp) - - if fp.endswith(".conf"): - return fp[:-(len(".conf"))] + self.option("le_vhost_ext") - return fp + self.option("le_vhost_ext") - - def _sift_rewrite_rule(self, line): - """Decides whether a line should be copied to a SSL vhost. - - A canonical example of when sifting a line is required: - When the http vhost contains a RewriteRule that unconditionally - redirects any request to the https version of the same site. - e.g: - RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,QSA,R=permanent] - Copying the above line to the ssl vhost would cause a - redirection loop. - - :param str line: a line extracted from the http vhost. - - :returns: True - don't copy line from http vhost to SSL vhost. - :rtype: bool - - """ - if not line.lower().lstrip().startswith("rewriterule"): - return False - - # According to: http://httpd.apache.org/docs/2.4/rewrite/flags.html - # The syntax of a RewriteRule is: - # RewriteRule pattern target [Flag1,Flag2,Flag3] - # i.e. target is required, so it must exist. - target = line.split()[2].strip() - - # target may be surrounded with quotes - if target[0] in ("'", '"') and target[0] == target[-1]: - target = target[1:-1] - - # Sift line if it redirects the request to a HTTPS site - return target.startswith("https://") - - def _copy_create_ssl_vhost_skeleton(self, vhost, ssl_fp): - """Copies over existing Vhost with IfModule mod_ssl.c> skeleton. - - :param obj.VirtualHost vhost: Original VirtualHost object - :param str ssl_fp: Full path where the new ssl_vhost will reside. - - A new file is created on the filesystem. - - """ - # First register the creation so that it is properly removed if - # configuration is rolled back - if os.path.exists(ssl_fp): - notes = "Appended new VirtualHost directive to file %s" % ssl_fp - files = set() - files.add(ssl_fp) - self.reverter.add_to_checkpoint(files, notes) - else: - self.reverter.register_file_creation(False, ssl_fp) - sift = False - - try: - orig_contents = self._get_vhost_block(vhost) - ssl_vh_contents, sift = self._sift_rewrite_rules(orig_contents) - - with open(ssl_fp, "a") as new_file: - new_file.write("\n") - new_file.write("\n".join(ssl_vh_contents)) - # The content does not include the closing tag, so add it - new_file.write("\n") - new_file.write("\n") - # Add new file to augeas paths if we're supposed to handle - # activation (it's not included as default) - if not self.parser.parsed_in_current(ssl_fp): - self.parser.parse_file(ssl_fp) - except IOError: - logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True) - raise errors.PluginError("Unable to write/read in make_vhost_ssl") - - if sift: - reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message( - "Some rewrite rules copied from {0} were disabled in the " - "vhost for your HTTPS site located at {1} because they have " - "the potential to create redirection loops.".format( - vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY) - self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") - self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") - - def _sift_rewrite_rules(self, contents): - """ Helper function for _copy_create_ssl_vhost_skeleton to prepare the - new HTTPS VirtualHost contents. Currently disabling the rewrites """ - - result = [] - sift = False - contents = iter(contents) - - comment = ("# Some rewrite rules in this file were " - "disabled on your HTTPS site,\n" - "# because they have the potential to create " - "redirection loops.\n") - - for line in contents: - A = line.lower().lstrip().startswith("rewritecond") - B = line.lower().lstrip().startswith("rewriterule") - - if not (A or B): - result.append(line) - continue - - # A RewriteRule that doesn't need filtering - if B and not self._sift_rewrite_rule(line): - result.append(line) - continue - - # A RewriteRule that does need filtering - if B and self._sift_rewrite_rule(line): - if not sift: - result.append(comment) - sift = True - result.append("# " + line) - continue - - # We save RewriteCond(s) and their corresponding - # RewriteRule in 'chunk'. - # We then decide whether we comment out the entire - # chunk based on its RewriteRule. - chunk = [] - if A: - chunk.append(line) - line = next(contents) - - # RewriteCond(s) must be followed by one RewriteRule - while not line.lower().lstrip().startswith("rewriterule"): - chunk.append(line) - line = next(contents) - - # Now, current line must start with a RewriteRule - chunk.append(line) - - if self._sift_rewrite_rule(line): - if not sift: - result.append(comment) - sift = True - - result.append('\n'.join( - ['# ' + l for l in chunk])) - continue - else: - result.append('\n'.join(chunk)) - continue - return result, sift - - def _get_vhost_block(self, vhost): - """ Helper method to get VirtualHost contents from the original file. - This is done with help of augeas span, which returns the span start and - end positions - - :returns: `list` of VirtualHost block content lines without closing tag - """ - - try: - span_val = self.parser.aug.span(vhost.path) - except ValueError: - logger.critical("Error while reading the VirtualHost %s from " - "file %s", vhost.name, vhost.filep, exc_info=True) - raise errors.PluginError("Unable to read VirtualHost from file") - span_filep = span_val[0] - span_start = span_val[5] - span_end = span_val[6] - with open(span_filep, 'r') as fh: - fh.seek(span_start) - vh_contents = fh.read(span_end-span_start).split("\n") - self._remove_closing_vhost_tag(vh_contents) - return vh_contents - - def _remove_closing_vhost_tag(self, vh_contents): - """Removes the closing VirtualHost tag if it exists. - - This method modifies vh_contents directly to remove the closing - tag. If the closing vhost tag is found, everything on the line - after it is also removed. Whether or not this tag is included - in the result of span depends on the Augeas version. - - :param list vh_contents: VirtualHost block contents to check - - """ - for offset, line in enumerate(reversed(vh_contents)): - if line: - line_index = line.lower().find("") - if line_index != -1: - content_index = len(vh_contents) - offset - 1 - vh_contents[content_index] = line[:line_index] - break - - def _update_ssl_vhosts_addrs(self, vh_path): - ssl_addrs = set() - ssl_addr_p = self.parser.aug.match(vh_path + "/arg") - - for addr in ssl_addr_p: - old_addr = obj.Addr.fromstring( - str(self.parser.get_arg(addr))) - ssl_addr = old_addr.get_addr_obj("443") - self.parser.aug.set(addr, str(ssl_addr)) - ssl_addrs.add(ssl_addr) - - return ssl_addrs - - def _clean_vhost(self, vhost): - # remove duplicated or conflicting ssl directives - self._deduplicate_directives(vhost.path, - ["SSLCertificateFile", - "SSLCertificateKeyFile"]) - # remove all problematic directives - self._remove_directives(vhost.path, ["SSLCertificateChainFile"]) - - def _deduplicate_directives(self, vh_path, directives): - for directive in directives: - while len(self.parser.find_dir(directive, None, - vh_path, False)) > 1: - directive_path = self.parser.find_dir(directive, None, - vh_path, False) - self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) - - def _remove_directives(self, vh_path, directives): - for directive in directives: - while self.parser.find_dir(directive, None, vh_path, False): - directive_path = self.parser.find_dir(directive, None, - vh_path, False) - self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) - - def _add_dummy_ssl_directives(self, vh_path): - self.parser.add_dir(vh_path, "SSLCertificateFile", - "insert_cert_file_path") - self.parser.add_dir(vh_path, "SSLCertificateKeyFile", - "insert_key_file_path") - # Only include the TLS configuration if not already included - existing_inc = self.parser.find_dir("Include", self.mod_ssl_conf, vh_path) - if not existing_inc: - self.parser.add_dir(vh_path, "Include", self.mod_ssl_conf) - - def _add_servername_alias(self, target_name, vhost): - vh_path = vhost.path - sname, saliases = self._get_vhost_names(vh_path) - if target_name == sname or target_name in saliases: - return - if self._has_matching_wildcard(vh_path, target_name): - return - if not self.parser.find_dir("ServerName", None, - start=vh_path, exclude=False): - self.parser.add_dir(vh_path, "ServerName", target_name) - else: - self.parser.add_dir(vh_path, "ServerAlias", target_name) - self._add_servernames(vhost) - - def _has_matching_wildcard(self, vh_path, target_name): - """Is target_name already included in a wildcard in the vhost? - - :param str vh_path: Augeas path to the vhost - :param str target_name: name to compare with wildcards - - :returns: True if there is a wildcard covering target_name in - the vhost in vhost_path, otherwise, False - :rtype: bool - - """ - matches = self.parser.find_dir( - "ServerAlias", start=vh_path, exclude=False) - aliases = (self.parser.aug.get(match) for match in matches) - return self.domain_in_names(aliases, target_name) - - def _add_name_vhost_if_necessary(self, vhost): - """Add NameVirtualHost Directives if necessary for new vhost. - - NameVirtualHosts was a directive in Apache < 2.4 - https://httpd.apache.org/docs/2.2/mod/core.html#namevirtualhost - - :param vhost: New virtual host that was recently created. - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - """ - need_to_save = False - - # See if the exact address appears in any other vhost - # Remember 1.1.1.1:* == 1.1.1.1 -> hence any() - for addr in vhost.addrs: - # In Apache 2.2, when a NameVirtualHost directive is not - # set, "*" and "_default_" will conflict when sharing a port - addrs = set((addr,)) - if addr.get_addr() in ("*", "_default_"): - addrs.update(obj.Addr((a, addr.get_port(),)) - for a in ("*", "_default_")) - - for test_vh in self.vhosts: - if (vhost.filep != test_vh.filep and - any(test_addr in addrs for - test_addr in test_vh.addrs) and - not self.is_name_vhost(addr)): - self.add_name_vhost(addr) - logger.info("Enabling NameVirtualHosts on %s", addr) - need_to_save = True - break - - if need_to_save: - self.save() - - def find_vhost_by_id(self, id_str): - """ - Searches through VirtualHosts and tries to match the id in a comment - - :param str id_str: Id string for matching - - :returns: The matched VirtualHost or None - :rtype: :class:`~certbot_apache.obj.VirtualHost` or None - - :raises .errors.PluginError: If no VirtualHost is found - """ - - for vh in self.vhosts: - if self._find_vhost_id(vh) == id_str: - return vh - msg = "No VirtualHost with ID {} was found.".format(id_str) - logger.warning(msg) - raise errors.PluginError(msg) - - def _find_vhost_id(self, vhost): - """Tries to find the unique ID from the VirtualHost comments. This is - used for keeping track of VirtualHost directive over time. - - :param vhost: Virtual host to add the id - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :returns: The unique ID or None - :rtype: str or None - """ - - # Strip the {} off from the format string - search_comment = constants.MANAGED_COMMENT_ID.format("") - - id_comment = self.parser.find_comments(search_comment, vhost.path) - if id_comment: - # Use the first value, multiple ones shouldn't exist - comment = self.parser.get_arg(id_comment[0]) - return comment.split(" ")[-1] - return None - - def add_vhost_id(self, vhost): - """Adds an unique ID to the VirtualHost as a comment for mapping back - to it on later invocations, as the config file order might have changed. - If ID already exists, returns that instead. - - :param vhost: Virtual host to add or find the id - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :returns: The unique ID for vhost - :rtype: str or None - """ - - vh_id = self._find_vhost_id(vhost) - if vh_id: - return vh_id - - id_string = apache_util.unique_id() - comment = constants.MANAGED_COMMENT_ID.format(id_string) - self.parser.add_comment(vhost.path, comment) - return id_string - - def _escape(self, fp): - fp = fp.replace(",", "\\,") - fp = fp.replace("[", "\\[") - fp = fp.replace("]", "\\]") - fp = fp.replace("|", "\\|") - fp = fp.replace("=", "\\=") - fp = fp.replace("(", "\\(") - fp = fp.replace(")", "\\)") - fp = fp.replace("!", "\\!") - return fp - - ###################################################################### - # Enhancements - ###################################################################### - def supported_enhancements(self): # pylint: disable=no-self-use - """Returns currently supported enhancements.""" - return ["redirect", "ensure-http-header", "staple-ocsp"] - - def enhance(self, domain, enhancement, options=None): - """Enhance configuration. - - :param str domain: domain to enhance - :param str enhancement: enhancement type defined in - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - :param options: options for the enhancement - See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - documentation for appropriate parameter. - - :raises .errors.PluginError: If Enhancement is not supported, or if - there is any other problem with the enhancement. - - """ - try: - func = self._enhance_func[enhancement] - except KeyError: - raise errors.PluginError( - "Unsupported enhancement: {0}".format(enhancement)) - - matched_vhosts = self.choose_vhosts(domain, create_if_no_ssl=False) - # We should be handling only SSL vhosts for enhancements - vhosts = [vhost for vhost in matched_vhosts if vhost.ssl] - - if not vhosts: - msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a " - "domain {0} for enabling enhancement \"{1}\". The requested " - "enhancement was not configured.") - msg_enhancement = enhancement - if options: - msg_enhancement += ": " + options - msg = msg_tmpl.format(domain, msg_enhancement) - logger.warning(msg) - raise errors.PluginError(msg) - try: - for vhost in vhosts: - func(vhost, options) - except errors.PluginError: - logger.warning("Failed %s for %s", enhancement, domain) - raise - - def _autohsts_increase(self, vhost, id_str, nextstep): - """Increase the AutoHSTS max-age value - - :param vhost: Virtual host object to modify - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :param str id_str: The unique ID string of VirtualHost - - :param int nextstep: Next AutoHSTS max-age value index - - """ - nextstep_value = constants.AUTOHSTS_STEPS[nextstep] - self._autohsts_write(vhost, nextstep_value) - self._autohsts[id_str] = {"laststep": nextstep, "timestamp": time.time()} - - def _autohsts_write(self, vhost, nextstep_value): - """ - Write the new HSTS max-age value to the VirtualHost file - """ - - hsts_dirpath = None - header_path = self.parser.find_dir("Header", None, vhost.path) - if header_path: - pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' - for match in header_path: - if re.search(pat, self.parser.aug.get(match).lower()): - hsts_dirpath = match - if not hsts_dirpath: - err_msg = ("Certbot was unable to find the existing HSTS header " - "from the VirtualHost at path {0}.").format(vhost.filep) - raise errors.PluginError(err_msg) - - # Prepare the HSTS header value - hsts_maxage = "\"max-age={0}\"".format(nextstep_value) - - # Update the header - # Our match statement was for string strict-transport-security, but - # we need to update the value instead. The next index is for the value - hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]") - self.parser.aug.set(hsts_dirpath, hsts_maxage) - note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost " - "in {1}\n".format(nextstep_value, vhost.filep)) - logger.debug(note_msg) - self.save_notes += note_msg - self.save(note_msg) - - def _autohsts_fetch_state(self): - """ - Populates the AutoHSTS state from the pluginstorage - """ - try: - self._autohsts = self.storage.fetch("autohsts") - except KeyError: - self._autohsts = dict() - - def _autohsts_save_state(self): - """ - Saves the state of AutoHSTS object to pluginstorage - """ - self.storage.put("autohsts", self._autohsts) - self.storage.save() - - def _autohsts_vhost_in_lineage(self, vhost, lineage): - """ - Searches AutoHSTS managed VirtualHosts that belong to the lineage. - Matches the private key path. - """ - - return bool( - self.parser.find_dir("SSLCertificateKeyFile", - lineage.key_path, vhost.path)) - - def _enable_ocsp_stapling(self, ssl_vhost, unused_options): - """Enables OCSP Stapling - - In OCSP, each client (e.g. browser) would have to query the - OCSP Responder to validate that the site certificate was not revoked. - - Enabling OCSP Stapling, would allow the web-server to query the OCSP - Responder, and staple its response to the offered certificate during - TLS. i.e. clients would not have to query the OCSP responder. - - OCSP Stapling enablement on Apache implicitly depends on - SSLCertificateChainFile being set by other code. - - .. note:: This function saves the configuration - - :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` - - :param unused_options: Not currently used - :type unused_options: Not Available - - :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, :class:`~certbot_apache.obj.VirtualHost`) - - """ - min_apache_ver = (2, 3, 3) - if self.get_version() < min_apache_ver: - raise errors.PluginError( - "Unable to set OCSP directives.\n" - "Apache version is below 2.3.3.") - - if "socache_shmcb_module" not in self.parser.modules: - self.enable_mod("socache_shmcb") - - # Check if there's an existing SSLUseStapling directive on. - use_stapling_aug_path = self.parser.find_dir("SSLUseStapling", - "on", start=ssl_vhost.path) - if not use_stapling_aug_path: - self.parser.add_dir(ssl_vhost.path, "SSLUseStapling", "on") - - ssl_vhost_aug_path = self._escape(parser.get_aug_path(ssl_vhost.filep)) - - # Check if there's an existing SSLStaplingCache directive. - stapling_cache_aug_path = self.parser.find_dir('SSLStaplingCache', - None, ssl_vhost_aug_path) - - # We'll simply delete the directive, so that we'll have a - # consistent OCSP cache path. - if stapling_cache_aug_path: - self.parser.aug.remove( - re.sub(r"/\w*$", "", stapling_cache_aug_path[0])) - - self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path, - "SSLStaplingCache", - ["shmcb:/var/run/apache2/stapling_cache(128000)"]) - - msg = "OCSP Stapling was enabled on SSL Vhost: %s.\n"%( - ssl_vhost.filep) - self.save_notes += msg - self.save() - logger.info(msg) - - def _set_http_header(self, ssl_vhost, header_substring): - """Enables header that is identified by header_substring on ssl_vhost. - - If the header identified by header_substring is not already set, - a new Header directive is placed in ssl_vhost's configuration with - arguments from: constants.HTTP_HEADER[header_substring] - - .. note:: This function saves the configuration - - :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` - - :param header_substring: string that uniquely identifies a header. - e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. - :type str - - :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, :class:`~certbot_apache.obj.VirtualHost`) - - :raises .errors.PluginError: If no viable HTTP host can be created or - set with header header_substring. - - """ - if "headers_module" not in self.parser.modules: - self.enable_mod("headers") - - # Check if selected header is already set - self._verify_no_matching_http_header(ssl_vhost, header_substring) - - # Add directives to server - self.parser.add_dir(ssl_vhost.path, "Header", - constants.HEADER_ARGS[header_substring]) - - self.save_notes += ("Adding %s header to ssl vhost in %s\n" % - (header_substring, ssl_vhost.filep)) - - self.save() - logger.info("Adding %s header to ssl vhost in %s", header_substring, - ssl_vhost.filep) - - def _verify_no_matching_http_header(self, ssl_vhost, header_substring): - """Checks to see if an there is an existing Header directive that - contains the string header_substring. - - :param ssl_vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :param header_substring: string that uniquely identifies a header. - e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. - :type str - - :returns: boolean - :rtype: (bool) - - :raises errors.PluginEnhancementAlreadyPresent When header - header_substring exists - - """ - header_path = self.parser.find_dir("Header", None, - start=ssl_vhost.path) - if header_path: - # "Existing Header directive for virtualhost" - pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower()) - for match in header_path: - if re.search(pat, self.parser.aug.get(match).lower()): - raise errors.PluginEnhancementAlreadyPresent( - "Existing %s header" % (header_substring)) - - def _enable_redirect(self, ssl_vhost, unused_options): - """Redirect all equivalent HTTP traffic to ssl_vhost. - - .. todo:: This enhancement should be rewritten and will - unfortunately require lots of debugging by hand. - - Adds Redirect directive to the port 80 equivalent of ssl_vhost - First the function attempts to find the vhost with equivalent - ip addresses that serves on non-ssl ports - The function then adds the directive - - .. note:: This function saves the configuration - - :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` - - :param unused_options: Not currently used - :type unused_options: Not Available - - :raises .errors.PluginError: If no viable HTTP host can be created or - used for the redirect. - - """ - if "rewrite_module" not in self.parser.modules: - self.enable_mod("rewrite") - general_vh = self._get_http_vhost(ssl_vhost) - - if general_vh is None: - # Add virtual_server with redirect - logger.debug("Did not find http version of ssl virtual host " - "attempting to create") - redirect_addrs = self._get_proposed_addrs(ssl_vhost) - for vhost in self.vhosts: - if vhost.enabled and vhost.conflicts(redirect_addrs): - raise errors.PluginError( - "Unable to find corresponding HTTP vhost; " - "Unable to create one as intended addresses conflict; " - "Current configuration does not support automated " - "redirection") - self._create_redirect_vhost(ssl_vhost) - else: - if general_vh in self._enhanced_vhosts["redirect"]: - logger.debug("Already enabled redirect for this vhost") - return - - # Check if Certbot redirection already exists - self._verify_no_certbot_redirect(general_vh) - - # Note: if code flow gets here it means we didn't find the exact - # certbot RewriteRule config for redirection. Finding - # another RewriteRule is likely to be fine in most or all cases, - # but redirect loops are possible in very obscure cases; see #1620 - # for reasoning. - if self._is_rewrite_exists(general_vh): - logger.warning("Added an HTTP->HTTPS rewrite in addition to " - "other RewriteRules; you may wish to check for " - "overall consistency.") - - # Add directives to server - # Note: These are not immediately searchable in sites-enabled - # even with save() and load() - if not self._is_rewrite_engine_on(general_vh): - self.parser.add_dir(general_vh.path, "RewriteEngine", "on") - - names = ssl_vhost.get_names() - for idx, name in enumerate(names): - args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"] - if idx == len(names) - 1: - args.pop() - self.parser.add_dir(general_vh.path, "RewriteCond", args) - - self._set_https_redirection_rewrite_rule(general_vh) - - self.save_notes += ("Redirecting host in %s to ssl vhost in %s\n" % - (general_vh.filep, ssl_vhost.filep)) - self.save() - - self._enhanced_vhosts["redirect"].add(general_vh) - logger.info("Redirecting vhost in %s to ssl vhost in %s", - general_vh.filep, ssl_vhost.filep) - - def _set_https_redirection_rewrite_rule(self, vhost): - if self.get_version() >= (2, 3, 9): - self.parser.add_dir(vhost.path, "RewriteRule", - constants.REWRITE_HTTPS_ARGS_WITH_END) - else: - self.parser.add_dir(vhost.path, "RewriteRule", - constants.REWRITE_HTTPS_ARGS) - - def _verify_no_certbot_redirect(self, vhost): - """Checks to see if a redirect was already installed by certbot. - - Checks to see if virtualhost already contains a rewrite rule that is - identical to Certbot's redirection rewrite rule. - - For graceful transition to new rewrite rules for HTTPS redireciton we - delete certbot's old rewrite rules and set the new one instead. - - :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :raises errors.PluginEnhancementAlreadyPresent: When the exact - certbot redirection WriteRule exists in virtual host. - """ - rewrite_path = self.parser.find_dir( - "RewriteRule", None, start=vhost.path) - - # There can be other RewriteRule directive lines in vhost config. - # rewrite_args_dict keys are directive ids and the corresponding value - # for each is a list of arguments to that directive. - rewrite_args_dict = defaultdict(list) # type: DefaultDict[str, List[str]] - pat = r'(.*directive\[\d+\]).*' - for match in rewrite_path: - m = re.match(pat, match) - if m: - dir_path = m.group(1) - rewrite_args_dict[dir_path].append(match) - - if rewrite_args_dict: - redirect_args = [constants.REWRITE_HTTPS_ARGS, - constants.REWRITE_HTTPS_ARGS_WITH_END] - - for dir_path, args_paths in rewrite_args_dict.items(): - arg_vals = [self.parser.aug.get(x) for x in args_paths] - - # Search for past redirection rule, delete it, set the new one - if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS: - self.parser.aug.remove(dir_path) - self._set_https_redirection_rewrite_rule(vhost) - self.save() - raise errors.PluginEnhancementAlreadyPresent( - "Certbot has already enabled redirection") - - if arg_vals in redirect_args: - raise errors.PluginEnhancementAlreadyPresent( - "Certbot has already enabled redirection") - - def _is_rewrite_exists(self, vhost): - """Checks if there exists a RewriteRule directive in vhost - - :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :returns: True if a RewriteRule directive exists. - :rtype: bool - - """ - rewrite_path = self.parser.find_dir( - "RewriteRule", None, start=vhost.path) - return bool(rewrite_path) - - def _is_rewrite_engine_on(self, vhost): - """Checks if a RewriteEngine directive is on - - :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - """ - rewrite_engine_path_list = self.parser.find_dir("RewriteEngine", "on", - start=vhost.path) - if rewrite_engine_path_list: - for re_path in rewrite_engine_path_list: - # A RewriteEngine directive may also be included in per - # directory .htaccess files. We only care about the VirtualHost. - if 'virtualhost' in re_path.lower(): - return self.parser.get_arg(re_path) - return False - - def _create_redirect_vhost(self, ssl_vhost): - """Creates an http_vhost specifically to redirect for the ssl_vhost. - - :param ssl_vhost: ssl vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` - - :returns: tuple of the form - (`success`, :class:`~certbot_apache.obj.VirtualHost`) - :rtype: tuple - - """ - text = self._get_redirect_config_str(ssl_vhost) - - redirect_filepath = self._write_out_redirect(ssl_vhost, text) - - self.parser.aug.load() - # Make a new vhost data structure and add it to the lists - new_vhost = self._create_vhost(parser.get_aug_path(self._escape(redirect_filepath))) - self.vhosts.append(new_vhost) - self._enhanced_vhosts["redirect"].add(new_vhost) - - # Finally create documentation for the change - self.save_notes += ("Created a port 80 vhost, %s, for redirection to " - "ssl vhost %s\n" % - (new_vhost.filep, ssl_vhost.filep)) - - def _get_redirect_config_str(self, ssl_vhost): - # get servernames and serveraliases - serveralias = "" - servername = "" - - if ssl_vhost.name is not None: - servername = "ServerName " + ssl_vhost.name - if ssl_vhost.aliases: - serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases) - - rewrite_rule_args = [] # type: List[str] - if self.get_version() >= (2, 3, 9): - rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END - else: - rewrite_rule_args = constants.REWRITE_HTTPS_ARGS - - return ("\n" - "%s \n" - "%s \n" - "ServerSignature Off\n" - "\n" - "RewriteEngine On\n" - "RewriteRule %s\n" - "\n" - "ErrorLog %s/redirect.error.log\n" - "LogLevel warn\n" - "\n" - % (" ".join(str(addr) for - addr in self._get_proposed_addrs(ssl_vhost)), - servername, serveralias, - " ".join(rewrite_rule_args), - self.option("logs_root"))) - - def _write_out_redirect(self, ssl_vhost, text): - # This is the default name - redirect_filename = "le-redirect.conf" - - # See if a more appropriate name can be applied - if ssl_vhost.name is not None: - # make sure servername doesn't exceed filename length restriction - if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)): - redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name - - redirect_filepath = os.path.join(self.option("vhost_root"), - redirect_filename) - - # Register the new file that will be created - # Note: always register the creation before writing to ensure file will - # be removed in case of unexpected program exit - self.reverter.register_file_creation(False, redirect_filepath) - - # Write out file - with open(redirect_filepath, "w") as redirect_file: - redirect_file.write(text) - - # Add new include to configuration if it doesn't exist yet - if not self.parser.parsed_in_current(redirect_filepath): - self.parser.parse_file(redirect_filepath) - - logger.info("Created redirect file: %s", redirect_filename) - - return redirect_filepath - - def _get_http_vhost(self, ssl_vhost): - """Find appropriate HTTP vhost for ssl_vhost.""" - # First candidate vhosts filter - if ssl_vhost.ancestor: - return ssl_vhost.ancestor - candidate_http_vhs = [ - vhost for vhost in self.vhosts if not vhost.ssl - ] - - # Second filter - check addresses - for http_vh in candidate_http_vhs: - if http_vh.same_server(ssl_vhost): - return http_vh - # Third filter - if none with same names, return generic - for http_vh in candidate_http_vhs: - if http_vh.same_server(ssl_vhost, generic=True): - return http_vh - - return None - - def _get_proposed_addrs(self, vhost, port="80"): - """Return all addrs of vhost with the port replaced with the specified. - - :param obj.VirtualHost ssl_vhost: Original Vhost - :param str port: Desired port for new addresses - - :returns: `set` of :class:`~obj.Addr` - - """ - redirects = set() - for addr in vhost.addrs: - redirects.add(addr.get_addr_obj(port)) - - return redirects - - def enable_site(self, vhost): - """Enables an available site, Apache reload required. - - .. note:: Does not make sure that the site correctly works or that all - modules are enabled appropriately. - .. note:: The distribution specific override replaces functionality - of this method where available. - - :param vhost: vhost to enable - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :raises .errors.NotSupportedError: If filesystem layout is not - supported. - - """ - if vhost.enabled: - return - - if not self.parser.parsed_in_original(vhost.filep): - # Add direct include to root conf - logger.info("Enabling site %s by adding Include to root configuration", - vhost.filep) - self.save_notes += "Enabled site %s\n" % vhost.filep - self.parser.add_include(self.parser.loc["default"], vhost.filep) - vhost.enabled = True - return - - def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument - """Enables module in Apache. - - Both enables and reloads Apache so module is active. - - :param str mod_name: Name of the module to enable. (e.g. 'ssl') - :param bool temp: Whether or not this is a temporary action. - - .. note:: The distribution specific override replaces functionality - of this method where available. - - :raises .errors.MisconfigurationError: We cannot enable modules in - generic fashion. - - """ - mod_message = ("Apache needs to have module \"{0}\" active for the " + - "requested installation options. Unfortunately Certbot is unable " + - "to install or enable it for you. Please install the module, and " + - "run Certbot again.") - raise errors.MisconfigurationError(mod_message.format(mod_name)) - - def restart(self): - """Runs a config test and reloads the Apache server. - - :raises .errors.MisconfigurationError: If either the config test - or reload fails. - - """ - self.config_test() - self._reload() - - def _reload(self): - """Reloads the Apache server. - - :raises .errors.MisconfigurationError: If reload fails - - """ - try: - util.run_script(self.option("restart_cmd")) - except errors.SubprocessError as err: - logger.info("Unable to restart apache using %s", - self.option("restart_cmd")) - alt_restart = self.option("restart_cmd_alt") - if alt_restart: - logger.debug("Trying alternative restart command: %s", - alt_restart) - # There is an alternative restart command available - # This usually is "restart" verb while original is "graceful" - try: - util.run_script(self.option( - "restart_cmd_alt")) - return - except errors.SubprocessError as secerr: - error = str(secerr) - else: - error = str(err) - raise errors.MisconfigurationError(error) - - def config_test(self): # pylint: disable=no-self-use - """Check the configuration of Apache for errors. - - :raises .errors.MisconfigurationError: If config_test fails - - """ - try: - util.run_script(self.option("conftest_cmd")) - except errors.SubprocessError as err: - raise errors.MisconfigurationError(str(err)) - - def get_version(self): - """Return version of Apache Server. - - Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) - - :returns: version - :rtype: tuple - - :raises .PluginError: if unable to find Apache version - - """ - try: - stdout, _ = util.run_script(self.option("version_cmd")) - except errors.SubprocessError: - raise errors.PluginError( - "Unable to run %s -v" % - self.option("version_cmd")) - - regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE) - matches = regex.findall(stdout) - - if len(matches) != 1: - raise errors.PluginError("Unable to find Apache version") - - return tuple([int(i) for i in matches[0].split(".")]) - - def more_info(self): - """Human-readable string to help understand the module""" - return ( - "Configures Apache to authenticate and install HTTPS.{0}" - "Server root: {root}{0}" - "Version: {version}".format( - os.linesep, root=self.parser.loc["root"], - version=".".join(str(i) for i in self.version)) - ) - - ########################################################################### - # Challenges Section - ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use - """Return list of challenge preferences.""" - return [challenges.HTTP01] - - def perform(self, achalls): - """Perform the configuration related challenge. - - This function currently assumes all challenges will be fulfilled. - If this turns out not to be the case in the future. Cleanup and - outstanding challenges will have to be designed better. - - """ - self._chall_out.update(achalls) - responses = [None] * len(achalls) - http_doer = http_01.ApacheHttp01(self) - - for i, achall in enumerate(achalls): - # Currently also have chall_doer hold associated index of the - # challenge. This helps to put all of the responses back together - # when they are all complete. - http_doer.add_chall(achall, i) - - http_response = http_doer.perform() - if http_response: - # Must reload in order to activate the challenges. - # Handled here because we may be able to load up other challenge - # types - self.restart() - - # TODO: Remove this dirty hack. We need to determine a reliable way - # of identifying when the new configuration is being used. - time.sleep(3) - - self._update_responses(responses, http_response, http_doer) - - return responses - - def _update_responses(self, responses, chall_response, chall_doer): - # Go through all of the challenges and assign them to the proper - # place in the responses return value. All responses must be in the - # same order as the original challenges. - for i, resp in enumerate(chall_response): - responses[chall_doer.indices[i]] = resp - - def cleanup(self, achalls): - """Revert all challenges.""" - self._chall_out.difference_update(achalls) - - # If all of the challenges have been finished, clean up everything - if not self._chall_out: - self.revert_challenge_config() - self.restart() - self.parser.reset_modules() - - def install_ssl_options_conf(self, options_ssl, options_ssl_digest): - """Copy Certbot's SSL options file into the system's config dir if required.""" - - # XXX if we ever try to enforce a local privilege boundary (eg, running - # certbot for unprivileged users via setuid), this function will need - # to be modified. - return common.install_version_controlled_file(options_ssl, options_ssl_digest, - self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES) - - def enable_autohsts(self, _unused_lineage, domains): - """ - Enable the AutoHSTS enhancement for defined domains - - :param _unused_lineage: Certificate lineage object, unused - :type _unused_lineage: certbot._internal.storage.RenewableCert - - :param domains: List of domains in certificate to enhance - :type domains: str - """ - - self._autohsts_fetch_state() - _enhanced_vhosts = [] - for d in domains: - matched_vhosts = self.choose_vhosts(d, create_if_no_ssl=False) - # We should be handling only SSL vhosts for AutoHSTS - vhosts = [vhost for vhost in matched_vhosts if vhost.ssl] - - if not vhosts: - msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a " - "domain {0} for enabling AutoHSTS enhancement.") - msg = msg_tmpl.format(d) - logger.warning(msg) - raise errors.PluginError(msg) - for vh in vhosts: - try: - self._enable_autohsts_domain(vh) - _enhanced_vhosts.append(vh) - except errors.PluginEnhancementAlreadyPresent: - if vh in _enhanced_vhosts: - continue - msg = ("VirtualHost for domain {0} in file {1} has a " + - "String-Transport-Security header present, exiting.") - raise errors.PluginEnhancementAlreadyPresent( - msg.format(d, vh.filep)) - if _enhanced_vhosts: - note_msg = "Enabling AutoHSTS" - self.save(note_msg) - logger.info(note_msg) - self.restart() - - # Save the current state to pluginstorage - self._autohsts_save_state() - - def _enable_autohsts_domain(self, ssl_vhost): - """Do the initial AutoHSTS deployment to a vhost - - :param ssl_vhost: The VirtualHost object to deploy the AutoHSTS - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` or None - - :raises errors.PluginEnhancementAlreadyPresent: When already enhanced - - """ - # This raises the exception - self._verify_no_matching_http_header(ssl_vhost, - "Strict-Transport-Security") - - if "headers_module" not in self.parser.modules: - self.enable_mod("headers") - # Prepare the HSTS header value - hsts_header = constants.HEADER_ARGS["Strict-Transport-Security"][:-1] - initial_maxage = constants.AUTOHSTS_STEPS[0] - hsts_header.append("\"max-age={0}\"".format(initial_maxage)) - - # Add ID to the VirtualHost for mapping back to it later - uniq_id = self.add_vhost_id(ssl_vhost) - self.save_notes += "Adding unique ID {0} to VirtualHost in {1}\n".format( - uniq_id, ssl_vhost.filep) - # Add the actual HSTS header - self.parser.add_dir(ssl_vhost.path, "Header", hsts_header) - note_msg = ("Adding gradually increasing HSTS header with initial value " - "of {0} to VirtualHost in {1}\n".format( - initial_maxage, ssl_vhost.filep)) - self.save_notes += note_msg - - # Save the current state to pluginstorage - self._autohsts[uniq_id] = {"laststep": 0, "timestamp": time.time()} - - def update_autohsts(self, _unused_domain): - """ - Increase the AutoHSTS values of VirtualHosts that the user has enabled - this enhancement for. - - :param _unused_domain: Not currently used - :type _unused_domain: Not Available - - """ - self._autohsts_fetch_state() - if not self._autohsts: - # No AutoHSTS enabled for any domain - return - curtime = time.time() - save_and_restart = False - for id_str, config in list(self._autohsts.items()): - if config["timestamp"] + constants.AUTOHSTS_FREQ > curtime: - # Skip if last increase was < AUTOHSTS_FREQ ago - continue - nextstep = config["laststep"] + 1 - if nextstep < len(constants.AUTOHSTS_STEPS): - # If installer hasn't been prepared yet, do it now - if not self._prepared: - self.prepare() - # Have not reached the max value yet - try: - vhost = self.find_vhost_by_id(id_str) - except errors.PluginError: - msg = ("Could not find VirtualHost with ID {0}, disabling " - "AutoHSTS for this VirtualHost").format(id_str) - logger.warning(msg) - # Remove the orphaned AutoHSTS entry from pluginstorage - self._autohsts.pop(id_str) - continue - self._autohsts_increase(vhost, id_str, nextstep) - msg = ("Increasing HSTS max-age value for VirtualHost with id " - "{0}").format(id_str) - self.save_notes += msg - save_and_restart = True - - if save_and_restart: - self.save("Increased HSTS max-age values") - self.restart() - - self._autohsts_save_state() - - def deploy_autohsts(self, lineage): - """ - Checks if autohsts vhost has reached maximum auto-increased value - and changes the HSTS max-age to a high value. - - :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert - """ - self._autohsts_fetch_state() - if not self._autohsts: - # No autohsts enabled for any vhost - return - - vhosts = [] - affected_ids = [] - # Copy, as we are removing from the dict inside the loop - for id_str, config in list(self._autohsts.items()): - if config["laststep"]+1 >= len(constants.AUTOHSTS_STEPS): - # max value reached, try to make permanent - try: - vhost = self.find_vhost_by_id(id_str) - except errors.PluginError: - msg = ("VirtualHost with id {} was not found, unable to " - "make HSTS max-age permanent.").format(id_str) - logger.warning(msg) - self._autohsts.pop(id_str) - continue - if self._autohsts_vhost_in_lineage(vhost, lineage): - vhosts.append(vhost) - affected_ids.append(id_str) - - save_and_restart = False - for vhost in vhosts: - self._autohsts_write(vhost, constants.AUTOHSTS_PERMANENT) - msg = ("Strict-Transport-Security max-age value for " - "VirtualHost in {0} was made permanent.").format(vhost.filep) - logger.debug(msg) - self.save_notes += msg+"\n" - save_and_restart = True - - if save_and_restart: - self.save("Made HSTS max-age permanent") - self.restart() - - for id_str in affected_ids: - self._autohsts.pop(id_str) - - # Update AutoHSTS storage (We potentially removed vhosts from managed) - self._autohsts_save_state() - - -AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member diff --git a/certbot-apache/certbot_apache/constants.py b/certbot-apache/certbot_apache/constants.py deleted file mode 100644 index f8184a42f..000000000 --- a/certbot-apache/certbot_apache/constants.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Apache plugin constants.""" -import pkg_resources - - -MOD_SSL_CONF_DEST = "options-ssl-apache.conf" -"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" - - -UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt" -"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" - -# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING! -ALL_SSL_OPTIONS_HASHES = [ - '2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a', - '4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a', - '5a922826719981c0a234b1fbcd495f3213e49d2519e845ea0748ba513044b65b', - '4066b90268c03c9ba0201068eaa39abbc02acf9558bb45a788b630eb85dadf27', - 'f175e2e7c673bd88d0aff8220735f385f916142c44aa83b09f1df88dd4767a88', - 'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b', - '80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791', - 'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082', - '717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20', - '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', - '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', - '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', -] -"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" - -AUGEAS_LENS_DIR = pkg_resources.resource_filename( - "certbot_apache", "augeas_lens") -"""Path to the Augeas lens directory""" - -REWRITE_HTTPS_ARGS = [ - "^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,NE,R=permanent]"] -"""Apache version<2.3.9 rewrite rule arguments used for redirections to -https vhost""" - -REWRITE_HTTPS_ARGS_WITH_END = [ - "^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,NE,R=permanent]"] -"""Apache version >= 2.3.9 rewrite rule arguments used for redirections to - https vhost""" - -OLD_REWRITE_HTTPS_ARGS = [ - ["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"], - ["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]] - -HSTS_ARGS = ["always", "set", "Strict-Transport-Security", - "\"max-age=31536000\""] -"""Apache header arguments for HSTS""" - -UIR_ARGS = ["always", "set", "Content-Security-Policy", - "upgrade-insecure-requests"] - -HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS, - "Upgrade-Insecure-Requests": UIR_ARGS} - -AUTOHSTS_STEPS = [60, 300, 900, 3600, 21600, 43200, 86400] -"""AutoHSTS increase steps: 1min, 5min, 15min, 1h, 6h, 12h, 24h""" - -AUTOHSTS_PERMANENT = 31536000 -"""Value for the last max-age of HSTS""" - -AUTOHSTS_FREQ = 172800 -"""Minimum time since last increase to perform a new one: 48h""" - -MANAGED_COMMENT = "DO NOT REMOVE - Managed by Certbot" -MANAGED_COMMENT_ID = MANAGED_COMMENT+", VirtualHost id: {0}" -"""Managed by Certbot comments and the VirtualHost identification template""" diff --git a/certbot-apache/certbot_apache/display_ops.py b/certbot-apache/certbot_apache/display_ops.py deleted file mode 100644 index 2639eabd1..000000000 --- a/certbot-apache/certbot_apache/display_ops.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Contains UI methods for Apache operations.""" -import logging - -import zope.component - -import certbot.display.util as display_util -from certbot import errors -from certbot import interfaces -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -def select_vhost_multiple(vhosts): - """Select multiple Vhosts to install the certificate for - - :param vhosts: Available Apache VirtualHosts - :type vhosts: :class:`list` of type `~obj.Vhost` - - :returns: List of VirtualHosts - :rtype: :class:`list`of type `~obj.Vhost` - """ - if not vhosts: - return list() - tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] - # Remove the extra newline from the last entry - if tags_list: - tags_list[-1] = tags_list[-1][:-1] - code, names = zope.component.getUtility(interfaces.IDisplay).checklist( - "Which VirtualHosts would you like to install the wildcard certificate for?", - tags=tags_list, force_interactive=True) - if code == display_util.OK: - return_vhosts = _reversemap_vhosts(names, vhosts) - return return_vhosts - return [] - -def _reversemap_vhosts(names, vhosts): - """Helper function for select_vhost_multiple for mapping string - representations back to actual vhost objects""" - return_vhosts = list() - - for selection in names: - for vhost in vhosts: - if vhost.display_repr().strip() == selection.strip(): - return_vhosts.append(vhost) - return return_vhosts - -def select_vhost(domain, vhosts): - """Select an appropriate Apache Vhost. - - :param vhosts: Available Apache VirtualHosts - :type vhosts: :class:`list` of type `~obj.Vhost` - - :returns: VirtualHost or `None` - :rtype: `~obj.Vhost` or `None` - - """ - if not vhosts: - return None - code, tag = _vhost_menu(domain, vhosts) - if code == display_util.OK: - return vhosts[tag] - return None - -def _vhost_menu(domain, vhosts): - """Select an appropriate Apache Vhost. - - :param vhosts: Available Apache Virtual Hosts - :type vhosts: :class:`list` of type `~obj.Vhost` - - :returns: Display tuple - ('code', tag') - :rtype: `tuple` - - """ - # Free characters in the line of display text (9 is for ' | ' formatting) - free_chars = display_util.WIDTH - len("HTTPS") - len("Enabled") - 9 - - if free_chars < 2: - logger.debug("Display size is too small for " - "certbot_apache.display_ops._vhost_menu()") - # This runs the edge off the screen, but it doesn't cause an "error" - filename_size = 1 - disp_name_size = 1 - else: - # Filename is a bit more important and probably longer with 000-* - filename_size = int(free_chars * .6) - disp_name_size = free_chars - filename_size - - choices = [] - for vhost in vhosts: - if len(vhost.get_names()) == 1: - disp_name = next(iter(vhost.get_names())) - elif not vhost.get_names(): - disp_name = "" - else: - disp_name = "Multiple Names" - - choices.append( - "{fn:{fn_size}s} | {name:{name_size}s} | {https:5s} | " - "{active:7s}".format( - fn=os.path.basename(vhost.filep)[:filename_size], - name=disp_name[:disp_name_size], - https="HTTPS" if vhost.ssl else "", - active="Enabled" if vhost.enabled else "", - fn_size=filename_size, - name_size=disp_name_size) - ) - - try: - code, tag = zope.component.getUtility(interfaces.IDisplay).menu( - "We were unable to find a vhost with a ServerName " - "or Address of {0}.{1}Which virtual host would you " - "like to choose?".format(domain, os.linesep), - choices, force_interactive=True) - except errors.MissingCommandlineFlag: - msg = ( - "Encountered vhost ambiguity when trying to find a vhost for " - "{0} but was unable to ask for user " - "guidance in non-interactive mode. Certbot may need " - "vhosts to be explicitly labelled with ServerName or " - "ServerAlias directives.".format(domain)) - logger.warning(msg) - raise errors.MissingCommandlineFlag(msg) - - return code, tag diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/entrypoint.py deleted file mode 100644 index 9e04ff889..000000000 --- a/certbot-apache/certbot_apache/entrypoint.py +++ /dev/null @@ -1,69 +0,0 @@ -""" Entry point for Apache Plugin """ -# Pylint does not like disutils.version when running inside a venv. -# See: https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error - -from certbot import util - -from certbot_apache import configurator -from certbot_apache import override_arch -from certbot_apache import override_fedora -from certbot_apache import override_darwin -from certbot_apache import override_debian -from certbot_apache import override_centos -from certbot_apache import override_gentoo -from certbot_apache import override_suse - -OVERRIDE_CLASSES = { - "arch": override_arch.ArchConfigurator, - "cloudlinux": override_centos.CentOSConfigurator, - "darwin": override_darwin.DarwinConfigurator, - "debian": override_debian.DebianConfigurator, - "ubuntu": override_debian.DebianConfigurator, - "centos": override_centos.CentOSConfigurator, - "centos linux": override_centos.CentOSConfigurator, - "fedora_old": override_centos.CentOSConfigurator, - "fedora": override_fedora.FedoraConfigurator, - "linuxmint": override_debian.DebianConfigurator, - "ol": override_centos.CentOSConfigurator, - "oracle": override_centos.CentOSConfigurator, - "redhatenterpriseserver": override_centos.CentOSConfigurator, - "red hat enterprise linux server": override_centos.CentOSConfigurator, - "rhel": override_centos.CentOSConfigurator, - "amazon": override_centos.CentOSConfigurator, - "gentoo": override_gentoo.GentooConfigurator, - "gentoo base system": override_gentoo.GentooConfigurator, - "opensuse": override_suse.OpenSUSEConfigurator, - "suse": override_suse.OpenSUSEConfigurator, - "sles": override_suse.OpenSUSEConfigurator, - "scientific": override_centos.CentOSConfigurator, - "scientific linux": override_centos.CentOSConfigurator, -} - - -def get_configurator(): - """ Get correct configurator class based on the OS fingerprint """ - os_name, os_version = util.get_os_info() - os_name = os_name.lower() - override_class = None - - # Special case for older Fedora versions - if os_name == 'fedora' and LooseVersion(os_version) < LooseVersion('29'): - os_name = 'fedora_old' - - try: - override_class = OVERRIDE_CLASSES[os_name] - except KeyError: - # OS not found in the list - os_like = util.get_systemd_os_like() - if os_like: - for os_name in os_like: - if os_name in OVERRIDE_CLASSES.keys(): - override_class = OVERRIDE_CLASSES[os_name] - if not override_class: - # No override class found, return the generic configurator - override_class = configurator.ApacheConfigurator - return override_class - - -ENTRYPOINT = get_configurator() diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/http_01.py deleted file mode 100644 index de22edd85..000000000 --- a/certbot-apache/certbot_apache/http_01.py +++ /dev/null @@ -1,210 +0,0 @@ -"""A class that performs HTTP-01 challenges for Apache""" -import logging - -from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem -from certbot.plugins import common - -from certbot_apache.obj import VirtualHost # pylint: disable=unused-import -from certbot_apache.parser import get_aug_path - -logger = logging.getLogger(__name__) - - -class ApacheHttp01(common.ChallengePerformer): - """Class that performs HTTP-01 challenges within the Apache configurator.""" - - CONFIG_TEMPLATE22_PRE = """\ - RewriteEngine on - RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [L] - - """ - CONFIG_TEMPLATE22_POST = """\ - - Order Allow,Deny - Allow from all - - - Order Allow,Deny - Allow from all - - """ - - CONFIG_TEMPLATE24_PRE = """\ - RewriteEngine on - RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [END] - """ - CONFIG_TEMPLATE24_POST = """\ - - Require all granted - - - Require all granted - - """ - - def __init__(self, *args, **kwargs): - super(ApacheHttp01, self).__init__(*args, **kwargs) - self.challenge_conf_pre = os.path.join( - self.configurator.conf("challenge-location"), - "le_http_01_challenge_pre.conf") - self.challenge_conf_post = os.path.join( - self.configurator.conf("challenge-location"), - "le_http_01_challenge_post.conf") - self.challenge_dir = os.path.join( - self.configurator.config.work_dir, - "http_challenges") - self.moded_vhosts = set() # type: Set[VirtualHost] - - def perform(self): - """Perform all HTTP-01 challenges.""" - if not self.achalls: - return [] - # Save any changes to the configuration as a precaution - # About to make temporary changes to the config - self.configurator.save("Changes before challenge setup", True) - - self.configurator.ensure_listen(str( - self.configurator.config.http01_port)) - self.prepare_http01_modules() - - responses = self._set_up_challenges() - - self._mod_config() - # Save reversible changes - self.configurator.save("HTTP Challenge", True) - - return responses - - def prepare_http01_modules(self): - """Make sure that we have the needed modules available for http01""" - - if self.configurator.conf("handle-modules"): - needed_modules = ["rewrite"] - if self.configurator.version < (2, 4): - needed_modules.append("authz_host") - else: - needed_modules.append("authz_core") - for mod in needed_modules: - if mod + "_module" not in self.configurator.parser.modules: - self.configurator.enable_mod(mod, temp=True) - - def _mod_config(self): - selected_vhosts = [] # type: List[VirtualHost] - http_port = str(self.configurator.config.http01_port) - for chall in self.achalls: - # Search for matching VirtualHosts - for vh in self._matching_vhosts(chall.domain): - selected_vhosts.append(vh) - - # Ensure that we have one or more VirtualHosts that we can continue - # with. (one that listens to port configured with --http-01-port) - found = False - for vhost in selected_vhosts: - if any(a.is_wildcard() or a.get_port() == http_port for a in vhost.addrs): - found = True - - if not found: - for vh in self._relevant_vhosts(): - selected_vhosts.append(vh) - - # Add the challenge configuration - for vh in selected_vhosts: - self._set_up_include_directives(vh) - - self.configurator.reverter.register_file_creation( - True, self.challenge_conf_pre) - self.configurator.reverter.register_file_creation( - True, self.challenge_conf_post) - - if self.configurator.version < (2, 4): - config_template_pre = self.CONFIG_TEMPLATE22_PRE - config_template_post = self.CONFIG_TEMPLATE22_POST - else: - config_template_pre = self.CONFIG_TEMPLATE24_PRE - config_template_post = self.CONFIG_TEMPLATE24_POST - - config_text_pre = config_template_pre.format(self.challenge_dir) - config_text_post = config_template_post.format(self.challenge_dir) - - logger.debug("writing a pre config file with text:\n %s", config_text_pre) - with open(self.challenge_conf_pre, "w") as new_conf: - new_conf.write(config_text_pre) - logger.debug("writing a post config file with text:\n %s", config_text_post) - with open(self.challenge_conf_post, "w") as new_conf: - new_conf.write(config_text_post) - - def _matching_vhosts(self, domain): - """Return all VirtualHost objects that have the requested domain name or - a wildcard name that would match the domain in ServerName or ServerAlias - directive. - """ - matching_vhosts = [] - for vhost in self.configurator.vhosts: - if self.configurator.domain_in_names(vhost.get_names(), domain): - # domain_in_names also matches the exact names, so no need - # to check "domain in vhost.get_names()" explicitly here - matching_vhosts.append(vhost) - - return matching_vhosts - - def _relevant_vhosts(self): - http01_port = str(self.configurator.config.http01_port) - relevant_vhosts = [] - for vhost in self.configurator.vhosts: - if any(a.is_wildcard() or a.get_port() == http01_port for a in vhost.addrs): - if not vhost.ssl: - relevant_vhosts.append(vhost) - if not relevant_vhosts: - raise errors.PluginError( - "Unable to find a virtual host listening on port {0} which is" - " currently needed for Certbot to prove to the CA that you" - " control your domain. Please add a virtual host for port" - " {0}.".format(http01_port)) - - return relevant_vhosts - - def _set_up_challenges(self): - if not os.path.isdir(self.challenge_dir): - filesystem.makedirs(self.challenge_dir, 0o755) - - responses = [] - for achall in self.achalls: - responses.append(self._set_up_challenge(achall)) - - return responses - - def _set_up_challenge(self, achall): - response, validation = achall.response_and_validation() - - name = os.path.join(self.challenge_dir, achall.chall.encode("token")) - - self.configurator.reverter.register_file_creation(True, name) - with open(name, 'wb') as f: - f.write(validation.encode()) - filesystem.chmod(name, 0o644) - - return response - - def _set_up_include_directives(self, vhost): - """Includes override configuration to the beginning and to the end of - VirtualHost. Note that this include isn't added to Augeas search tree""" - - if vhost not in self.moded_vhosts: - logger.debug( - "Adding a temporary challenge validation Include for name: %s " + - "in: %s", vhost.name, vhost.filep) - self.configurator.parser.add_dir_beginning( - vhost.path, "Include", self.challenge_conf_pre) - self.configurator.parser.add_dir( - vhost.path, "Include", self.challenge_conf_post) - - if not vhost.enabled: - self.configurator.parser.add_dir( - get_aug_path(self.configurator.parser.loc["default"]), - "Include", vhost.filep) - - self.moded_vhosts.add(vhost) diff --git a/certbot-apache/certbot_apache/obj.py b/certbot-apache/certbot_apache/obj.py deleted file mode 100644 index 9cea1380a..000000000 --- a/certbot-apache/certbot_apache/obj.py +++ /dev/null @@ -1,269 +0,0 @@ -"""Module contains classes used by the Apache Configurator.""" -import re - -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module -from certbot.plugins import common - - -class Addr(common.Addr): - """Represents an Apache address.""" - - def __eq__(self, other): - """This is defined as equivalent within Apache. - - ip_addr:* == ip_addr - - """ - if isinstance(other, self.__class__): - return ((self.tup == other.tup) or - (self.tup[0] == other.tup[0] and - self.is_wildcard() and other.is_wildcard())) - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return "certbot_apache.obj.Addr(" + repr(self.tup) + ")" - - def __hash__(self): # pylint: disable=useless-super-delegation - # Python 3 requires explicit overridden for __hash__ if __eq__ or - # __cmp__ is overridden. See https://bugs.python.org/issue2235 - return super(Addr, self).__hash__() - - def _addr_less_specific(self, addr): - """Returns if addr.get_addr() is more specific than self.get_addr().""" - # pylint: disable=protected-access - return addr._rank_specific_addr() > self._rank_specific_addr() - - def _rank_specific_addr(self): - """Returns numerical rank for get_addr() - - :returns: 2 - FQ, 1 - wildcard, 0 - _default_ - :rtype: int - - """ - if self.get_addr() == "_default_": - return 0 - elif self.get_addr() == "*": - return 1 - return 2 - - def conflicts(self, addr): - r"""Returns if address could conflict with correct function of self. - - Could addr take away service provided by self within Apache? - - .. note::IP Address is more important than wildcard. - Connection from 127.0.0.1:80 with choices of *:80 and 127.0.0.1:* - chooses 127.0.0.1:\* - - .. todo:: Handle domain name addrs... - - Examples: - - ========================================= ===== - ``127.0.0.1:\*.conflicts(127.0.0.1:443)`` True - ``127.0.0.1:443.conflicts(127.0.0.1:\*)`` False - ``\*:443.conflicts(\*:80)`` False - ``_default_:443.conflicts(\*:443)`` True - ========================================= ===== - - """ - if self._addr_less_specific(addr): - return True - elif self.get_addr() == addr.get_addr(): - if self.is_wildcard() or self.get_port() == addr.get_port(): - return True - return False - - def is_wildcard(self): - """Returns if address has a wildcard port.""" - return self.tup[1] == "*" or not self.tup[1] - - def get_sni_addr(self, port): - """Returns the least specific address that resolves on the port. - - Examples: - - - ``1.2.3.4:443`` -> ``1.2.3.4:`` - - ``1.2.3.4:*`` -> ``1.2.3.4:*`` - - :param str port: Desired port - - """ - if self.is_wildcard(): - return self - - return self.get_addr_obj(port) - - -class VirtualHost(object): - """Represents an Apache Virtualhost. - - :ivar str filep: file path of VH - :ivar str path: Augeas path to virtual host - :ivar set addrs: Virtual Host addresses (:class:`set` of - :class:`common.Addr`) - :ivar str name: ServerName of VHost - :ivar list aliases: Server aliases of vhost - (:class:`list` of :class:`str`) - - :ivar bool ssl: SSLEngine on in vhost - :ivar bool enabled: Virtual host is enabled - :ivar bool modmacro: VirtualHost is using mod_macro - :ivar VirtualHost ancestor: A non-SSL VirtualHost this is based on - - https://httpd.apache.org/docs/2.4/vhosts/details.html - - .. todo:: Any vhost that includes the magic _default_ wildcard is given the - same ServerName as the main server. - - """ - # ?: is used for not returning enclosed characters - strip_name = re.compile(r"^(?:.+://)?([^ :$]*)") - - def __init__(self, filep, path, addrs, ssl, enabled, name=None, - aliases=None, modmacro=False, ancestor=None): - - """Initialize a VH.""" - self.filep = filep - self.path = path - self.addrs = addrs - self.name = name - self.aliases = aliases if aliases is not None else set() - self.ssl = ssl - self.enabled = enabled - self.modmacro = modmacro - self.ancestor = ancestor - - def get_names(self): - """Return a set of all names.""" - all_names = set() # type: Set[str] - all_names.update(self.aliases) - # Strip out any scheme:// and field from servername - if self.name is not None: - all_names.add(VirtualHost.strip_name.findall(self.name)[0]) - - return all_names - - def __str__(self): - return ( - "File: {filename}\n" - "Vhost path: {vhpath}\n" - "Addresses: {addrs}\n" - "Name: {name}\n" - "Aliases: {aliases}\n" - "TLS Enabled: {tls}\n" - "Site Enabled: {active}\n" - "mod_macro Vhost: {modmacro}".format( - filename=self.filep, - vhpath=self.path, - addrs=", ".join(str(addr) for addr in self.addrs), - name=self.name if self.name is not None else "", - aliases=", ".join(name for name in self.aliases), - tls="Yes" if self.ssl else "No", - active="Yes" if self.enabled else "No", - modmacro="Yes" if self.modmacro else "No")) - - def display_repr(self): - """Return a representation of VHost to be used in dialog""" - return ( - "File: {filename}\n" - "Addresses: {addrs}\n" - "Names: {names}\n" - "HTTPS: {https}\n".format( - filename=self.filep, - addrs=", ".join(str(addr) for addr in self.addrs), - names=", ".join(self.get_names()), - https="Yes" if self.ssl else "No")) - - - def __eq__(self, other): - if isinstance(other, self.__class__): - return (self.filep == other.filep and self.path == other.path and - self.addrs == other.addrs and - self.get_names() == other.get_names() and - self.ssl == other.ssl and - self.enabled == other.enabled and - self.modmacro == other.modmacro) - - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return hash((self.filep, self.path, - tuple(self.addrs), tuple(self.get_names()), - self.ssl, self.enabled, self.modmacro)) - - def conflicts(self, addrs): - """See if vhost conflicts with any of the addrs. - - This determines whether or not these addresses would/could overwrite - the vhost addresses. - - :param addrs: Iterable Addresses - :type addrs: Iterable :class:~obj.Addr - - :returns: If addresses conflicts with vhost - :rtype: bool - - """ - for pot_addr in addrs: - for addr in self.addrs: - if addr.conflicts(pot_addr): - return True - return False - - def same_server(self, vhost, generic=False): - """Determines if the vhost is the same 'server'. - - Used in redirection - indicates whether or not the two virtual hosts - serve on the exact same IP combinations, but different ports. - The generic flag indicates that that we're trying to match to a - default or generic vhost - - .. todo:: Handle _default_ - - """ - - if not generic: - if vhost.get_names() != self.get_names(): - return False - - # If equal and set is not empty... assume same server - if self.name is not None or self.aliases: - return True - # If we're looking for a generic vhost, - # don't return one with a ServerName - elif self.name: - return False - - # Both sets of names are empty. - - # Make conservative educated guess... this is very restrictive - # Consider adding more safety checks. - if len(vhost.addrs) != len(self.addrs): - return False - - # already_found acts to keep everything very conservative. - # Don't allow multiple ip:ports in same set. - already_found = set() # type: Set[str] - - for addr in vhost.addrs: - for local_addr in self.addrs: - if (local_addr.get_addr() == addr.get_addr() and - local_addr != addr and - local_addr.get_addr() not in already_found): - - # This intends to make sure we aren't double counting... - # e.g. 127.0.0.1:* - We require same number of addrs - # currently - already_found.add(local_addr.get_addr()) - break - else: - return False - - return True diff --git a/certbot-apache/certbot_apache/options-ssl-apache.conf b/certbot-apache/certbot_apache/options-ssl-apache.conf deleted file mode 100644 index 8113ee81e..000000000 --- a/certbot-apache/certbot_apache/options-ssl-apache.conf +++ /dev/null @@ -1,26 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on -SSLCompression off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/override_arch.py deleted file mode 100644 index c5620e9f9..000000000 --- a/certbot-apache/certbot_apache/override_arch.py +++ /dev/null @@ -1,31 +0,0 @@ -""" Distribution specific override class for Arch Linux """ -import pkg_resources - -import zope.interface - -from certbot import interfaces - -from certbot_apache import configurator - -@zope.interface.provider(interfaces.IPluginFactory) -class ArchConfigurator(configurator.ApacheConfigurator): - """Arch Linux specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/httpd", - vhost_root="/etc/httpd/conf", - vhost_files="*.conf", - logs_root="/var/log/httpd", - ctl="apachectl", - version_cmd=['apachectl', '-v'], - restart_cmd=['apachectl', 'graceful'], - conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/httpd/conf", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/override_centos.py deleted file mode 100644 index 7c7492dbf..000000000 --- a/certbot-apache/certbot_apache/override_centos.py +++ /dev/null @@ -1,217 +0,0 @@ -""" Distribution specific override class for CentOS family (RHEL, Fedora) """ -import logging - -import pkg_resources -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.errors import MisconfigurationError - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser - - -logger = logging.getLogger(__name__) - - -@zope.interface.provider(interfaces.IPluginFactory) -class CentOSConfigurator(configurator.ApacheConfigurator): - """CentOS specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/httpd", - vhost_root="/etc/httpd/conf.d", - vhost_files="*.conf", - logs_root="/var/log/httpd", - ctl="apachectl", - version_cmd=['apachectl', '-v'], - restart_cmd=['apachectl', 'graceful'], - restart_cmd_alt=['apachectl', 'restart'], - conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/httpd/conf.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "centos-options-ssl-apache.conf") - ) - - def config_test(self): - """ - Override config_test to mitigate configtest error in vanilla installation - of mod_ssl in Fedora. The error is caused by non-existent self-signed - certificates referenced by the configuration, that would be autogenerated - during the first (re)start of httpd. - """ - - os_info = util.get_os_info() - fedora = os_info[0].lower() == "fedora" - - try: - super(CentOSConfigurator, self).config_test() - except errors.MisconfigurationError: - if fedora: - self._try_restart_fedora() - else: - raise - - def _try_restart_fedora(self): - """ - Tries to restart httpd using systemctl to generate the self signed keypair. - """ - - try: - util.run_script(['systemctl', 'restart', 'httpd']) - except errors.SubprocessError as err: - raise errors.MisconfigurationError(str(err)) - - # Finish with actual config check to see if systemctl restart helped - super(CentOSConfigurator, self).config_test() - - def _prepare_options(self): - """ - Override the options dictionary initialization in order to support - alternative restart cmd used in CentOS. - """ - super(CentOSConfigurator, self)._prepare_options() - self.options["restart_cmd_alt"][0] = self.option("ctl") - - def get_parser(self): - """Initializes the ApacheParser""" - return CentOSParser( - self.option("server_root"), self.option("vhost_root"), - self.version, configurator=self) - - def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ - """ - Override _deploy_cert in order to ensure that the Apache configuration - has "LoadModule ssl_module..." before parsing the VirtualHost configuration - that was created by Certbot - """ - super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs) - if self.version < (2, 4, 0): - self._deploy_loadmodule_ssl_if_needed() - - def _deploy_loadmodule_ssl_if_needed(self): - """ - Add "LoadModule ssl_module " to main httpd.conf if - it doesn't exist there already. - """ - - loadmods = self.parser.find_dir("LoadModule", "ssl_module", exclude=False) - - correct_ifmods = [] # type: List[str] - loadmod_args = [] # type: List[str] - loadmod_paths = [] # type: List[str] - for m in loadmods: - noarg_path = m.rpartition("/")[0] - path_args = self.parser.get_all_args(noarg_path) - if loadmod_args: - if loadmod_args != path_args: - msg = ("Certbot encountered multiple LoadModule directives " - "for LoadModule ssl_module with differing library paths. " - "Please remove or comment out the one(s) that are not in " - "use, and run Certbot again.") - raise MisconfigurationError(msg) - else: - loadmod_args = path_args - - if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member - if self.parser.loc["default"] in noarg_path: - # LoadModule already in the main configuration file - if ("ifmodule/" in noarg_path.lower() or - "ifmodule[1]" in noarg_path.lower()): - # It's the first or only IfModule in the file - return - # Populate the list of known !mod_ssl.c IfModules - nodir_path = noarg_path.rpartition("/directive")[0] - correct_ifmods.append(nodir_path) - else: - loadmod_paths.append(noarg_path) - - if not loadmod_args: - # Do not try to enable mod_ssl - return - - # Force creation as the directive wasn't found from the beginning of - # httpd.conf - rootconf_ifmod = self.parser.create_ifmod( - parser.get_aug_path(self.parser.loc["default"]), - "!mod_ssl.c", beginning=True) - # parser.get_ifmod returns a path postfixed with "/", remove that - self.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", loadmod_args) - correct_ifmods.append(rootconf_ifmod[:-1]) - self.save_notes += "Added LoadModule ssl_module to main configuration.\n" - - # Wrap LoadModule mod_ssl inside of if it's not - # configured like this already. - for loadmod_path in loadmod_paths: - nodir_path = loadmod_path.split("/directive")[0] - # Remove the old LoadModule directive - self.parser.aug.remove(loadmod_path) - - # Create a new IfModule !mod_ssl.c if not already found on path - ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", - beginning=True)[:-1] - if ssl_ifmod not in correct_ifmods: - self.parser.add_dir(ssl_ifmod, "LoadModule", loadmod_args) - correct_ifmods.append(ssl_ifmod) - self.save_notes += ("Wrapped pre-existing LoadModule ssl_module " - "inside of block.\n") - - -class CentOSParser(parser.ApacheParser): - """CentOS specific ApacheParser override class""" - def __init__(self, *args, **kwargs): - # CentOS specific configuration file for Apache - self.sysconfig_filep = "/etc/sysconfig/httpd" - super(CentOSParser, self).__init__(*args, **kwargs) - - def update_runtime_variables(self): - """ Override for update_runtime_variables for custom parsing """ - # Opportunistic, works if SELinux not enforced - super(CentOSParser, self).update_runtime_variables() - self.parse_sysconfig_var() - - def parse_sysconfig_var(self): - """ Parses Apache CLI options from CentOS configuration file """ - defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") - for k in defines: - self.variables[k] = defines[k] - - def not_modssl_ifmodule(self, path): - """Checks if the provided Augeas path has argument !mod_ssl""" - - if "ifmodule" not in path.lower(): - return False - - # Trim the path to the last ifmodule - workpath = path.lower() - while workpath: - # Get path to the last IfModule (ignore the tail) - parts = workpath.rpartition("ifmodule") - - if not parts[0]: - # IfModule not found - break - ifmod_path = parts[0] + parts[1] - # Check if ifmodule had an index - if parts[2].startswith("["): - # Append the index from tail - ifmod_path += parts[2].partition("/")[0] - # Get the original path trimmed to correct length - # This is required to preserve cases - ifmod_real_path = path[0:len(ifmod_path)] - if "!mod_ssl.c" in self.get_all_args(ifmod_real_path): - return True - # Set the workpath to the heading part - workpath = parts[0] - - return False diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/override_darwin.py deleted file mode 100644 index 4e2a6acac..000000000 --- a/certbot-apache/certbot_apache/override_darwin.py +++ /dev/null @@ -1,31 +0,0 @@ -""" Distribution specific override class for macOS """ -import pkg_resources - -import zope.interface - -from certbot import interfaces - -from certbot_apache import configurator - -@zope.interface.provider(interfaces.IPluginFactory) -class DarwinConfigurator(configurator.ApacheConfigurator): - """macOS specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/other", - vhost_files="*.conf", - logs_root="/var/log/apache2", - ctl="apachectl", - version_cmd=['apachectl', '-v'], - restart_cmd=['apachectl', 'graceful'], - conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/apache2/other", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/override_debian.py deleted file mode 100644 index 58492bd01..000000000 --- a/certbot-apache/certbot_apache/override_debian.py +++ /dev/null @@ -1,146 +0,0 @@ -""" Distribution specific override class for Debian family (Ubuntu/Debian) """ -import logging - -import pkg_resources -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import filesystem -from certbot.compat import os - -from certbot_apache import apache_util -from certbot_apache import configurator - -logger = logging.getLogger(__name__) - - -@zope.interface.provider(interfaces.IPluginFactory) -class DebianConfigurator(configurator.ApacheConfigurator): - """Debian specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/sites-available", - vhost_files="*", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod="a2enmod", - dismod="a2dismod", - le_vhost_ext="-le-ssl.conf", - handle_modules=True, - handle_sites=True, - challenge_location="/etc/apache2", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) - - def enable_site(self, vhost): - """Enables an available site, Apache reload required. - - .. note:: Does not make sure that the site correctly works or that all - modules are enabled appropriately. - - :param vhost: vhost to enable - :type vhost: :class:`~certbot_apache.obj.VirtualHost` - - :raises .errors.NotSupportedError: If filesystem layout is not - supported. - - """ - if vhost.enabled: - return None - - enabled_path = ("%s/sites-enabled/%s" % - (self.parser.root, - os.path.basename(vhost.filep))) - if not os.path.isdir(os.path.dirname(enabled_path)): - # For some reason, sites-enabled / sites-available do not exist - # Call the parent method - return super(DebianConfigurator, self).enable_site(vhost) - self.reverter.register_file_creation(False, enabled_path) - try: - os.symlink(vhost.filep, enabled_path) - except OSError as err: - if os.path.islink(enabled_path) and filesystem.realpath( - enabled_path) == vhost.filep: - # Already in shape - vhost.enabled = True - return None - else: - logger.warning( - "Could not symlink %s to %s, got error: %s", enabled_path, - vhost.filep, err.strerror) - errstring = ("Encountered error while trying to enable a " + - "newly created VirtualHost located at {0} by " + - "linking to it from {1}") - raise errors.NotSupportedError(errstring.format(vhost.filep, - enabled_path)) - vhost.enabled = True - logger.info("Enabling available site: %s", vhost.filep) - self.save_notes += "Enabled site %s\n" % vhost.filep - return None - - def enable_mod(self, mod_name, temp=False): - """Enables module in Apache. - - Both enables and reloads Apache so module is active. - - :param str mod_name: Name of the module to enable. (e.g. 'ssl') - :param bool temp: Whether or not this is a temporary action. - - :raises .errors.NotSupportedError: If the filesystem layout is not - supported. - :raises .errors.MisconfigurationError: If a2enmod or a2dismod cannot be - run. - - """ - avail_path = os.path.join(self.parser.root, "mods-available") - enabled_path = os.path.join(self.parser.root, "mods-enabled") - if not os.path.isdir(avail_path) or not os.path.isdir(enabled_path): - raise errors.NotSupportedError( - "Unsupported directory layout. You may try to enable mod %s " - "and try again." % mod_name) - - deps = apache_util.get_mod_deps(mod_name) - - # Enable all dependencies - for dep in deps: - if (dep + "_module") not in self.parser.modules: - self._enable_mod_debian(dep, temp) - self.parser.add_mod(dep) - note = "Enabled dependency of %s module - %s" % (mod_name, dep) - if not temp: - self.save_notes += note + os.linesep - logger.debug(note) - - # Enable actual module - self._enable_mod_debian(mod_name, temp) - self.parser.add_mod(mod_name) - - if not temp: - self.save_notes += "Enabled %s module in Apache\n" % mod_name - logger.info("Enabled Apache %s module", mod_name) - - # Modules can enable additional config files. Variables may be defined - # within these new configuration sections. - # Reload is not necessary as DUMP_RUN_CFG uses latest config. - self.parser.update_runtime_variables() - - def _enable_mod_debian(self, mod_name, temp): - """Assumes mods-available, mods-enabled layout.""" - # Generate reversal command. - # Try to be safe here... check that we can probably reverse before - # applying enmod command - if not util.exe_exists(self.option("dismod")): - raise errors.MisconfigurationError( - "Unable to find a2dismod, please make sure a2enmod and " - "a2dismod are configured correctly for certbot.") - - self.reverter.register_undo_command( - temp, [self.option("dismod"), "-f", mod_name]) - util.run_script([self.option("enmod"), mod_name]) diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/override_fedora.py deleted file mode 100644 index 786ada0fc..000000000 --- a/certbot-apache/certbot_apache/override_fedora.py +++ /dev/null @@ -1,98 +0,0 @@ -""" Distribution specific override class for Fedora 29+ """ -import pkg_resources -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot import util - -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser - - -@zope.interface.provider(interfaces.IPluginFactory) -class FedoraConfigurator(configurator.ApacheConfigurator): - """Fedora 29+ specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/httpd", - vhost_root="/etc/httpd/conf.d", - vhost_files="*.conf", - logs_root="/var/log/httpd", - ctl="httpd", - version_cmd=['httpd', '-v'], - restart_cmd=['apachectl', 'graceful'], - restart_cmd_alt=['apachectl', 'restart'], - conftest_cmd=['apachectl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/httpd/conf.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - # TODO: eventually newest version of Fedora will need their own config - "certbot_apache", "centos-options-ssl-apache.conf") - ) - - def config_test(self): - """ - Override config_test to mitigate configtest error in vanilla installation - of mod_ssl in Fedora. The error is caused by non-existent self-signed - certificates referenced by the configuration, that would be autogenerated - during the first (re)start of httpd. - """ - try: - super(FedoraConfigurator, self).config_test() - except errors.MisconfigurationError: - self._try_restart_fedora() - - def get_parser(self): - """Initializes the ApacheParser""" - return FedoraParser( - self.option("server_root"), self.option("vhost_root"), - self.version, configurator=self) - - def _try_restart_fedora(self): - """ - Tries to restart httpd using systemctl to generate the self signed keypair. - """ - try: - util.run_script(['systemctl', 'restart', 'httpd']) - except errors.SubprocessError as err: - raise errors.MisconfigurationError(str(err)) - - # Finish with actual config check to see if systemctl restart helped - super(FedoraConfigurator, self).config_test() - - def _prepare_options(self): - """ - Override the options dictionary initialization to keep using apachectl - instead of httpd and so take advantages of this new bash script in newer versions - of Fedora to restart httpd. - """ - super(FedoraConfigurator, self)._prepare_options() - self.options["restart_cmd"][0] = 'apachectl' - self.options["restart_cmd_alt"][0] = 'apachectl' - self.options["conftest_cmd"][0] = 'apachectl' - - -class FedoraParser(parser.ApacheParser): - """Fedora 29+ specific ApacheParser override class""" - def __init__(self, *args, **kwargs): - # Fedora 29+ specific configuration file for Apache - self.sysconfig_filep = "/etc/sysconfig/httpd" - super(FedoraParser, self).__init__(*args, **kwargs) - - def update_runtime_variables(self): - """ Override for update_runtime_variables for custom parsing """ - # Opportunistic, works if SELinux not enforced - super(FedoraParser, self).update_runtime_variables() - self._parse_sysconfig_var() - - def _parse_sysconfig_var(self): - """ Parses Apache CLI options from Fedora configuration file """ - defines = apache_util.parse_define_file(self.sysconfig_filep, "OPTIONS") - for k in defines: - self.variables[k] = defines[k] diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py deleted file mode 100644 index c358a10fa..000000000 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ /dev/null @@ -1,75 +0,0 @@ -""" Distribution specific override class for Gentoo Linux """ -import pkg_resources - -import zope.interface - -from certbot import interfaces - -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser - -@zope.interface.provider(interfaces.IPluginFactory) -class GentooConfigurator(configurator.ApacheConfigurator): - """Gentoo specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/vhosts.d", - vhost_files="*.conf", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - restart_cmd_alt=['apache2ctl', 'restart'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod=None, - dismod=None, - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/apache2/vhosts.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) - - def _prepare_options(self): - """ - Override the options dictionary initialization in order to support - alternative restart cmd used in Gentoo. - """ - super(GentooConfigurator, self)._prepare_options() - self.options["restart_cmd_alt"][0] = self.option("ctl") - - def get_parser(self): - """Initializes the ApacheParser""" - return GentooParser( - self.option("server_root"), self.option("vhost_root"), - self.version, configurator=self) - - -class GentooParser(parser.ApacheParser): - """Gentoo specific ApacheParser override class""" - def __init__(self, *args, **kwargs): - # Gentoo specific configuration file for Apache2 - self.apacheconfig_filep = "/etc/conf.d/apache2" - super(GentooParser, self).__init__(*args, **kwargs) - - def update_runtime_variables(self): - """ Override for update_runtime_variables for custom parsing """ - self.parse_sysconfig_var() - self.update_modules() - - def parse_sysconfig_var(self): - """ Parses Apache CLI options from Gentoo configuration file """ - defines = apache_util.parse_define_file(self.apacheconfig_filep, - "APACHE2_OPTS") - for k in defines: - self.variables[k] = defines[k] - - def update_modules(self): - """Get loaded modules from httpd process, and add them to DOM""" - mod_cmd = [self.configurator.option("ctl"), "modules"] - matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") - for mod in matches: - self.add_mod(mod.strip()) diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/override_suse.py deleted file mode 100644 index 3d0043afe..000000000 --- a/certbot-apache/certbot_apache/override_suse.py +++ /dev/null @@ -1,31 +0,0 @@ -""" Distribution specific override class for OpenSUSE """ -import pkg_resources - -import zope.interface - -from certbot import interfaces - -from certbot_apache import configurator - -@zope.interface.provider(interfaces.IPluginFactory) -class OpenSUSEConfigurator(configurator.ApacheConfigurator): - """OpenSUSE specific ApacheConfigurator override class""" - - OS_DEFAULTS = dict( - server_root="/etc/apache2", - vhost_root="/etc/apache2/vhosts.d", - vhost_files="*.conf", - logs_root="/var/log/apache2", - ctl="apache2ctl", - version_cmd=['apache2ctl', '-v'], - restart_cmd=['apache2ctl', 'graceful'], - conftest_cmd=['apache2ctl', 'configtest'], - enmod="a2enmod", - dismod="a2dismod", - le_vhost_ext="-le-ssl.conf", - handle_modules=False, - handle_sites=False, - challenge_location="/etc/apache2/vhosts.d", - MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") - ) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/parser.py deleted file mode 100644 index ceb09548f..000000000 --- a/certbot-apache/certbot_apache/parser.py +++ /dev/null @@ -1,1009 +0,0 @@ -"""ApacheParser is a member object of the ApacheConfigurator class.""" -import copy -import fnmatch -import logging -import re -import subprocess -import sys - -import six - -from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os - -from certbot_apache import constants - -logger = logging.getLogger(__name__) - - -class ApacheParser(object): - """Class handles the fine details of parsing the Apache Configuration. - - .. todo:: Make parsing general... remove sites-available etc... - - :ivar str root: Normalized absolute path to the server root - directory. Without trailing slash. - :ivar set modules: All module names that are currently enabled. - :ivar dict loc: Location to place directives, root - configuration origin, - default - user config file, name - NameVirtualHost, - - """ - arg_var_interpreter = re.compile(r"\$\{[^ \}]*}") - fnmatch_chars = set(["*", "?", "\\", "[", "]"]) - - def __init__(self, root, vhostroot=None, version=(2, 4), - configurator=None): - # Note: Order is important here. - - # Needed for calling save() with reverter functionality that resides in - # AugeasConfigurator superclass of ApacheConfigurator. This resolves - # issues with aug.load() after adding new files / defines to parse tree - self.configurator = configurator - - # Initialize augeas - self.aug = None - self.init_augeas() - - if not self.check_aug_version(): - raise errors.NotSupportedError( - "Apache plugin support requires libaugeas0 and augeas-lenses " - "version 1.2.0 or higher, please make sure you have you have " - "those installed.") - - self.modules = set() # type: Set[str] - self.parser_paths = {} # type: Dict[str, List[str]] - self.variables = {} # type: Dict[str, str] - - # Find configuration root and make sure augeas can parse it. - self.root = os.path.abspath(root) - self.loc = {"root": self._find_config_root()} - self.parse_file(self.loc["root"]) - - if version >= (2, 4): - # Look up variables from httpd and add to DOM if not already parsed - self.update_runtime_variables() - - # This problem has been fixed in Augeas 1.0 - self.standardize_excl() - - # Parse LoadModule directives from configuration files - self.parse_modules() - - # Set up rest of locations - self.loc.update(self._set_locations()) - - # list of the active include paths, before modifications - self.existing_paths = copy.deepcopy(self.parser_paths) - - # Must also attempt to parse additional virtual host root - if vhostroot: - self.parse_file(os.path.abspath(vhostroot) + "/" + - self.configurator.option("vhost_files")) - - # check to see if there were unparsed define statements - if version < (2, 4): - if self.find_dir("Define", exclude=False): - raise errors.PluginError("Error parsing runtime variables") - - def init_augeas(self): - """ Initialize the actual Augeas instance """ - - try: - import augeas - except ImportError: # pragma: no cover - raise errors.NoInstallationError("Problem in Augeas installation") - - self.aug = augeas.Augeas( - # specify a directory to load our preferred lens from - loadpath=constants.AUGEAS_LENS_DIR, - # Do not save backup (we do it ourselves), do not load - # anything by default - flags=(augeas.Augeas.NONE | - augeas.Augeas.NO_MODL_AUTOLOAD | - augeas.Augeas.ENABLE_SPAN)) - - def check_parsing_errors(self, lens): - """Verify Augeas can parse all of the lens files. - - :param str lens: lens to check for errors - - :raises .errors.PluginError: If there has been an error in parsing with - the specified lens. - - """ - error_files = self.aug.match("/augeas//error") - - for path in error_files: - # Check to see if it was an error resulting from the use of - # the httpd lens - lens_path = self.aug.get(path + "/lens") - # As aug.get may return null - if lens_path and lens in lens_path: - msg = ( - "There has been an error in parsing the file {0} on line {1}: " - "{2}".format( - # Strip off /augeas/files and /error - path[13:len(path) - 6], - self.aug.get(path + "/line"), - self.aug.get(path + "/message"))) - raise errors.PluginError(msg) - - def check_aug_version(self): - """ Checks that we have recent enough version of libaugeas. - If augeas version is recent enough, it will support case insensitive - regexp matching""" - - self.aug.set("/test/path/testing/arg", "aRgUMeNT") - try: - matches = self.aug.match( - "/test//*[self::arg=~regexp('argument', 'i')]") - except RuntimeError: - self.aug.remove("/test/path") - return False - self.aug.remove("/test/path") - return matches - - def unsaved_files(self): - """Lists files that have modified Augeas DOM but the changes have not - been written to the filesystem yet, used by `self.save()` and - ApacheConfigurator to check the file state. - - :raises .errors.PluginError: If there was an error in Augeas, in - an attempt to save the configuration, or an error creating a - checkpoint - - :returns: `set` of unsaved files - """ - save_state = self.aug.get("/augeas/save") - self.aug.set("/augeas/save", "noop") - # Existing Errors - ex_errs = self.aug.match("/augeas//error") - try: - # This is a noop save - self.aug.save() - except (RuntimeError, IOError): - self._log_save_errors(ex_errs) - # Erase Save Notes - self.configurator.save_notes = "" - raise errors.PluginError( - "Error saving files, check logs for more info.") - - # Return the original save method - self.aug.set("/augeas/save", save_state) - - # Retrieve list of modified files - # Note: Noop saves can cause the file to be listed twice, I used a - # set to remove this possibility. This is a known augeas 0.10 error. - save_paths = self.aug.match("/augeas/events/saved") - - save_files = set() - if save_paths: - for path in save_paths: - save_files.add(self.aug.get(path)[6:]) - return save_files - - def ensure_augeas_state(self): - """Makes sure that all Augeas dom changes are written to files to avoid - loss of configuration directives when doing additional augeas parsing, - causing a possible augeas.load() resulting dom reset - """ - - if self.unsaved_files(): - self.configurator.save_notes += "(autosave)" - self.configurator.save() - - def save(self, save_files): - """Saves all changes to the configuration files. - - save() is called from ApacheConfigurator to handle the parser specific - tasks of saving. - - :param list save_files: list of strings of file paths that we need to save. - - """ - self.configurator.save_notes = "" - self.aug.save() - - # Force reload if files were modified - # This is needed to recalculate augeas directive span - if save_files: - for sf in save_files: - self.aug.remove("/files/"+sf) - self.aug.load() - - def _log_save_errors(self, ex_errs): - """Log errors due to bad Augeas save. - - :param list ex_errs: Existing errors before save - - """ - # Check for the root of save problems - new_errs = self.aug.match("/augeas//error") - # logger.error("During Save - %s", mod_conf) - logger.error("Unable to save files: %s. Attempted Save Notes: %s", - ", ".join(err[13:len(err) - 6] for err in new_errs - # Only new errors caused by recent save - if err not in ex_errs), self.configurator.save_notes) - - def add_include(self, main_config, inc_path): - """Add Include for a new configuration file if one does not exist - - :param str main_config: file path to main Apache config file - :param str inc_path: path of file to include - - """ - if not self.find_dir(case_i("Include"), inc_path): - logger.debug("Adding Include %s to %s", - inc_path, get_aug_path(main_config)) - self.add_dir( - get_aug_path(main_config), - "Include", inc_path) - - # Add new path to parser paths - new_dir = os.path.dirname(inc_path) - new_file = os.path.basename(inc_path) - self.existing_paths.setdefault(new_dir, []).append(new_file) - - def add_mod(self, mod_name): - """Shortcut for updating parser modules.""" - if mod_name + "_module" not in self.modules: - self.modules.add(mod_name + "_module") - if "mod_" + mod_name + ".c" not in self.modules: - self.modules.add("mod_" + mod_name + ".c") - - def reset_modules(self): - """Reset the loaded modules list. This is called from cleanup to clear - temporarily loaded modules.""" - self.modules = set() - self.update_modules() - self.parse_modules() - - def parse_modules(self): - """Iterates on the configuration until no new modules are loaded. - - ..todo:: This should be attempted to be done with a binary to avoid - the iteration issue. Else... parse and enable mods at same time. - - """ - mods = set() # type: Set[str] - matches = self.find_dir("LoadModule") - iterator = iter(matches) - # Make sure prev_size != cur_size for do: while: iteration - prev_size = -1 - - while len(mods) != prev_size: - prev_size = len(mods) - - for match_name, match_filename in six.moves.zip( - iterator, iterator): - mod_name = self.get_arg(match_name) - mod_filename = self.get_arg(match_filename) - if mod_name and mod_filename: - mods.add(mod_name) - mods.add(os.path.basename(mod_filename)[:-2] + "c") - else: - logger.debug("Could not read LoadModule directive from " + - "Augeas path: %s", match_name[6:]) - self.modules.update(mods) - - def update_runtime_variables(self): - """Update Includes, Defines and Includes from httpd config dump data""" - self.update_defines() - self.update_includes() - self.update_modules() - - def update_defines(self): - """Get Defines from httpd process""" - - variables = dict() - define_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_RUN_CFG"] - matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)") - try: - matches.remove("DUMP_RUN_CFG") - except ValueError: - return - - for match in matches: - if match.count("=") > 1: - logger.error("Unexpected number of equal signs in " - "runtime config dump.") - raise errors.PluginError( - "Error parsing Apache runtime variables") - parts = match.partition("=") - variables[parts[0]] = parts[2] - - self.variables = variables - - def update_includes(self): - """Get includes from httpd process, and add them to DOM if needed""" - - # Find_dir iterates over configuration for Include and IncludeOptional - # directives to make sure we see the full include tree present in the - # configuration files - _ = self.find_dir("Include") - - inc_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_INCLUDES"] - matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)") - if matches: - for i in matches: - if not self.parsed_in_current(i): - self.parse_file(i) - - def update_modules(self): - """Get loaded modules from httpd process, and add them to DOM""" - - mod_cmd = [self.configurator.option("ctl"), "-t", "-D", - "DUMP_MODULES"] - matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module") - for mod in matches: - self.add_mod(mod.strip()) - - def parse_from_subprocess(self, command, regexp): - """Get values from stdout of subprocess command - - :param list command: Command to run - :param str regexp: Regexp for parsing - - :returns: list parsed from command output - :rtype: list - - """ - stdout = self._get_runtime_cfg(command) - return re.compile(regexp).findall(stdout) - - def _get_runtime_cfg(self, command): # pylint: disable=no-self-use - """Get runtime configuration info. - :param command: Command to run - - :returns: stdout from command - - """ - try: - proc = subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - stdout, stderr = proc.communicate() - - except (OSError, ValueError): - logger.error( - "Error running command %s for runtime parameters!%s", - command, os.linesep) - raise errors.MisconfigurationError( - "Error accessing loaded Apache parameters: {0}".format( - command)) - # Small errors that do not impede - if proc.returncode != 0: - logger.warning("Error in checking parameter list: %s", stderr) - raise errors.MisconfigurationError( - "Apache is unable to check whether or not the module is " - "loaded because Apache is misconfigured.") - - return stdout - - def filter_args_num(self, matches, args): # pylint: disable=no-self-use - """Filter out directives with specific number of arguments. - - This function makes the assumption that all related arguments are given - in order. Thus /files/apache/directive[5]/arg[2] must come immediately - after /files/apache/directive[5]/arg[1]. Runs in 1 linear pass. - - :param string matches: Matches of all directives with arg nodes - :param int args: Number of args you would like to filter - - :returns: List of directives that contain # of arguments. - (arg is stripped off) - - """ - filtered = [] - if args == 1: - for i, match in enumerate(matches): - if match.endswith("/arg"): - filtered.append(matches[i][:-4]) - else: - for i, match in enumerate(matches): - if match.endswith("/arg[%d]" % args): - # Make sure we don't cause an IndexError (end of list) - # Check to make sure arg + 1 doesn't exist - if (i == (len(matches) - 1) or - not matches[i + 1].endswith("/arg[%d]" % - (args + 1))): - filtered.append(matches[i][:-len("/arg[%d]" % args)]) - - return filtered - - def add_dir_to_ifmodssl(self, aug_conf_path, directive, args): - """Adds directive and value to IfMod ssl block. - - Adds given directive and value along configuration path within - an IfMod mod_ssl.c block. If the IfMod block does not exist in - the file, it is created. - - :param str aug_conf_path: Desired Augeas config path to add directive - :param str directive: Directive you would like to add, e.g. Listen - :param args: Values of the directive; str "443" or list of str - :type args: list - - """ - # TODO: Add error checking code... does the path given even exist? - # Does it throw exceptions? - if_mod_path = self.get_ifmod(aug_conf_path, "mod_ssl.c") - # IfModule can have only one valid argument, so append after - self.aug.insert(if_mod_path + "arg", "directive", False) - nvh_path = if_mod_path + "directive[1]" - self.aug.set(nvh_path, directive) - if len(args) == 1: - self.aug.set(nvh_path + "/arg", args[0]) - else: - for i, arg in enumerate(args): - self.aug.set("%s/arg[%d]" % (nvh_path, i + 1), arg) - - def get_ifmod(self, aug_conf_path, mod, beginning=False): - """Returns the path to and creates one if it doesn't exist. - - :param str aug_conf_path: Augeas configuration path - :param str mod: module ie. mod_ssl.c - :param bool beginning: If the IfModule should be created to the beginning - of augeas path DOM tree. - - :returns: Augeas path of the requested IfModule directive that pre-existed - or was created during the process. The path may be dynamic, - i.e. .../IfModule[last()] - :rtype: str - - """ - if_mods = self.aug.match(("%s/IfModule/*[self::arg='%s']" % - (aug_conf_path, mod))) - if not if_mods: - return self.create_ifmod(aug_conf_path, mod, beginning) - - # Strip off "arg" at end of first ifmod path - return if_mods[0].rpartition("arg")[0] - - def create_ifmod(self, aug_conf_path, mod, beginning=False): - """Creates a new and returns its path. - - :param str aug_conf_path: Augeas configuration path - :param str mod: module ie. mod_ssl.c - :param bool beginning: If the IfModule should be created to the beginning - of augeas path DOM tree. - - :returns: Augeas path of the newly created IfModule directive. - The path may be dynamic, i.e. .../IfModule[last()] - :rtype: str - - """ - if beginning: - c_path_arg = "{}/IfModule[1]/arg".format(aug_conf_path) - # Insert IfModule before the first directive - self.aug.insert("{}/directive[1]".format(aug_conf_path), - "IfModule", True) - retpath = "{}/IfModule[1]/".format(aug_conf_path) - else: - c_path = "{}/IfModule[last() + 1]".format(aug_conf_path) - c_path_arg = "{}/IfModule[last()]/arg".format(aug_conf_path) - self.aug.set(c_path, "") - retpath = "{}/IfModule[last()]/".format(aug_conf_path) - self.aug.set(c_path_arg, mod) - return retpath - - def add_dir(self, aug_conf_path, directive, args): - """Appends directive to the end fo the file given by aug_conf_path. - - .. note:: Not added to AugeasConfigurator because it may depend - on the lens - - :param str aug_conf_path: Augeas configuration path to add directive - :param str directive: Directive to add - :param args: Value of the directive. ie. Listen 443, 443 is arg - :type args: list or str - - """ - self.aug.set(aug_conf_path + "/directive[last() + 1]", directive) - if isinstance(args, list): - for i, value in enumerate(args, 1): - self.aug.set( - "%s/directive[last()]/arg[%d]" % (aug_conf_path, i), value) - else: - self.aug.set(aug_conf_path + "/directive[last()]/arg", args) - - def add_dir_beginning(self, aug_conf_path, dirname, args): - """Adds the directive to the beginning of defined aug_conf_path. - - :param str aug_conf_path: Augeas configuration path to add directive - :param str dirname: Directive to add - :param args: Value of the directive. ie. Listen 443, 443 is arg - :type args: list or str - """ - first_dir = aug_conf_path + "/directive[1]" - self.aug.insert(first_dir, "directive", True) - self.aug.set(first_dir, dirname) - if isinstance(args, list): - for i, value in enumerate(args, 1): - self.aug.set(first_dir + "/arg[%d]" % (i), value) - else: - self.aug.set(first_dir + "/arg", args) - - def add_comment(self, aug_conf_path, comment): - """Adds the comment to the augeas path - - :param str aug_conf_path: Augeas configuration path to add directive - :param str comment: Comment content - - """ - self.aug.set(aug_conf_path + "/#comment[last() + 1]", comment) - - def find_comments(self, arg, start=None): - """Finds a comment with specified content from the provided DOM path - - :param str arg: Comment content to search - :param str start: Beginning Augeas path to begin looking - - :returns: List of augeas paths containing the comment content - :rtype: list - - """ - if not start: - start = get_aug_path(self.root) - - comments = self.aug.match("%s//*[label() = '#comment']" % start) - - results = [] - for comment in comments: - c_content = self.aug.get(comment) - if c_content and arg in c_content: - results.append(comment) - return results - - def find_dir(self, directive, arg=None, start=None, exclude=True): - """Finds directive in the configuration. - - Recursively searches through config files to find directives - Directives should be in the form of a case insensitive regex currently - - .. todo:: arg should probably be a list - .. todo:: arg search currently only supports direct matching. It does - not handle the case of variables or quoted arguments. This should - be adapted to use a generic search for the directive and then do a - case-insensitive self.get_arg filter - - Note: Augeas is inherently case sensitive while Apache is case - insensitive. Augeas 1.0 allows case insensitive regexes like - regexp(/Listen/, "i"), however the version currently supported - by Ubuntu 0.10 does not. Thus I have included my own case insensitive - transformation by calling case_i() on everything to maintain - compatibility. - - :param str directive: Directive to look for - :param arg: Specific value directive must have, None if all should - be considered - :type arg: str or None - - :param str start: Beginning Augeas path to begin looking - :param bool exclude: Whether or not to exclude directives based on - variables and enabled modules - - """ - # Cannot place member variable in the definition of the function so... - if not start: - start = get_aug_path(self.loc["root"]) - - # No regexp code - # if arg is None: - # matches = self.aug.match(start + - # "//*[self::directive='" + directive + "']/arg") - # else: - # matches = self.aug.match(start + - # "//*[self::directive='" + directive + - # "']/* [self::arg='" + arg + "']") - - # includes = self.aug.match(start + - # "//* [self::directive='Include']/* [label()='arg']") - - regex = "(%s)|(%s)|(%s)" % (case_i(directive), - case_i("Include"), - case_i("IncludeOptional")) - matches = self.aug.match( - "%s//*[self::directive=~regexp('%s')]" % (start, regex)) - - if exclude: - matches = self._exclude_dirs(matches) - - if arg is None: - arg_suffix = "/arg" - else: - arg_suffix = "/*[self::arg=~regexp('%s')]" % case_i(arg) - - ordered_matches = [] # type: List[str] - - # TODO: Wildcards should be included in alphabetical order - # https://httpd.apache.org/docs/2.4/mod/core.html#include - for match in matches: - dir_ = self.aug.get(match).lower() - if dir_ == "include" or dir_ == "includeoptional": - ordered_matches.extend(self.find_dir( - directive, arg, - self._get_include_path(self.get_arg(match + "/arg")), - exclude)) - # This additionally allows Include - if dir_ == directive.lower(): - ordered_matches.extend(self.aug.match(match + arg_suffix)) - - return ordered_matches - - def get_all_args(self, match): - """ - Tries to fetch all arguments for a directive. See get_arg. - - Note that if match is an ancestor node, it returns all names of - child directives as well as the list of arguments. - - """ - - if match[-1] != "/": - match = match+"/" - allargs = self.aug.match(match + '*') - return [self.get_arg(arg) for arg in allargs] - - def get_arg(self, match): - """Uses augeas.get to get argument value and interprets result. - - This also converts all variables and parameters appropriately. - - """ - value = self.aug.get(match) - - # No need to strip quotes for variables, as apache2ctl already does - # this, but we do need to strip quotes for all normal arguments. - - # Note: normal argument may be a quoted variable - # e.g. strip now, not later - if not value: - return None - else: - value = value.strip("'\"") - - variables = ApacheParser.arg_var_interpreter.findall(value) - - for var in variables: - # Strip off ${ and } - try: - value = value.replace(var, self.variables[var[2:-1]]) - except KeyError: - raise errors.PluginError("Error Parsing variable: %s" % var) - - return value - - def _exclude_dirs(self, matches): - """Exclude directives that are not loaded into the configuration.""" - filters = [("ifmodule", self.modules), ("ifdefine", self.variables)] - - valid_matches = [] - - for match in matches: - for filter_ in filters: - if not self._pass_filter(match, filter_): - break - else: - valid_matches.append(match) - return valid_matches - - def _pass_filter(self, match, filter_): - """Determine if directive passes a filter. - - :param str match: Augeas path - :param list filter: list of tuples of form - [("lowercase if directive", set of relevant parameters)] - - """ - match_l = match.lower() - last_match_idx = match_l.find(filter_[0]) - - while last_match_idx != -1: - # Check args - end_of_if = match_l.find("/", last_match_idx) - # This should be aug.get (vars are not used e.g. parser.aug_get) - expression = self.aug.get(match[:end_of_if] + "/arg") - - if expression.startswith("!"): - # Strip off "!" - if expression[1:] in filter_[1]: - return False - else: - if expression not in filter_[1]: - return False - - last_match_idx = match_l.find(filter_[0], end_of_if) - - return True - - def _get_include_path(self, arg): - """Converts an Apache Include directive into Augeas path. - - Converts an Apache Include directive argument into an Augeas - searchable path - - .. todo:: convert to use os.path.join() - - :param str arg: Argument of Include directive - - :returns: Augeas path string - :rtype: str - - """ - # Check to make sure only expected characters are used <- maybe remove - # validChars = re.compile("[a-zA-Z0-9.*?_-/]*") - # matchObj = validChars.match(arg) - # if matchObj.group() != arg: - # logger.error("Error: Invalid regexp characters in %s", arg) - # return [] - - # Remove beginning and ending quotes - arg = arg.strip("'\"") - - # Standardize the include argument based on server root - if not arg.startswith("/"): - # Normpath will condense ../ - arg = os.path.normpath(os.path.join(self.root, arg)) - else: - arg = os.path.normpath(arg) - - # Attempts to add a transform to the file if one does not already exist - if os.path.isdir(arg): - self.parse_file(os.path.join(arg, "*")) - else: - self.parse_file(arg) - - # Argument represents an fnmatch regular expression, convert it - # Split up the path and convert each into an Augeas accepted regex - # then reassemble - split_arg = arg.split("/") - for idx, split in enumerate(split_arg): - if any(char in ApacheParser.fnmatch_chars for char in split): - # Turn it into a augeas regex - # TODO: Can this instead be an augeas glob instead of regex - split_arg[idx] = ("* [label()=~regexp('%s')]" % - self.fnmatch_to_re(split)) - # Reassemble the argument - # Note: This also normalizes the argument /serverroot/ -> /serverroot - arg = "/".join(split_arg) - - return get_aug_path(arg) - - def fnmatch_to_re(self, clean_fn_match): # pylint: disable=no-self-use - """Method converts Apache's basic fnmatch to regular expression. - - Assumption - Configs are assumed to be well-formed and only writable by - privileged users. - - https://apr.apache.org/docs/apr/2.0/apr__fnmatch_8h_source.html - http://apache2.sourcearchive.com/documentation/2.2.16-6/apr__fnmatch_8h_source.html - - :param str clean_fn_match: Apache style filename match, like globs - - :returns: regex suitable for augeas - :rtype: str - - """ - if sys.version_info < (3, 6): - # This strips off final /Z(?ms) - return fnmatch.translate(clean_fn_match)[:-7] - # Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z - return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover - - def parse_file(self, filepath): - """Parse file with Augeas - - Checks to see if file_path is parsed by Augeas - If filepath isn't parsed, the file is added and Augeas is reloaded - - :param str filepath: Apache config file path - - """ - use_new, remove_old = self._check_path_actions(filepath) - # Ensure that we have the latest Augeas DOM state on disk before - # calling aug.load() which reloads the state from disk - self.ensure_augeas_state() - # Test if augeas included file for Httpd.lens - # Note: This works for augeas globs, ie. *.conf - if use_new: - inc_test = self.aug.match( - "/augeas/load/Httpd['%s' =~ glob(incl)]" % filepath) - if not inc_test: - # Load up files - # This doesn't seem to work on TravisCI - # self.aug.add_transform("Httpd.lns", [filepath]) - if remove_old: - self._remove_httpd_transform(filepath) - self._add_httpd_transform(filepath) - self.aug.load() - - def parsed_in_current(self, filep): - """Checks if the file path is parsed by current Augeas parser config - ie. returns True if the file is found on a path that's found in live - Augeas configuration. - - :param str filep: Path to match - - :returns: True if file is parsed in existing configuration tree - :rtype: bool - """ - return self._parsed_by_parser_paths(filep, self.parser_paths) - - def parsed_in_original(self, filep): - """Checks if the file path is parsed by existing Apache config. - ie. returns True if the file is found on a path that matches Include or - IncludeOptional statement in the Apache configuration. - - :param str filep: Path to match - - :returns: True if file is parsed in existing configuration tree - :rtype: bool - """ - return self._parsed_by_parser_paths(filep, self.existing_paths) - - def _parsed_by_parser_paths(self, filep, paths): - """Helper function that searches through provided paths and returns - True if file path is found in the set""" - for directory in paths.keys(): - for filename in paths[directory]: - if fnmatch.fnmatch(filep, os.path.join(directory, filename)): - return True - return False - - def _check_path_actions(self, filepath): - """Determine actions to take with a new augeas path - - This helper function will return a tuple that defines - if we should try to append the new filepath to augeas - parser paths, and / or remove the old one with more - narrow matching. - - :param str filepath: filepath to check the actions for - - """ - - try: - new_file_match = os.path.basename(filepath) - existing_matches = self.parser_paths[os.path.dirname(filepath)] - if "*" in existing_matches: - use_new = False - else: - use_new = True - remove_old = new_file_match == "*" - except KeyError: - use_new = True - remove_old = False - return use_new, remove_old - - def _remove_httpd_transform(self, filepath): - """Remove path from Augeas transform - - :param str filepath: filepath to remove - """ - - remove_basenames = self.parser_paths[os.path.dirname(filepath)] - remove_dirname = os.path.dirname(filepath) - for name in remove_basenames: - remove_path = remove_dirname + "/" + name - remove_inc = self.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % remove_path) - self.aug.remove(remove_inc[0]) - self.parser_paths.pop(remove_dirname) - - def _add_httpd_transform(self, incl): - """Add a transform to Augeas. - - This function will correctly add a transform to augeas - The existing augeas.add_transform in python doesn't seem to work for - Travis CI as it loads in libaugeas.so.0.10.0 - - :param str incl: filepath to include for transform - - """ - last_include = self.aug.match("/augeas/load/Httpd/incl [last()]") - if last_include: - # Insert a new node immediately after the last incl - self.aug.insert(last_include[0], "incl", False) - self.aug.set("/augeas/load/Httpd/incl[last()]", incl) - # On first use... must load lens and add file to incl - else: - # Augeas uses base 1 indexing... insert at beginning... - self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") - self.aug.set("/augeas/load/Httpd/incl", incl) - # Add included path to paths dictionary - try: - self.parser_paths[os.path.dirname(incl)].append( - os.path.basename(incl)) - except KeyError: - self.parser_paths[os.path.dirname(incl)] = [ - os.path.basename(incl)] - - def standardize_excl(self): - """Standardize the excl arguments for the Httpd lens in Augeas. - - Note: Hack! - Standardize the excl arguments for the Httpd lens in Augeas - Servers sometimes give incorrect defaults - Note: This problem should be fixed in Augeas 1.0. Unfortunately, - Augeas 0.10 appears to be the most popular version currently. - - """ - # attempt to protect against augeas error in 0.10.0 - ubuntu - # *.augsave -> /*.augsave upon augeas.load() - # Try to avoid bad httpd files - # There has to be a better way... but after a day and a half of testing - # I had no luck - # This is a hack... work around... submit to augeas if still not fixed - - excl = ["*.augnew", "*.augsave", "*.dpkg-dist", "*.dpkg-bak", - "*.dpkg-new", "*.dpkg-old", "*.rpmsave", "*.rpmnew", - "*~", - self.root + "/*.augsave", - self.root + "/*~", - self.root + "/*/*augsave", - self.root + "/*/*~", - self.root + "/*/*/*.augsave", - self.root + "/*/*/*~"] - - for i, excluded in enumerate(excl, 1): - self.aug.set("/augeas/load/Httpd/excl[%d]" % i, excluded) - - self.aug.load() - - def _set_locations(self): - """Set default location for directives. - - Locations are given as file_paths - .. todo:: Make sure that files are included - - """ - default = self.loc["root"] - - temp = os.path.join(self.root, "ports.conf") - if os.path.isfile(temp): - listen = temp - name = temp - else: - listen = default - name = default - - return {"default": default, "listen": listen, "name": name} - - def _find_config_root(self): - """Find the Apache Configuration Root file.""" - location = ["apache2.conf", "httpd.conf", "conf/httpd.conf"] - for name in location: - if os.path.isfile(os.path.join(self.root, name)): - return os.path.join(self.root, name) - raise errors.NoInstallationError("Could not find configuration root") - - -def case_i(string): - """Returns case insensitive regex. - - Returns a sloppy, but necessary version of a case insensitive regex. - Any string should be able to be submitted and the string is - escaped and then made case insensitive. - May be replaced by a more proper /i once augeas 1.0 is widely - supported. - - :param str string: string to make case i regex - - """ - return "".join(["[" + c.upper() + c.lower() + "]" - if c.isalpha() else c for c in re.escape(string)]) - - -def get_aug_path(file_path): - """Return augeas path for full filepath. - - :param str file_path: Full filepath - - """ - return "/files%s" % file_path diff --git a/certbot-apache/certbot_apache/tests/autohsts_test.py b/certbot-apache/certbot_apache/tests/autohsts_test.py index 28fb172ed..098bed019 100644 --- a/certbot-apache/certbot_apache/tests/autohsts_test.py +++ b/certbot-apache/certbot_apache/tests/autohsts_test.py @@ -1,5 +1,5 @@ # pylint: disable=too-many-lines -"""Test for certbot_apache.configurator AutoHSTS functionality""" +"""Test for certbot_apache._internal.configurator AutoHSTS functionality""" import re import unittest import mock @@ -7,7 +7,7 @@ import mock import six # pylint: disable=unused-import from certbot import errors -from certbot_apache import constants +from certbot_apache._internal import constants from certbot_apache.tests import util @@ -39,24 +39,24 @@ class AutoHSTSTest(util.ApacheTest): head.replace("arg[3]", "arg[4]")) return None # pragma: no cover - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_autohsts_enable_headers_mod(self, mock_enable, _restart): self.config.parser.modules.discard("headers_module") self.config.parser.modules.discard("mod_header.c") self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) self.assertTrue(mock_enable.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_autohsts_deploy_already_exists(self, _restart): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) self.assertRaises(errors.PluginEnhancementAlreadyPresent, self.config.enable_autohsts, mock.MagicMock(), ["ocspvhost.com"]) - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.prepare") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.prepare") def test_autohsts_increase(self, mock_prepare, _mock_restart): self.config._prepared = False maxage = "\"max-age={0}\"" @@ -74,8 +74,8 @@ class AutoHSTSTest(util.ApacheTest): inc_val) self.assertTrue(mock_prepare.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator._autohsts_increase") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._autohsts_increase") def test_autohsts_increase_noop(self, mock_increase, _restart): maxage = "\"max-age={0}\"" initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) @@ -89,8 +89,8 @@ class AutoHSTSTest(util.ApacheTest): self.assertFalse(mock_increase.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) def test_autohsts_increase_no_header(self, _restart): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) # Remove the header @@ -102,8 +102,8 @@ class AutoHSTSTest(util.ApacheTest): self.config.update_autohsts, mock.MagicMock()) - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_autohsts_increase_and_make_permanent(self, _mock_restart): maxage = "\"max-age={0}\"" max_val = maxage.format(constants.AUTOHSTS_PERMANENT) @@ -141,18 +141,18 @@ class AutoHSTSTest(util.ApacheTest): # Make sure that the execution does not continue when no entries in store self.assertFalse(self.config.storage.put.called) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_autohsts_no_ssl_vhost(self, mock_select): mock_select.return_value = self.vh_truth[0] - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.assertRaises(errors.PluginError, self.config.enable_autohsts, mock.MagicMock(), "invalid.example.com") self.assertTrue( "Certbot was not able to find SSL" in mock_log.call_args[0][0]) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.add_vhost_id") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.add_vhost_id") def test_autohsts_dont_enhance_twice(self, mock_id, _restart): mock_id.return_value = "1234567" self.config.enable_autohsts(mock.MagicMock(), @@ -177,7 +177,7 @@ class AutoHSTSTest(util.ApacheTest): self.config._autohsts_fetch_state() self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0} self.config._autohsts_save_state() - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.config.deploy_autohsts(mock.MagicMock()) self.assertTrue(mock_log.called) self.assertTrue( diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/certbot_apache/tests/centos6_test.py index 3c427ee91..daee4cd8c 100644 --- a/certbot-apache/certbot_apache/tests/centos6_test.py +++ b/certbot-apache/certbot_apache/tests/centos6_test.py @@ -1,12 +1,12 @@ -"""Test for certbot_apache.configurator for CentOS 6 overrides""" +"""Test for certbot_apache._internal.configurator for CentOS 6 overrides""" import unittest from certbot.compat import os from certbot.errors import MisconfigurationError -from certbot_apache import obj -from certbot_apache import override_centos -from certbot_apache import parser +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos +from certbot_apache._internal import parser from certbot_apache.tests import util diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py index dddbf489e..4fa838976 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/certbot_apache/tests/centos_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator for Centos overrides""" +"""Test for certbot_apache._internal.configurator for Centos overrides""" import unittest import mock @@ -7,8 +7,8 @@ from certbot import errors from certbot.compat import filesystem from certbot.compat import os -from certbot_apache import obj -from certbot_apache import override_centos +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos from certbot_apache.tests import util @@ -55,7 +55,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test() def test_non_fedora_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: mock_test.side_effect = errors.MisconfigurationError with mock.patch("certbot.util.get_os_info") as mock_info: @@ -64,7 +64,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test) def test_fedora_restart_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] @@ -74,7 +74,7 @@ class FedoraRestartTest(util.ApacheTest): self._run_fedora_test) def test_fedora_restart(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: with mock.patch("certbot.util.run_script") as mock_run: # First call raises error, second doesn't @@ -107,7 +107,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_centos.CentOSParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -156,7 +156,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls @@ -177,13 +177,13 @@ class MultipleVhostsTestCentOS(util.ApacheTest): self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, diff --git a/certbot-apache/certbot_apache/tests/complex_parsing_test.py b/certbot-apache/certbot_apache/tests/complex_parsing_test.py index 8712eb5bf..7627b7a48 100644 --- a/certbot-apache/certbot_apache/tests/complex_parsing_test.py +++ b/certbot-apache/certbot_apache/tests/complex_parsing_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_apache.parser.""" +"""Tests for certbot_apache._internal.parser.""" import shutil import unittest @@ -88,7 +88,7 @@ class ComplexParserTest(util.ParserTest): def verify_fnmatch(self, arg, hit=True): """Test if Include was correctly parsed.""" - from certbot_apache import parser + from certbot_apache._internal import parser self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), "Include", [arg]) if hit: diff --git a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py index f3c418a3a..045815e1f 100644 --- a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator implementations of reverter""" +"""Test for certbot_apache._internal.configurator implementations of reverter""" import shutil import unittest diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index c12dd81bc..4321a1fa0 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -1,5 +1,5 @@ # pylint: disable=too-many-lines -"""Test for certbot_apache.configurator.""" +"""Test for certbot_apache._internal.configurator.""" import copy import shutil import socket @@ -20,10 +20,10 @@ from certbot.compat import filesystem from certbot.tests import acme_util from certbot.tests import util as certbot_util -from certbot_apache import apache_util -from certbot_apache import constants -from certbot_apache import obj -from certbot_apache import parser +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants +from certbot_apache._internal import obj +from certbot_apache._internal import parser from certbot_apache.tests import util @@ -45,13 +45,13 @@ class MultipleVhostsTest(util.ApacheTest): def mocked_deploy_cert(*args, **kwargs): """a helper to mock a deployed cert""" - g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" with mock.patch(g_mod): config.real_deploy_cert(*args, **kwargs) self.config.deploy_cert = mocked_deploy_cert return self.config - @mock.patch("certbot_apache.configurator.path_surgery") + @mock.patch("certbot_apache._internal.configurator.path_surgery") def test_prepare_no_install(self, mock_surgery): silly_path = {"PATH": "/tmp/nothingness2342"} mock_surgery.return_value = False @@ -59,8 +59,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertRaises(errors.NoInstallationError, self.config.prepare) self.assertEqual(mock_surgery.call_count, 1) - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") def test_prepare_version(self, mock_exe_exists, _): mock_exe_exists.return_value = True self.config.version = None @@ -76,8 +76,8 @@ class MultipleVhostsTest(util.ApacheTest): os.remove(os.path.join(server_root, ".certbot.lock")) certbot_util.lock_and_call(self._test_prepare_locked, server_root) - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") def _test_prepare_locked(self, unused_parser, unused_exe_exists): try: self.config.prepare() @@ -89,13 +89,13 @@ class MultipleVhostsTest(util.ApacheTest): self.fail("Exception wasn't raised!") def test_add_parser_arguments(self): # pylint: disable=no-self-use - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.configurator import ApacheConfigurator # Weak test.. ApacheConfigurator.add_parser_arguments(mock.MagicMock()) def test_docs_parser_arguments(self): os.environ["CERTBOT_DOCS"] = "1" - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.configurator import ApacheConfigurator mock_add = mock.MagicMock() ApacheConfigurator.add_parser_arguments(mock_add) parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", @@ -121,13 +121,13 @@ class MultipleVhostsTest(util.ApacheTest): del os.environ["CERTBOT_DOCS"] def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use - from certbot_apache.entrypoint import OVERRIDE_CLASSES + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES for cls in OVERRIDE_CLASSES.values(): cls.add_parser_arguments(mock.MagicMock()) def test_all_configurators_defaults_defined(self): - from certbot_apache.entrypoint import OVERRIDE_CLASSES - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES + from certbot_apache._internal.configurator import ApacheConfigurator parameters = set(ApacheConfigurator.OS_DEFAULTS.keys()) for cls in OVERRIDE_CLASSES.values(): self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys()))) @@ -149,7 +149,7 @@ class MultipleVhostsTest(util.ApacheTest): )) @certbot_util.patch_get_utility() - @mock.patch("certbot_apache.configurator.socket.gethostbyaddr") + @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") def test_get_all_names_addrs(self, mock_gethost, mock_getutility): mock_gethost.side_effect = [("google.com", "", ""), socket.error] mock_utility = mock_getutility() @@ -175,7 +175,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access def test_get_aug_internal_path(self): - from certbot_apache.apache_util import get_internal_aug_path + from certbot_apache._internal.apache_util import get_internal_aug_path internal_paths = [ "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost", "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost", @@ -220,26 +220,26 @@ class MultipleVhostsTest(util.ApacheTest): # Handle case of non-debian layout get_virtual_hosts with mock.patch( - "certbot_apache.configurator.ApacheConfigurator.conf" + "certbot_apache._internal.configurator.ApacheConfigurator.conf" ) as mock_conf: mock_conf.return_value = False vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 12) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_none_avail(self, mock_select): mock_select.return_value = None self.assertRaises( errors.PluginError, self.config.choose_vhost, "none.com") - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_ssl(self, mock_select): mock_select.return_value = self.vh_truth[1] self.assertEqual( self.vh_truth[1], self.config.choose_vhost("none.com")) - @mock.patch("certbot_apache.display_ops.select_vhost") - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select): mock_select.return_value = self.vh_truth[0] mock_conf.return_value = False @@ -252,8 +252,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(self.vh_truth[0].ssl) self.assertTrue(chosen_vhost.ssl) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._find_best_vhost") - @mock.patch("certbot_apache.parser.ApacheParser.add_dir") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") + @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") def test_choose_vhost_and_servername_addition(self, mock_add, mock_find): ret_vh = self.vh_truth[8] ret_vh.enabled = False @@ -261,13 +261,13 @@ class MultipleVhostsTest(util.ApacheTest): self.config.choose_vhost("whatever.com") self.assertTrue(mock_add.called) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_with_temp(self, mock_select): mock_select.return_value = self.vh_truth[0] chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) self.assertEqual(self.vh_truth[0], chosen_vhost) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): mock_select.return_value = self.vh_truth[3] conflicting_vhost = obj.VirtualHost( @@ -784,8 +784,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config._add_name_vhost_if_necessary(self.vh_truth[0]) self.assertEqual(self.config.add_name_vhost.call_count, 2) - @mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_perform(self, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -801,8 +801,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(mock_restart.call_count, 1) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_cleanup(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -817,8 +817,8 @@ class MultipleVhostsTest(util.ApacheTest): else: self.assertFalse(mock_restart.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_cleanup_no_errors(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -855,11 +855,11 @@ class MultipleVhostsTest(util.ApacheTest): mock_script.side_effect = errors.SubprocessError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_restart(self, _): self.config.restart() - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_restart_bad_process(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError] @@ -902,8 +902,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.vh_truth[0].name, res.name) self.assertEqual(self.vh_truth[0].aliases, res.aliases) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._get_http_vhost") - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") @mock.patch("certbot.util.exe_exists") def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get): self.config.parser.modules.add("rewrite_module") @@ -926,7 +926,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enhance, "certbot.demo", "unknown_enhancement") def test_enhance_no_ssl_vhost(self): - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.assertRaises(errors.PluginError, self.config.enhance, "certbot.demo", "redirect") # Check that correct logger.warning was printed @@ -1231,7 +1231,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.choose_vhost("red.blue.purple.com") self.config.enhance("red.blue.purple.com", "redirect") - verify_no_redirect = ("certbot_apache.configurator." + verify_no_redirect = ("certbot_apache._internal.configurator." "ApacheConfigurator._verify_no_certbot_redirect") with mock.patch(verify_no_redirect) as mock_verify: self.config.enhance("green.blue.purple.com", "redirect") @@ -1333,8 +1333,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules.add("socache_shmcb_module") tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) filesystem.chmod(tmp_path, 0o755) - mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path" - mock_a = "certbot_apache.parser.ApacheParser.add_include" + mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path" + mock_a = "certbot_apache._internal.parser.ApacheParser.add_include" with mock.patch(mock_p) as mock_path: mock_path.return_value = os.path.join(tmp_path, "whatever.conf") @@ -1347,7 +1347,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertTrue(mock_add.called) shutil.rmtree(tmp_path) - @mock.patch("certbot_apache.parser.ApacheParser.parsed_in_original") + @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed): ret_vh = self.vh_truth[8] ret_vh.enabled = True @@ -1369,7 +1369,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[3]] vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", @@ -1385,10 +1385,10 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(vhs[0] == self.vh_truth[3]) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.make_vhost_ssl") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): # pylint: disable=protected-access - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[1]] vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", @@ -1396,13 +1396,13 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(mock_makessl.called) 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") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w): # pylint: disable=protected-access # Already SSL vhost mock_vh_for_w.return_value = [self.vh_truth[7]] - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[7]] vhs = self.config._choose_vhosts_wildcard("whatever", @@ -1423,7 +1423,7 @@ class MultipleVhostsTest(util.ApacheTest): mock_choose_vhosts = mock.MagicMock() mock_choose_vhosts.return_value = [self.vh_truth[7]] self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_apache.configurator.ApacheConfigurator._deploy_cert" + mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert" with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @@ -1431,7 +1431,7 @@ class MultipleVhostsTest(util.ApacheTest): 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") + @mock.patch("certbot_apache._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] @@ -1440,7 +1440,7 @@ class MultipleVhostsTest(util.ApacheTest): "*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - @mock.patch("certbot_apache.configurator.ApacheConfigurator._choose_vhosts_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") def test_enhance_wildcard_after_install(self, mock_choose): # pylint: disable=protected-access self.config.parser.modules.add("mod_ssl.c") @@ -1451,7 +1451,7 @@ class MultipleVhostsTest(util.ApacheTest): "Upgrade-Insecure-Requests") self.assertFalse(mock_choose.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._choose_vhosts_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") def test_enhance_wildcard_no_install(self, mock_choose): self.vh_truth[3].ssl = True mock_choose.return_value = [self.vh_truth[3]] @@ -1528,7 +1528,7 @@ class AugeasVhostsTest(util.ApacheTest): chosen_vhost = self.config._create_vhost(path) self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._create_vhost") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") def test_get_vhost_continue(self, mock_vhost): mock_vhost.return_value = None vhs = self.config.get_virtual_hosts() @@ -1540,18 +1540,18 @@ class AugeasVhostsTest(util.ApacheTest): for name in names: self.assertFalse(name in self.config.choose_vhost(name).aliases) - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): mock_conflicts.return_value = False - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): for name in ("a.example.net", "other.example.net"): self.assertTrue(name in self.config.choose_vhost(name).aliases) - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_wildcard_not_found(self, mock_conflicts): mock_conflicts.return_value = False - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" names = ( "abc.example.net", "not.there.tld", "aa.wildcard.tld" ) @@ -1563,7 +1563,7 @@ class AugeasVhostsTest(util.ApacheTest): self.assertEqual(mock_select.call_count - orig_cc, 1) def test_choose_vhost_wildcard_found(self): - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" names = ( "ab.example.net", "a.wildcard.tld", "yetanother.example.net" ) @@ -1617,7 +1617,7 @@ class MultiVhostsTest(util.ApacheTest): self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]), self.config.is_name_vhost(ssl_vhost)) - mock_path = "certbot_apache.configurator.ApacheConfigurator._get_new_vh_path" + mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" with mock.patch(mock_path) as mock_getpath: mock_getpath.return_value = None self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, @@ -1723,7 +1723,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self._assert_current_file() def test_prev_file_updates_to_current(self): - from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match") with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] @@ -1762,7 +1762,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.assertFalse(mock_logger.warning.called) def test_current_file_hash_in_all_hashes(self): - from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py index 54ced2d0b..d52c5b3c1 100644 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ b/certbot-apache/certbot_apache/tests/debian_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator for Debian overrides""" +"""Test for certbot_apache._internal.configurator for Debian overrides""" import shutil import unittest @@ -7,8 +7,8 @@ import mock from certbot import errors from certbot.compat import os -from certbot_apache import apache_util -from certbot_apache import obj +from certbot_apache._internal import apache_util +from certbot_apache._internal import obj from certbot_apache.tests import util @@ -32,8 +32,8 @@ class MultipleVhostsTestDebian(util.ApacheTest): def mocked_deploy_cert(*args, **kwargs): """a helper to mock a deployed cert""" - g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod" - d_mod = "certbot_apache.override_debian.DebianConfigurator.enable_mod" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" + d_mod = "certbot_apache._internal.override_debian.DebianConfigurator.enable_mod" with mock.patch(g_mod): with mock.patch(d_mod): config.real_deploy_cert(*args, **kwargs) @@ -47,7 +47,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") mock_popen().returncode = 0 @@ -196,7 +196,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): def test_enable_site_call_parent(self): with mock.patch( - "certbot_apache.configurator.ApacheConfigurator.enable_site") as e_s: + "certbot_apache._internal.configurator.ApacheConfigurator.enable_site") as e_s: self.config.parser.root = "/tmp/nonexistent" vh = self.vh_truth[0] vh.enabled = False diff --git a/certbot-apache/certbot_apache/tests/display_ops_test.py b/certbot-apache/certbot_apache/tests/display_ops_test.py index df5cdbac0..6202bf4b0 100644 --- a/certbot-apache/certbot_apache/tests/display_ops_test.py +++ b/certbot-apache/certbot_apache/tests/display_ops_test.py @@ -1,4 +1,4 @@ -"""Test certbot_apache.display_ops.""" +"""Test certbot_apache._internal.display_ops.""" import unittest import mock @@ -9,14 +9,14 @@ from certbot.display import util as display_util from certbot.tests import util as certbot_util -from certbot_apache import obj +from certbot_apache._internal import obj -from certbot_apache.display_ops import select_vhost_multiple +from certbot_apache._internal.display_ops import select_vhost_multiple from certbot_apache.tests import util class SelectVhostMultiTest(unittest.TestCase): - """Tests for certbot_apache.display_ops.select_vhost_multiple.""" + """Tests for certbot_apache._internal.display_ops.select_vhost_multiple.""" def setUp(self): self.base_dir = "/example_path" @@ -45,7 +45,7 @@ class SelectVhostMultiTest(unittest.TestCase): self.assertFalse(vhs) class SelectVhostTest(unittest.TestCase): - """Tests for certbot_apache.display_ops.select_vhost.""" + """Tests for certbot_apache._internal.display_ops.select_vhost.""" def setUp(self): self.base_dir = "/example_path" @@ -54,7 +54,7 @@ class SelectVhostTest(unittest.TestCase): @classmethod def _call(cls, vhosts): - from certbot_apache.display_ops import select_vhost + from certbot_apache._internal.display_ops import select_vhost return select_vhost("example.com", vhosts) @certbot_util.patch_get_utility() @@ -81,9 +81,9 @@ class SelectVhostTest(unittest.TestCase): def test_no_vhosts(self): self.assertEqual(self._call([]), None) - @mock.patch("certbot_apache.display_ops.display_util") + @mock.patch("certbot_apache._internal.display_ops.display_util") @certbot_util.patch_get_utility() - @mock.patch("certbot_apache.display_ops.logger") + @mock.patch("certbot_apache._internal.display_ops.logger") def test_small_display(self, mock_logger, mock_util, mock_display_util): mock_display_util.WIDTH = 20 mock_util().menu.return_value = (display_util.OK, 0) diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/certbot_apache/tests/entrypoint_test.py index 9adcd46dc..04c393bdf 100644 --- a/certbot-apache/certbot_apache/tests/entrypoint_test.py +++ b/certbot-apache/certbot_apache/tests/entrypoint_test.py @@ -1,10 +1,10 @@ -"""Test for certbot_apache.entrypoint for override class resolution""" +"""Test for certbot_apache._internal.entrypoint for override class resolution""" import unittest import mock -from certbot_apache import configurator -from certbot_apache import entrypoint +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint class EntryPointTest(unittest.TestCase): diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/certbot_apache/tests/fedora_test.py index 4d3f3a313..0db6eb60a 100644 --- a/certbot-apache/certbot_apache/tests/fedora_test.py +++ b/certbot-apache/certbot_apache/tests/fedora_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator for Fedora 29+ overrides""" +"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides""" import unittest import mock @@ -7,8 +7,8 @@ from certbot import errors from certbot.compat import filesystem from certbot.compat import os -from certbot_apache import obj -from certbot_apache import override_fedora +from certbot_apache._internal import obj +from certbot_apache._internal import override_fedora from certbot_apache.tests import util @@ -58,7 +58,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test() def test_fedora_restart_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] @@ -68,7 +68,7 @@ class FedoraRestartTest(util.ApacheTest): self._run_fedora_test) def test_fedora_restart(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: with mock.patch("certbot.util.run_script") as mock_run: # First call raises error, second doesn't @@ -101,7 +101,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -135,7 +135,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): self.assertTrue("TEST2" in self.config.parser.variables.keys()) self.assertTrue("mod_another.c" in self.config.parser.modules) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_get_version(self, mock_run_script): mock_run_script.return_value = ('', None) self.assertRaises(errors.PluginError, self.config.get_version) @@ -156,7 +156,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls @@ -177,13 +177,13 @@ class MultipleVhostsTestFedora(util.ApacheTest): self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py index d0d3ba0dd..2eb6335b4 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/certbot_apache/tests/gentoo_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator for Gentoo overrides""" +"""Test for certbot_apache._internal.configurator for Gentoo overrides""" import unittest import mock @@ -7,8 +7,8 @@ from certbot import errors from certbot.compat import filesystem from certbot.compat import os -from certbot_apache import obj -from certbot_apache import override_gentoo +from certbot_apache._internal import obj +from certbot_apache._internal import override_gentoo from certbot_apache.tests import util @@ -52,7 +52,8 @@ class MultipleVhostsTestGentoo(util.ApacheTest): config_root=config_root, vhost_root=vhost_root) - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_runtime_variables"): + # pylint: disable=line-too-long + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"): self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="gentoo") @@ -85,17 +86,17 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.config.parser.apacheconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../conf.d/apache2")) self.config.parser.variables = {} - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"): + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() for define in defines: self.assertTrue(define in self.config.parser.variables.keys()) - @mock.patch("certbot_apache.parser.ApacheParser.parse_from_subprocess") + @mock.patch("certbot_apache._internal.parser.ApacheParser.parse_from_subprocess") def test_no_binary_configdump(self, mock_subprocess): """Make sure we don't call binary dumps other than modules from Apache as this is not supported in Gentoo currently""" - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"): + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() self.config.parser.reset_modules() self.assertFalse(mock_subprocess.called) @@ -104,7 +105,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.config.parser.reset_modules() self.assertTrue(mock_subprocess.called) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): mod_val = ( 'Loaded Modules:\n' @@ -128,7 +129,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): 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") + @mock.patch("certbot_apache._internal.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() diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py index fade85b3a..ab83bfb8b 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/certbot_apache/tests/http_01_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.http_01.""" +"""Test for certbot_apache._internal.http_01.""" import unittest import mock @@ -11,7 +11,7 @@ from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util -from certbot_apache.parser import get_aug_path +from certbot_apache._internal.parser import get_aug_path from certbot_apache.tests import util @@ -19,7 +19,7 @@ NUM_ACHALLS = 3 class ApacheHttp01Test(util.ApacheTest): - """Test for certbot_apache.http_01.ApacheHttp01.""" + """Test for certbot_apache._internal.http_01.ApacheHttp01.""" def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ super(ApacheHttp01Test, self).setUp(*args, **kwargs) @@ -45,13 +45,13 @@ class ApacheHttp01Test(util.ApacheTest): self.config.parser.modules.add("mod_{0}.c".format(mod)) self.config.parser.modules.add(mod + "_module") - from certbot_apache.http_01 import ApacheHttp01 + from certbot_apache._internal.http_01 import ApacheHttp01 self.http = ApacheHttp01(self.config) def test_empty_perform(self): self.assertFalse(self.http.perform()) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_enable_modules_apache_2_2(self, mock_enmod): self.config.version = (2, 2) self.config.parser.modules.remove("authz_host_module") @@ -60,7 +60,7 @@ class ApacheHttp01Test(util.ApacheTest): enmod_calls = self.common_enable_modules_test(mock_enmod) self.assertEqual(enmod_calls[0][0][0], "authz_host") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_enable_modules_apache_2_4(self, mock_enmod): self.config.parser.modules.remove("authz_core_module") self.config.parser.modules.remove("mod_authz_core.c") diff --git a/certbot-apache/certbot_apache/tests/obj_test.py b/certbot-apache/certbot_apache/tests/obj_test.py index 10dba18bc..1761b9c94 100644 --- a/certbot-apache/certbot_apache/tests/obj_test.py +++ b/certbot-apache/certbot_apache/tests/obj_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_apache.obj.""" +"""Tests for certbot_apache._internal.obj.""" import unittest @@ -6,8 +6,8 @@ class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from certbot_apache.obj import Addr - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost self.addr1 = Addr.fromstring("127.0.0.1") self.addr2 = Addr.fromstring("127.0.0.1:443") @@ -23,7 +23,8 @@ class VirtualHostTest(unittest.TestCase): "fp", "vhp", set([self.addr2]), False, False, "localhost") def test_repr(self): - self.assertEqual(repr(self.addr2), "certbot_apache.obj.Addr(('127.0.0.1', '443'))") + self.assertEqual(repr(self.addr2), + "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))") def test_eq(self): self.assertTrue(self.vhost1b == self.vhost1) @@ -36,8 +37,8 @@ class VirtualHostTest(unittest.TestCase): self.assertFalse(self.vhost1 != self.vhost1b) def test_conflicts(self): - from certbot_apache.obj import Addr - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost complex_vh = VirtualHost( "fp", "vhp", @@ -54,7 +55,7 @@ class VirtualHostTest(unittest.TestCase): self.addr_default])) def test_same_server(self): - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import VirtualHost no_name1 = VirtualHost( "fp", "vhp", set([self.addr1]), False, False, None) no_name2 = VirtualHost( @@ -77,7 +78,7 @@ class VirtualHostTest(unittest.TestCase): class AddrTest(unittest.TestCase): """Test obj.Addr.""" def setUp(self): - from certbot_apache.obj import Addr + from certbot_apache._internal.obj import Addr self.addr = Addr.fromstring("*:443") self.addr1 = Addr.fromstring("127.0.0.1") @@ -92,7 +93,7 @@ class AddrTest(unittest.TestCase): self.assertTrue(self.addr2.is_wildcard()) def test_get_sni_addr(self): - from certbot_apache.obj import Addr + from certbot_apache._internal.obj import Addr self.assertEqual( self.addr.get_sni_addr("443"), Addr.fromstring("*:443")) self.assertEqual( diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py index 0b92a0b92..9c650eeda 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/certbot_apache/tests/parser_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_apache.parser.""" +"""Tests for certbot_apache._internal.parser.""" import shutil import unittest @@ -113,7 +113,7 @@ class BasicParserTest(util.ParserTest): Path must be valid before attempting to add to augeas """ - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path # This makes sure that find_dir will work self.parser.modules.add("mod_ssl.c") @@ -127,7 +127,7 @@ class BasicParserTest(util.ParserTest): self.assertTrue("IfModule" in matches[0]) def test_add_dir_to_ifmodssl_multiple(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path # This makes sure that find_dir will work self.parser.modules.add("mod_ssl.c") @@ -141,11 +141,11 @@ class BasicParserTest(util.ParserTest): self.assertTrue("IfModule" in matches[0]) def test_get_aug_path(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) def test_set_locations(self): - with mock.patch("certbot_apache.parser.os.path") as mock_path: + with mock.patch("certbot_apache._internal.parser.os.path") as mock_path: mock_path.isfile.side_effect = [False, False] @@ -155,18 +155,18 @@ class BasicParserTest(util.ParserTest): self.assertEqual(results["default"], results["listen"]) self.assertEqual(results["default"], results["name"]) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser.get_arg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser.get_arg") def test_parse_modules_bad_syntax(self, mock_arg, mock_find): mock_find.return_value = ["1", "2", "3", "4", "5", "6", "7", "8"] mock_arg.return_value = None - with mock.patch("certbot_apache.parser.logger") as mock_logger: + with mock.patch("certbot_apache._internal.parser.logger") as mock_logger: self.parser.parse_modules() # Make sure that we got None return value and logged the file self.assertTrue(mock_logger.debug.called) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_variables(self, mock_cfg, _): define_val = ( 'ServerRoot: "/etc/apache2"\n' @@ -263,7 +263,7 @@ class BasicParserTest(util.ParserTest): self.parser.modules = set() with mock.patch( - "certbot_apache.parser.ApacheParser.parse_file") as mock_parse: + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() self.assertEqual(self.parser.variables, expected_vars) self.assertEqual(len(self.parser.modules), 58) @@ -271,8 +271,8 @@ class BasicParserTest(util.ParserTest): # Make sure we tried to include them all. self.assertEqual(mock_parse.call_count, 25) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_variables_alt_values(self, mock_cfg, _): inc_val = ( 'Included configuration files:\n' @@ -286,7 +286,7 @@ class BasicParserTest(util.ParserTest): self.parser.modules = set() with mock.patch( - "certbot_apache.parser.ApacheParser.parse_file") as mock_parse: + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() # No matching modules should have been found self.assertEqual(len(self.parser.modules), 0) @@ -294,7 +294,7 @@ class BasicParserTest(util.ParserTest): # path derived from root configuration Include statements self.assertEqual(mock_parse.call_count, 1) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_vars_bad_output(self, mock_cfg): mock_cfg.return_value = "Define: TLS=443=24" self.parser.update_runtime_variables() @@ -303,8 +303,8 @@ class BasicParserTest(util.ParserTest): self.assertRaises( errors.PluginError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.option") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): mock_popen.side_effect = OSError mock_opt.return_value = "nonexistent" @@ -312,7 +312,7 @@ class BasicParserTest(util.ParserTest): errors.MisconfigurationError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_update_runtime_vars_bad_exit(self, mock_popen): mock_popen().communicate.return_value = ("", "") mock_popen.returncode = -1 @@ -321,7 +321,7 @@ class BasicParserTest(util.ParserTest): self.parser.update_runtime_variables) def test_add_comment(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456") comm = self.parser.find_comments("123456") self.assertEqual(len(comm), 1) @@ -337,9 +337,9 @@ class ParserInitTest(util.ApacheTest): shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) - @mock.patch("certbot_apache.parser.ApacheParser.init_augeas") + @mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas") def test_prepare_no_augeas(self, mock_init_augeas): - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser mock_init_augeas.side_effect = errors.NoInstallationError self.config.config_test = mock.Mock() self.assertRaises( @@ -348,17 +348,17 @@ class ParserInitTest(util.ApacheTest): version=(2, 4, 22), configurator=self.config) def test_init_old_aug(self): - from certbot_apache.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser.check_aug_version") as mock_c: + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser.check_aug_version") as mock_c: mock_c.return_value = False self.assertRaises( errors.NotSupportedError, ApacheParser, os.path.relpath(self.config_path), "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_unparseable(self, mock_cfg): - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser mock_cfg.return_value = ('Define: TEST') self.assertRaises( errors.PluginError, @@ -366,9 +366,9 @@ class ParserInitTest(util.ApacheTest): "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config) def test_root_normalized(self): - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): path = os.path.join( self.temp_dir, @@ -379,8 +379,8 @@ class ParserInitTest(util.ApacheTest): self.assertEqual(parser.root, self.config_path) def test_root_absolute(self): - from certbot_apache.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( os.path.relpath(self.config_path), @@ -389,8 +389,8 @@ class ParserInitTest(util.ApacheTest): self.assertEqual(parser.root, self.config_path) def test_root_no_trailing_slash(self): - from certbot_apache.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( self.config_path + os.path.sep, diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py index aa94a943f..3349e0ed8 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/certbot_apache/tests/util.py @@ -13,9 +13,9 @@ from certbot.display import util as display_util from certbot.plugins import common from certbot.tests import util as test_util -from certbot_apache import configurator -from certbot_apache import entrypoint -from certbot_apache import obj +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint +from certbot_apache._internal import obj class ApacheTest(unittest.TestCase): @@ -72,10 +72,10 @@ class ParserTest(ApacheTest): zope.component.provideUtility(display_util.FileDisplay(sys.stdout, False)) - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser self.aug = augeas.Augeas( flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): self.parser = ApacheParser( self.config_path, self.vhost_path, configurator=self.config) @@ -105,11 +105,11 @@ def get_apache_configurator( in_progress_dir=os.path.join(backups, "IN_PROGRESS"), work_dir=work_dir) - with mock.patch("certbot_apache.configurator.util.run_script"): - with mock.patch("certbot_apache.configurator.util." + with mock.patch("certbot_apache._internal.configurator.util.run_script"): + with mock.patch("certbot_apache._internal.configurator.util." "exe_exists") as mock_exe_exists: mock_exe_exists.return_value = True - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): try: config_class = entrypoint.OVERRIDE_CLASSES[os_info] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index c8e5e7073..5b62f37d7 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -71,7 +71,7 @@ setup( install_requires=install_requires, entry_points={ 'certbot.plugins': [ - 'apache = certbot_apache.entrypoint:ENTRYPOINT', + 'apache = certbot_apache._internal.entrypoint:ENTRYPOINT', ], }, test_suite='certbot_apache', diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 68978ec81..67cbc7ad9 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -9,7 +9,7 @@ import zope.interface from certbot._internal import configuration from certbot import errors as le_errors from certbot import util as certbot_util -from certbot_apache import entrypoint +from certbot_apache._internal import entrypoint from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces from certbot_compatibility_test import util @@ -27,7 +27,7 @@ class Proxy(configurators_common.Proxy): self.modules = self.server_root = self.test_conf = self.version = None patch = mock.patch( - "certbot_apache.configurator.display_ops.select_vhost") + "certbot_apache._internal.configurator.display_ops.select_vhost") mock_display = patch.start() mock_display.side_effect = le_errors.PluginError( "Unable to determine vhost") diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/obj.py index dff48ce02..1a92c8b37 100644 --- a/certbot-nginx/certbot_nginx/obj.py +++ b/certbot-nginx/certbot_nginx/obj.py @@ -121,7 +121,7 @@ class Addr(common.Addr): def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ - # See certbot-apache/certbot_apache/obj.py for more information + # See certbot-apache/certbot_apache/_internal/obj.py for more information return super(Addr, self).__hash__() def super_eq(self, other): diff --git a/certbot-nginx/certbot_nginx/tests/display_ops_test.py b/certbot-nginx/certbot_nginx/tests/display_ops_test.py index e3c6fb66b..d36765767 100644 --- a/certbot-nginx/certbot_nginx/tests/display_ops_test.py +++ b/certbot-nginx/certbot_nginx/tests/display_ops_test.py @@ -1,4 +1,4 @@ -"""Test certbot_apache.display_ops.""" +"""Test certbot_nginx._internal.display_ops.""" import unittest from certbot.display import util as display_util diff --git a/docs/install.rst b/docs/install.rst index 1e709e2ee..1cadc9453 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -41,7 +41,7 @@ client as root, either `letsencrypt-nosudo The Apache plugin currently requires an OS with augeas version 1.0; currently `it supports -`_ +`_ modern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin. diff --git a/docs/using.rst b/docs/using.rst index 83d824058..a67d28c89 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -58,8 +58,8 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate. | 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) or - | perform domain validation yourself. Additionally allows you dns-01_ (53) - | to specify scripts to automate the validation task in a + | perform domain validation yourself. Additionally allows you dns-01_ (53) + | to specify scripts to automate the validation task in a | customized way. =========== ==== ==== =============================================================== ============================= @@ -83,7 +83,7 @@ Apache ------ The Apache plugin currently `supports -`_ +`_ modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. This automates both obtaining *and* installing certificates on an Apache webserver. To specify this plugin on the command line, simply include @@ -680,8 +680,8 @@ Where are my certificates? ========================== All generated keys and issued certificates can be found in -``/etc/letsencrypt/live/$domain``. In the case of creating a SAN certificate -with multiple alternative names, ``$domain`` is the first domain passed in +``/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 -- cgit v1.2.3 From d56cd4ef01d3dbc1323c3464cedc7d150bb854f1 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 10:26:05 -0800 Subject: Make the contents of the DNS plugins private (#7580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #5775. ``` modify_item () { mkdir certbot-dns-$1/certbot_dns_$1/_internal git grep -l "from certbot_dns_$1 import dns_$1" | xargs sed -i "s/from certbot_dns_$1 import dns_$1/from certbot_dns_$1._internal import dns_$1/g" git grep -l "certbot_dns_$1\.dns_$1" | xargs sed -i "s/certbot_dns_$1\.dns_$1/certbot_dns_$1._internal.dns_$1/g" git checkout -- certbot-dns-$1/certbot_dns_$1/__init__.py echo '"""Internal implementation of \`~certbot_dns_$1.dns_$1\` plugin."""' > certbot-dns-$1/certbot_dns_$1/_internal/__init__.py mv certbot-dns-$1/certbot_dns_$1/dns_$1.py certbot-dns-$1/certbot_dns_$1/_internal git checkout -- CHANGELOG.md git status git add -A git commit -m "Move certbot-dns-$1 to _internal structure" } ``` Structure now looks like this: ``` certbot-dns-cloudflare/ ├── certbot_dns_cloudflare │   ├── dns_cloudflare_test.py │   ├── __init__.py │   └── _internal │   ├── dns_cloudflare.py │   └── __init__.py ``` * Move certbot-dns-cloudflare to _internal structure * Move certbot-dns-cloudxns to _internal structure * Move certbot-dns-digitalocean to _internal structure * Move certbot-dns-dnsimple to _internal structure * Move certbot-dns-dnsmadeeasy to _internal structure * Move certbot-dns-gehirn to _internal structure * Move certbot-dns-google to _internal structure * Move certbot-dns-linode to _internal structure * Move certbot-dns-luadns to _internal structure * Move certbot-dns-nsone to _internal structure * Move certbot-dns-ovh to _internal structure * Move certbot-dns-rfc2136 to _internal structure * Move certbot-dns-sakuracloud to _internal structure * Init file comments need to be comments * Move certbot-dns-route53 to _internal structure * Fix comment in route53 init --- .../certbot_dns_cloudflare/_internal/__init__.py | 1 + .../_internal/dns_cloudflare.py | 199 +++++++++++++ .../certbot_dns_cloudflare/dns_cloudflare.py | 199 ------------- .../certbot_dns_cloudflare/dns_cloudflare_test.py | 6 +- certbot-dns-cloudflare/setup.py | 2 +- .../certbot_dns_cloudxns/_internal/__init__.py | 1 + .../certbot_dns_cloudxns/_internal/dns_cloudxns.py | 87 ++++++ .../certbot_dns_cloudxns/dns_cloudxns.py | 87 ------ .../certbot_dns_cloudxns/dns_cloudxns_test.py | 6 +- certbot-dns-cloudxns/setup.py | 2 +- .../certbot_dns_digitalocean/_internal/__init__.py | 1 + .../_internal/dns_digitalocean.py | 168 +++++++++++ .../certbot_dns_digitalocean/dns_digitalocean.py | 168 ----------- .../dns_digitalocean_test.py | 6 +- certbot-dns-digitalocean/setup.py | 2 +- .../certbot_dns_dnsimple/_internal/__init__.py | 1 + .../certbot_dns_dnsimple/_internal/dns_dnsimple.py | 82 ++++++ .../certbot_dns_dnsimple/dns_dnsimple.py | 82 ------ .../certbot_dns_dnsimple/dns_dnsimple_test.py | 6 +- certbot-dns-dnsimple/setup.py | 2 +- .../certbot_dns_dnsmadeeasy/_internal/__init__.py | 1 + .../_internal/dns_dnsmadeeasy.py | 92 ++++++ .../certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py | 92 ------ .../dns_dnsmadeeasy_test.py | 6 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- .../certbot_dns_gehirn/_internal/__init__.py | 1 + .../certbot_dns_gehirn/_internal/dns_gehirn.py | 87 ++++++ .../certbot_dns_gehirn/dns_gehirn.py | 87 ------ .../certbot_dns_gehirn/dns_gehirn_test.py | 6 +- certbot-dns-gehirn/setup.py | 2 +- .../certbot_dns_google/_internal/__init__.py | 1 + .../certbot_dns_google/_internal/dns_google.py | 307 +++++++++++++++++++++ .../certbot_dns_google/dns_google.py | 307 --------------------- .../certbot_dns_google/dns_google_test.py | 46 +-- certbot-dns-google/setup.py | 2 +- .../certbot_dns_linode/_internal/__init__.py | 1 + .../certbot_dns_linode/_internal/dns_linode.py | 108 ++++++++ .../certbot_dns_linode/dns_linode.py | 108 -------- .../certbot_dns_linode/dns_linode_test.py | 8 +- certbot-dns-linode/setup.py | 2 +- .../certbot_dns_luadns/_internal/__init__.py | 1 + .../certbot_dns_luadns/_internal/dns_luadns.py | 86 ++++++ .../certbot_dns_luadns/dns_luadns.py | 86 ------ .../certbot_dns_luadns/dns_luadns_test.py | 6 +- certbot-dns-luadns/setup.py | 2 +- .../certbot_dns_nsone/_internal/__init__.py | 1 + .../certbot_dns_nsone/_internal/dns_nsone.py | 85 ++++++ certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py | 85 ------ .../certbot_dns_nsone/dns_nsone_test.py | 6 +- certbot-dns-nsone/setup.py | 2 +- .../certbot_dns_ovh/_internal/__init__.py | 1 + .../certbot_dns_ovh/_internal/dns_ovh.py | 105 +++++++ certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py | 105 ------- certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py | 6 +- certbot-dns-ovh/setup.py | 2 +- .../certbot_dns_rfc2136/_internal/__init__.py | 1 + .../certbot_dns_rfc2136/_internal/dns_rfc2136.py | 226 +++++++++++++++ .../certbot_dns_rfc2136/dns_rfc2136.py | 226 --------------- .../certbot_dns_rfc2136/dns_rfc2136_test.py | 6 +- certbot-dns-rfc2136/setup.py | 2 +- .../certbot_dns_route53/_internal/__init__.py | 1 + .../certbot_dns_route53/_internal/dns_route53.py | 151 ++++++++++ .../certbot_dns_route53/authenticator.py | 7 +- .../certbot_dns_route53/dns_route53.py | 151 ---------- .../certbot_dns_route53/dns_route53_test.py | 6 +- certbot-dns-route53/setup.py | 2 +- .../certbot_dns_sakuracloud/_internal/__init__.py | 1 + .../_internal/dns_sakuracloud.py | 90 ++++++ .../certbot_dns_sakuracloud/dns_sakuracloud.py | 90 ------ .../dns_sakuracloud_test.py | 6 +- certbot-dns-sakuracloud/setup.py | 2 +- 71 files changed, 1969 insertions(+), 1952 deletions(-) create mode 100644 certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py create mode 100644 certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py delete mode 100644 certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py create mode 100644 certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py create mode 100644 certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py delete mode 100644 certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py create mode 100644 certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py create mode 100644 certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py delete mode 100644 certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py create mode 100644 certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py create mode 100644 certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py delete mode 100644 certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py create mode 100644 certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py create mode 100644 certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py delete mode 100644 certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py create mode 100644 certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py create mode 100644 certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py delete mode 100644 certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py create mode 100644 certbot-dns-google/certbot_dns_google/_internal/__init__.py create mode 100644 certbot-dns-google/certbot_dns_google/_internal/dns_google.py delete mode 100644 certbot-dns-google/certbot_dns_google/dns_google.py create mode 100644 certbot-dns-linode/certbot_dns_linode/_internal/__init__.py create mode 100644 certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py delete mode 100644 certbot-dns-linode/certbot_dns_linode/dns_linode.py create mode 100644 certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py create mode 100644 certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py delete mode 100644 certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py create mode 100644 certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py create mode 100644 certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py delete mode 100644 certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py create mode 100644 certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py create mode 100644 certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py delete mode 100644 certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py create mode 100644 certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py create mode 100644 certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py delete mode 100644 certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py create mode 100644 certbot-dns-route53/certbot_dns_route53/_internal/__init__.py create mode 100644 certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py delete mode 100644 certbot-dns-route53/certbot_dns_route53/dns_route53.py create mode 100644 certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py create mode 100644 certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py delete mode 100644 certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py new file mode 100644 index 000000000..93b0672b5 --- /dev/null +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_cloudflare.dns_cloudflare` plugin.""" diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py new file mode 100644 index 000000000..0bbdf703a --- /dev/null +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -0,0 +1,199 @@ +"""DNS Authenticator for Cloudflare.""" +import logging + +import CloudFlare +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for Cloudflare + + This Authenticator uses the Cloudflare API to fulfill a dns-01 challenge. + """ + + description = ('Obtain certificates using a DNS TXT record (if you are using Cloudflare for ' + 'DNS).') + ttl = 120 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add) + add('credentials', help='Cloudflare credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the Cloudflare API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'Cloudflare credentials INI file', + { + 'email': 'email address associated with Cloudflare account', + 'api-key': 'API key for Cloudflare account, obtained from {0}'.format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_cloudflare_client().add_txt_record(domain, validation_name, validation, self.ttl) + + def _cleanup(self, domain, validation_name, validation): + self._get_cloudflare_client().del_txt_record(domain, validation_name, validation) + + def _get_cloudflare_client(self): + return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key')) + + +class _CloudflareClient(object): + """ + Encapsulates all communication with the Cloudflare API. + """ + + def __init__(self, email, api_key): + self.cf = CloudFlare.CloudFlare(email, api_key) + + def add_txt_record(self, domain, record_name, record_content, record_ttl): + """ + Add a TXT record using the supplied information. + + :param str domain: The domain to use to look up the Cloudflare zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :param int record_ttl: The record TTL (number of seconds that the record may be cached). + :raises certbot.errors.PluginError: if an error occurs communicating with the Cloudflare API + """ + + zone_id = self._find_zone_id(domain) + + data = {'type': 'TXT', + 'name': record_name, + 'content': record_content, + 'ttl': record_ttl} + + try: + logger.debug('Attempting to add record to zone %s: %s', zone_id, data) + self.cf.zones.dns_records.post(zone_id, data=data) # zones | pylint: disable=no-member + except CloudFlare.exceptions.CloudFlareAPIError as e: + logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e) + raise errors.PluginError('Error communicating with the Cloudflare API: {0}'.format(e)) + + record_id = self._find_txt_record_id(zone_id, record_name, record_content) + logger.debug('Successfully added TXT record with record_id: %s', record_id) + + def del_txt_record(self, domain, record_name, record_content): + """ + Delete a TXT record using the supplied information. + + Note that both the record's name and content are used to ensure that similar records + created concurrently (e.g., due to concurrent invocations of this plugin) are not deleted. + + Failures are logged, but not raised. + + :param str domain: The domain to use to look up the Cloudflare zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + """ + + try: + zone_id = self._find_zone_id(domain) + except errors.PluginError as e: + logger.debug('Encountered error finding zone_id during deletion: %s', e) + return + + if zone_id: + record_id = self._find_txt_record_id(zone_id, record_name, record_content) + if record_id: + try: + # zones | pylint: disable=no-member + self.cf.zones.dns_records.delete(zone_id, record_id) + logger.debug('Successfully deleted TXT record.') + except CloudFlare.exceptions.CloudFlareAPIError as e: + logger.warning('Encountered CloudFlareAPIError deleting TXT record: %s', e) + else: + logger.debug('TXT record not found; no cleanup needed.') + else: + logger.debug('Zone not found; no cleanup needed.') + + def _find_zone_id(self, domain): + """ + Find the zone_id for a given domain. + + :param str domain: The domain for which to find the zone_id. + :returns: The zone_id, if found. + :rtype: str + :raises certbot.errors.PluginError: if no zone_id is found. + """ + + zone_name_guesses = dns_common.base_domain_name_guesses(domain) + + for zone_name in zone_name_guesses: + params = {'name': zone_name, + 'per_page': 1} + + try: + zones = self.cf.zones.get(params=params) # zones | pylint: disable=no-member + except CloudFlare.exceptions.CloudFlareAPIError as e: + code = int(e) + hint = None + + if code == 6003: + hint = 'Did you copy your entire API key?' + elif code == 9103: + hint = 'Did you enter the correct email address?' + + raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm that ' + 'you have supplied valid Cloudflare API credentials.{2}' + .format(code, e, ' ({0})'.format(hint) if hint else '')) + + if zones: + zone_id = zones[0]['id'] + logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name) + return zone_id + + raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. ' + 'Please confirm that the domain name has been entered correctly ' + 'and is already associated with the supplied Cloudflare account.' + .format(domain, zone_name_guesses)) + + def _find_txt_record_id(self, zone_id, record_name, record_content): + """ + Find the record_id for a TXT record with the given name and content. + + :param str zone_id: The zone_id which contains the record. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :returns: The record_id, if found. + :rtype: str + """ + + params = {'type': 'TXT', + 'name': record_name, + 'content': record_content, + 'per_page': 1} + try: + # zones | pylint: disable=no-member + records = self.cf.zones.dns_records.get(zone_id, params=params) + except CloudFlare.exceptions.CloudFlareAPIError as e: + logger.debug('Encountered CloudFlareAPIError getting TXT record_id: %s', e) + records = [] + + if records: + # Cleanup is returning the system to the state we found it. If, for some reason, + # there are multiple matching records, we only delete one because we only added one. + return records[0]['id'] + logger.debug('Unable to find TXT record.') + return None diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py deleted file mode 100644 index 0bbdf703a..000000000 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py +++ /dev/null @@ -1,199 +0,0 @@ -"""DNS Authenticator for Cloudflare.""" -import logging - -import CloudFlare -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Cloudflare - - This Authenticator uses the Cloudflare API to fulfill a dns-01 challenge. - """ - - description = ('Obtain certificates using a DNS TXT record (if you are using Cloudflare for ' - 'DNS).') - ttl = 120 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add) - add('credentials', help='Cloudflare credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Cloudflare API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'Cloudflare credentials INI file', - { - 'email': 'email address associated with Cloudflare account', - 'api-key': 'API key for Cloudflare account, obtained from {0}'.format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_cloudflare_client().add_txt_record(domain, validation_name, validation, self.ttl) - - def _cleanup(self, domain, validation_name, validation): - self._get_cloudflare_client().del_txt_record(domain, validation_name, validation) - - def _get_cloudflare_client(self): - return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key')) - - -class _CloudflareClient(object): - """ - Encapsulates all communication with the Cloudflare API. - """ - - def __init__(self, email, api_key): - self.cf = CloudFlare.CloudFlare(email, api_key) - - def add_txt_record(self, domain, record_name, record_content, record_ttl): - """ - Add a TXT record using the supplied information. - - :param str domain: The domain to use to look up the Cloudflare zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the Cloudflare API - """ - - zone_id = self._find_zone_id(domain) - - data = {'type': 'TXT', - 'name': record_name, - 'content': record_content, - 'ttl': record_ttl} - - try: - logger.debug('Attempting to add record to zone %s: %s', zone_id, data) - self.cf.zones.dns_records.post(zone_id, data=data) # zones | pylint: disable=no-member - except CloudFlare.exceptions.CloudFlareAPIError as e: - logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e) - raise errors.PluginError('Error communicating with the Cloudflare API: {0}'.format(e)) - - record_id = self._find_txt_record_id(zone_id, record_name, record_content) - logger.debug('Successfully added TXT record with record_id: %s', record_id) - - def del_txt_record(self, domain, record_name, record_content): - """ - Delete a TXT record using the supplied information. - - Note that both the record's name and content are used to ensure that similar records - created concurrently (e.g., due to concurrent invocations of this plugin) are not deleted. - - Failures are logged, but not raised. - - :param str domain: The domain to use to look up the Cloudflare zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - """ - - try: - zone_id = self._find_zone_id(domain) - except errors.PluginError as e: - logger.debug('Encountered error finding zone_id during deletion: %s', e) - return - - if zone_id: - record_id = self._find_txt_record_id(zone_id, record_name, record_content) - if record_id: - try: - # zones | pylint: disable=no-member - self.cf.zones.dns_records.delete(zone_id, record_id) - logger.debug('Successfully deleted TXT record.') - except CloudFlare.exceptions.CloudFlareAPIError as e: - logger.warning('Encountered CloudFlareAPIError deleting TXT record: %s', e) - else: - logger.debug('TXT record not found; no cleanup needed.') - else: - logger.debug('Zone not found; no cleanup needed.') - - def _find_zone_id(self, domain): - """ - Find the zone_id for a given domain. - - :param str domain: The domain for which to find the zone_id. - :returns: The zone_id, if found. - :rtype: str - :raises certbot.errors.PluginError: if no zone_id is found. - """ - - zone_name_guesses = dns_common.base_domain_name_guesses(domain) - - for zone_name in zone_name_guesses: - params = {'name': zone_name, - 'per_page': 1} - - try: - zones = self.cf.zones.get(params=params) # zones | pylint: disable=no-member - except CloudFlare.exceptions.CloudFlareAPIError as e: - code = int(e) - hint = None - - if code == 6003: - hint = 'Did you copy your entire API key?' - elif code == 9103: - hint = 'Did you enter the correct email address?' - - raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm that ' - 'you have supplied valid Cloudflare API credentials.{2}' - .format(code, e, ' ({0})'.format(hint) if hint else '')) - - if zones: - zone_id = zones[0]['id'] - logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name) - return zone_id - - raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. ' - 'Please confirm that the domain name has been entered correctly ' - 'and is already associated with the supplied Cloudflare account.' - .format(domain, zone_name_guesses)) - - def _find_txt_record_id(self, zone_id, record_name, record_content): - """ - Find the record_id for a TXT record with the given name and content. - - :param str zone_id: The zone_id which contains the record. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :returns: The record_id, if found. - :rtype: str - """ - - params = {'type': 'TXT', - 'name': record_name, - 'content': record_content, - 'per_page': 1} - try: - # zones | pylint: disable=no-member - records = self.cf.zones.dns_records.get(zone_id, params=params) - except CloudFlare.exceptions.CloudFlareAPIError as e: - logger.debug('Encountered CloudFlareAPIError getting TXT record_id: %s', e) - records = [] - - if records: - # Cleanup is returning the system to the state we found it. If, for some reason, - # there are multiple matching records, we only delete one because we only added one. - return records[0]['id'] - logger.debug('Unable to find TXT record.') - return None diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py index 4b9419ca8..b24628b0d 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_cloudflare.dns_cloudflare.""" +"""Tests for certbot_dns_cloudflare._internal.dns_cloudflare.""" import unittest @@ -19,7 +19,7 @@ EMAIL = 'example@example.com' class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_cloudflare.dns_cloudflare import Authenticator + from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator super(AuthenticatorTest, self).setUp() @@ -58,7 +58,7 @@ class CloudflareClientTest(unittest.TestCase): record_id = 2 def setUp(self): - from certbot_dns_cloudflare.dns_cloudflare import _CloudflareClient + from certbot_dns_cloudflare._internal.dns_cloudflare import _CloudflareClient self.cloudflare_client = _CloudflareClient(EMAIL, API_KEY) diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 6b72d0a76..c059a1ed5 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-cloudflare = certbot_dns_cloudflare.dns_cloudflare:Authenticator', + 'dns-cloudflare = certbot_dns_cloudflare._internal.dns_cloudflare:Authenticator', ], }, test_suite='certbot_dns_cloudflare', diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py new file mode 100644 index 000000000..e2177417d --- /dev/null +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_cloudxns.dns_cloudxns` plugin.""" diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py new file mode 100644 index 000000000..5132137f8 --- /dev/null +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -0,0 +1,87 @@ +"""DNS Authenticator for CloudXNS DNS.""" +import logging + +import zope.interface +from lexicon.providers import cloudxns + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://www.cloudxns.net/en/AccountManage/apimanage.html' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for CloudXNS DNS + + This Authenticator uses the CloudXNS DNS API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record (if you are using CloudXNS for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='CloudXNS credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the CloudXNS API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'CloudXNS credentials INI file', + { + 'api-key': 'API key for CloudXNS account, obtained from {0}'.format(ACCOUNT_URL), + 'secret-key': 'Secret key for CloudXNS account, obtained from {0}' + .format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_cloudxns_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_cloudxns_client().del_txt_record(domain, validation_name, validation) + + def _get_cloudxns_client(self): + return _CloudXNSLexiconClient(self.credentials.conf('api-key'), + self.credentials.conf('secret-key'), + self.ttl) + + +class _CloudXNSLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the CloudXNS via Lexicon. + """ + + def __init__(self, api_key, secret_key, ttl): + super(_CloudXNSLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('cloudxns', { + 'ttl': ttl, + }, { + 'auth_username': api_key, + 'auth_token': secret_key, + }) + + self.provider = cloudxns.Provider(config) + + def _handle_http_error(self, e, domain_name): + hint = None + if str(e).startswith('400 Client Error:'): + hint = 'Are your API key and Secret key values correct?' + + return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' + .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py deleted file mode 100644 index 5132137f8..000000000 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py +++ /dev/null @@ -1,87 +0,0 @@ -"""DNS Authenticator for CloudXNS DNS.""" -import logging - -import zope.interface -from lexicon.providers import cloudxns - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://www.cloudxns.net/en/AccountManage/apimanage.html' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for CloudXNS DNS - - This Authenticator uses the CloudXNS DNS API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record (if you are using CloudXNS for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='CloudXNS credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the CloudXNS API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'CloudXNS credentials INI file', - { - 'api-key': 'API key for CloudXNS account, obtained from {0}'.format(ACCOUNT_URL), - 'secret-key': 'Secret key for CloudXNS account, obtained from {0}' - .format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_cloudxns_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_cloudxns_client().del_txt_record(domain, validation_name, validation) - - def _get_cloudxns_client(self): - return _CloudXNSLexiconClient(self.credentials.conf('api-key'), - self.credentials.conf('secret-key'), - self.ttl) - - -class _CloudXNSLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the CloudXNS via Lexicon. - """ - - def __init__(self, api_key, secret_key, ttl): - super(_CloudXNSLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('cloudxns', { - 'ttl': ttl, - }, { - 'auth_username': api_key, - 'auth_token': secret_key, - }) - - self.provider = cloudxns.Provider(config) - - def _handle_http_error(self, e, domain_name): - hint = None - if str(e).startswith('400 Client Error:'): - hint = 'Are your API key and Secret key values correct?' - - return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' - .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py index 6bc1e1f79..7b8d0944d 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_cloudxns.dns_cloudxns.""" +"""Tests for certbot_dns_cloudxns._internal.dns_cloudxns.""" import unittest @@ -24,7 +24,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_cloudxns.dns_cloudxns import Authenticator + from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"cloudxns_api_key": API_KEY, "cloudxns_secret_key": SECRET}, path) @@ -42,7 +42,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, class CloudXNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): def setUp(self): - from certbot_dns_cloudxns.dns_cloudxns import _CloudXNSLexiconClient + from certbot_dns_cloudxns._internal.dns_cloudxns import _CloudXNSLexiconClient self.client = _CloudXNSLexiconClient(API_KEY, SECRET, 0) diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4c58d56d0..399692ae5 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-cloudxns = certbot_dns_cloudxns.dns_cloudxns:Authenticator', + 'dns-cloudxns = certbot_dns_cloudxns._internal.dns_cloudxns:Authenticator', ], }, test_suite='certbot_dns_cloudxns', diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py new file mode 100644 index 000000000..0291a9341 --- /dev/null +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_digitalocean.dns_digitalocean` plugin.""" diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py new file mode 100644 index 000000000..7f3abbe31 --- /dev/null +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py @@ -0,0 +1,168 @@ +"""DNS Authenticator for DigitalOcean.""" +import logging + +import digitalocean +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for DigitalOcean + + This Authenticator uses the DigitalOcean API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certs using a DNS TXT record (if you are using DigitalOcean for DNS).' + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add) + add('credentials', help='DigitalOcean credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the DigitalOcean API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'DigitalOcean credentials INI file', + { + 'token': 'API token for DigitalOcean account' + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_digitalocean_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_digitalocean_client().del_txt_record(domain, validation_name, validation) + + def _get_digitalocean_client(self): + return _DigitalOceanClient(self.credentials.conf('token')) + + +class _DigitalOceanClient(object): + """ + Encapsulates all communication with the DigitalOcean API. + """ + + def __init__(self, token): + self.manager = digitalocean.Manager(token=token) + + def add_txt_record(self, domain_name, record_name, record_content): + """ + Add a TXT record using the supplied information. + + :param str domain_name: The domain to use to associate the record with. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :raises certbot.errors.PluginError: if an error occurs communicating with the DigitalOcean + API + """ + + try: + domain = self._find_domain(domain_name) + except digitalocean.Error as e: + hint = None + + if str(e).startswith("Unable to authenticate"): + hint = 'Did you provide a valid API token?' + + logger.debug('Error finding domain using the DigitalOcean API: %s', e) + raise errors.PluginError('Error finding domain using the DigitalOcean API: {0}{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) + + try: + result = domain.create_new_domain_record( + type='TXT', + name=self._compute_record_name(domain, record_name), + data=record_content) + + record_id = result['domain_record']['id'] + + logger.debug('Successfully added TXT record with id: %d', record_id) + except digitalocean.Error as e: + logger.debug('Error adding TXT record using the DigitalOcean API: %s', e) + raise errors.PluginError('Error adding TXT record using the DigitalOcean API: {0}' + .format(e)) + + def del_txt_record(self, domain_name, record_name, record_content): + """ + Delete a TXT record using the supplied information. + + Note that both the record's name and content are used to ensure that similar records + created concurrently (e.g., due to concurrent invocations of this plugin) are not deleted. + + Failures are logged, but not raised. + + :param str domain_name: The domain to use to associate the record with. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + """ + + try: + domain = self._find_domain(domain_name) + except digitalocean.Error as e: + logger.debug('Error finding domain using the DigitalOcean API: %s', e) + return + + try: + domain_records = domain.get_records() + + matching_records = [record for record in domain_records + if record.type == 'TXT' + and record.name == self._compute_record_name(domain, record_name) + and record.data == record_content] + except digitalocean.Error as e: + logger.debug('Error getting DNS records using the DigitalOcean API: %s', e) + return + + for record in matching_records: + try: + logger.debug('Removing TXT record with id: %s', record.id) + record.destroy() + except digitalocean.Error as e: + logger.warning('Error deleting TXT record %s using the DigitalOcean API: %s', + record.id, e) + + def _find_domain(self, domain_name): + """ + Find the domain object for a given domain name. + + :param str domain_name: The domain name for which to find the corresponding Domain. + :returns: The Domain, if found. + :rtype: `~digitalocean.Domain` + :raises certbot.errors.PluginError: if no matching Domain is found. + """ + + domain_name_guesses = dns_common.base_domain_name_guesses(domain_name) + + domains = self.manager.get_all_domains() + + for guess in domain_name_guesses: + matches = [domain for domain in domains if domain.name == guess] + + if matches: + domain = matches[0] + logger.debug('Found base domain for %s using name %s', domain_name, guess) + return domain + + raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' + .format(domain_name, domain_name_guesses)) + + @staticmethod + def _compute_record_name(domain, full_record_name): + # The domain, from DigitalOcean's point of view, is automatically appended. + return full_record_name.rpartition("." + domain.name)[0] diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py deleted file mode 100644 index 7f3abbe31..000000000 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py +++ /dev/null @@ -1,168 +0,0 @@ -"""DNS Authenticator for DigitalOcean.""" -import logging - -import digitalocean -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for DigitalOcean - - This Authenticator uses the DigitalOcean API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certs using a DNS TXT record (if you are using DigitalOcean for DNS).' - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add) - add('credentials', help='DigitalOcean credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the DigitalOcean API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'DigitalOcean credentials INI file', - { - 'token': 'API token for DigitalOcean account' - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_digitalocean_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_digitalocean_client().del_txt_record(domain, validation_name, validation) - - def _get_digitalocean_client(self): - return _DigitalOceanClient(self.credentials.conf('token')) - - -class _DigitalOceanClient(object): - """ - Encapsulates all communication with the DigitalOcean API. - """ - - def __init__(self, token): - self.manager = digitalocean.Manager(token=token) - - def add_txt_record(self, domain_name, record_name, record_content): - """ - Add a TXT record using the supplied information. - - :param str domain_name: The domain to use to associate the record with. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :raises certbot.errors.PluginError: if an error occurs communicating with the DigitalOcean - API - """ - - try: - domain = self._find_domain(domain_name) - except digitalocean.Error as e: - hint = None - - if str(e).startswith("Unable to authenticate"): - hint = 'Did you provide a valid API token?' - - logger.debug('Error finding domain using the DigitalOcean API: %s', e) - raise errors.PluginError('Error finding domain using the DigitalOcean API: {0}{1}' - .format(e, ' ({0})'.format(hint) if hint else '')) - - try: - result = domain.create_new_domain_record( - type='TXT', - name=self._compute_record_name(domain, record_name), - data=record_content) - - record_id = result['domain_record']['id'] - - logger.debug('Successfully added TXT record with id: %d', record_id) - except digitalocean.Error as e: - logger.debug('Error adding TXT record using the DigitalOcean API: %s', e) - raise errors.PluginError('Error adding TXT record using the DigitalOcean API: {0}' - .format(e)) - - def del_txt_record(self, domain_name, record_name, record_content): - """ - Delete a TXT record using the supplied information. - - Note that both the record's name and content are used to ensure that similar records - created concurrently (e.g., due to concurrent invocations of this plugin) are not deleted. - - Failures are logged, but not raised. - - :param str domain_name: The domain to use to associate the record with. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - """ - - try: - domain = self._find_domain(domain_name) - except digitalocean.Error as e: - logger.debug('Error finding domain using the DigitalOcean API: %s', e) - return - - try: - domain_records = domain.get_records() - - matching_records = [record for record in domain_records - if record.type == 'TXT' - and record.name == self._compute_record_name(domain, record_name) - and record.data == record_content] - except digitalocean.Error as e: - logger.debug('Error getting DNS records using the DigitalOcean API: %s', e) - return - - for record in matching_records: - try: - logger.debug('Removing TXT record with id: %s', record.id) - record.destroy() - except digitalocean.Error as e: - logger.warning('Error deleting TXT record %s using the DigitalOcean API: %s', - record.id, e) - - def _find_domain(self, domain_name): - """ - Find the domain object for a given domain name. - - :param str domain_name: The domain name for which to find the corresponding Domain. - :returns: The Domain, if found. - :rtype: `~digitalocean.Domain` - :raises certbot.errors.PluginError: if no matching Domain is found. - """ - - domain_name_guesses = dns_common.base_domain_name_guesses(domain_name) - - domains = self.manager.get_all_domains() - - for guess in domain_name_guesses: - matches = [domain for domain in domains if domain.name == guess] - - if matches: - domain = matches[0] - logger.debug('Found base domain for %s using name %s', domain_name, guess) - return domain - - raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' - .format(domain_name, domain_name_guesses)) - - @staticmethod - def _compute_record_name(domain, full_record_name): - # The domain, from DigitalOcean's point of view, is automatically appended. - return full_record_name.rpartition("." + domain.name)[0] diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py index 3cb49e9fb..71301a47c 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_digitalocean.dns_digitalocean.""" +"""Tests for certbot_dns_digitalocean._internal.dns_digitalocean.""" import unittest @@ -18,7 +18,7 @@ TOKEN = 'a-token' class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_digitalocean.dns_digitalocean import Authenticator + from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator super(AuthenticatorTest, self).setUp() @@ -57,7 +57,7 @@ class DigitalOceanClientTest(unittest.TestCase): record_content = "bar" def setUp(self): - from certbot_dns_digitalocean.dns_digitalocean import _DigitalOceanClient + from certbot_dns_digitalocean._internal.dns_digitalocean import _DigitalOceanClient self.digitalocean_client = _DigitalOceanClient(TOKEN) diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 4dabc207f..d626da66f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -61,7 +61,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-digitalocean = certbot_dns_digitalocean.dns_digitalocean:Authenticator', + 'dns-digitalocean = certbot_dns_digitalocean._internal.dns_digitalocean:Authenticator', ], }, test_suite='certbot_dns_digitalocean', diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py new file mode 100644 index 000000000..070927555 --- /dev/null +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_dnsimple.dns_dnsimple` plugin.""" diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py new file mode 100644 index 000000000..ad2a3fa30 --- /dev/null +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -0,0 +1,82 @@ +"""DNS Authenticator for DNSimple DNS.""" +import logging + +import zope.interface +from lexicon.providers import dnsimple + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://dnsimple.com/user' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for DNSimple + + This Authenticator uses the DNSimple v2 API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record (if you are using DNSimple for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='DNSimple credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the DNSimple API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'DNSimple credentials INI file', + { + 'token': 'User access token for DNSimple v2 API. (See {0}.)'.format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_dnsimple_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_dnsimple_client().del_txt_record(domain, validation_name, validation) + + def _get_dnsimple_client(self): + return _DNSimpleLexiconClient(self.credentials.conf('token'), self.ttl) + + +class _DNSimpleLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the DNSimple via Lexicon. + """ + + def __init__(self, token, ttl): + super(_DNSimpleLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('dnssimple', { + 'ttl': ttl, + }, { + 'auth_token': token, + }) + + self.provider = dnsimple.Provider(config) + + def _handle_http_error(self, e, domain_name): + hint = None + if str(e).startswith('401 Client Error: Unauthorized for url:'): + hint = 'Is your API token value correct?' + + return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' + .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py deleted file mode 100644 index ad2a3fa30..000000000 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py +++ /dev/null @@ -1,82 +0,0 @@ -"""DNS Authenticator for DNSimple DNS.""" -import logging - -import zope.interface -from lexicon.providers import dnsimple - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://dnsimple.com/user' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for DNSimple - - This Authenticator uses the DNSimple v2 API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record (if you are using DNSimple for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='DNSimple credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the DNSimple API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'DNSimple credentials INI file', - { - 'token': 'User access token for DNSimple v2 API. (See {0}.)'.format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_dnsimple_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_dnsimple_client().del_txt_record(domain, validation_name, validation) - - def _get_dnsimple_client(self): - return _DNSimpleLexiconClient(self.credentials.conf('token'), self.ttl) - - -class _DNSimpleLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the DNSimple via Lexicon. - """ - - def __init__(self, token, ttl): - super(_DNSimpleLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('dnssimple', { - 'ttl': ttl, - }, { - 'auth_token': token, - }) - - self.provider = dnsimple.Provider(config) - - def _handle_http_error(self, e, domain_name): - hint = None - if str(e).startswith('401 Client Error: Unauthorized for url:'): - hint = 'Is your API token value correct?' - - return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' - .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py index d84bf71ed..ca5eb4f36 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_dnsimple.dns_dnsimple.""" +"""Tests for certbot_dns_dnsimple._internal.dns_dnsimple.""" import unittest @@ -19,7 +19,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_dnsimple.dns_dnsimple import Authenticator + from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"dnsimple_token": TOKEN}, path) @@ -39,7 +39,7 @@ class DNSimpleLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseL LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: ...') def setUp(self): - from certbot_dns_dnsimple.dns_dnsimple import _DNSimpleLexiconClient + from certbot_dns_dnsimple._internal.dns_dnsimple import _DNSimpleLexiconClient self.client = _DNSimpleLexiconClient(TOKEN, 0) diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 20ed5457d..3359fc578 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -72,7 +72,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-dnsimple = certbot_dns_dnsimple.dns_dnsimple:Authenticator', + 'dns-dnsimple = certbot_dns_dnsimple._internal.dns_dnsimple:Authenticator', ], }, test_suite='certbot_dns_dnsimple', diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py new file mode 100644 index 000000000..37350ce0b --- /dev/null +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_dnsmadeeasy.dns_dnsmadeeasy` plugin.""" diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py new file mode 100644 index 000000000..4cd8721ce --- /dev/null +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -0,0 +1,92 @@ +"""DNS Authenticator for DNS Made Easy DNS.""" +import logging + +import zope.interface +from lexicon.providers import dnsmadeeasy + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://cp.dnsmadeeasy.com/account/info' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for DNS Made Easy + + This Authenticator uses the DNS Made Easy API to fulfill a dns-01 challenge. + """ + + description = ('Obtain certificates using a DNS TXT record (if you are using DNS Made Easy for ' + 'DNS).') + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + add('credentials', help='DNS Made Easy credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the DNS Made Easy API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'DNS Made Easy credentials INI file', + { + 'api-key': 'API key for DNS Made Easy account, obtained from {0}' + .format(ACCOUNT_URL), + 'secret-key': 'Secret key for DNS Made Easy account, obtained from {0}' + .format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_dnsmadeeasy_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_dnsmadeeasy_client().del_txt_record(domain, validation_name, validation) + + def _get_dnsmadeeasy_client(self): + return _DNSMadeEasyLexiconClient(self.credentials.conf('api-key'), + self.credentials.conf('secret-key'), + self.ttl) + + +class _DNSMadeEasyLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the DNS Made Easy via Lexicon. + """ + + def __init__(self, api_key, secret_key, ttl): + super(_DNSMadeEasyLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('dnsmadeeasy', { + 'ttl': ttl, + }, { + 'auth_username': api_key, + 'auth_token': secret_key, + }) + + self.provider = dnsmadeeasy.Provider(config) + + def _handle_http_error(self, e, domain_name): + if domain_name in str(e) and str(e).startswith('404 Client Error: Not Found for url:'): + return None + + hint = None + if str(e).startswith('403 Client Error: Forbidden for url:'): + hint = 'Are your API key and Secret key values correct?' + + return errors.PluginError('Error determining zone identifier: {0}.{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py deleted file mode 100644 index 4cd8721ce..000000000 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py +++ /dev/null @@ -1,92 +0,0 @@ -"""DNS Authenticator for DNS Made Easy DNS.""" -import logging - -import zope.interface -from lexicon.providers import dnsmadeeasy - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://cp.dnsmadeeasy.com/account/info' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for DNS Made Easy - - This Authenticator uses the DNS Made Easy API to fulfill a dns-01 challenge. - """ - - description = ('Obtain certificates using a DNS TXT record (if you are using DNS Made Easy for ' - 'DNS).') - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) - add('credentials', help='DNS Made Easy credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the DNS Made Easy API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'DNS Made Easy credentials INI file', - { - 'api-key': 'API key for DNS Made Easy account, obtained from {0}' - .format(ACCOUNT_URL), - 'secret-key': 'Secret key for DNS Made Easy account, obtained from {0}' - .format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_dnsmadeeasy_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_dnsmadeeasy_client().del_txt_record(domain, validation_name, validation) - - def _get_dnsmadeeasy_client(self): - return _DNSMadeEasyLexiconClient(self.credentials.conf('api-key'), - self.credentials.conf('secret-key'), - self.ttl) - - -class _DNSMadeEasyLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the DNS Made Easy via Lexicon. - """ - - def __init__(self, api_key, secret_key, ttl): - super(_DNSMadeEasyLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('dnsmadeeasy', { - 'ttl': ttl, - }, { - 'auth_username': api_key, - 'auth_token': secret_key, - }) - - self.provider = dnsmadeeasy.Provider(config) - - def _handle_http_error(self, e, domain_name): - if domain_name in str(e) and str(e).startswith('404 Client Error: Not Found for url:'): - return None - - hint = None - if str(e).startswith('403 Client Error: Forbidden for url:'): - hint = 'Are your API key and Secret key values correct?' - - return errors.PluginError('Error determining zone identifier: {0}.{1}' - .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py index f0901664c..b94cc7d05 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_dnsmadeeasy.dns_dnsmadeeasy.""" +"""Tests for certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy.""" import unittest @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_dnsmadeeasy.dns_dnsmadeeasy import Authenticator + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"dnsmadeeasy_api_key": API_KEY, @@ -44,7 +44,7 @@ class DNSMadeEasyLexiconClientTest(unittest.TestCase, LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_dnsmadeeasy.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient self.client = _DNSMadeEasyLexiconClient(API_KEY, SECRET_KEY, 0) diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 85dcaafa3..5812765e4 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-dnsmadeeasy = certbot_dns_dnsmadeeasy.dns_dnsmadeeasy:Authenticator', + 'dns-dnsmadeeasy = certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy:Authenticator', ], }, test_suite='certbot_dns_dnsmadeeasy', diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py new file mode 100644 index 000000000..f8d6485dc --- /dev/null +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_gehirn.dns_gehirn` plugin.""" diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py new file mode 100644 index 000000000..e64e62da9 --- /dev/null +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -0,0 +1,87 @@ +"""DNS Authenticator for Gehirn Infrastracture Service DNS.""" +import logging + +import zope.interface +from lexicon.providers import gehirn + +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +DASHBOARD_URL = "https://gis.gehirn.jp/" + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for Gehirn Infrastracture Service DNS + + This Authenticator uses the Gehirn Infrastracture Service API to fulfill + a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record ' + \ + '(if you are using Gehirn Infrastracture Service for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='Gehirn Infrastracture Service credentials file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the Gehirn Infrastracture Service API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'Gehirn Infrastracture Service credentials file', + { + 'api-token': 'API token for Gehirn Infrastracture Service ' + \ + 'API obtained from {0}'.format(DASHBOARD_URL), + 'api-secret': 'API secret for Gehirn Infrastracture Service ' + \ + 'API obtained from {0}'.format(DASHBOARD_URL), + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_gehirn_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_gehirn_client().del_txt_record(domain, validation_name, validation) + + def _get_gehirn_client(self): + return _GehirnLexiconClient( + self.credentials.conf('api-token'), + self.credentials.conf('api-secret'), + self.ttl + ) + + +class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the Gehirn Infrastracture Service via Lexicon. + """ + + def __init__(self, api_token, api_secret, ttl): + super(_GehirnLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('gehirn', { + 'ttl': ttl, + }, { + 'auth_token': api_token, + 'auth_secret': api_secret, + }) + + self.provider = gehirn.Provider(config) + + def _handle_http_error(self, e, domain_name): + if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): + return None # Expected errors when zone name guess is wrong + return super(_GehirnLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py deleted file mode 100644 index e64e62da9..000000000 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py +++ /dev/null @@ -1,87 +0,0 @@ -"""DNS Authenticator for Gehirn Infrastracture Service DNS.""" -import logging - -import zope.interface -from lexicon.providers import gehirn - -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -DASHBOARD_URL = "https://gis.gehirn.jp/" - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Gehirn Infrastracture Service DNS - - This Authenticator uses the Gehirn Infrastracture Service API to fulfill - a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record ' + \ - '(if you are using Gehirn Infrastracture Service for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='Gehirn Infrastracture Service credentials file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Gehirn Infrastracture Service API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'Gehirn Infrastracture Service credentials file', - { - 'api-token': 'API token for Gehirn Infrastracture Service ' + \ - 'API obtained from {0}'.format(DASHBOARD_URL), - 'api-secret': 'API secret for Gehirn Infrastracture Service ' + \ - 'API obtained from {0}'.format(DASHBOARD_URL), - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_gehirn_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_gehirn_client().del_txt_record(domain, validation_name, validation) - - def _get_gehirn_client(self): - return _GehirnLexiconClient( - self.credentials.conf('api-token'), - self.credentials.conf('api-secret'), - self.ttl - ) - - -class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the Gehirn Infrastracture Service via Lexicon. - """ - - def __init__(self, api_token, api_secret, ttl): - super(_GehirnLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('gehirn', { - 'ttl': ttl, - }, { - 'auth_token': api_token, - 'auth_secret': api_secret, - }) - - self.provider = gehirn.Provider(config) - - def _handle_http_error(self, e, domain_name): - if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): - return None # Expected errors when zone name guess is wrong - return super(_GehirnLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py index 5a591392b..f5b95b6c3 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_gehirn.dns_gehirn.""" +"""Tests for certbot_dns_gehirn._internal.dns_gehirn.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_gehirn.dns_gehirn import Authenticator + from certbot_dns_gehirn._internal.dns_gehirn import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write( @@ -43,7 +43,7 @@ class GehirnLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_gehirn.dns_gehirn import _GehirnLexiconClient + from certbot_dns_gehirn._internal.dns_gehirn import _GehirnLexiconClient self.client = _GehirnLexiconClient(API_TOKEN, API_SECRET, 0) diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f020354dc..5fb46576b 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -59,7 +59,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-gehirn = certbot_dns_gehirn.dns_gehirn:Authenticator', + 'dns-gehirn = certbot_dns_gehirn._internal.dns_gehirn:Authenticator', ], }, test_suite='certbot_dns_gehirn', diff --git a/certbot-dns-google/certbot_dns_google/_internal/__init__.py b/certbot-dns-google/certbot_dns_google/_internal/__init__.py new file mode 100644 index 000000000..f433213ff --- /dev/null +++ b/certbot-dns-google/certbot_dns_google/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_google.dns_google` plugin.""" diff --git a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py new file mode 100644 index 000000000..b722a38cf --- /dev/null +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -0,0 +1,307 @@ +"""DNS Authenticator for Google Cloud DNS.""" +import json +import logging + +import httplib2 +import zope.interface +from googleapiclient import discovery +from googleapiclient import errors as googleapiclient_errors +from oauth2client.service_account import ServiceAccountCredentials + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common + +logger = logging.getLogger(__name__) + +ACCT_URL = 'https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount' +PERMISSIONS_URL = 'https://cloud.google.com/dns/access-control#permissions_and_roles' +METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/' +METADATA_HEADERS = {'Metadata-Flavor': 'Google'} + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for Google Cloud DNS + + This Authenticator uses the Google Cloud DNS API to fulfill a dns-01 challenge. + """ + + description = ('Obtain certificates using a DNS TXT record (if you are using Google Cloud DNS ' + 'for DNS).') + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + add('credentials', + help=('Path to Google Cloud DNS service account JSON file. (See {0} for' + + 'information about creating a service account and {1} for information about the' + + 'required permissions.)').format(ACCT_URL, PERMISSIONS_URL), + default=None) + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the Google Cloud DNS API.' + + def _setup_credentials(self): + if self.conf('credentials') is None: + try: + # use project_id query to check for availability of google metadata server + # we won't use the result but know we're not on GCP when an exception is thrown + _GoogleClient.get_project_id() + except (ValueError, httplib2.ServerNotFoundError): + raise errors.PluginError('Unable to get Google Cloud Metadata and no credentials' + ' specified. Automatic credential lookup is only ' + 'available on Google Cloud Platform. Please configure' + ' credentials using --dns-google-credentials ') + else: + self._configure_file('credentials', + 'path to Google Cloud DNS service account JSON file') + + dns_common.validate_file_permissions(self.conf('credentials')) + + def _perform(self, domain, validation_name, validation): + self._get_google_client().add_txt_record(domain, validation_name, validation, self.ttl) + + def _cleanup(self, domain, validation_name, validation): + self._get_google_client().del_txt_record(domain, validation_name, validation, self.ttl) + + def _get_google_client(self): + return _GoogleClient(self.conf('credentials')) + + +class _GoogleClient(object): + """ + Encapsulates all communication with the Google Cloud DNS API. + """ + + def __init__(self, account_json=None, dns_api=None): + + scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite'] + if account_json is not None: + credentials = ServiceAccountCredentials.from_json_keyfile_name(account_json, scopes) + with open(account_json) as account: + self.project_id = json.load(account)['project_id'] + else: + credentials = None + self.project_id = self.get_project_id() + + if not dns_api: + self.dns = discovery.build('dns', 'v1', + credentials=credentials, + cache_discovery=False) + else: + self.dns = dns_api + + def add_txt_record(self, domain, record_name, record_content, record_ttl): + """ + Add a TXT record using the supplied information. + + :param str domain: The domain to use to look up the managed zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :param int record_ttl: The record TTL (number of seconds that the record may be cached). + :raises certbot.errors.PluginError: if an error occurs communicating with the Google API + """ + + zone_id = self._find_managed_zone_id(domain) + + record_contents = self.get_existing_txt_rrset(zone_id, record_name) + if record_contents is None: + record_contents = [] + add_records = record_contents[:] + + if "\""+record_content+"\"" in record_contents: + # The process was interrupted previously and validation token exists + return + + add_records.append(record_content) + + data = { + "kind": "dns#change", + "additions": [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": record_name + ".", + "rrdatas": add_records, + "ttl": record_ttl, + }, + ], + } + + if record_contents: + # We need to remove old records in the same request + data["deletions"] = [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": record_name + ".", + "rrdatas": record_contents, + "ttl": record_ttl, + }, + ] + + changes = self.dns.changes() # changes | pylint: disable=no-member + + try: + request = changes.create(project=self.project_id, managedZone=zone_id, body=data) + response = request.execute() + + status = response['status'] + change = response['id'] + while status == 'pending': + request = changes.get(project=self.project_id, managedZone=zone_id, changeId=change) + response = request.execute() + status = response['status'] + except googleapiclient_errors.Error as e: + logger.error('Encountered error adding TXT record: %s', e) + raise errors.PluginError('Error communicating with the Google Cloud DNS API: {0}' + .format(e)) + + def del_txt_record(self, domain, record_name, record_content, record_ttl): + """ + Delete a TXT record using the supplied information. + + :param str domain: The domain to use to look up the managed zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :param int record_ttl: The record TTL (number of seconds that the record may be cached). + :raises certbot.errors.PluginError: if an error occurs communicating with the Google API + """ + + try: + zone_id = self._find_managed_zone_id(domain) + except errors.PluginError as e: + logger.warning('Error finding zone. Skipping cleanup.') + return + + record_contents = self.get_existing_txt_rrset(zone_id, record_name) + if record_contents is None: + record_contents = ["\"" + record_content + "\""] + + data = { + "kind": "dns#change", + "deletions": [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": record_name + ".", + "rrdatas": record_contents, + "ttl": record_ttl, + }, + ], + } + + # Remove the record being deleted from the list + readd_contents = [r for r in record_contents if r != "\"" + record_content + "\""] + if readd_contents: + # We need to remove old records in the same request + data["additions"] = [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": record_name + ".", + "rrdatas": readd_contents, + "ttl": record_ttl, + }, + ] + + changes = self.dns.changes() # changes | pylint: disable=no-member + + try: + request = changes.create(project=self.project_id, managedZone=zone_id, body=data) + request.execute() + except googleapiclient_errors.Error as e: + logger.warning('Encountered error deleting TXT record: %s', e) + + def get_existing_txt_rrset(self, zone_id, record_name): + """ + Get existing TXT records from the RRset for the record name. + + If an error occurs while requesting the record set, it is suppressed + and None is returned. + + :param str zone_id: The ID of the managed zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + + :returns: List of TXT record values or None + :rtype: `list` of `string` or `None` + + """ + rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member + request = rrs_request.list(managedZone=zone_id, project=self.project_id) + # Add dot as the API returns absolute domains + record_name += "." + try: + response = request.execute() + except googleapiclient_errors.Error: + logger.info("Unable to list existing records. If you're " + "requesting a wildcard certificate, this might not work.") + logger.debug("Error was:", exc_info=True) + else: + if response: + for rr in response["rrsets"]: + if rr["name"] == record_name and rr["type"] == "TXT": + return rr["rrdatas"] + return None + + def _find_managed_zone_id(self, domain): + """ + Find the managed zone for a given domain. + + :param str domain: The domain for which to find the managed zone. + :returns: The ID of the managed zone, if found. + :rtype: str + :raises certbot.errors.PluginError: if the managed zone cannot be found. + """ + + zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) + + mz = self.dns.managedZones() # managedZones | pylint: disable=no-member + for zone_name in zone_dns_name_guesses: + try: + request = mz.list(project=self.project_id, dnsName=zone_name + '.') + response = request.execute() + zones = response['managedZones'] + except googleapiclient_errors.Error as e: + raise errors.PluginError('Encountered error finding managed zone: {0}' + .format(e)) + + for zone in zones: + zone_id = zone['id'] + if 'privateVisibilityConfig' not in zone: + logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) + return zone_id + + raise errors.PluginError('Unable to determine managed zone for {0} using zone names: {1}.' + .format(domain, zone_dns_name_guesses)) + + @staticmethod + def get_project_id(): + """ + Query the google metadata service for the current project ID + + This only works on Google Cloud Platform + + :raises ServerNotFoundError: Not running on Google Compute or DNS not available + :raises ValueError: Server is found, but response code is not 200 + :returns: project id + """ + url = '{0}project/project-id'.format(METADATA_URL) + + # Request an access token from the metadata server. + http = httplib2.Http() + r, content = http.request(url, headers=METADATA_HEADERS) + if r.status != 200: + raise ValueError("Invalid status code: {0}".format(r)) + + if isinstance(content, bytes): + return content.decode() + return content diff --git a/certbot-dns-google/certbot_dns_google/dns_google.py b/certbot-dns-google/certbot_dns_google/dns_google.py deleted file mode 100644 index b722a38cf..000000000 --- a/certbot-dns-google/certbot_dns_google/dns_google.py +++ /dev/null @@ -1,307 +0,0 @@ -"""DNS Authenticator for Google Cloud DNS.""" -import json -import logging - -import httplib2 -import zope.interface -from googleapiclient import discovery -from googleapiclient import errors as googleapiclient_errors -from oauth2client.service_account import ServiceAccountCredentials - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -logger = logging.getLogger(__name__) - -ACCT_URL = 'https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount' -PERMISSIONS_URL = 'https://cloud.google.com/dns/access-control#permissions_and_roles' -METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/' -METADATA_HEADERS = {'Metadata-Flavor': 'Google'} - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Google Cloud DNS - - This Authenticator uses the Google Cloud DNS API to fulfill a dns-01 challenge. - """ - - description = ('Obtain certificates using a DNS TXT record (if you are using Google Cloud DNS ' - 'for DNS).') - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) - add('credentials', - help=('Path to Google Cloud DNS service account JSON file. (See {0} for' + - 'information about creating a service account and {1} for information about the' + - 'required permissions.)').format(ACCT_URL, PERMISSIONS_URL), - default=None) - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Google Cloud DNS API.' - - def _setup_credentials(self): - if self.conf('credentials') is None: - try: - # use project_id query to check for availability of google metadata server - # we won't use the result but know we're not on GCP when an exception is thrown - _GoogleClient.get_project_id() - except (ValueError, httplib2.ServerNotFoundError): - raise errors.PluginError('Unable to get Google Cloud Metadata and no credentials' - ' specified. Automatic credential lookup is only ' - 'available on Google Cloud Platform. Please configure' - ' credentials using --dns-google-credentials ') - else: - self._configure_file('credentials', - 'path to Google Cloud DNS service account JSON file') - - dns_common.validate_file_permissions(self.conf('credentials')) - - def _perform(self, domain, validation_name, validation): - self._get_google_client().add_txt_record(domain, validation_name, validation, self.ttl) - - def _cleanup(self, domain, validation_name, validation): - self._get_google_client().del_txt_record(domain, validation_name, validation, self.ttl) - - def _get_google_client(self): - return _GoogleClient(self.conf('credentials')) - - -class _GoogleClient(object): - """ - Encapsulates all communication with the Google Cloud DNS API. - """ - - def __init__(self, account_json=None, dns_api=None): - - scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite'] - if account_json is not None: - credentials = ServiceAccountCredentials.from_json_keyfile_name(account_json, scopes) - with open(account_json) as account: - self.project_id = json.load(account)['project_id'] - else: - credentials = None - self.project_id = self.get_project_id() - - if not dns_api: - self.dns = discovery.build('dns', 'v1', - credentials=credentials, - cache_discovery=False) - else: - self.dns = dns_api - - def add_txt_record(self, domain, record_name, record_content, record_ttl): - """ - Add a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the Google API - """ - - zone_id = self._find_managed_zone_id(domain) - - record_contents = self.get_existing_txt_rrset(zone_id, record_name) - if record_contents is None: - record_contents = [] - add_records = record_contents[:] - - if "\""+record_content+"\"" in record_contents: - # The process was interrupted previously and validation token exists - return - - add_records.append(record_content) - - data = { - "kind": "dns#change", - "additions": [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": record_name + ".", - "rrdatas": add_records, - "ttl": record_ttl, - }, - ], - } - - if record_contents: - # We need to remove old records in the same request - data["deletions"] = [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": record_name + ".", - "rrdatas": record_contents, - "ttl": record_ttl, - }, - ] - - changes = self.dns.changes() # changes | pylint: disable=no-member - - try: - request = changes.create(project=self.project_id, managedZone=zone_id, body=data) - response = request.execute() - - status = response['status'] - change = response['id'] - while status == 'pending': - request = changes.get(project=self.project_id, managedZone=zone_id, changeId=change) - response = request.execute() - status = response['status'] - except googleapiclient_errors.Error as e: - logger.error('Encountered error adding TXT record: %s', e) - raise errors.PluginError('Error communicating with the Google Cloud DNS API: {0}' - .format(e)) - - def del_txt_record(self, domain, record_name, record_content, record_ttl): - """ - Delete a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the Google API - """ - - try: - zone_id = self._find_managed_zone_id(domain) - except errors.PluginError as e: - logger.warning('Error finding zone. Skipping cleanup.') - return - - record_contents = self.get_existing_txt_rrset(zone_id, record_name) - if record_contents is None: - record_contents = ["\"" + record_content + "\""] - - data = { - "kind": "dns#change", - "deletions": [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": record_name + ".", - "rrdatas": record_contents, - "ttl": record_ttl, - }, - ], - } - - # Remove the record being deleted from the list - readd_contents = [r for r in record_contents if r != "\"" + record_content + "\""] - if readd_contents: - # We need to remove old records in the same request - data["additions"] = [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": record_name + ".", - "rrdatas": readd_contents, - "ttl": record_ttl, - }, - ] - - changes = self.dns.changes() # changes | pylint: disable=no-member - - try: - request = changes.create(project=self.project_id, managedZone=zone_id, body=data) - request.execute() - except googleapiclient_errors.Error as e: - logger.warning('Encountered error deleting TXT record: %s', e) - - def get_existing_txt_rrset(self, zone_id, record_name): - """ - Get existing TXT records from the RRset for the record name. - - If an error occurs while requesting the record set, it is suppressed - and None is returned. - - :param str zone_id: The ID of the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - - :returns: List of TXT record values or None - :rtype: `list` of `string` or `None` - - """ - rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member - request = rrs_request.list(managedZone=zone_id, project=self.project_id) - # Add dot as the API returns absolute domains - record_name += "." - try: - response = request.execute() - except googleapiclient_errors.Error: - logger.info("Unable to list existing records. If you're " - "requesting a wildcard certificate, this might not work.") - logger.debug("Error was:", exc_info=True) - else: - if response: - for rr in response["rrsets"]: - if rr["name"] == record_name and rr["type"] == "TXT": - return rr["rrdatas"] - return None - - def _find_managed_zone_id(self, domain): - """ - Find the managed zone for a given domain. - - :param str domain: The domain for which to find the managed zone. - :returns: The ID of the managed zone, if found. - :rtype: str - :raises certbot.errors.PluginError: if the managed zone cannot be found. - """ - - zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) - - mz = self.dns.managedZones() # managedZones | pylint: disable=no-member - for zone_name in zone_dns_name_guesses: - try: - request = mz.list(project=self.project_id, dnsName=zone_name + '.') - response = request.execute() - zones = response['managedZones'] - except googleapiclient_errors.Error as e: - raise errors.PluginError('Encountered error finding managed zone: {0}' - .format(e)) - - for zone in zones: - zone_id = zone['id'] - if 'privateVisibilityConfig' not in zone: - logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name) - return zone_id - - raise errors.PluginError('Unable to determine managed zone for {0} using zone names: {1}.' - .format(domain, zone_dns_name_guesses)) - - @staticmethod - def get_project_id(): - """ - Query the google metadata service for the current project ID - - This only works on Google Cloud Platform - - :raises ServerNotFoundError: Not running on Google Compute or DNS not available - :raises ValueError: Server is found, but response code is not 200 - :returns: project id - """ - url = '{0}project/project-id'.format(METADATA_URL) - - # Request an access token from the metadata server. - http = httplib2.Http() - r, content = http.request(url, headers=METADATA_HEADERS) - if r.status != 200: - raise ValueError("Invalid status code: {0}".format(r)) - - if isinstance(content, bytes): - return content.decode() - return content diff --git a/certbot-dns-google/certbot_dns_google/dns_google_test.py b/certbot-dns-google/certbot_dns_google/dns_google_test.py index 288357bc1..e91db58ab 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google_test.py +++ b/certbot-dns-google/certbot_dns_google/dns_google_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_google.dns_google.""" +"""Tests for certbot_dns_google._internal.dns_google.""" import unittest @@ -25,7 +25,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_google.dns_google import Authenticator + from certbot_dns_google._internal.dns_google import Authenticator path = os.path.join(self.tempdir, 'file.json') open(path, "wb").close() @@ -68,7 +68,7 @@ class GoogleClientTest(unittest.TestCase): change = "an-id" def _setUp_client_with_mock(self, zone_request_side_effect): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient pwd = os.path.dirname(__file__) rel_path = 'testdata/discovery.json' @@ -96,18 +96,18 @@ class GoogleClientTest(unittest.TestCase): @mock.patch('googleapiclient.discovery.build') @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google._GoogleClient.get_project_id') + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') def test_client_without_credentials(self, get_project_id_mock, credential_mock, unused_discovery_mock): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient _GoogleClient(None) self.assertFalse(credential_mock.called) self.assertTrue(get_project_id_mock.called) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - @mock.patch('certbot_dns_google.dns_google._GoogleClient.get_project_id') + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') def test_add_txt_record(self, get_project_id_mock, credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) credential_mock.assert_called_once_with('/not/a/real/path.json', mock.ANY) @@ -133,7 +133,7 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_and_poll(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -151,12 +151,13 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_delete_old(self, unused_credential_mock): client, changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}]) - mock_get_rrs = "certbot_dns_google.dns_google._GoogleClient.get_existing_txt_rrset" + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = ["sample-txt-contents"] client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @@ -165,7 +166,7 @@ class GoogleClientTest(unittest.TestCase): changes.create.call_args_list[0][1]["body"]["deletions"][0]["rrdatas"]) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_noop(self, unused_credential_mock): client, changes = self._setUp_client_with_mock( @@ -175,7 +176,7 @@ class GoogleClientTest(unittest.TestCase): self.assertFalse(changes.create.called) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_zone_lookup(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock(API_ERROR) @@ -184,7 +185,7 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_zone_not_found(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, @@ -194,7 +195,7 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_add(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -204,12 +205,13 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - mock_get_rrs = "certbot_dns_google.dns_google._GoogleClient.get_existing_txt_rrset" + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = ["\"sample-txt-contents\"", "\"example-txt-contents\""] @@ -243,7 +245,7 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_zone_lookup(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock(API_ERROR) @@ -251,7 +253,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_zone_not_found(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, @@ -260,7 +262,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_delete(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -269,7 +271,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock( @@ -281,7 +283,7 @@ class GoogleClientTest(unittest.TestCase): self.assertEqual(not_found, None) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_fallback(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock( @@ -294,7 +296,7 @@ class GoogleClientTest(unittest.TestCase): self.assertFalse(rrset) def test_get_project_id(self): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient response = DummyResponse() response.status = 200 diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 61d365189..d53862610 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -63,7 +63,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-google = certbot_dns_google.dns_google:Authenticator', + 'dns-google = certbot_dns_google._internal.dns_google:Authenticator', ], }, test_suite='certbot_dns_google', diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py b/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py new file mode 100644 index 000000000..9090d92d3 --- /dev/null +++ b/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_linode.dns_linode` plugin.""" diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py new file mode 100644 index 000000000..507ad5e53 --- /dev/null +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -0,0 +1,108 @@ +"""DNS Authenticator for Linode.""" +import logging +import re + +import zope.interface +from lexicon.providers import linode +from lexicon.providers import linode4 + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +API_KEY_URL = 'https://manager.linode.com/profile/api' +API_KEY_URL_V4 = 'https://cloud.linode.com/profile/tokens' + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for Linode + + This Authenticator uses the Linode API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certs using a DNS TXT record (if you are using Linode for DNS).' + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=1200) + add('credentials', help='Linode credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the Linode API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'Linode credentials INI file', + { + 'key': 'API key for Linode account, obtained from {0} or {1}' + .format(API_KEY_URL, API_KEY_URL_V4) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_linode_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_linode_client().del_txt_record(domain, validation_name, validation) + + def _get_linode_client(self): + api_key = self.credentials.conf('key') + api_version = self.credentials.conf('version') + if api_version == '': + api_version = None + + if not api_version: + api_version = 3 + + # Match for v4 api key + regex_v4 = re.compile('^[0-9a-f]{64}$') + regex_match = regex_v4.match(api_key) + if regex_match: + api_version = 4 + else: + api_version = int(api_version) + + return _LinodeLexiconClient(api_key, api_version) + + +class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the Linode API. + """ + + def __init__(self, api_key, api_version): + super(_LinodeLexiconClient, self).__init__() + + self.api_version = api_version + + if api_version == 3: + config = dns_common_lexicon.build_lexicon_config('linode', {}, { + 'auth_token': api_key, + }) + + self.provider = linode.Provider(config) + elif api_version == 4: + config = dns_common_lexicon.build_lexicon_config('linode4', {}, { + 'auth_token': api_key, + }) + + self.provider = linode4.Provider(config) + else: + raise errors.PluginError('Invalid api version specified: {0}. (Supported: 3, 4)' + .format(api_version)) + + def _handle_general_error(self, e, domain_name): + if not str(e).startswith('Domain not found'): + return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' + .format(domain_name, e)) + return None diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/dns_linode.py deleted file mode 100644 index 507ad5e53..000000000 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode.py +++ /dev/null @@ -1,108 +0,0 @@ -"""DNS Authenticator for Linode.""" -import logging -import re - -import zope.interface -from lexicon.providers import linode -from lexicon.providers import linode4 - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -API_KEY_URL = 'https://manager.linode.com/profile/api' -API_KEY_URL_V4 = 'https://cloud.linode.com/profile/tokens' - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Linode - - This Authenticator uses the Linode API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certs using a DNS TXT record (if you are using Linode for DNS).' - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=1200) - add('credentials', help='Linode credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Linode API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'Linode credentials INI file', - { - 'key': 'API key for Linode account, obtained from {0} or {1}' - .format(API_KEY_URL, API_KEY_URL_V4) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_linode_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_linode_client().del_txt_record(domain, validation_name, validation) - - def _get_linode_client(self): - api_key = self.credentials.conf('key') - api_version = self.credentials.conf('version') - if api_version == '': - api_version = None - - if not api_version: - api_version = 3 - - # Match for v4 api key - regex_v4 = re.compile('^[0-9a-f]{64}$') - regex_match = regex_v4.match(api_key) - if regex_match: - api_version = 4 - else: - api_version = int(api_version) - - return _LinodeLexiconClient(api_key, api_version) - - -class _LinodeLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the Linode API. - """ - - def __init__(self, api_key, api_version): - super(_LinodeLexiconClient, self).__init__() - - self.api_version = api_version - - if api_version == 3: - config = dns_common_lexicon.build_lexicon_config('linode', {}, { - 'auth_token': api_key, - }) - - self.provider = linode.Provider(config) - elif api_version == 4: - config = dns_common_lexicon.build_lexicon_config('linode4', {}, { - 'auth_token': api_key, - }) - - self.provider = linode4.Provider(config) - else: - raise errors.PluginError('Invalid api version specified: {0}. (Supported: 3, 4)' - .format(api_version)) - - def _handle_general_error(self, e, domain_name): - if not str(e).startswith('Domain not found'): - return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' - .format(domain_name, e)) - return None diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py index 153f8b51d..3cf615486 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py +++ b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_linode.dns_linode.""" +"""Tests for certbot_dns_linode._internal.dns_linode.""" import unittest @@ -9,7 +9,7 @@ from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util -from certbot_dns_linode.dns_linode import Authenticator +from certbot_dns_linode._internal.dns_linode import Authenticator TOKEN = 'a-token' TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64' @@ -121,7 +121,7 @@ class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex DOMAIN_NOT_FOUND = Exception('Domain not found') def setUp(self): - from certbot_dns_linode.dns_linode import _LinodeLexiconClient + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient self.client = _LinodeLexiconClient(TOKEN, 3) @@ -133,7 +133,7 @@ class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLe DOMAIN_NOT_FOUND = Exception('Domain not found') def setUp(self): - from certbot_dns_linode.dns_linode import _LinodeLexiconClient + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient self.client = _LinodeLexiconClient(TOKEN, 4) diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 807e9b889..ce2c91078 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -58,7 +58,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-linode = certbot_dns_linode.dns_linode:Authenticator', + 'dns-linode = certbot_dns_linode._internal.dns_linode:Authenticator', ], }, test_suite='certbot_dns_linode', diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py new file mode 100644 index 000000000..8ab0a00e2 --- /dev/null +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_luadns.dns_luadns` plugin.""" diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py new file mode 100644 index 000000000..7cdd4c8e1 --- /dev/null +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -0,0 +1,86 @@ +"""DNS Authenticator for LuaDNS DNS.""" +import logging + +import zope.interface +from lexicon.providers import luadns + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://api.luadns.com/settings' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for LuaDNS + + This Authenticator uses the LuaDNS API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record (if you are using LuaDNS for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='LuaDNS credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the LuaDNS API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'LuaDNS credentials INI file', + { + 'email': 'email address associated with LuaDNS account', + 'token': 'API token for LuaDNS account, obtained from {0}'.format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_luadns_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_luadns_client().del_txt_record(domain, validation_name, validation) + + def _get_luadns_client(self): + return _LuaDNSLexiconClient(self.credentials.conf('email'), + self.credentials.conf('token'), + self.ttl) + + +class _LuaDNSLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the LuaDNS via Lexicon. + """ + + def __init__(self, email, token, ttl): + super(_LuaDNSLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('luadns', { + 'ttl': ttl, + }, { + 'auth_username': email, + 'auth_token': token, + }) + + self.provider = luadns.Provider(config) + + def _handle_http_error(self, e, domain_name): + hint = None + if str(e).startswith('401 Client Error: Unauthorized for url:'): + hint = 'Are your email and API token values correct?' + + return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' + .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py deleted file mode 100644 index 7cdd4c8e1..000000000 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py +++ /dev/null @@ -1,86 +0,0 @@ -"""DNS Authenticator for LuaDNS DNS.""" -import logging - -import zope.interface -from lexicon.providers import luadns - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://api.luadns.com/settings' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for LuaDNS - - This Authenticator uses the LuaDNS API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record (if you are using LuaDNS for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='LuaDNS credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the LuaDNS API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'LuaDNS credentials INI file', - { - 'email': 'email address associated with LuaDNS account', - 'token': 'API token for LuaDNS account, obtained from {0}'.format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_luadns_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_luadns_client().del_txt_record(domain, validation_name, validation) - - def _get_luadns_client(self): - return _LuaDNSLexiconClient(self.credentials.conf('email'), - self.credentials.conf('token'), - self.ttl) - - -class _LuaDNSLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the LuaDNS via Lexicon. - """ - - def __init__(self, email, token, ttl): - super(_LuaDNSLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('luadns', { - 'ttl': ttl, - }, { - 'auth_username': email, - 'auth_token': token, - }) - - self.provider = luadns.Provider(config) - - def _handle_http_error(self, e, domain_name): - hint = None - if str(e).startswith('401 Client Error: Unauthorized for url:'): - hint = 'Are your email and API token values correct?' - - return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' - .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py index 73cef6521..934d3e103 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py +++ b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_luadns.dns_luadns.""" +"""Tests for certbot_dns_luadns._internal.dns_luadns.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_luadns.dns_luadns import Authenticator + from certbot_dns_luadns._internal.dns_luadns import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"luadns_email": EMAIL, "luadns_token": TOKEN}, path) @@ -40,7 +40,7 @@ class LuaDNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex LOGIN_ERROR = HTTPError("401 Client Error: Unauthorized for url: ...") def setUp(self): - from certbot_dns_luadns.dns_luadns import _LuaDNSLexiconClient + from certbot_dns_luadns._internal.dns_luadns import _LuaDNSLexiconClient self.client = _LuaDNSLexiconClient(EMAIL, TOKEN, 0) diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 3ee8fb2fe..eb0d5b69b 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-luadns = certbot_dns_luadns.dns_luadns:Authenticator', + 'dns-luadns = certbot_dns_luadns._internal.dns_luadns:Authenticator', ], }, test_suite='certbot_dns_luadns', diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py new file mode 100644 index 000000000..40a095edf --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_nsone.dns_nsone` plugin.""" diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py new file mode 100644 index 000000000..b585ddb7a --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -0,0 +1,85 @@ +"""DNS Authenticator for NS1 DNS.""" +import logging + +import zope.interface +from lexicon.providers import nsone + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://my.nsone.net/#/account/settings' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for NS1 + + This Authenticator uses the NS1 API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record (if you are using NS1 for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='NS1 credentials file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the NS1 API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'NS1 credentials file', + { + 'api-key': 'API key for NS1 API, obtained from {0}'.format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_nsone_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_nsone_client().del_txt_record(domain, validation_name, validation) + + def _get_nsone_client(self): + return _NS1LexiconClient(self.credentials.conf('api-key'), self.ttl) + + +class _NS1LexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the NS1 via Lexicon. + """ + + def __init__(self, api_key, ttl): + super(_NS1LexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('nsone', { + 'ttl': ttl, + }, { + 'auth_token': api_key, + }) + + self.provider = nsone.Provider(config) + + def _handle_http_error(self, e, domain_name): + if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:') or \ + str(e).startswith("400 Client Error: Bad Request for url:")): + return None # Expected errors when zone name guess is wrong + hint = None + if str(e).startswith('401 Client Error: Unauthorized for url:'): + hint = 'Is your API key correct?' + + return errors.PluginError('Error determining zone identifier: {0}.{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py deleted file mode 100644 index b585ddb7a..000000000 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py +++ /dev/null @@ -1,85 +0,0 @@ -"""DNS Authenticator for NS1 DNS.""" -import logging - -import zope.interface -from lexicon.providers import nsone - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -ACCOUNT_URL = 'https://my.nsone.net/#/account/settings' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for NS1 - - This Authenticator uses the NS1 API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record (if you are using NS1 for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='NS1 credentials file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the NS1 API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'NS1 credentials file', - { - 'api-key': 'API key for NS1 API, obtained from {0}'.format(ACCOUNT_URL) - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_nsone_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_nsone_client().del_txt_record(domain, validation_name, validation) - - def _get_nsone_client(self): - return _NS1LexiconClient(self.credentials.conf('api-key'), self.ttl) - - -class _NS1LexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the NS1 via Lexicon. - """ - - def __init__(self, api_key, ttl): - super(_NS1LexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('nsone', { - 'ttl': ttl, - }, { - 'auth_token': api_key, - }) - - self.provider = nsone.Provider(config) - - def _handle_http_error(self, e, domain_name): - if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:') or \ - str(e).startswith("400 Client Error: Bad Request for url:")): - return None # Expected errors when zone name guess is wrong - hint = None - if str(e).startswith('401 Client Error: Unauthorized for url:'): - hint = 'Is your API key correct?' - - return errors.PluginError('Error determining zone identifier: {0}.{1}' - .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py index b2db2f603..dd6168f08 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py +++ b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_nsone.dns_nsone.""" +"""Tests for certbot_dns_nsone._internal.dns_nsone.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_nsone.dns_nsone import Authenticator + from certbot_dns_nsone._internal.dns_nsone import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"nsone_api_key": API_KEY}, path) @@ -40,7 +40,7 @@ class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_nsone.dns_nsone import _NS1LexiconClient + from certbot_dns_nsone._internal.dns_nsone import _NS1LexiconClient self.client = _NS1LexiconClient(API_KEY, 0) diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 2cc44406f..f3abb4d9d 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-nsone = certbot_dns_nsone.dns_nsone:Authenticator', + 'dns-nsone = certbot_dns_nsone._internal.dns_nsone:Authenticator', ], }, test_suite='certbot_dns_nsone', diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py new file mode 100644 index 000000000..133694b9e --- /dev/null +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_ovh.dns_ovh` plugin.""" diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py new file mode 100644 index 000000000..84771b0a8 --- /dev/null +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -0,0 +1,105 @@ +"""DNS Authenticator for OVH DNS.""" +import logging + +import zope.interface +from lexicon.providers import ovh + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +TOKEN_URL = 'https://eu.api.ovh.com/createToken/ or https://ca.api.ovh.com/createToken/' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for OVH + + This Authenticator uses the OVH API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record (if you are using OVH for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='OVH credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the OVH API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'OVH credentials INI file', + { + 'endpoint': 'OVH API endpoint (ovh-eu or ovh-ca)', + 'application-key': 'Application key for OVH API, obtained from {0}' + .format(TOKEN_URL), + 'application-secret': 'Application secret for OVH API, obtained from {0}' + .format(TOKEN_URL), + 'consumer-key': 'Consumer key for OVH API, obtained from {0}' + .format(TOKEN_URL), + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_ovh_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_ovh_client().del_txt_record(domain, validation_name, validation) + + def _get_ovh_client(self): + return _OVHLexiconClient( + self.credentials.conf('endpoint'), + self.credentials.conf('application-key'), + self.credentials.conf('application-secret'), + self.credentials.conf('consumer-key'), + self.ttl + ) + + +class _OVHLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the OVH API via Lexicon. + """ + + def __init__(self, endpoint, application_key, application_secret, consumer_key, ttl): + super(_OVHLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('ovh', { + 'ttl': ttl, + }, { + 'auth_entrypoint': endpoint, + 'auth_application_key': application_key, + 'auth_application_secret': application_secret, + 'auth_consumer_key': consumer_key, + }) + + self.provider = ovh.Provider(config) + + def _handle_http_error(self, e, domain_name): + hint = None + if str(e).startswith('400 Client Error:'): + hint = 'Is your Application Secret value correct?' + if str(e).startswith('403 Client Error:'): + hint = 'Are your Application Key and Consumer Key values correct?' + + return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' + .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) + + def _handle_general_error(self, e, domain_name): + if domain_name in str(e) and str(e).endswith('not found'): + return + + super(_OVHLexiconClient, self)._handle_general_error(e, domain_name) diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py deleted file mode 100644 index 84771b0a8..000000000 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py +++ /dev/null @@ -1,105 +0,0 @@ -"""DNS Authenticator for OVH DNS.""" -import logging - -import zope.interface -from lexicon.providers import ovh - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -TOKEN_URL = 'https://eu.api.ovh.com/createToken/ or https://ca.api.ovh.com/createToken/' - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for OVH - - This Authenticator uses the OVH API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record (if you are using OVH for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='OVH credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the OVH API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'OVH credentials INI file', - { - 'endpoint': 'OVH API endpoint (ovh-eu or ovh-ca)', - 'application-key': 'Application key for OVH API, obtained from {0}' - .format(TOKEN_URL), - 'application-secret': 'Application secret for OVH API, obtained from {0}' - .format(TOKEN_URL), - 'consumer-key': 'Consumer key for OVH API, obtained from {0}' - .format(TOKEN_URL), - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_ovh_client().add_txt_record(domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_ovh_client().del_txt_record(domain, validation_name, validation) - - def _get_ovh_client(self): - return _OVHLexiconClient( - self.credentials.conf('endpoint'), - self.credentials.conf('application-key'), - self.credentials.conf('application-secret'), - self.credentials.conf('consumer-key'), - self.ttl - ) - - -class _OVHLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the OVH API via Lexicon. - """ - - def __init__(self, endpoint, application_key, application_secret, consumer_key, ttl): - super(_OVHLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('ovh', { - 'ttl': ttl, - }, { - 'auth_entrypoint': endpoint, - 'auth_application_key': application_key, - 'auth_application_secret': application_secret, - 'auth_consumer_key': consumer_key, - }) - - self.provider = ovh.Provider(config) - - def _handle_http_error(self, e, domain_name): - hint = None - if str(e).startswith('400 Client Error:'): - hint = 'Is your Application Secret value correct?' - if str(e).startswith('403 Client Error:'): - hint = 'Are your Application Key and Consumer Key values correct?' - - return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}' - .format(domain_name, e, ' ({0})'.format(hint) if hint else '')) - - def _handle_general_error(self, e, domain_name): - if domain_name in str(e) and str(e).endswith('not found'): - return - - super(_OVHLexiconClient, self)._handle_general_error(e, domain_name) diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py index b48a85055..a420239ab 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py +++ b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_ovh.dns_ovh.""" +"""Tests for certbot_dns_ovh._internal.dns_ovh.""" import unittest @@ -22,7 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_ovh.dns_ovh import Authenticator + from certbot_dns_ovh._internal.dns_ovh import Authenticator path = os.path.join(self.tempdir, 'file.ini') credentials = { @@ -48,7 +48,7 @@ class OVHLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/...') def setUp(self): - from certbot_dns_ovh.dns_ovh import _OVHLexiconClient + from certbot_dns_ovh._internal.dns_ovh import _OVHLexiconClient self.client = _OVHLexiconClient( ENDPOINT, APPLICATION_KEY, APPLICATION_SECRET, CONSUMER_KEY, 0 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index b3abce754..308229ade 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-ovh = certbot_dns_ovh.dns_ovh:Authenticator', + 'dns-ovh = certbot_dns_ovh._internal.dns_ovh:Authenticator', ], }, test_suite='certbot_dns_ovh', diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py new file mode 100644 index 000000000..44894bb35 --- /dev/null +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_rfc2136.dns_rfc2136` plugin.""" diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py new file mode 100644 index 000000000..ee71c9681 --- /dev/null +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -0,0 +1,226 @@ +"""DNS Authenticator using RFC 2136 Dynamic Updates.""" +import logging + +import dns.flags +import dns.message +import dns.name +import dns.query +import dns.rdataclass +import dns.rdatatype +import dns.tsig +import dns.tsigkeyring +import dns.update +import zope.interface + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator using RFC 2136 Dynamic Updates + + This Authenticator uses RFC 2136 Dynamic Updates to fulfull a dns-01 challenge. + """ + + ALGORITHMS = { + 'HMAC-MD5': dns.tsig.HMAC_MD5, + 'HMAC-SHA1': dns.tsig.HMAC_SHA1, + 'HMAC-SHA224': dns.tsig.HMAC_SHA224, + 'HMAC-SHA256': dns.tsig.HMAC_SHA256, + 'HMAC-SHA384': dns.tsig.HMAC_SHA384, + 'HMAC-SHA512': dns.tsig.HMAC_SHA512 + } + + PORT = 53 + + description = 'Obtain certificates using a DNS TXT record (if you are using BIND for DNS).' + ttl = 120 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) + add('credentials', help='RFC 2136 credentials INI file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'RFC 2136 Dynamic Updates.' + + def _validate_algorithm(self, credentials): + algorithm = credentials.conf('algorithm') + if algorithm: + if not self.ALGORITHMS.get(algorithm.upper()): + raise errors.PluginError("Unknown algorithm: {0}.".format(algorithm)) + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'RFC 2136 credentials INI file', + { + 'name': 'TSIG key name', + 'secret': 'TSIG key secret', + 'server': 'The target DNS server' + }, + self._validate_algorithm + ) + + def _perform(self, _domain, validation_name, validation): + self._get_rfc2136_client().add_txt_record(validation_name, validation, self.ttl) + + def _cleanup(self, _domain, validation_name, validation): + self._get_rfc2136_client().del_txt_record(validation_name, validation) + + def _get_rfc2136_client(self): + return _RFC2136Client(self.credentials.conf('server'), + int(self.credentials.conf('port') or self.PORT), + self.credentials.conf('name'), + self.credentials.conf('secret'), + self.ALGORITHMS.get(self.credentials.conf('algorithm'), + dns.tsig.HMAC_MD5)) + + +class _RFC2136Client(object): + """ + Encapsulates all communication with the target DNS server. + """ + def __init__(self, server, port, key_name, key_secret, key_algorithm): + self.server = server + self.port = port + self.keyring = dns.tsigkeyring.from_text({ + key_name: key_secret + }) + self.algorithm = key_algorithm + + def add_txt_record(self, record_name, record_content, record_ttl): + """ + Add a TXT record using the supplied information. + + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :param int record_ttl: The record TTL (number of seconds that the record may be cached). + :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server + """ + + domain = self._find_domain(record_name) + + n = dns.name.from_text(record_name) + o = dns.name.from_text(domain) + rel = n.relativize(o) + + update = dns.update.Update( + domain, + keyring=self.keyring, + keyalgorithm=self.algorithm) + update.add(rel, record_ttl, dns.rdatatype.TXT, record_content) + + try: + response = dns.query.tcp(update, self.server, port=self.port) + except Exception as e: + raise errors.PluginError('Encountered error adding TXT record: {0}' + .format(e)) + rcode = response.rcode() + + if rcode == dns.rcode.NOERROR: + logger.debug('Successfully added TXT record') + else: + raise errors.PluginError('Received response from server: {0}' + .format(dns.rcode.to_text(rcode))) + + def del_txt_record(self, record_name, record_content): + """ + Delete a TXT record using the supplied information. + + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :param int record_ttl: The record TTL (number of seconds that the record may be cached). + :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server + """ + + domain = self._find_domain(record_name) + + n = dns.name.from_text(record_name) + o = dns.name.from_text(domain) + rel = n.relativize(o) + + update = dns.update.Update( + domain, + keyring=self.keyring, + keyalgorithm=self.algorithm) + update.delete(rel, dns.rdatatype.TXT, record_content) + + try: + response = dns.query.tcp(update, self.server, port=self.port) + except Exception as e: + raise errors.PluginError('Encountered error deleting TXT record: {0}' + .format(e)) + rcode = response.rcode() + + if rcode == dns.rcode.NOERROR: + logger.debug('Successfully deleted TXT record') + else: + raise errors.PluginError('Received response from server: {0}' + .format(dns.rcode.to_text(rcode))) + + def _find_domain(self, record_name): + """ + Find the closest domain with an SOA record for a given domain name. + + :param str record_name: The record name for which to find the closest SOA record. + :returns: The domain, if found. + :rtype: str + :raises certbot.errors.PluginError: if no SOA record can be found. + """ + + domain_name_guesses = dns_common.base_domain_name_guesses(record_name) + + # Loop through until we find an authoritative SOA record + for guess in domain_name_guesses: + if self._query_soa(guess): + return guess + + raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' + .format(record_name, domain_name_guesses)) + + def _query_soa(self, domain_name): + """ + Query a domain name for an authoritative SOA record. + + :param str domain_name: The domain name to query for an SOA record. + :returns: True if found, False otherwise. + :rtype: bool + :raises certbot.errors.PluginError: if no response is received. + """ + + domain = dns.name.from_text(domain_name) + + request = dns.message.make_query(domain, dns.rdatatype.SOA, dns.rdataclass.IN) + # Turn off Recursion Desired bit in query + request.flags ^= dns.flags.RD + + try: + try: + response = dns.query.tcp(request, self.server, port=self.port) + except OSError as e: + logger.debug('TCP query failed, fallback to UDP: %s', e) + response = dns.query.udp(request, self.server, port=self.port) + rcode = response.rcode() + + # Authoritative Answer bit should be set + if (rcode == dns.rcode.NOERROR and response.get_rrset(response.answer, + domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA): + logger.debug('Received authoritative SOA response for %s', domain_name) + return True + + logger.debug('No authoritative SOA record found for %s', domain_name) + return False + except Exception as e: + raise errors.PluginError('Encountered error when making query: {0}' + .format(e)) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py deleted file mode 100644 index ee71c9681..000000000 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ /dev/null @@ -1,226 +0,0 @@ -"""DNS Authenticator using RFC 2136 Dynamic Updates.""" -import logging - -import dns.flags -import dns.message -import dns.name -import dns.query -import dns.rdataclass -import dns.rdatatype -import dns.tsig -import dns.tsigkeyring -import dns.update -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator using RFC 2136 Dynamic Updates - - This Authenticator uses RFC 2136 Dynamic Updates to fulfull a dns-01 challenge. - """ - - ALGORITHMS = { - 'HMAC-MD5': dns.tsig.HMAC_MD5, - 'HMAC-SHA1': dns.tsig.HMAC_SHA1, - 'HMAC-SHA224': dns.tsig.HMAC_SHA224, - 'HMAC-SHA256': dns.tsig.HMAC_SHA256, - 'HMAC-SHA384': dns.tsig.HMAC_SHA384, - 'HMAC-SHA512': dns.tsig.HMAC_SHA512 - } - - PORT = 53 - - description = 'Obtain certificates using a DNS TXT record (if you are using BIND for DNS).' - ttl = 120 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60) - add('credentials', help='RFC 2136 credentials INI file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'RFC 2136 Dynamic Updates.' - - def _validate_algorithm(self, credentials): - algorithm = credentials.conf('algorithm') - if algorithm: - if not self.ALGORITHMS.get(algorithm.upper()): - raise errors.PluginError("Unknown algorithm: {0}.".format(algorithm)) - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'RFC 2136 credentials INI file', - { - 'name': 'TSIG key name', - 'secret': 'TSIG key secret', - 'server': 'The target DNS server' - }, - self._validate_algorithm - ) - - def _perform(self, _domain, validation_name, validation): - self._get_rfc2136_client().add_txt_record(validation_name, validation, self.ttl) - - def _cleanup(self, _domain, validation_name, validation): - self._get_rfc2136_client().del_txt_record(validation_name, validation) - - def _get_rfc2136_client(self): - return _RFC2136Client(self.credentials.conf('server'), - int(self.credentials.conf('port') or self.PORT), - self.credentials.conf('name'), - self.credentials.conf('secret'), - self.ALGORITHMS.get(self.credentials.conf('algorithm'), - dns.tsig.HMAC_MD5)) - - -class _RFC2136Client(object): - """ - Encapsulates all communication with the target DNS server. - """ - def __init__(self, server, port, key_name, key_secret, key_algorithm): - self.server = server - self.port = port - self.keyring = dns.tsigkeyring.from_text({ - key_name: key_secret - }) - self.algorithm = key_algorithm - - def add_txt_record(self, record_name, record_content, record_ttl): - """ - Add a TXT record using the supplied information. - - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server - """ - - domain = self._find_domain(record_name) - - n = dns.name.from_text(record_name) - o = dns.name.from_text(domain) - rel = n.relativize(o) - - update = dns.update.Update( - domain, - keyring=self.keyring, - keyalgorithm=self.algorithm) - update.add(rel, record_ttl, dns.rdatatype.TXT, record_content) - - try: - response = dns.query.tcp(update, self.server, port=self.port) - except Exception as e: - raise errors.PluginError('Encountered error adding TXT record: {0}' - .format(e)) - rcode = response.rcode() - - if rcode == dns.rcode.NOERROR: - logger.debug('Successfully added TXT record') - else: - raise errors.PluginError('Received response from server: {0}' - .format(dns.rcode.to_text(rcode))) - - def del_txt_record(self, record_name, record_content): - """ - Delete a TXT record using the supplied information. - - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the DNS server - """ - - domain = self._find_domain(record_name) - - n = dns.name.from_text(record_name) - o = dns.name.from_text(domain) - rel = n.relativize(o) - - update = dns.update.Update( - domain, - keyring=self.keyring, - keyalgorithm=self.algorithm) - update.delete(rel, dns.rdatatype.TXT, record_content) - - try: - response = dns.query.tcp(update, self.server, port=self.port) - except Exception as e: - raise errors.PluginError('Encountered error deleting TXT record: {0}' - .format(e)) - rcode = response.rcode() - - if rcode == dns.rcode.NOERROR: - logger.debug('Successfully deleted TXT record') - else: - raise errors.PluginError('Received response from server: {0}' - .format(dns.rcode.to_text(rcode))) - - def _find_domain(self, record_name): - """ - Find the closest domain with an SOA record for a given domain name. - - :param str record_name: The record name for which to find the closest SOA record. - :returns: The domain, if found. - :rtype: str - :raises certbot.errors.PluginError: if no SOA record can be found. - """ - - domain_name_guesses = dns_common.base_domain_name_guesses(record_name) - - # Loop through until we find an authoritative SOA record - for guess in domain_name_guesses: - if self._query_soa(guess): - return guess - - raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.' - .format(record_name, domain_name_guesses)) - - def _query_soa(self, domain_name): - """ - Query a domain name for an authoritative SOA record. - - :param str domain_name: The domain name to query for an SOA record. - :returns: True if found, False otherwise. - :rtype: bool - :raises certbot.errors.PluginError: if no response is received. - """ - - domain = dns.name.from_text(domain_name) - - request = dns.message.make_query(domain, dns.rdatatype.SOA, dns.rdataclass.IN) - # Turn off Recursion Desired bit in query - request.flags ^= dns.flags.RD - - try: - try: - response = dns.query.tcp(request, self.server, port=self.port) - except OSError as e: - logger.debug('TCP query failed, fallback to UDP: %s', e) - response = dns.query.udp(request, self.server, port=self.port) - rcode = response.rcode() - - # Authoritative Answer bit should be set - if (rcode == dns.rcode.NOERROR and response.get_rrset(response.answer, - domain, dns.rdataclass.IN, dns.rdatatype.SOA) and response.flags & dns.flags.AA): - logger.debug('Received authoritative SOA response for %s', domain_name) - return True - - logger.debug('No authoritative SOA record found for %s', domain_name) - return False - except Exception as e: - raise errors.PluginError('Encountered error when making query: {0}' - .format(e)) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py index 1950ee62e..c767dba23 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_rfc2136.dns_rfc2136.""" +"""Tests for certbot_dns_rfc2136._internal.dns_rfc2136.""" import unittest @@ -23,7 +23,7 @@ VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret" class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import Authenticator + from certbot_dns_rfc2136._internal.dns_rfc2136 import Authenticator super(AuthenticatorTest, self).setUp() @@ -73,7 +73,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic class RFC2136ClientTest(unittest.TestCase): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client + from certbot_dns_rfc2136._internal.dns_rfc2136 import _RFC2136Client self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 0da7cc98a..37b0e600c 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -60,7 +60,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-rfc2136 = certbot_dns_rfc2136.dns_rfc2136:Authenticator', + 'dns-rfc2136 = certbot_dns_rfc2136._internal.dns_rfc2136:Authenticator', ], }, test_suite='certbot_dns_rfc2136', diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py b/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py new file mode 100644 index 000000000..ac9ead791 --- /dev/null +++ b/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_route53.dns_route53` plugin.""" diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py new file mode 100644 index 000000000..e32017b34 --- /dev/null +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -0,0 +1,151 @@ +"""Certbot Route53 authenticator plugin.""" +import collections +import logging +import time + +import boto3 +import zope.interface +from botocore.exceptions import NoCredentialsError, ClientError + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common + +from acme.magic_typing import DefaultDict, List, Dict # pylint: disable=unused-import, no-name-in-module + +logger = logging.getLogger(__name__) + +INSTRUCTIONS = ( + "To use certbot-dns-route53, configure credentials as described at " + "https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials " # pylint: disable=line-too-long + "and add the necessary permissions for Route53 access.") + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """Route53 Authenticator + + This authenticator solves a DNS01 challenge by uploading the answer to AWS + Route53. + """ + + description = ("Obtain certificates using a DNS TXT record (if you are using AWS Route53 for " + "DNS).") + ttl = 10 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.r53 = boto3.client("route53") + self._resource_records = collections.defaultdict(list) # type: DefaultDict[str, List[Dict[str, str]]] + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return "Solve a DNS01 challenge using AWS Route53" + + def _setup_credentials(self): + pass + + def _perform(self, domain, validation_name, validation): # pylint: disable=missing-docstring + pass + + def perform(self, achalls): + self._attempt_cleanup = True + + try: + change_ids = [ + self._change_txt_record("UPSERT", + achall.validation_domain_name(achall.domain), + achall.validation(achall.account_key)) + for achall in achalls + ] + + for change_id in change_ids: + self._wait_for_change(change_id) + except (NoCredentialsError, ClientError) as e: + logger.debug('Encountered error during perform: %s', e, exc_info=True) + raise errors.PluginError("\n".join([str(e), INSTRUCTIONS])) + return [achall.response(achall.account_key) for achall in achalls] + + def _cleanup(self, domain, validation_name, validation): + try: + self._change_txt_record("DELETE", validation_name, validation) + except (NoCredentialsError, ClientError) as e: + logger.debug('Encountered error during cleanup: %s', e, exc_info=True) + + def _find_zone_id_for_domain(self, domain): + """Find the zone id responsible a given FQDN. + + That is, the id for the zone whose name is the longest parent of the + domain. + """ + paginator = self.r53.get_paginator("list_hosted_zones") + zones = [] + target_labels = domain.rstrip(".").split(".") + for page in paginator.paginate(): + for zone in page["HostedZones"]: + if zone["Config"]["PrivateZone"]: + continue + + candidate_labels = zone["Name"].rstrip(".").split(".") + if candidate_labels == target_labels[-len(candidate_labels):]: + zones.append((zone["Name"], zone["Id"])) + + if not zones: + raise errors.PluginError( + "Unable to find a Route53 hosted zone for {0}".format(domain) + ) + + # Order the zones that are suffixes for our desired to domain by + # length, this puts them in an order like: + # ["foo.bar.baz.com", "bar.baz.com", "baz.com", "com"] + # And then we choose the first one, which will be the most specific. + zones.sort(key=lambda z: len(z[0]), reverse=True) + return zones[0][1] + + def _change_txt_record(self, action, validation_domain_name, validation): + zone_id = self._find_zone_id_for_domain(validation_domain_name) + + rrecords = self._resource_records[validation_domain_name] + challenge = {"Value": '"{0}"'.format(validation)} + if action == "DELETE": + # Remove the record being deleted from the list of tracked records + rrecords.remove(challenge) + if rrecords: + # Need to update instead, as we're not deleting the rrset + action = "UPSERT" + else: + # Create a new list containing the record to use with DELETE + rrecords = [challenge] + else: + rrecords.append(challenge) + + response = self.r53.change_resource_record_sets( + HostedZoneId=zone_id, + ChangeBatch={ + "Comment": "certbot-dns-route53 certificate validation " + action, + "Changes": [ + { + "Action": action, + "ResourceRecordSet": { + "Name": validation_domain_name, + "Type": "TXT", + "TTL": self.ttl, + "ResourceRecords": rrecords, + } + } + ] + } + ) + return response["ChangeInfo"]["Id"] + + def _wait_for_change(self, change_id): + """Wait for a change to be propagated to all Route53 DNS servers. + https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetChange.html + """ + for unused_n in range(0, 120): + response = self.r53.get_change(Id=change_id) + if response["ChangeInfo"]["Status"] == "INSYNC": + return + time.sleep(5) + raise errors.PluginError( + "Timed out waiting for Route53 change. Current status: %s" % + response["ChangeInfo"]["Status"]) diff --git a/certbot-dns-route53/certbot_dns_route53/authenticator.py b/certbot-dns-route53/certbot_dns_route53/authenticator.py index 53215ea1d..2987934a1 100644 --- a/certbot-dns-route53/certbot_dns_route53/authenticator.py +++ b/certbot-dns-route53/certbot_dns_route53/authenticator.py @@ -1,16 +1,17 @@ -"""Shim around `~certbot_dns_route53.dns_route53` for backwards compatibility.""" +"""Shim around `~certbot_dns_route53._internal.dns_route53` for backwards compatibility.""" import warnings import zope.interface from certbot import interfaces -from certbot_dns_route53 import dns_route53 +from certbot_dns_route53._internal import dns_route53 @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_route53.Authenticator): - """Shim around `~certbot_dns_route53.dns_route53.Authenticator` for backwards compatibility.""" + """Shim around `~certbot_dns_route53._internal.dns_route53.Authenticator` + for backwards compatibility.""" hidden = True diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/dns_route53.py deleted file mode 100644 index e32017b34..000000000 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Certbot Route53 authenticator plugin.""" -import collections -import logging -import time - -import boto3 -import zope.interface -from botocore.exceptions import NoCredentialsError, ClientError - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -from acme.magic_typing import DefaultDict, List, Dict # pylint: disable=unused-import, no-name-in-module - -logger = logging.getLogger(__name__) - -INSTRUCTIONS = ( - "To use certbot-dns-route53, configure credentials as described at " - "https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials " # pylint: disable=line-too-long - "and add the necessary permissions for Route53 access.") - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """Route53 Authenticator - - This authenticator solves a DNS01 challenge by uploading the answer to AWS - Route53. - """ - - description = ("Obtain certificates using a DNS TXT record (if you are using AWS Route53 for " - "DNS).") - ttl = 10 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.r53 = boto3.client("route53") - self._resource_records = collections.defaultdict(list) # type: DefaultDict[str, List[Dict[str, str]]] - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return "Solve a DNS01 challenge using AWS Route53" - - def _setup_credentials(self): - pass - - def _perform(self, domain, validation_name, validation): # pylint: disable=missing-docstring - pass - - def perform(self, achalls): - self._attempt_cleanup = True - - try: - change_ids = [ - self._change_txt_record("UPSERT", - achall.validation_domain_name(achall.domain), - achall.validation(achall.account_key)) - for achall in achalls - ] - - for change_id in change_ids: - self._wait_for_change(change_id) - except (NoCredentialsError, ClientError) as e: - logger.debug('Encountered error during perform: %s', e, exc_info=True) - raise errors.PluginError("\n".join([str(e), INSTRUCTIONS])) - return [achall.response(achall.account_key) for achall in achalls] - - def _cleanup(self, domain, validation_name, validation): - try: - self._change_txt_record("DELETE", validation_name, validation) - except (NoCredentialsError, ClientError) as e: - logger.debug('Encountered error during cleanup: %s', e, exc_info=True) - - def _find_zone_id_for_domain(self, domain): - """Find the zone id responsible a given FQDN. - - That is, the id for the zone whose name is the longest parent of the - domain. - """ - paginator = self.r53.get_paginator("list_hosted_zones") - zones = [] - target_labels = domain.rstrip(".").split(".") - for page in paginator.paginate(): - for zone in page["HostedZones"]: - if zone["Config"]["PrivateZone"]: - continue - - candidate_labels = zone["Name"].rstrip(".").split(".") - if candidate_labels == target_labels[-len(candidate_labels):]: - zones.append((zone["Name"], zone["Id"])) - - if not zones: - raise errors.PluginError( - "Unable to find a Route53 hosted zone for {0}".format(domain) - ) - - # Order the zones that are suffixes for our desired to domain by - # length, this puts them in an order like: - # ["foo.bar.baz.com", "bar.baz.com", "baz.com", "com"] - # And then we choose the first one, which will be the most specific. - zones.sort(key=lambda z: len(z[0]), reverse=True) - return zones[0][1] - - def _change_txt_record(self, action, validation_domain_name, validation): - zone_id = self._find_zone_id_for_domain(validation_domain_name) - - rrecords = self._resource_records[validation_domain_name] - challenge = {"Value": '"{0}"'.format(validation)} - if action == "DELETE": - # Remove the record being deleted from the list of tracked records - rrecords.remove(challenge) - if rrecords: - # Need to update instead, as we're not deleting the rrset - action = "UPSERT" - else: - # Create a new list containing the record to use with DELETE - rrecords = [challenge] - else: - rrecords.append(challenge) - - response = self.r53.change_resource_record_sets( - HostedZoneId=zone_id, - ChangeBatch={ - "Comment": "certbot-dns-route53 certificate validation " + action, - "Changes": [ - { - "Action": action, - "ResourceRecordSet": { - "Name": validation_domain_name, - "Type": "TXT", - "TTL": self.ttl, - "ResourceRecords": rrecords, - } - } - ] - } - ) - return response["ChangeInfo"]["Id"] - - def _wait_for_change(self, change_id): - """Wait for a change to be propagated to all Route53 DNS servers. - https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetChange.html - """ - for unused_n in range(0, 120): - response = self.r53.get_change(Id=change_id) - if response["ChangeInfo"]["Status"] == "INSYNC": - return - time.sleep(5) - raise errors.PluginError( - "Timed out waiting for Route53 change. Current status: %s" % - response["ChangeInfo"]["Status"]) diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py index 36c391690..180ebdf6b 100644 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py +++ b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_route53.dns_route53.Authenticator""" +"""Tests for certbot_dns_route53._internal.dns_route53.Authenticator""" import unittest @@ -15,7 +15,7 @@ class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest # pylint: disable=protected-access def setUp(self): - from certbot_dns_route53.dns_route53 import Authenticator + from certbot_dns_route53._internal.dns_route53 import Authenticator super(AuthenticatorTest, self).setUp() @@ -122,7 +122,7 @@ class ClientTest(unittest.TestCase): } def setUp(self): - from certbot_dns_route53.dns_route53 import Authenticator + from certbot_dns_route53._internal.dns_route53 import Authenticator super(ClientTest, self).setUp() diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 548a567b4..e766bf684 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -51,7 +51,7 @@ setup( keywords=['certbot', 'route53', 'aws'], entry_points={ 'certbot.plugins': [ - 'dns-route53 = certbot_dns_route53.dns_route53:Authenticator', + 'dns-route53 = certbot_dns_route53._internal.dns_route53:Authenticator', 'certbot-route53:auth = certbot_dns_route53.authenticator:Authenticator' ], }, diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py new file mode 100644 index 000000000..0c8839024 --- /dev/null +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_sakuracloud.dns_sakuracloud` plugin.""" diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py new file mode 100644 index 000000000..d6e20894d --- /dev/null +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -0,0 +1,90 @@ +"""DNS Authenticator for Sakura Cloud DNS.""" +import logging + +import zope.interface +from lexicon.providers import sakuracloud + +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +APIKEY_URL = "https://secure.sakura.ad.jp/cloud/#!/apikey/top/" + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for Sakura Cloud DNS + + This Authenticator uses the Sakura Cloud API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certificates using a DNS TXT record ' + \ + '(if you are using Sakura Cloud for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments( + add, default_propagation_seconds=90) + add('credentials', help='Sakura Cloud credentials file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the Sakura Cloud API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'Sakura Cloud credentials file', + { + 'api-token': \ + 'API token for Sakura Cloud API obtained from {0}'.format(APIKEY_URL), + 'api-secret': \ + 'API secret for Sakura Cloud API obtained from {0}'.format(APIKEY_URL), + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_sakuracloud_client().add_txt_record( + domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_sakuracloud_client().del_txt_record( + domain, validation_name, validation) + + def _get_sakuracloud_client(self): + return _SakuraCloudLexiconClient( + self.credentials.conf('api-token'), + self.credentials.conf('api-secret'), + self.ttl + ) + + +class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the Sakura Cloud via Lexicon. + """ + + def __init__(self, api_token, api_secret, ttl): + super(_SakuraCloudLexiconClient, self).__init__() + + config = dns_common_lexicon.build_lexicon_config('sakuracloud', { + 'ttl': ttl, + }, { + 'auth_token': api_token, + 'auth_secret': api_secret, + }) + + self.provider = sakuracloud.Provider(config) + + def _handle_http_error(self, e, domain_name): + if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): + return None # Expected errors when zone name guess is wrong + return super(_SakuraCloudLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py deleted file mode 100644 index d6e20894d..000000000 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py +++ /dev/null @@ -1,90 +0,0 @@ -"""DNS Authenticator for Sakura Cloud DNS.""" -import logging - -import zope.interface -from lexicon.providers import sakuracloud - -from certbot import interfaces -from certbot.plugins import dns_common -from certbot.plugins import dns_common_lexicon - -logger = logging.getLogger(__name__) - -APIKEY_URL = "https://secure.sakura.ad.jp/cloud/#!/apikey/top/" - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Sakura Cloud DNS - - This Authenticator uses the Sakura Cloud API to fulfill a dns-01 challenge. - """ - - description = 'Obtain certificates using a DNS TXT record ' + \ - '(if you are using Sakura Cloud for DNS).' - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments( - add, default_propagation_seconds=90) - add('credentials', help='Sakura Cloud credentials file.') - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Sakura Cloud API.' - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - 'credentials', - 'Sakura Cloud credentials file', - { - 'api-token': \ - 'API token for Sakura Cloud API obtained from {0}'.format(APIKEY_URL), - 'api-secret': \ - 'API secret for Sakura Cloud API obtained from {0}'.format(APIKEY_URL), - } - ) - - def _perform(self, domain, validation_name, validation): - self._get_sakuracloud_client().add_txt_record( - domain, validation_name, validation) - - def _cleanup(self, domain, validation_name, validation): - self._get_sakuracloud_client().del_txt_record( - domain, validation_name, validation) - - def _get_sakuracloud_client(self): - return _SakuraCloudLexiconClient( - self.credentials.conf('api-token'), - self.credentials.conf('api-secret'), - self.ttl - ) - - -class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient): - """ - Encapsulates all communication with the Sakura Cloud via Lexicon. - """ - - def __init__(self, api_token, api_secret, ttl): - super(_SakuraCloudLexiconClient, self).__init__() - - config = dns_common_lexicon.build_lexicon_config('sakuracloud', { - 'ttl': ttl, - }, { - 'auth_token': api_token, - 'auth_secret': api_secret, - }) - - self.provider = sakuracloud.Provider(config) - - def _handle_http_error(self, e, domain_name): - if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')): - return None # Expected errors when zone name guess is wrong - return super(_SakuraCloudLexiconClient, self)._handle_http_error(e, domain_name) diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py index 10abc29e2..16890b5a9 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_sakuracloud.dns_sakuracloud.""" +"""Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_sakuracloud.dns_sakuracloud import Authenticator + from certbot_dns_sakuracloud._internal.dns_sakuracloud import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write( @@ -44,7 +44,7 @@ class SakuraCloudLexiconClientTest(unittest.TestCase, LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_sakuracloud.dns_sakuracloud import _SakuraCloudLexiconClient + from certbot_dns_sakuracloud._internal.dns_sakuracloud import _SakuraCloudLexiconClient self.client = _SakuraCloudLexiconClient(API_TOKEN, API_SECRET, 0) diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 7c5bfe7f3..779d7a9d8 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -59,7 +59,7 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-sakuracloud = certbot_dns_sakuracloud.dns_sakuracloud:Authenticator', + 'dns-sakuracloud = certbot_dns_sakuracloud._internal.dns_sakuracloud:Authenticator', ], }, test_suite='certbot_dns_sakuracloud', -- cgit v1.2.3 From 4abd81e2186eddc67551d61a8260440bd177d18d Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 14:28:06 -0800 Subject: Refactor certbot/ and certbot/tests/ to use the same structure as the other packages (#7544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary of changes in this PR: - Refactor files involved in the `certbot` module to be of a similar structure to every other package; that is, inside a directory inside the main repo root (see below). - Make repo root README symlink to `certbot` README. - Pull tests outside of the distributed module. - Make `certbot/tests` not be a module so that `certbot` isn't added to Python's path for module discovery. - Remove `--pyargs` from test calls, and make sure to call tests from repo root since without `--pyargs`, `pytest` takes directory names rather than package names as arguments. - Replace mentions of `.` with `certbot` when referring to packages to install, usually editably. - Clean up some unused code around executing tests in a different directory. - Create public shim around main and make that the entry point. New directory structure summary: ``` repo root ("certbot", probably, but for clarity all files I mention are relative to here) ├── certbot │   ├── setup.py │   ├── certbot │   │   ├── __init__.py │   │   ├── achallenges.py │   │   ├── _internal │   │   │   ├── __init__.py │   │   │   ├── account.py │   │   │   ├── ... │   │   ├── ... │   ├── tests │   │   ├── account_test.py │   │   ├── display │   │   │   ├── __init__.py │   │   │   ├── ... │   │   ├── ... # note no __init__.py at this level │   ├── ... ├── acme │   ├── ... ├── certbot-apache │   ├── ... ├── ... ``` * refactor certbot/ and certbot/tests/ to use the same structure as the other packages * git grep -lE "\-e(\s+)\." | xargs sed -i -E "s/\-e(\s+)\./-e certbot/g" * git grep -lE "\.\[dev\]" | xargs sed -i -E "s/\.\[dev\]/certbot[dev]/g" * git grep -lE "\.\[dev3\]" | xargs sed -i -E "s/\.\[dev3\]/certbot[dev3]/g" * Remove replacement of certbot into . in install_and_test.py * copy license back out to main folder * remove linter_plugin.py and CONTRIBUTING.md from certbot/MANIFEST.in because these files are not under certbot/ * Move README back into main folder, and make the version inside certbot/ a symlink * symlink certbot READMEs the other way around * move testdata into the public api certbot zone * update source_paths in tox.ini to certbot/certbot to find the right subfolder for tests * certbot version has been bumped down a directory level * make certbot tests directory not a package and import sibling as module * Remove unused script cruft * change . to certbot in test_sdists * remove outdated comment referencing a command that doesn't work * Install instructions should reference an existing file * update file paths in Dockerfile * some package named in tox.ini were manually specified, change those to certbot * new directory format doesn't work easily with pyargs according to http://doc.pytest.org/en/latest/goodpractices.html#tests-as-part-of-application-code * remove other instance of pyargs * fix up some references in _release.sh by searching for ' . ' and manual check * another stray . in tox.ini * fix paths in tools/_release.sh * Remove final --pyargs call, and now-unnecessary call to modules instead of local files, since that's fixed by certbot's code being one layer deeper * Create public shim around main and make that the entry point * without pyargs, tests cannot be run from an empty directory * Remove cruft for running certbot directly from main * Have main shim take real arg * add docs/api file for main, and fix up main comment * Update certbot/docs/install.rst Co-Authored-By: Brad Warren * Fix comments in readthedocs requirements files to refer to current package * Update .[docs] reference in contributing.rst * Move plugins tests to certbot tests directory * add certbot tests to MANIFEST.in so packagers can run python setup.py test * move examples directory inside certbot/ * Move CHANGELOG into certbot, and create a top-level symlink * Remove unused sys and logging from main shim * nginx http01 test no longer relies on certbot plugins common test --- CHANGELOG.md | 1853 +------------------- MANIFEST.in | 9 - README.rst | 132 +- acme/readthedocs.org.requirements.txt | 6 +- certbot-compatibility-test/Dockerfile | 4 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- certbot-dns-nsone/readthedocs.org.requirements.txt | 8 +- certbot-dns-ovh/readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- .../readthedocs.org.requirements.txt | 8 +- certbot-nginx/certbot_nginx/tests/http_01_test.py | 7 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot/CHANGELOG.md | 1852 +++++++++++++++++++ certbot/LICENSE.txt | 205 +++ certbot/MANIFEST.in | 8 + certbot/README.rst | 131 ++ certbot/__init__.py | 4 - certbot/_internal/__init__.py | 6 - certbot/_internal/account.py | 344 ---- certbot/_internal/auth_handler.py | 470 ----- certbot/_internal/cert_manager.py | 388 ---- certbot/_internal/cli.py | 1583 ----------------- certbot/_internal/client.py | 742 -------- certbot/_internal/configuration.py | 160 -- certbot/_internal/constants.py | 219 --- certbot/_internal/display/__init__.py | 1 - certbot/_internal/display/completer.py | 61 - certbot/_internal/display/dummy_readline.py | 21 - certbot/_internal/display/enhancements.py | 64 - certbot/_internal/eff.py | 98 -- certbot/_internal/error_handler.py | 172 -- certbot/_internal/hooks.py | 272 --- certbot/_internal/lock.py | 261 --- certbot/_internal/log.py | 353 ---- certbot/_internal/main.py | 1357 -------------- certbot/_internal/notify.py | 34 - certbot/_internal/ocsp.py | 292 --- certbot/_internal/plugins/__init__.py | 1 - certbot/_internal/plugins/disco.py | 289 --- certbot/_internal/plugins/manual.py | 185 -- certbot/_internal/plugins/null.py | 56 - certbot/_internal/plugins/selection.py | 338 ---- certbot/_internal/plugins/standalone.py | 210 --- certbot/_internal/plugins/webroot.py | 288 --- certbot/_internal/renewal.py | 476 ----- certbot/_internal/reporter.py | 100 -- certbot/_internal/storage.py | 1128 ------------ certbot/_internal/updater.py | 122 -- certbot/achallenges.py | 60 - certbot/certbot/__init__.py | 4 + certbot/certbot/_internal/__init__.py | 6 + certbot/certbot/_internal/account.py | 344 ++++ certbot/certbot/_internal/auth_handler.py | 470 +++++ certbot/certbot/_internal/cert_manager.py | 388 ++++ certbot/certbot/_internal/cli.py | 1583 +++++++++++++++++ certbot/certbot/_internal/client.py | 742 ++++++++ certbot/certbot/_internal/configuration.py | 160 ++ certbot/certbot/_internal/constants.py | 219 +++ certbot/certbot/_internal/display/__init__.py | 1 + certbot/certbot/_internal/display/completer.py | 61 + .../certbot/_internal/display/dummy_readline.py | 21 + certbot/certbot/_internal/display/enhancements.py | 64 + certbot/certbot/_internal/eff.py | 98 ++ certbot/certbot/_internal/error_handler.py | 172 ++ certbot/certbot/_internal/hooks.py | 272 +++ certbot/certbot/_internal/lock.py | 261 +++ certbot/certbot/_internal/log.py | 353 ++++ certbot/certbot/_internal/main.py | 1350 ++++++++++++++ certbot/certbot/_internal/notify.py | 34 + certbot/certbot/_internal/ocsp.py | 292 +++ certbot/certbot/_internal/plugins/__init__.py | 1 + certbot/certbot/_internal/plugins/disco.py | 289 +++ certbot/certbot/_internal/plugins/manual.py | 185 ++ certbot/certbot/_internal/plugins/null.py | 56 + certbot/certbot/_internal/plugins/selection.py | 338 ++++ certbot/certbot/_internal/plugins/standalone.py | 210 +++ certbot/certbot/_internal/plugins/webroot.py | 288 +++ certbot/certbot/_internal/renewal.py | 476 +++++ certbot/certbot/_internal/reporter.py | 100 ++ certbot/certbot/_internal/storage.py | 1128 ++++++++++++ certbot/certbot/_internal/updater.py | 122 ++ certbot/certbot/achallenges.py | 60 + certbot/certbot/compat/__init__.py | 6 + certbot/certbot/compat/_path.py | 31 + certbot/certbot/compat/filesystem.py | 602 +++++++ certbot/certbot/compat/misc.py | 110 ++ certbot/certbot/compat/os.py | 136 ++ certbot/certbot/crypto_util.py | 491 ++++++ certbot/certbot/display/__init__.py | 1 + certbot/certbot/display/ops.py | 368 ++++ certbot/certbot/display/util.py | 598 +++++++ certbot/certbot/errors.py | 110 ++ certbot/certbot/interfaces.py | 604 +++++++ certbot/certbot/main.py | 14 + certbot/certbot/plugins/__init__.py | 1 + certbot/certbot/plugins/common.py | 458 +++++ certbot/certbot/plugins/dns_common.py | 335 ++++ certbot/certbot/plugins/dns_common_lexicon.py | 138 ++ certbot/certbot/plugins/dns_test_common.py | 63 + certbot/certbot/plugins/dns_test_common_lexicon.py | 128 ++ certbot/certbot/plugins/enhancements.py | 175 ++ certbot/certbot/plugins/storage.py | 123 ++ certbot/certbot/plugins/util.py | 58 + certbot/certbot/reverter.py | 535 ++++++ certbot/certbot/ssl-dhparams.pem | 8 + certbot/certbot/tests/__init__.py | 1 + certbot/certbot/tests/acme_util.py | 98 ++ certbot/certbot/tests/testdata/README | 11 + certbot/certbot/tests/testdata/cert-5sans_512.pem | 16 + .../tests/testdata/cert-nosans_nistp256.pem | 11 + certbot/certbot/tests/testdata/cert-san_512.pem | 14 + certbot/certbot/tests/testdata/cert_2048.pem | 20 + certbot/certbot/tests/testdata/cert_512.pem | 13 + certbot/certbot/tests/testdata/cert_512_bad.pem | 15 + .../certbot/tests/testdata/cert_fullchain_2048.pem | 40 + certbot/certbot/tests/testdata/cli.ini | 1 + certbot/certbot/tests/testdata/csr-6sans_512.conf | 29 + certbot/certbot/tests/testdata/csr-6sans_512.pem | 12 + certbot/certbot/tests/testdata/csr-nonames_512.pem | 8 + certbot/certbot/tests/testdata/csr-nosans_512.conf | 16 + certbot/certbot/tests/testdata/csr-nosans_512.pem | 9 + .../certbot/tests/testdata/csr-nosans_nistp256.pem | 8 + certbot/certbot/tests/testdata/csr-san_512.pem | 10 + certbot/certbot/tests/testdata/csr_512.der | Bin 0 -> 281 bytes certbot/certbot/tests/testdata/csr_512.pem | 8 + certbot/certbot/tests/testdata/nistp256_key.pem | 5 + .../certbot/tests/testdata/ocsp_certificate.pem | 37 + .../tests/testdata/ocsp_issuer_certificate.pem | 38 + .../tests/testdata/ocsp_responder_certificate.pem | 27 + certbot/certbot/tests/testdata/os-release | 7 + certbot/certbot/tests/testdata/rsa2048_key.pem | 28 + certbot/certbot/tests/testdata/rsa256_key.pem | 6 + certbot/certbot/tests/testdata/rsa512_key.pem | 9 + .../tests/testdata/sample-archive/cert1.pem | 28 + .../tests/testdata/sample-archive/chain1.pem | 19 + .../tests/testdata/sample-archive/fullchain1.pem | 47 + .../tests/testdata/sample-archive/privkey1.pem | 28 + .../tests/testdata/sample-renewal-ancient.conf | 73 + certbot/certbot/tests/testdata/sample-renewal.conf | 75 + certbot/certbot/tests/testdata/webrootconftest.ini | 3 + certbot/certbot/tests/util.py | 399 +++++ certbot/certbot/util.py | 603 +++++++ certbot/compat/__init__.py | 6 - certbot/compat/_path.py | 31 - certbot/compat/filesystem.py | 602 ------- certbot/compat/misc.py | 110 -- certbot/compat/os.py | 136 -- certbot/crypto_util.py | 491 ------ certbot/display/__init__.py | 1 - certbot/display/ops.py | 368 ---- certbot/display/util.py | 598 ------- certbot/docs/.gitignore | 1 + certbot/docs/Makefile | 183 ++ certbot/docs/_static/.gitignore | 0 certbot/docs/_templates/footer.html | 52 + certbot/docs/api.rst | 8 + certbot/docs/api/achallenges.rst | 5 + certbot/docs/api/crypto_util.rst | 5 + certbot/docs/api/display.rst | 17 + certbot/docs/api/errors.rst | 5 + certbot/docs/api/index.rst | 5 + certbot/docs/api/interfaces.rst | 5 + certbot/docs/api/main.rst | 5 + certbot/docs/api/plugins/common.rst | 5 + certbot/docs/api/plugins/dns_common.rst | 5 + certbot/docs/api/plugins/dns_common_lexicon.rst | 5 + certbot/docs/api/plugins/util.rst | 5 + certbot/docs/api/reverter.rst | 5 + certbot/docs/api/util.rst | 5 + certbot/docs/challenges.rst | 70 + certbot/docs/ciphers.rst | 339 ++++ certbot/docs/cli-help.txt | 718 ++++++++ certbot/docs/conf.py | 323 ++++ certbot/docs/contributing.rst | 596 +++++++ certbot/docs/index.rst | 26 + certbot/docs/install.rst | 336 ++++ certbot/docs/intro.rst | 10 + certbot/docs/make.bat | 263 +++ certbot/docs/man/certbot.rst | 1 + certbot/docs/packaging.rst | 49 + certbot/docs/resources.rst | 7 + certbot/docs/using.rst | 990 +++++++++++ certbot/docs/what.rst | 31 + certbot/errors.py | 110 -- certbot/examples/.gitignore | 3 + certbot/examples/cli.ini | 22 + certbot/examples/dev-cli.ini | 20 + certbot/examples/generate-csr.sh | 28 + certbot/examples/openssl.cnf | 5 + .../examples/plugins/certbot_example_plugins.py | 31 + certbot/examples/plugins/setup.py | 17 + certbot/interfaces.py | 604 ------- certbot/local-oldest-requirements.txt | 2 + certbot/plugins/__init__.py | 1 - certbot/plugins/common.py | 458 ----- certbot/plugins/common_test.py | 356 ---- certbot/plugins/disco_test.py | 292 --- certbot/plugins/dns_common.py | 335 ---- certbot/plugins/dns_common_lexicon.py | 138 -- certbot/plugins/dns_common_lexicon_test.py | 27 - certbot/plugins/dns_common_test.py | 230 --- certbot/plugins/dns_test_common.py | 63 - certbot/plugins/dns_test_common_lexicon.py | 128 -- certbot/plugins/enhancements.py | 175 -- certbot/plugins/enhancements_test.py | 65 - certbot/plugins/manual_test.py | 133 -- certbot/plugins/null_test.py | 22 - certbot/plugins/selection_test.py | 220 --- certbot/plugins/standalone_test.py | 186 -- certbot/plugins/storage.py | 123 -- certbot/plugins/storage_test.py | 119 -- certbot/plugins/util.py | 58 - certbot/plugins/util_test.py | 45 - certbot/plugins/webroot_test.py | 318 ---- certbot/readthedocs.org.requirements.txt | 11 + certbot/reverter.py | 535 ------ certbot/setup.cfg | 5 + certbot/setup.py | 175 ++ certbot/ssl-dhparams.pem | 8 - certbot/tests/__init__.py | 1 - certbot/tests/acme_util.py | 98 -- certbot/tests/cert_manager_test.py | 3 +- certbot/tests/plugins/__init__.py | 1 + certbot/tests/plugins/common_test.py | 356 ++++ certbot/tests/plugins/disco_test.py | 292 +++ certbot/tests/plugins/dns_common_lexicon_test.py | 27 + certbot/tests/plugins/dns_common_test.py | 230 +++ certbot/tests/plugins/enhancements_test.py | 65 + certbot/tests/plugins/manual_test.py | 133 ++ certbot/tests/plugins/null_test.py | 22 + certbot/tests/plugins/selection_test.py | 220 +++ certbot/tests/plugins/standalone_test.py | 186 ++ certbot/tests/plugins/storage_test.py | 119 ++ certbot/tests/plugins/util_test.py | 45 + certbot/tests/plugins/webroot_test.py | 318 ++++ certbot/tests/testdata/README | 11 - certbot/tests/testdata/cert-5sans_512.pem | 16 - certbot/tests/testdata/cert-nosans_nistp256.pem | 11 - certbot/tests/testdata/cert-san_512.pem | 14 - certbot/tests/testdata/cert_2048.pem | 20 - certbot/tests/testdata/cert_512.pem | 13 - certbot/tests/testdata/cert_512_bad.pem | 15 - certbot/tests/testdata/cert_fullchain_2048.pem | 40 - certbot/tests/testdata/cli.ini | 1 - certbot/tests/testdata/csr-6sans_512.conf | 29 - certbot/tests/testdata/csr-6sans_512.pem | 12 - certbot/tests/testdata/csr-nonames_512.pem | 8 - certbot/tests/testdata/csr-nosans_512.conf | 16 - certbot/tests/testdata/csr-nosans_512.pem | 9 - certbot/tests/testdata/csr-nosans_nistp256.pem | 8 - certbot/tests/testdata/csr-san_512.pem | 10 - certbot/tests/testdata/csr_512.der | Bin 281 -> 0 bytes certbot/tests/testdata/csr_512.pem | 8 - certbot/tests/testdata/nistp256_key.pem | 5 - certbot/tests/testdata/ocsp_certificate.pem | 37 - certbot/tests/testdata/ocsp_issuer_certificate.pem | 38 - .../tests/testdata/ocsp_responder_certificate.pem | 27 - certbot/tests/testdata/os-release | 7 - certbot/tests/testdata/rsa2048_key.pem | 28 - certbot/tests/testdata/rsa256_key.pem | 6 - certbot/tests/testdata/rsa512_key.pem | 9 - certbot/tests/testdata/sample-archive/cert1.pem | 28 - certbot/tests/testdata/sample-archive/chain1.pem | 19 - .../tests/testdata/sample-archive/fullchain1.pem | 47 - certbot/tests/testdata/sample-archive/privkey1.pem | 28 - certbot/tests/testdata/sample-renewal-ancient.conf | 73 - certbot/tests/testdata/sample-renewal.conf | 75 - certbot/tests/testdata/webrootconftest.ini | 3 - certbot/tests/util.py | 399 ----- certbot/util.py | 603 ------- docs/.gitignore | 1 - docs/Makefile | 183 -- docs/_static/.gitignore | 0 docs/_templates/footer.html | 52 - docs/api.rst | 8 - docs/api/achallenges.rst | 5 - docs/api/crypto_util.rst | 5 - docs/api/display.rst | 17 - docs/api/errors.rst | 5 - docs/api/index.rst | 5 - docs/api/interfaces.rst | 5 - docs/api/plugins/common.rst | 5 - docs/api/plugins/dns_common.rst | 5 - docs/api/plugins/dns_common_lexicon.rst | 5 - docs/api/plugins/util.rst | 5 - docs/api/reverter.rst | 5 - docs/api/util.rst | 5 - docs/challenges.rst | 70 - docs/ciphers.rst | 339 ---- docs/cli-help.txt | 718 -------- docs/conf.py | 323 ---- docs/contributing.rst | 596 ------- docs/index.rst | 26 - docs/install.rst | 336 ---- docs/intro.rst | 10 - docs/make.bat | 263 --- docs/man/certbot.rst | 1 - docs/packaging.rst | 49 - docs/resources.rst | 7 - docs/using.rst | 990 ----------- docs/what.rst | 31 - examples/.gitignore | 3 - examples/cli.ini | 22 - examples/dev-cli.ini | 20 - examples/generate-csr.sh | 28 - examples/openssl.cnf | 5 - examples/plugins/certbot_example_plugins.py | 31 - examples/plugins/setup.py | 17 - letsencrypt-auto-source/rebuild_dependencies.py | 2 +- letsencrypt-auto-source/version.py | 1 + letshelp-certbot/readthedocs.org.requirements.txt | 4 +- local-oldest-requirements.txt | 2 - readthedocs.org.requirements.txt | 11 - setup.cfg | 5 - setup.py | 177 -- tests/letstest/scripts/test_apache2.sh | 2 +- tests/letstest/scripts/test_sdists.sh | 2 +- tests/letstest/scripts/test_tests.sh | 11 +- tools/_release.sh | 24 +- tools/_venv_common.py | 2 +- tools/deps.sh | 15 - tools/install_and_test.py | 18 +- tools/pip_install.py | 4 +- tools/venv3.py | 2 +- tox.cover.py | 4 +- tox.ini | 20 +- windows-installer/construct.py | 2 +- 336 files changed, 25738 insertions(+), 25540 deletions(-) mode change 100644 => 120000 CHANGELOG.md delete mode 100644 MANIFEST.in mode change 100644 => 120000 README.rst create mode 100644 certbot/CHANGELOG.md create mode 100644 certbot/LICENSE.txt create mode 100644 certbot/MANIFEST.in create mode 100644 certbot/README.rst delete mode 100644 certbot/__init__.py delete mode 100644 certbot/_internal/__init__.py delete mode 100644 certbot/_internal/account.py delete mode 100644 certbot/_internal/auth_handler.py delete mode 100644 certbot/_internal/cert_manager.py delete mode 100644 certbot/_internal/cli.py delete mode 100644 certbot/_internal/client.py delete mode 100644 certbot/_internal/configuration.py delete mode 100644 certbot/_internal/constants.py delete mode 100644 certbot/_internal/display/__init__.py delete mode 100644 certbot/_internal/display/completer.py delete mode 100644 certbot/_internal/display/dummy_readline.py delete mode 100644 certbot/_internal/display/enhancements.py delete mode 100644 certbot/_internal/eff.py delete mode 100644 certbot/_internal/error_handler.py delete mode 100644 certbot/_internal/hooks.py delete mode 100644 certbot/_internal/lock.py delete mode 100644 certbot/_internal/log.py delete mode 100644 certbot/_internal/main.py delete mode 100644 certbot/_internal/notify.py delete mode 100644 certbot/_internal/ocsp.py delete mode 100644 certbot/_internal/plugins/__init__.py delete mode 100644 certbot/_internal/plugins/disco.py delete mode 100644 certbot/_internal/plugins/manual.py delete mode 100644 certbot/_internal/plugins/null.py delete mode 100644 certbot/_internal/plugins/selection.py delete mode 100644 certbot/_internal/plugins/standalone.py delete mode 100644 certbot/_internal/plugins/webroot.py delete mode 100644 certbot/_internal/renewal.py delete mode 100644 certbot/_internal/reporter.py delete mode 100644 certbot/_internal/storage.py delete mode 100644 certbot/_internal/updater.py delete mode 100644 certbot/achallenges.py create mode 100644 certbot/certbot/__init__.py create mode 100644 certbot/certbot/_internal/__init__.py create mode 100644 certbot/certbot/_internal/account.py create mode 100644 certbot/certbot/_internal/auth_handler.py create mode 100644 certbot/certbot/_internal/cert_manager.py create mode 100644 certbot/certbot/_internal/cli.py create mode 100644 certbot/certbot/_internal/client.py create mode 100644 certbot/certbot/_internal/configuration.py create mode 100644 certbot/certbot/_internal/constants.py create mode 100644 certbot/certbot/_internal/display/__init__.py create mode 100644 certbot/certbot/_internal/display/completer.py create mode 100644 certbot/certbot/_internal/display/dummy_readline.py create mode 100644 certbot/certbot/_internal/display/enhancements.py create mode 100644 certbot/certbot/_internal/eff.py create mode 100644 certbot/certbot/_internal/error_handler.py create mode 100644 certbot/certbot/_internal/hooks.py create mode 100644 certbot/certbot/_internal/lock.py create mode 100644 certbot/certbot/_internal/log.py create mode 100644 certbot/certbot/_internal/main.py create mode 100644 certbot/certbot/_internal/notify.py create mode 100644 certbot/certbot/_internal/ocsp.py create mode 100644 certbot/certbot/_internal/plugins/__init__.py create mode 100644 certbot/certbot/_internal/plugins/disco.py create mode 100644 certbot/certbot/_internal/plugins/manual.py create mode 100644 certbot/certbot/_internal/plugins/null.py create mode 100644 certbot/certbot/_internal/plugins/selection.py create mode 100644 certbot/certbot/_internal/plugins/standalone.py create mode 100644 certbot/certbot/_internal/plugins/webroot.py create mode 100644 certbot/certbot/_internal/renewal.py create mode 100644 certbot/certbot/_internal/reporter.py create mode 100644 certbot/certbot/_internal/storage.py create mode 100644 certbot/certbot/_internal/updater.py create mode 100644 certbot/certbot/achallenges.py create mode 100644 certbot/certbot/compat/__init__.py create mode 100644 certbot/certbot/compat/_path.py create mode 100644 certbot/certbot/compat/filesystem.py create mode 100644 certbot/certbot/compat/misc.py create mode 100644 certbot/certbot/compat/os.py create mode 100644 certbot/certbot/crypto_util.py create mode 100644 certbot/certbot/display/__init__.py create mode 100644 certbot/certbot/display/ops.py create mode 100644 certbot/certbot/display/util.py create mode 100644 certbot/certbot/errors.py create mode 100644 certbot/certbot/interfaces.py create mode 100644 certbot/certbot/main.py create mode 100644 certbot/certbot/plugins/__init__.py create mode 100644 certbot/certbot/plugins/common.py create mode 100644 certbot/certbot/plugins/dns_common.py create mode 100644 certbot/certbot/plugins/dns_common_lexicon.py create mode 100644 certbot/certbot/plugins/dns_test_common.py create mode 100644 certbot/certbot/plugins/dns_test_common_lexicon.py create mode 100644 certbot/certbot/plugins/enhancements.py create mode 100644 certbot/certbot/plugins/storage.py create mode 100644 certbot/certbot/plugins/util.py create mode 100644 certbot/certbot/reverter.py create mode 100644 certbot/certbot/ssl-dhparams.pem create mode 100644 certbot/certbot/tests/__init__.py create mode 100644 certbot/certbot/tests/acme_util.py create mode 100644 certbot/certbot/tests/testdata/README create mode 100644 certbot/certbot/tests/testdata/cert-5sans_512.pem create mode 100644 certbot/certbot/tests/testdata/cert-nosans_nistp256.pem create mode 100644 certbot/certbot/tests/testdata/cert-san_512.pem create mode 100644 certbot/certbot/tests/testdata/cert_2048.pem create mode 100644 certbot/certbot/tests/testdata/cert_512.pem create mode 100644 certbot/certbot/tests/testdata/cert_512_bad.pem create mode 100644 certbot/certbot/tests/testdata/cert_fullchain_2048.pem create mode 100644 certbot/certbot/tests/testdata/cli.ini create mode 100644 certbot/certbot/tests/testdata/csr-6sans_512.conf create mode 100644 certbot/certbot/tests/testdata/csr-6sans_512.pem create mode 100644 certbot/certbot/tests/testdata/csr-nonames_512.pem create mode 100644 certbot/certbot/tests/testdata/csr-nosans_512.conf create mode 100644 certbot/certbot/tests/testdata/csr-nosans_512.pem create mode 100644 certbot/certbot/tests/testdata/csr-nosans_nistp256.pem create mode 100644 certbot/certbot/tests/testdata/csr-san_512.pem create mode 100644 certbot/certbot/tests/testdata/csr_512.der create mode 100644 certbot/certbot/tests/testdata/csr_512.pem create mode 100644 certbot/certbot/tests/testdata/nistp256_key.pem create mode 100644 certbot/certbot/tests/testdata/ocsp_certificate.pem create mode 100644 certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem create mode 100644 certbot/certbot/tests/testdata/ocsp_responder_certificate.pem create mode 100644 certbot/certbot/tests/testdata/os-release create mode 100644 certbot/certbot/tests/testdata/rsa2048_key.pem create mode 100644 certbot/certbot/tests/testdata/rsa256_key.pem create mode 100644 certbot/certbot/tests/testdata/rsa512_key.pem create mode 100644 certbot/certbot/tests/testdata/sample-archive/cert1.pem create mode 100644 certbot/certbot/tests/testdata/sample-archive/chain1.pem create mode 100644 certbot/certbot/tests/testdata/sample-archive/fullchain1.pem create mode 100644 certbot/certbot/tests/testdata/sample-archive/privkey1.pem create mode 100644 certbot/certbot/tests/testdata/sample-renewal-ancient.conf create mode 100644 certbot/certbot/tests/testdata/sample-renewal.conf create mode 100644 certbot/certbot/tests/testdata/webrootconftest.ini create mode 100644 certbot/certbot/tests/util.py create mode 100644 certbot/certbot/util.py delete mode 100644 certbot/compat/__init__.py delete mode 100644 certbot/compat/_path.py delete mode 100644 certbot/compat/filesystem.py delete mode 100644 certbot/compat/misc.py delete mode 100644 certbot/compat/os.py delete mode 100644 certbot/crypto_util.py delete mode 100644 certbot/display/__init__.py delete mode 100644 certbot/display/ops.py delete mode 100644 certbot/display/util.py create mode 100644 certbot/docs/.gitignore create mode 100644 certbot/docs/Makefile create mode 100644 certbot/docs/_static/.gitignore create mode 100644 certbot/docs/_templates/footer.html create mode 100644 certbot/docs/api.rst create mode 100644 certbot/docs/api/achallenges.rst create mode 100644 certbot/docs/api/crypto_util.rst create mode 100644 certbot/docs/api/display.rst create mode 100644 certbot/docs/api/errors.rst create mode 100644 certbot/docs/api/index.rst create mode 100644 certbot/docs/api/interfaces.rst create mode 100644 certbot/docs/api/main.rst create mode 100644 certbot/docs/api/plugins/common.rst create mode 100644 certbot/docs/api/plugins/dns_common.rst create mode 100644 certbot/docs/api/plugins/dns_common_lexicon.rst create mode 100644 certbot/docs/api/plugins/util.rst create mode 100644 certbot/docs/api/reverter.rst create mode 100644 certbot/docs/api/util.rst create mode 100644 certbot/docs/challenges.rst create mode 100644 certbot/docs/ciphers.rst create mode 100644 certbot/docs/cli-help.txt create mode 100644 certbot/docs/conf.py create mode 100644 certbot/docs/contributing.rst create mode 100644 certbot/docs/index.rst create mode 100644 certbot/docs/install.rst create mode 100644 certbot/docs/intro.rst create mode 100644 certbot/docs/make.bat create mode 100644 certbot/docs/man/certbot.rst create mode 100644 certbot/docs/packaging.rst create mode 100644 certbot/docs/resources.rst create mode 100644 certbot/docs/using.rst create mode 100644 certbot/docs/what.rst delete mode 100644 certbot/errors.py create mode 100644 certbot/examples/.gitignore create mode 100644 certbot/examples/cli.ini create mode 100644 certbot/examples/dev-cli.ini create mode 100755 certbot/examples/generate-csr.sh create mode 100644 certbot/examples/openssl.cnf create mode 100644 certbot/examples/plugins/certbot_example_plugins.py create mode 100644 certbot/examples/plugins/setup.py delete mode 100644 certbot/interfaces.py create mode 100644 certbot/local-oldest-requirements.txt delete mode 100644 certbot/plugins/__init__.py delete mode 100644 certbot/plugins/common.py delete mode 100644 certbot/plugins/common_test.py delete mode 100644 certbot/plugins/disco_test.py delete mode 100644 certbot/plugins/dns_common.py delete mode 100644 certbot/plugins/dns_common_lexicon.py delete mode 100644 certbot/plugins/dns_common_lexicon_test.py delete mode 100644 certbot/plugins/dns_common_test.py delete mode 100644 certbot/plugins/dns_test_common.py delete mode 100644 certbot/plugins/dns_test_common_lexicon.py delete mode 100644 certbot/plugins/enhancements.py delete mode 100644 certbot/plugins/enhancements_test.py delete mode 100644 certbot/plugins/manual_test.py delete mode 100644 certbot/plugins/null_test.py delete mode 100644 certbot/plugins/selection_test.py delete mode 100644 certbot/plugins/standalone_test.py delete mode 100644 certbot/plugins/storage.py delete mode 100644 certbot/plugins/storage_test.py delete mode 100644 certbot/plugins/util.py delete mode 100644 certbot/plugins/util_test.py delete mode 100644 certbot/plugins/webroot_test.py create mode 100644 certbot/readthedocs.org.requirements.txt delete mode 100644 certbot/reverter.py create mode 100644 certbot/setup.cfg create mode 100644 certbot/setup.py delete mode 100644 certbot/ssl-dhparams.pem delete mode 100644 certbot/tests/__init__.py delete mode 100644 certbot/tests/acme_util.py create mode 100644 certbot/tests/plugins/__init__.py create mode 100644 certbot/tests/plugins/common_test.py create mode 100644 certbot/tests/plugins/disco_test.py create mode 100644 certbot/tests/plugins/dns_common_lexicon_test.py create mode 100644 certbot/tests/plugins/dns_common_test.py create mode 100644 certbot/tests/plugins/enhancements_test.py create mode 100644 certbot/tests/plugins/manual_test.py create mode 100644 certbot/tests/plugins/null_test.py create mode 100644 certbot/tests/plugins/selection_test.py create mode 100644 certbot/tests/plugins/standalone_test.py create mode 100644 certbot/tests/plugins/storage_test.py create mode 100644 certbot/tests/plugins/util_test.py create mode 100644 certbot/tests/plugins/webroot_test.py delete mode 100644 certbot/tests/testdata/README delete mode 100644 certbot/tests/testdata/cert-5sans_512.pem delete mode 100644 certbot/tests/testdata/cert-nosans_nistp256.pem delete mode 100644 certbot/tests/testdata/cert-san_512.pem delete mode 100644 certbot/tests/testdata/cert_2048.pem delete mode 100644 certbot/tests/testdata/cert_512.pem delete mode 100644 certbot/tests/testdata/cert_512_bad.pem delete mode 100644 certbot/tests/testdata/cert_fullchain_2048.pem delete mode 100644 certbot/tests/testdata/cli.ini delete mode 100644 certbot/tests/testdata/csr-6sans_512.conf delete mode 100644 certbot/tests/testdata/csr-6sans_512.pem delete mode 100644 certbot/tests/testdata/csr-nonames_512.pem delete mode 100644 certbot/tests/testdata/csr-nosans_512.conf delete mode 100644 certbot/tests/testdata/csr-nosans_512.pem delete mode 100644 certbot/tests/testdata/csr-nosans_nistp256.pem delete mode 100644 certbot/tests/testdata/csr-san_512.pem delete mode 100644 certbot/tests/testdata/csr_512.der delete mode 100644 certbot/tests/testdata/csr_512.pem delete mode 100644 certbot/tests/testdata/nistp256_key.pem delete mode 100644 certbot/tests/testdata/ocsp_certificate.pem delete mode 100644 certbot/tests/testdata/ocsp_issuer_certificate.pem delete mode 100644 certbot/tests/testdata/ocsp_responder_certificate.pem delete mode 100644 certbot/tests/testdata/os-release delete mode 100644 certbot/tests/testdata/rsa2048_key.pem delete mode 100644 certbot/tests/testdata/rsa256_key.pem delete mode 100644 certbot/tests/testdata/rsa512_key.pem delete mode 100644 certbot/tests/testdata/sample-archive/cert1.pem delete mode 100644 certbot/tests/testdata/sample-archive/chain1.pem delete mode 100644 certbot/tests/testdata/sample-archive/fullchain1.pem delete mode 100644 certbot/tests/testdata/sample-archive/privkey1.pem delete mode 100644 certbot/tests/testdata/sample-renewal-ancient.conf delete mode 100644 certbot/tests/testdata/sample-renewal.conf delete mode 100644 certbot/tests/testdata/webrootconftest.ini delete mode 100644 certbot/tests/util.py delete mode 100644 certbot/util.py delete mode 100644 docs/.gitignore delete mode 100644 docs/Makefile delete mode 100644 docs/_static/.gitignore delete mode 100644 docs/_templates/footer.html delete mode 100644 docs/api.rst delete mode 100644 docs/api/achallenges.rst delete mode 100644 docs/api/crypto_util.rst delete mode 100644 docs/api/display.rst delete mode 100644 docs/api/errors.rst delete mode 100644 docs/api/index.rst delete mode 100644 docs/api/interfaces.rst delete mode 100644 docs/api/plugins/common.rst delete mode 100644 docs/api/plugins/dns_common.rst delete mode 100644 docs/api/plugins/dns_common_lexicon.rst delete mode 100644 docs/api/plugins/util.rst delete mode 100644 docs/api/reverter.rst delete mode 100644 docs/api/util.rst delete mode 100644 docs/challenges.rst delete mode 100644 docs/ciphers.rst delete mode 100644 docs/cli-help.txt delete mode 100644 docs/conf.py delete mode 100644 docs/contributing.rst delete mode 100644 docs/index.rst delete mode 100644 docs/install.rst delete mode 100644 docs/intro.rst delete mode 100644 docs/make.bat delete mode 100644 docs/man/certbot.rst delete mode 100644 docs/packaging.rst delete mode 100644 docs/resources.rst delete mode 100644 docs/using.rst delete mode 100644 docs/what.rst delete mode 100644 examples/.gitignore delete mode 100644 examples/cli.ini delete mode 100644 examples/dev-cli.ini delete mode 100755 examples/generate-csr.sh delete mode 100644 examples/openssl.cnf delete mode 100644 examples/plugins/certbot_example_plugins.py delete mode 100644 examples/plugins/setup.py delete mode 100644 local-oldest-requirements.txt delete mode 100644 readthedocs.org.requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100755 tools/deps.sh diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index dfb6acde8..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1852 +0,0 @@ -# Certbot change log - -Certbot adheres to [Semantic Versioning](https://semver.org/). - -## 1.0.0 - master - -### Added - -* - -### Removed - -* The `docs` extras for the `certbot-apache` and `certbot-nginx` packages - have been removed. - -### Changed - -* certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are - not running on x86-64. This primarily affects RHEL 6 based systems. -* Certbot's `config_changes` subcommand has been removed -* `certbot.plugins.common.TLSSNI01` has been removed. -* Deprecated attributes related to the TLS-SNI-01 challenge in - `acme.challenges` and `acme.standalone` - have been removed. -* The functions `certbot.client.view_config_changes`, - `certbot.main.config_changes`, - `certbot.plugins.common.Installer.view_config_changes`, - `certbot.reverter.Reverter.view_config_changes`, and - `certbot.util.get_systemd_os_info` have been removed -* Certbot's `register --update-registration` subcommand has been removed - -### Fixed - -* - -More details about these changes can be found on our GitHub repo. - -## 0.40.1 - 2019-11-05 - -### Changed - -* Added back support for Python 3.4 to Certbot components and certbot-auto due - to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. - -More details about these changes can be found on our GitHub repo. - -## 0.40.0 - 2019-11-05 - -### Added - -* - -### Changed - -* We deprecated support for Python 3.4 in Certbot and its ACME library. Support - for Python 3.4 will be removed in the next major release of Certbot. - certbot-auto users on RHEL 6 based systems will be asked to enable Software - Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can - enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based - systems will be asked to do this manually. -* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the - staging server instead of the live server when `--dry-run` is used. -* `--dry-run` now requests fresh authorizations every time, fixing the issue - where it was prone to falsely reporting success. -* Updated certbot-dns-google to depend on newer versions of - google-api-python-client and oauth2client. -* The OS detection logic again uses distro library for Linux OSes -* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a - future release. -* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. -* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no - longer accepted. -* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script` -* acme.standalone.BaseRequestHandlerWithLogging and - acme.standalone.simple_tls_sni_01_server have been deprecated and will be - removed in a future release of the library. -* certbot-dns-rfc2136 now use TCP to query SOA records. - -### Fixed - -* - -More details about these changes can be found on our GitHub repo. - -## 0.39.0 - 2019-10-01 - -### Added - -* Support for Python 3.8 was added to Certbot and all of its components. -* Support for CentOS 8 was added to certbot-auto. - -### Changed - -* Don't send OCSP requests for expired certificates -* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 -* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11. - -### Fixed - -* Fixed OS detection in the Apache plugin on RHEL 6. - -More details about these changes can be found on our GitHub repo. - -## 0.38.0 - 2019-09-03 - -### Added - -* Disable session tickets for Nginx users when appropriate. - -### Changed - -* If Certbot fails to rollback your server configuration, the error message - links to the Let's Encrypt forum. Change the link to the Help category now - that the Server category has been closed. -* Replace platform.linux_distribution with distro.linux_distribution as a step - towards Python 3.8 support in Certbot. - -### Fixed - -* Fixed OS detection in the Apache plugin on Scientific Linux. - -More details about these changes can be found on our GitHub repo. - -## 0.37.2 - 2019-08-21 - -* Stop disabling TLS session tickets in Nginx as it caused TLS failures on - some systems. - -More details about these changes can be found on our GitHub repo. - -## 0.37.1 - 2019-08-08 - -### Fixed - -* Stop disabling TLS session tickets in Apache as it caused TLS failures on - some systems. - -More details about these changes can be found on our GitHub repo. - -## 0.37.0 - 2019-08-07 - -### Added - -* Turn off session tickets for apache plugin by default -* acme: Authz deactivation added to `acme` module. - -### Changed - -* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers, - and ssl_prefer_server_ciphers - -### Fixed - -* Fix certbot-auto failures on RHEL 8. - -More details about these changes can be found on our GitHub repo. - -## 0.36.0 - 2019-07-11 - -### Added - -* Turn off session tickets for nginx plugin by default -* Added missing error types from RFC8555 to acme - -### Changed - -* Support for Ubuntu 14.04 Trusty has been removed. -* Update the 'manage your account' help to be more generic. -* The error message when Certbot's Apache plugin is unable to modify your - Apache configuration has been improved. -* Certbot's config_changes subcommand has been deprecated and will be - removed in a future release. -* `certbot config_changes` no longer accepts a --num parameter. -* The functions `certbot.plugins.common.Installer.view_config_changes` and - `certbot.reverter.Reverter.view_config_changes` have been deprecated and will - be removed in a future release. - -### Fixed - -* Replace some unnecessary platform-specific line separation. - -More details about these changes can be found on our GitHub repo. - -## 0.35.1 - 2019-06-10 - -### Fixed - -* Support for specifying an authoritative base domain in our dns-rfc2136 plugin - has been removed. This feature was added in our last release but had a bug - which caused the plugin to fail so the feature has been removed until it can - be added properly. - -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-dns-rfc2136 - -More details about these changes can be found on our GitHub repo. - -## 0.35.0 - 2019-06-05 - -### Added - -* dns_rfc2136 plugin now supports explicitly specifing an authorative - base domain for cases when the automatic method does not work (e.g. - Split horizon DNS) - -### Changed - -* - -### Fixed - -* Renewal parameter `webroot_path` is always saved, avoiding some regressions - when `webroot` authenticator plugin is invoked with no challenge to perform. -* Certbot now accepts OCSP responses when an explicit authorized - responder, different from the issuer, is used to sign OCSP - responses. -* Scripts in Certbot hook directories are no longer executed when their - filenames end in a tilde. - -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 -* certbot-dns-rfc2136 - -More details about these changes can be found on our GitHub repo. - -## 0.34.2 - 2019-05-07 - -### Fixed - -* certbot-auto no longer writes a check_permissions.py script at the root - of the filesystem. - -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 -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.34.1 - 2019-05-06 - -### Fixed - -* certbot-auto no longer prints a blank line when there are no permissions - problems. - -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 -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.34.0 - 2019-05-01 - -### Changed - -* Apache plugin now tries to restart httpd on Fedora using systemctl if a - configuration test error is detected. This has to be done due to the way - Fedora now generates the self signed certificate files upon first - restart. -* Updated Certbot and its plugins to improve the handling of file system permissions - on Windows as a step towards adding proper Windows support to Certbot. -* Updated urllib3 to 1.24.2 in certbot-auto. -* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response - with a `keyAuthorization` if sending the response without this field caused a - `malformed` error to be received from the ACME server. -* Linode DNS plugin now supports api keys created from their new panel - at [cloud.linode.com](https://cloud.linode.com) - -### Fixed - -* Fixed Google DNS Challenge issues when private zones exist -* Adding a warning noting that future versions of Certbot will automatically configure the - webserver so that all requests redirect to secure HTTPS access. You can control this - behavior and disable this warning with the --redirect and --no-redirect flags. -* certbot-auto now prints warnings when run as root with insecure file system - permissions. If you see these messages, you should fix the problem by - following the instructions at - https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, - however, these warnings can be disabled as necessary with the flag - --no-permissions-check. -* `acme` module uses now a POST-as-GET request to retrieve the registration - from an ACME v2 server -* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to - all uppercase letters before validating. This makes the value in the config case - insensitive. - -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-cloudxns -* certbot-dns-digitalocean -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-google -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-rfc2136 -* certbot-dns-route53 -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.33.1 - 2019-04-04 - -### Fixed - -* A bug causing certbot-auto to print warnings or crash on some RHEL based - systems has been resolved. - -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 -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.33.0 - 2019-04-03 - -### Added - -* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation - path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. -* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces - `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while - setting up a new SSL vhost. By default the HTTPS port is 443. - -### Changed - -* Support for TLS-SNI-01 has been removed from all official Certbot plugins. -* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone` - modules are deprecated and will be removed soon. -* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will - generate a deprecation warning if used, and will be removed soon. -* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, - will generate a deprecation warning if used, and will be removed soon. -* CLI flag `--standalone-supported-challenges` has been removed. - -### Fixed - -* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 - is installed. We fixed a bug in Certbot causing it to interpret timestamps in - the OCSP response as being in the local timezone rather than UTC. -* Issue causing the default CentOS 6 TLS configuration to ignore some of the - HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main - http.conf for this environment where possible. - -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-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.32.0 - 2019-03-06 - -### Added - -* If possible, Certbot uses built-in support for OCSP from recent cryptography - versions instead of the OpenSSL binary: as a consequence Certbot does not need - the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. - -### Changed - -* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the - warnings described at https://github.com/certbot/josepy/issues/13. -* Apache plugin now respects CERTBOT_DOCS environment variable when adding - command line defaults. -* The running of manual plugin hooks is now always included in Certbot's log - output. -* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. -* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as - specified in the ACME protocol, to indicate when the next polling should occur. Certbot now - reads this header if set and respect its value. -* The `acme` module avoids sending the `keyAuthorization` field in the JWS - payload when responding to a challenge as the field is not included in the - current ACME protocol. To ease the migration path for ACME CA servers, - Certbot and its `acme` module will first try the request without the - `keyAuthorization` field but will temporarily retry the request with the - field included if a `malformed` error is received. This fallback will be - removed in version 0.34.0. - -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-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.31.0 - 2019-02-07 - -### Added - -* Avoid reprocessing challenges that are already validated - when a certificate is issued. -* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges - with the `acme` module. - -### Changed - -* Certbot's official Docker images are now based on Alpine Linux 3.9 rather - than 3.7. The new version comes with OpenSSL 1.1.1. -* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support - on 2.x branch is maintained). -* Apache plugin now attempts to configure all VirtualHosts matching requested - domain name instead of only a single one when answering the HTTP-01 challenge. - -### Fixed - -* Fixed accessing josepy contents through acme.jose when the full acme.jose - path is used. -* Clarify behavior for deleting certs as part of revocation. - -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-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-sakuracloud - -More details about these changes can be found on our GitHub repo. - -## 0.30.2 - 2019-01-25 - -### Fixed - -* Update the version of setuptools pinned in certbot-auto to 40.6.3 to - solve installation problems on newer OSes. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, this -release only affects certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.30.1 - 2019-01-24 - -### Fixed - -* Always download the pinned version of pip in pipstrap to address breakages -* Rename old,default.conf to old-and-default.conf to address commas in filenames - breaking recent versions of pip. -* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages - from venv downloading the latest pip - -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-apache - -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 - -## 0.28.0 - 2018-11-7 - -### Added - -* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`. -* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory. - -### Changed - -* Removed documentation mentions of `#letsencrypt` IRC on Freenode. -* Write README to the base of (config-dir)/live directory -* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges. -* Warn when using deprecated acme.challenges.TLSSNI01 -* Log warning about TLS-SNI deprecation in Certbot -* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins -* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies -* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. - -### Fixed - -* Match Nginx parser update in allowing variable names to start with `${`. -* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first -* Correct OVH integration tests on machines without internet access. -* Stop caching the results of ipv6_info in http01.py -* Test fix for Route53 plugin to prevent boto3 making outgoing connections. -* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors. -* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and - Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+. - -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-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-route53 -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/59?closed=1 - -## 0.27.1 - 2018-09-06 - -### Fixed - -* Fixed parameter name in OpenSUSE overrides for default parameters in the - Apache plugin. Certbot on OpenSUSE works again. - -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-apache - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/60?closed=1 - -## 0.27.0 - 2018-09-05 - -### Added - -* The Apache plugin now accepts the parameter --apache-ctl which can be - used to configure the path to the Apache control script. - -### Changed - -* When using `acme.client.ClientV2` (or - `acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a - newer version of the ACME protocol), an `acme.errors.ConflictError` will be - raised if you try to create an ACME account with a key that has already been - used. Previously, a JSON parsing error was raised in this scenario when using - the library with Let's Encrypt's ACMEv2 endpoint. - -### Fixed - -* When Apache is not installed, Certbot's Apache plugin no longer prints - messages about being unable to find apachectl to the terminal when the plugin - is not selected. -* If you're using the Apache plugin with the --apache-vhost-root flag set to a - directory containing a disabled virtual host for the domain you're requesting - a certificate for, the virtual host will now be temporarily enabled if - necessary to pass the HTTP challenge. -* The documentation for the Certbot package can now be built using Sphinx 1.6+. -* You can now call `query_registration` without having to first call - `new_account` on `acme.client.ClientV2` objects. -* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`. -* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura - Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on. - -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-ovh -* certbot-dns-sakuracloud - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/57?closed=1 - -## 0.26.1 - 2018-07-17 - -### Fixed - -* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs. - -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: -https://github.com/certbot/certbot/milestone/58?closed=1 - -## 0.26.0 - 2018-07-11 - -### Added - -* A new security enhancement which we're calling AutoHSTS has been added to - Certbot's Apache plugin. This enhancement configures your webserver to send a - HTTP Strict Transport Security header with a low max-age value that is slowly - increased over time. The max-age value is not increased to a large value - until you've successfully managed to renew your certificate. This enhancement - can be requested with the --auto-hsts flag. -* New official DNS plugins have been created for Gehirn Infrastracture Service, - Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub - page at https://hub.docker.com/u/certbot and on PyPI. -* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on - Let's Encrypt's ACMEv2 endpoint has been added. -* Certbot and its components now support Python 3.7. -* Certbot's install subcommand now allows you to interactively choose which - certificate to install from the list of certificates managed by Certbot. -* Certbot now accepts the flag `--no-autorenew` which causes any obtained - certificates to not be automatically renewed when it approaches expiration. -* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme - library. - -### Changed - -* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2 - endpoint. By default, this server will now be used for both new certificate - lineages and renewals. -* The Nginx plugin is no longer marked labeled as an "Alpha" version. -* The `prepare` method of Certbot's plugins is no longer called before running - "Updater" enhancements that are run on every invocation of `certbot renew`. - -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 -packages with functional changes were: - -* acme -* certbot -* certbot-apache -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-ovh -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/55?closed=1 - -## 0.25.1 - 2018-06-13 - -### Fixed - -* TLS-ALPN-01 support has been removed from our acme library. Using our current - dependencies, we are unable to provide a correct implementation of this - challenge so we decided to remove it from the library until we can provide - proper support. -* Issues causing test failures when running the tests in the acme package with - pytest<3.0 has been resolved. -* certbot-nginx now correctly depends on acme>=0.25.0. - -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 -packages with changes other than their version number were: - -* acme -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/56?closed=1 - -## 0.25.0 - 2018-06-06 - -### Added - -* Support for the ready status type was added to acme. Without this change, - Certbot and acme users will begin encountering errors when using Let's - Encrypt's ACMEv2 API starting on June 19th for the staging environment and - July 5th for production. See - https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more - information. -* Certbot now accepts the flag --reuse-key which will cause the same key to be - used in the certificate when the lineage is renewed rather than generating a - new key. -* You can now add multiple email addresses to your ACME account with Certbot by - providing a comma separated list of emails to the --email flag. -* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme. - For more information, see - https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1. -* acme now supports specifying the source address to bind to when sending - outgoing connections. You still cannot specify this address using Certbot. -* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't - already have an account registered at that server URL, Certbot will - automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint - if it exists. -* Interfaces were added to Certbot allowing plugins to be called at additional - points. The `GenericUpdater` interface allows plugins to perform actions - every time `certbot renew` is run, regardless of whether any certificates are - due for renewal, and the `RenewDeployer` interface allows plugins to perform - actions when a certificate is renewed. See `certbot.interfaces` for more - information. - -### Changed - -* When running Certbot with --dry-run and you don't already have a staging - account, the created account does not contain an email address even if one - was provided to avoid expiration emails from Let's Encrypt's staging server. -* certbot-nginx does a better job of automatically detecting the location of - Nginx's configuration files when run on BSD based systems. -* acme now requires and uses pytest when running tests with setuptools with - `python setup.py test`. -* `certbot config_changes` no longer waits for user input before exiting. - -### Fixed - -* Misleading log output that caused users to think that Certbot's standalone - plugin failed to bind to a port when performing a challenge has been - corrected. -* An issue where certbot-nginx would fail to enable HSTS if the server block - already had an `add_header` directive has been resolved. -* certbot-nginx now does a better job detecting the server block to base the - configuration for TLS-SNI challenges on. - -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 -packages with functional changes were: - -* acme -* certbot -* certbot-apache -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/54?closed=1 - -## 0.24.0 - 2018-05-02 - -### Added - -* certbot now has an enhance subcommand which allows you to configure security - enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without - reinstalling a certificate. -* certbot-dns-rfc2136 now allows the user to specify the port to use to reach - the DNS server in its credentials file. -* acme now parses the wildcard field included in authorizations so it can be - used by users of the library. - -### Changed - -* certbot-dns-route53 used to wait for each DNS update to propagate before - sending the next one, but now it sends all updates before waiting which - speeds up issuance for multiple domains dramatically. -* Certbot's official Docker images are now based on Alpine Linux 3.7 rather - than 3.4 because 3.4 has reached its end-of-life. -* We've doubled the time Certbot will spend polling authorizations before - timing out. -* The level of the message logged when Certbot is being used with - non-standard paths warning that crontabs for renewal included in Certbot - packages from OS package managers may not work has been reduced. This stops - the message from being written to stderr every time `certbot renew` runs. - -### Fixed - -* certbot-auto now works with Python 3.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 -packages with changes other than their version number were: - -* acme -* certbot -* certbot-apache -* certbot-dns-digitalocean (only style improvements to tests) -* certbot-dns-rfc2136 - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/52?closed=1 - -## 0.23.0 - 2018-04-04 - -### Added - -* Support for OpenResty was added to the Nginx plugin. - -### Changed - -* The timestamps in Certbot's logfiles now use the system's local time zone - rather than UTC. -* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able - to create and delete multiple TXT records on a single domain. -* certbot-dns-google's test suite now works without an internet connection. - -### Fixed - -* Removed a small window that if during which an error occurred, Certbot - wouldn't clean up performed challenges. -* The parameters `default` and `ipv6only` are now removed from `listen` - directives when creating a new server block in the Nginx plugin. -* `server_name` directives enclosed in quotation marks in Nginx are now properly - supported. -* Resolved an issue preventing the Apache plugin from starting Apache when it's - not currently running on RHEL and Gentoo based systems. - -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 -packages with changes other than their version number were: - -* certbot -* certbot-apache -* certbot-dns-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-google -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-rfc2136 -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/50?closed=1 - -## 0.22.2 - 2018-03-19 - -### Fixed - -* A type error introduced in 0.22.1 that would occur during challenge cleanup - when a Certbot plugin raises an exception while trying to complete the - challenge was 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 -packages with changes other than their version number were: - -* certbot - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/53?closed=1 - -## 0.22.1 - 2018-03-19 - -### Changed - -* The ACME server used with Certbot's --dry-run and --staging flags is now - Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2 - features with these flags. - -### Fixed - -* The HTTP Content-Type header is now set to the correct value during - certificate revocation with new versions of the ACME protocol. -* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank - line to the top of chain.pem and between the certificates in fullchain.pem - for each lineage. These blank lines have been removed. -* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to - work. -* Fixed a regression in acme.client.Client that caused the class to not work - when it was initialized without a ClientNetwork which is done by some of the - other projects using our ACME library. - -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 -packages with changes other than their version number were: - -* acme -* certbot - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/51?closed=1 - -## 0.22.0 - 2018-03-07 - -### Added - -* Support for obtaining wildcard certificates and a newer version of the ACME - protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2 - endpoint was added to Certbot and its ACME library. Certbot still works with - older ACME versions and will automatically change the version of the protocol - used based on the version the ACME CA implements. -* The Apache and Nginx plugins are now able to automatically install a wildcard - certificate to multiple virtual hosts that you select from your server - configuration. -* The `certbot install` command now accepts the `--cert-name` flag for - selecting a certificate. -* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library - which automatically handles most of the differences between new and old ACME - versions. `acme.client.ClientV2` is also available for people who only want - to support one version of the protocol or want to handle the differences - between versions themselves. -* certbot-auto now supports the flag --install-only which has the script - install Certbot and its dependencies and exit without invoking Certbot. -* Support for issuing a single certificate for a wildcard and base domain was - added to our Google Cloud DNS plugin. To do this, we now require your API - credentials have additional permissions, however, your credentials will - already have these permissions unless you defined a custom role with fewer - permissions than the standard DNS administrator role provided by Google. - These permissions are also only needed for the case described above so it - will continue to work for existing users. For more information about the - permissions changes, see the documentation in the plugin. - -### Changed - -* We have broken lockstep between our ACME library, Certbot, and its plugins. - This means that the different components do not need to be the same version - to work together like they did previously. This makes packaging easier - because not every piece of Certbot needs to be repackaged to ship a change to - a subset of its components. -* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot, - Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL - 6 based system, it will walk you through the process of installing Certbot - with Python 3 and refuse to upgrade to a newer version of Certbot until you - have done so. -* Certbot's components now work with older versions of setuptools to simplify - packaging for EPEL 7. - -### Fixed - -* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives - has been resolved. -* A problem where Certbot's Apache plugin would add redundant include - directives for the TLS configuration managed by Certbot has been fixed. -* Certbot's webroot plugin now properly deletes any directories it creates. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/48?closed=1 - -## 0.21.1 - 2018-01-25 - -### Fixed - -* When creating an HTTP to HTTPS redirect in Nginx, we now ensure the Host - header of the request is set to an expected value before redirecting users to - the domain found in the header. The previous way Certbot configured Nginx - redirects was a potential security issue which you can read more about at - https://community.letsencrypt.org/t/security-issue-with-redirects-added-by-certbots-nginx-plugin/51493. -* Fixed a problem where Certbot's Apache plugin could fail HTTP-01 challenges - if basic authentication is configured for the domain you request a - certificate for. -* certbot-auto --no-bootstrap now properly tries to use Python 3.4 on RHEL 6 - based systems rather than Python 2.6. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/49?closed=1 - -## 0.21.0 - 2018-01-17 - -### Added - -* Support for the HTTP-01 challenge type was added to our Apache and Nginx - plugins. For those not aware, Let's Encrypt disabled the TLS-SNI-01 challenge - type which was what was previously being used by our Apache and Nginx plugins - last week due to a security issue. For more information about Let's Encrypt's - change, click - [here](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188). - Our Apache and Nginx plugins will automatically switch to use HTTP-01 so no - changes need to be made to your Certbot configuration, however, you should - make sure your server is accessible on port 80 and isn't behind an external - proxy doing things like redirecting all traffic from HTTP to HTTPS. HTTP to - HTTPS redirects inside Apache and Nginx are fine. -* IPv6 support was added to the Nginx plugin. -* Support for automatically creating server blocks based on the default server - block was added to the Nginx plugin. -* The flags --delete-after-revoke and --no-delete-after-revoke were added - allowing users to control whether the revoke subcommand also deletes the - certificates it is revoking. - -### Changed - -* We deprecated support for Python 2.6 and Python 3.3 in Certbot and its ACME - library. Support for these versions of Python will be removed in the next - major release of Certbot. If you are using certbot-auto on a RHEL 6 based - system, it will guide you through the process of installing Python 3. -* We split our implementation of JOSE (Javascript Object Signing and - Encryption) out of our ACME library and into a separate package named josepy. - This package is available on [PyPI](https://pypi.python.org/pypi/josepy) and - on [GitHub](https://github.com/certbot/josepy). -* We updated the ciphersuites used in Apache to the new [values recommended by - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29). - The major change here is adding ChaCha20 to the list of supported - ciphersuites. - -### Fixed - -* An issue with our Apache plugin on Gentoo due to differences in their - apache2ctl command have been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/47?closed=1 - -## 0.20.0 - 2017-12-06 - -### Added - -* Certbot's ACME library now recognizes URL fields in challenge objects in - preparation for Let's Encrypt's new ACME endpoint. The value is still - accessible in our ACME library through the name "uri". - -### Changed - -* The Apache plugin now parses some distro specific Apache configuration files - on non-Debian systems allowing it to get a clearer picture on the running - configuration. Internally, these changes were structured so that external - contributors can easily write patches to make the plugin work in new Apache - configurations. -* Certbot better reports network failures by removing information about - connection retries from the error output. -* An unnecessary question when using Certbot's webroot plugin interactively has - been removed. - -### Fixed - -* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was - unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a - redirect for multiple domains. -* Problems where the Apache plugin was failing to find directives and - duplicating existing directives on openSUSE have been resolved. -* An issue running the test shipped with Certbot and some our DNS plugins with - older versions of mock have been resolved. -* On some systems, users reported strangely interleaved output depending on - when stdout and stderr were flushed. This problem was resolved by having - Certbot regularly flush these streams. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/44?closed=1 - -## 0.19.0 - 2017-10-04 - -### Added - -* Certbot now has renewal hook directories where executable files can be placed - for Certbot to run with the renew subcommand. Pre-hooks, deploy-hooks, and - post-hooks can be specified in the renewal-hooks/pre, renewal-hooks/deploy, - and renewal-hooks/post directories respectively in Certbot's configuration - directory (which is /etc/letsencrypt by default). Certbot will automatically - create these directories when it is run if they do not already exist. -* After revoking a certificate with the revoke subcommand, Certbot will offer - to delete the lineage associated with the certificate. When Certbot is run - with --non-interactive, it will automatically try to delete the associated - lineage. -* When using Certbot's Google Cloud DNS plugin on Google Compute Engine, you no - longer have to provide a credential file to Certbot if you have configured - sufficient permissions for the instance which Certbot can automatically - obtain using Google's metadata service. - -### Changed - -* When deleting certificates interactively using the delete subcommand, Certbot - will now allow you to select multiple lineages to be deleted at once. -* Certbot's Apache plugin no longer always parses Apache's sites-available on - Debian based systems and instead only parses virtual hosts included in your - Apache configuration. You can provide an additional directory for Certbot to - parse using the command line flag --apache-vhost-root. - -### Fixed - -* The plugins subcommand can now be run without root access. -* certbot-auto now includes a timeout when updating itself so it no longer - hangs indefinitely when it is unable to connect to the external server. -* An issue where Certbot's Apache plugin would sometimes fail to deploy a - certificate on Debian based systems if mod_ssl wasn't already enabled has - been resolved. -* A bug in our Docker image where the certificates subcommand could not report - if certificates maintained by Certbot had been revoked has been fixed. -* Certbot's RFC 2136 DNS plugin (for use with software like BIND) now properly - performs DNS challenges when the domain being verified contains a CNAME - record. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/43?closed=1 - -## 0.18.2 - 2017-09-20 - -### Fixed - -* An issue where Certbot's ACME module would raise an AttributeError trying to - create self-signed certificates when used with pyOpenSSL 17.3.0 has been - resolved. For Certbot users with this version of pyOpenSSL, this caused - Certbot to crash when performing a TLS SNI challenge or when the Nginx plugin - tried to create an SSL server block. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/46?closed=1 - -## 0.18.1 - 2017-09-08 - -### Fixed - -* If certbot-auto was running as an unprivileged user and it upgraded from - 0.17.0 to 0.18.0, it would crash with a permissions error and would need to - be run again to successfully complete the upgrade. This has been fixed and - certbot-auto should upgrade cleanly to 0.18.1. -* Certbot usually uses "certbot-auto" or "letsencrypt-auto" in error messages - and the User-Agent string instead of "certbot" when you are using one of - these wrapper scripts. Proper detection of this was broken with Certbot's new - installation path in /opt in 0.18.0 but this problem has been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/45?closed=1 - -## 0.18.0 - 2017-09-06 - -### Added - -* The Nginx plugin now configures Nginx to use 2048-bit Diffie-Hellman - parameters. Java 6 clients do not support Diffie-Hellman parameters larger - than 1024 bits, so if you need to support these clients you will need to - manually modify your Nginx configuration after using the Nginx installer. - -### Changed - -* certbot-auto now installs Certbot in directories under `/opt/eff.org`. If you - had an existing installation from certbot-auto, a symlink is created to the - new directory. You can configure certbot-auto to use a different path by - setting the environment variable VENV_PATH. -* The Nginx plugin can now be selected in Certbot's interactive output. -* Output verbosity of renewal failures when running with `--quiet` has been - reduced. -* The default revocation reason shown in Certbot help output now is a human - readable string instead of a numerical code. -* Plugin selection is now included in normal terminal output. - -### Fixed - -* A newer version of ConfigArgParse is now installed when using certbot-auto - causing values set to false in a Certbot INI configuration file to be handled - intuitively. Setting a boolean command line flag to false is equivalent to - not including it in the configuration file at all. -* New naming conventions preventing certbot-auto from installing OS - dependencies on Fedora 26 have been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/42?closed=1 - -## 0.17.0 - 2017-08-02 - -### Added - -* Support in our nginx plugin for modifying SSL server blocks that do - not contain certificate or key directives. -* A `--max-log-backups` flag to allow users to configure or even completely - disable Certbot's built in log rotation. -* A `--user-agent-comment` flag to allow people who build tools around Certbot - to differentiate their user agent string by adding a comment to its default - value. - -### Changed - -* Due to some awesome work by - [cryptography project](https://github.com/pyca/cryptography), compilation can - now be avoided on most systems when using certbot-auto. This eliminates many - problems people have had in the past such as running out of memory, having - invalid headers/libraries, and changes to the OS packages on their system - after compilation breaking Certbot. -* The `--renew-hook` flag has been hidden in favor of `--deploy-hook`. This new - flag works exactly the same way except it is always run when a certificate is - issued rather than just when it is renewed. -* We have started printing deprecation warnings in certbot-auto for - experimentally supported systems with OS packages available. -* A certificate lineage's name is included in error messages during renewal. - -### Fixed - -* Encoding errors that could occur when parsing error messages from the ACME - server containing Unicode have been resolved. -* certbot-auto no longer prints misleading messages about there being a newer - pip version available when installation fails. -* Certbot's ACME library now properly extracts domains from critical SAN - extensions. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.17.0+is%3Aclosed - -## 0.16.0 - 2017-07-05 - -### Added - -* A plugin for performing DNS challenges using dynamic DNS updates as defined - in RFC 2316. This plugin is packaged separately from Certbot and is available - at https://pypi.python.org/pypi/certbot-dns-rfc2136. It supports Python 2.6, - 2.7, and 3.3+. At this time, there isn't a good way to install this plugin - when using certbot-auto, but this should change in the near future. -* Plugins for performing DNS challenges for the providers - [DNS Made Easy](https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy) and - [LuaDNS](https://pypi.python.org/pypi/certbot-dns-luadns). These plugins are - packaged separately from Certbot and support Python 2.7 and 3.3+. Currently, - there isn't a good way to install these plugins when using certbot-auto, - but that should change soon. -* Support for performing TLS-SNI-01 challenges when using the manual plugin. -* Automatic detection of Arch Linux in the Apache plugin providing better - default settings for the plugin. - -### Changed - -* The text of the interactive question about whether a redirect from HTTP to - HTTPS should be added by Certbot has been rewritten to better explain the - choices to the user. -* Simplified HTTP challenge instructions in the manual plugin. - -### Fixed - -* Problems performing a dry run when using the Nginx plugin have been fixed. -* Resolved an issue where certbot-dns-digitalocean's test suite would sometimes - fail when ran using Python 3. -* On some systems, previous versions of certbot-auto would error out with a - message about a missing hash for setuptools. This has been fixed. -* A bug where Certbot would sometimes not print a space at the end of an - interactive prompt has been resolved. -* Nonfatal tracebacks are no longer shown in rare cases where Certbot - encounters an exception trying to close its TCP connection with the ACME - server. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.16.0+is%3Aclosed - -## 0.15.0 - 2017-06-08 - -### Added - -* Plugins for performing DNS challenges for popular providers. Like the Apache - and Nginx plugins, these plugins are packaged separately and not included in - Certbot by default. So far, we have plugins for - [Amazon Route 53](https://pypi.python.org/pypi/certbot-dns-route53), - [Cloudflare](https://pypi.python.org/pypi/certbot-dns-cloudflare), - [DigitalOcean](https://pypi.python.org/pypi/certbot-dns-digitalocean), and - [Google Cloud](https://pypi.python.org/pypi/certbot-dns-google) which all - work on Python 2.6, 2.7, and 3.3+. Additionally, we have plugins for - [CloudXNS](https://pypi.python.org/pypi/certbot-dns-cloudxns), - [DNSimple](https://pypi.python.org/pypi/certbot-dns-dnsimple), - [NS1](https://pypi.python.org/pypi/certbot-dns-nsone) which work on Python - 2.7 and 3.3+ (and not 2.6). Currently, there isn't a good way to install - these plugins when using `certbot-auto`, but that should change soon. -* IPv6 support in the standalone plugin. When performing a challenge, the - standalone plugin automatically handles listening for IPv4/IPv6 traffic based - on the configuration of your system. -* A mechanism for keeping your Apache and Nginx SSL/TLS configuration up to - date. When the Apache or Nginx plugins are used, they place SSL/TLS - configuration options in the root of Certbot's config directory - (`/etc/letsencrypt` by default). Now when a new version of these plugins run - on your system, they will automatically update the file to the newest - version if it is unmodified. If you manually modified the file, Certbot will - display a warning giving you a path to the updated file which you can use as - a reference to manually update your modified copy. -* `--http-01-address` and `--tls-sni-01-address` flags for controlling the - address Certbot listens on when using the standalone plugin. -* The command `certbot certificates` that lists certificates managed by Certbot - now performs additional validity checks to notify you if your files have - become corrupted. - -### Changed - -* Messages custom hooks print to `stdout` are now displayed by Certbot when not - running in `--quiet` mode. -* `jwk` and `alg` fields in JWS objects have been moved into the protected - header causing Certbot to more closely follow the latest version of the ACME - spec. - -### Fixed - -* Permissions on renewal configuration files are now properly preserved when - they are updated. -* A bug causing Certbot to display strange defaults in its help output when - using Python <= 2.7.4 has been fixed. -* Certbot now properly handles mixed case domain names found in custom CSRs. -* A number of poorly worded prompts and error messages. - -### Removed - -* Support for OpenSSL 1.0.0 in `certbot-auto` has been removed as we now pin a - newer version of `cryptography` which dropped support for this version. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.15.0+is%3Aclosed - -## 0.14.2 - 2017-05-25 - -### Fixed - -* Certbot 0.14.0 included a bug where Certbot would create a temporary log file -(usually in /tmp) if the program exited during argument parsing. If a user -provided -h/--help/help, --version, or an invalid command line argument, -Certbot would create this temporary log file. This was especially bothersome to -certbot-auto users as certbot-auto runs `certbot --version` internally to see -if the script needs to upgrade causing it to create at least one of these files -on every run. This problem has been resolved. - -More details about this change can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed - -## 0.14.1 - 2017-05-16 - -### Fixed - -* Certbot now works with configargparse 0.12.0. -* Issues with the Apache plugin and Augeas 1.7+ have been resolved. -* A problem where the Nginx plugin would fail to install certificates on -systems that had the plugin's SSL/TLS options file from 7+ months ago has been -fixed. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.1+is%3Aclosed - -## 0.14.0 - 2017-05-04 - -### Added - -* Python 3.3+ support for all Certbot packages. `certbot-auto` still currently -only supports Python 2, but the `acme`, `certbot`, `certbot-apache`, and -`certbot-nginx` packages on PyPI now fully support Python 2.6, 2.7, and 3.3+. -* Certbot's Apache plugin now handles multiple virtual hosts per file. -* Lockfiles to prevent multiple versions of Certbot running simultaneously. - -### Changed - -* When converting an HTTP virtual host to HTTPS in Apache, Certbot only copies -the virtual host rather than the entire contents of the file it's contained -in. -* The Nginx plugin now includes SSL/TLS directives in a separate file located -in Certbot's configuration directory rather than copying the contents of the -file into every modified `server` block. - -### Fixed - -* Ensure logging is configured before parts of Certbot attempt to log any -messages. -* Support for the `--quiet` flag in `certbot-auto`. -* Reverted a change made in a previous release to make the `acme` and `certbot` -packages always depend on `argparse`. This dependency is conditional again on -the user's Python version. -* Small bugs in the Nginx plugin such as properly handling empty `server` -blocks and setting `server_names_hash_bucket_size` during challenges. - -As always, a more complete list of changes can be found on GitHub: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.0+is%3Aclosed - -## 0.13.0 - 2017-04-06 - -### Added - -* `--debug-challenges` now pauses Certbot after setting up challenges for debugging. -* The Nginx parser can now handle all valid directives in configuration files. -* Nginx ciphersuites have changed to Mozilla Intermediate. -* `certbot-auto --no-bootstrap` provides the option to not install OS dependencies. - -### Fixed - -* `--register-unsafely-without-email` now respects `--quiet`. -* Hyphenated renewal parameters are now saved in renewal config files. -* `--dry-run` no longer persists keys and csrs. -* Certbot no longer hangs when trying to start Nginx in Arch Linux. -* Apache rewrite rules no longer double-encode characters. - -A full list of changes is available on GitHub: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.13.0%20is%3Aclosed%20 - -## 0.12.0 - 2017-03-02 - -### Added - -* Certbot now allows non-camelcase Apache VirtualHost names. -* Certbot now allows more log messages to be silenced. - -### Fixed - -* Fixed a regression around using `--cert-name` when getting new certificates - -More information about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0 - -## 0.11.1 - 2017-02-01 - -### Fixed - -* Resolved a problem where Certbot would crash while parsing command line -arguments in some cases. -* Fixed a typo. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed - -## 0.11.0 - 2017-02-01 - -### Added - -* When using the standalone plugin while running Certbot interactively -and a required port is bound by another process, Certbot will give you -the option to retry to grab the port rather than immediately exiting. -* You are now able to deactivate your account with the Let's Encrypt -server using the `unregister` subcommand. -* When revoking a certificate using the `revoke` subcommand, you now -have the option to provide the reason the certificate is being revoked -to Let's Encrypt with `--reason`. - -### Changed - -* Providing `--quiet` to `certbot-auto` now silences package manager output. - -### Removed - -* Removed the optional `dnspython` dependency in our `acme` package. -Now the library does not support client side verification of the DNS -challenge. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed - -## 0.10.2 - 2017-01-25 - -### Added - -* If Certbot receives a request with a `badNonce` error, it now -automatically retries the request. Since nonces from Let's Encrypt expire, -this helps people performing the DNS challenge with the `manual` plugin -who may have to wait an extended period of time for their DNS changes to -propagate. - -### Fixed - -* Certbot now saves the `--preferred-challenges` values for renewal. Previously -these values were discarded causing a different challenge type to be used when -renewing certs in some cases. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed - -## 0.10.1 - 2017-01-13 - -### Fixed - -* Resolve problems where when asking Certbot to update a certificate at -an existing path to include different domain names, the old names would -continue to be used. -* Fix issues successfully running our unit test suite on some systems. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed - -## 0.10.0 - 2017-01-11 - -## Added - -* Added the ability to customize and automatically complete DNS and HTTP -domain validation challenges with the manual plugin. The flags -`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided -when using the manual plugin to execute commands provided by the user to -perform and clean up challenges provided by the CA. This is best used in -complicated setups where the DNS challenge must be used or Certbot's -existing plugins cannot be used to perform HTTP challenges. For more -information on how this works, see `certbot --help manual`. -* Added a `--cert-name` flag for specifying the name to use for the -certificate in Certbot's configuration directory. Using this flag in -combination with `-d/--domains`, a user can easily request a new -certificate with different domains and save it with the name provided by -`--cert-name`. Additionally, `--cert-name` can be used to select a -certificate with the `certonly` and `run` subcommands so a full list of -domains in the certificate does not have to be provided. -* Added subcommand `certificates` for listing the certificates managed by -Certbot and their properties. -* Added the `delete` subcommand for removing certificates managed by Certbot -from the configuration directory. -* Certbot now supports requesting internationalized domain names (IDNs). -* Hooks provided to Certbot are now saved to be reused during renewal. -If you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook` -flags when obtaining a certificate, the provided commands will -automatically be saved and executed again when renewing the certificate. -A pre-hook and/or post-hook can also be given to the `certbot renew` -command either on the command line or in a [configuration -file](https://certbot.eff.org/docs/using.html#configuration-file) to run -an additional command before/after any certificate is renewed. Hooks -will only be run if a certificate is renewed. -* Support Busybox in certbot-auto. - -### Changed - -* Recategorized `-h/--help` output to improve documentation and -discoverability. - -### Removed - -* Removed the ncurses interface. This change solves problems people -were having on many systems, reduces the number of Certbot -dependencies, and simplifies our code. Certbot's only interface now is -the text interface which was available by providing `-t/--text` to -earlier versions of Certbot. - -### Fixed - -* Many small bug fixes. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed - -## 0.9.3 - 2016-10-13 - -### Added - -* The Apache plugin uses information about your OS to help determine the -layout of your Apache configuration directory. We added a patch to -ensure this code behaves the same way when testing on different systems -as the tests were failing in some cases. - -### Changed - -* Certbot adopted more conservative behavior about reporting a needed port as -unavailable when using the standalone plugin. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/27?closed=1 - -## 0.9.2 - 2016-10-12 - -### Added - -* Certbot stopped requiring that all possibly required ports are available when -using the standalone plugin. It now only verifies that the ports are available -when they are necessary. - -### Fixed - -* Certbot now verifies that our optional dependencies version matches what is -required by Certbot. -* Certnot now properly copies the `ssl on;` directives as necessary when -performing domain validation in the Nginx plugin. -* Fixed problem where symlinks were becoming files when they were -packaged, causing errors during testing and OS packaging. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/26?closed=1 - -## 0.9.1 - 2016-10-06 - -### Fixed - -* Fixed a bug that was introduced in version 0.9.0 where the command -line flag -q/--quiet wasn't respected in some cases. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/25?closed=1 - -## 0.9.0 - 2016-10-05 - -### Added - -* Added an alpha version of the Nginx plugin. This plugin fully automates the -process of obtaining and installing certificates with Nginx. -Additionally, it is able to automatically configure security -enhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use -this plugin, you must have the `certbot-nginx` package installed (which -is installed automatically when using `certbot-auto`) and provide -`--nginx` on the command line. This plugin is still in its early stages -so we recommend you use it with some caution and make sure you have a -backup of your Nginx configuration. -* Added support for the `DNS` challenge in the `acme` library and `DNS` in -Certbot's `manual` plugin. This allows you to create DNS records to -prove to Let's Encrypt you control the requested domain name. To use -this feature, include `--manual --preferred-challenges dns` on the -command line. -* Certbot now helps with enabling Extra Packages for Enterprise Linux (EPEL) on -CentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6, -the EPEL repository has to be enabled. `certbot-auto` will now prompt -users asking them if they would like the script to enable this for them -automatically. This is done without prompting users when using -`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is -included on the command line. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed - -## 0.8.1 - 2016-06-14 - -### Added - -* Certbot now preserves a certificate's common name when using `renew`. -* Certbot now saves webroot values for renewal when they are entered interactively. -* Certbot now gracefully reports that the Apache plugin isn't usable when Augeas is not installed. -* Added experimental support for Mageia has been added to `certbot-auto`. - -### Fixed - -* Fixed problems with an invalid user-agent string on OS X. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+ - -## 0.8.0 - 2016-06-02 - -### Added - -* Added the `register` subcommand which can be used to register an account -with the Let's Encrypt CA. -* You can now run `certbot register --update-registration` to -change the e-mail address associated with your registration. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+ - -## 0.7.0 - 2016-05-27 - -### Added - -* Added `--must-staple` to request certificates from Let's Encrypt -with the OCSP must staple extension. -* Certbot now automatically configures OSCP stapling for Apache. -* Certbot now allows requesting certificates for domains found in the common name -of a custom CSR. - -### Fixed - -* Fixed a number of miscellaneous bugs - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue - -## 0.6.0 - 2016-05-12 - -### Added - -* Versioned the datetime dependency in setup.py. - -### Changed - -* Renamed the client from `letsencrypt` to `certbot`. - -### Fixed - -* Fixed a small json deserialization error. -* Certbot now preserves domain order in generated CSRs. -* Fixed some minor bugs. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20 - -## 0.5.0 - 2016-04-05 - -### Added - -* Added the ability to use the webroot plugin interactively. -* Added the flags --pre-hook, --post-hook, and --renew-hook which can be used with -the renew subcommand to register shell commands to run in response to -renewal events. Pre-hook commands will be run before any certs are -renewed, post-hook commands will be run after any certs are renewed, -and renew-hook commands will be run after each cert is renewed. If no -certs are due for renewal, no command is run. -* Added a -q/--quiet flag which silences all output except errors. -* Added an --allow-subset-of-domains flag which can be used with the renew -command to prevent renewal failures for a subset of the requested -domains from causing the client to exit. - -### Changed - -* Certbot now uses renewal configuration files. In /etc/letsencrypt/renewal -by default, these files can be used to control what parameters are -used when renewing a specific certificate. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue - -## 0.4.2 - 2016-03-03 - -### Fixed - -* Resolved problems encountered when compiling letsencrypt -against the new OpenSSL release. -* Fixed problems encountered when using `letsencrypt renew` with configuration files -from the private beta. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2 - -## 0.4.1 - 2016-02-29 - -### Fixed - -* Fixed Apache parsing errors encountered with some configurations. -* Fixed Werkzeug dependency problems encountered on some Red Hat systems. -* Fixed bootstrapping failures when using letsencrypt-auto with --no-self-upgrade. -* Fixed problems with parsing renewal config files from private beta. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1 - -## 0.4.0 - 2016-02-10 - -### Added - -* Added the verb/subcommand `renew` which can be used to renew your existing -certificates as they approach expiration. Running `letsencrypt renew` -will examine all existing certificate lineages and determine if any are -less than 30 days from expiration. If so, the client will use the -settings provided when you previously obtained the certificate to renew -it. The subcommand finishes by printing a summary of which renewals were -successful, failed, or not yet due. -* Added a `--dry-run` flag to help with testing configuration -without affecting production rate limits. Currently supported by the -`renew` and `certonly` subcommands, providing `--dry-run` on the command -line will obtain certificates from the staging server without saving the -resulting certificates to disk. -* Added major improvements to letsencrypt-auto. This script -has been rewritten to include full support for Python 2.6, the ability -for letsencrypt-auto to update itself, and improvements to the -stability, security, and performance of the script. -* Added support for Apache 2.2 to the Apache plugin. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0 - -## 0.3.0 - 2016-01-27 - -### Added - -* Added a non-interactive mode which can be enabled by including `-n` or -`--non-interactive` on the command line. This can be used to guarantee -the client will not prompt when run automatically using cron/systemd. -* Added preparation for the new letsencrypt-auto script. Over the past -couple months, we've been working on increasing the reliability and -security of letsencrypt-auto. A number of changes landed in this -release to prepare for the new version of this script. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0 - -## 0.2.0 - 2016-01-14 - -### Added - -* Added Apache plugin support for non-Debian based systems. Support has been -added for modern Red Hat based systems such as Fedora 23, Red Hat 7, -and CentOS 7 running Apache 2.4. In theory, this plugin should be -able to be configured to run on any Unix-like OS running Apache 2.4. -* Relaxed PyOpenSSL version requirements. This adds support for systems -with PyOpenSSL versions 0.13 or 0.14. -* Improved error messages from the client. - -### Fixed - -* Resolved issues with the Apache plugin enabling an HTTP to HTTPS -redirect on some systems. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0 - -## 0.1.1 - 2015-12-15 - -### Added - -* Added a check that avoids attempting to issue for unqualified domain names like -"localhost". - -### Fixed - -* Fixed a confusing UI path that caused some users to repeatedly renew -their certs while experimenting with the client, in some cases hitting -issuance rate limits. -* Fixed numerous Apache configuration parser problems -* Fixed --webroot permission handling for non-root users - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 120000 index 000000000..ba7396f24 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +certbot/CHANGELOG.md \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 7f529c7a7..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -include README.rst -include CHANGELOG.md -include CONTRIBUTING.md -include LICENSE.txt -include linter_plugin.py -recursive-include docs * -recursive-include examples * -recursive-include certbot/tests/testdata * -include certbot/ssl-dhparams.pem diff --git a/README.rst b/README.rst deleted file mode 100644 index 5f5ea17a1..000000000 --- a/README.rst +++ /dev/null @@ -1,131 +0,0 @@ -.. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin - -Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server. - -Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment. - -How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide `_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access `_ to your web server to run Certbot. - -Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt. - -Certbot is a fully-featured, extensible client for the Let's -Encrypt CA (or any other CA that speaks the `ACME -`_ -protocol) that can automate the tasks of obtaining certificates and -configuring webservers to use them. This client runs on Unix-based operating -systems. - -To see the changes made to Certbot between versions please refer to our -`changelog `_. - -Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``, -depending on install method. Instructions on the Internet, and some pieces of the -software, may still refer to this older name. - -Contributing ------------- - -If you'd like to contribute to this project please read `Developer Guide -`_. - -This project is governed by `EFF's Public Projects Code of Conduct `_. - -.. _installation: - -How to run the client ---------------------- - -The easiest way to install and run Certbot is by visiting `certbot.eff.org`_, -where you can find the correct instructions for many web server and OS -combinations. For more information, see `Get Certbot -`_. - -.. _certbot.eff.org: https://certbot.eff.org/ - -Understanding the client in more depth --------------------------------------- - -To understand what the client is doing in detail, it's important to -understand the way it uses plugins. Please see the `explanation of -plugins `_ in -the User Guide. - -Links -===== - -.. Do not modify this comment unless you know what you're doing. tag:links-begin - -Documentation: https://certbot.eff.org/docs - -Software project: https://github.com/certbot/certbot - -Notes for developers: https://certbot.eff.org/docs/contributing.html - -Main Website: https://certbot.eff.org - -Let's Encrypt Website: https://letsencrypt.org - -Community: https://community.letsencrypt.org - -ACME spec: http://ietf-wg-acme.github.io/acme/ - -ACME working area in github: https://github.com/ietf-wg-acme/acme - -|build-status| |coverage| |docs| |container| - -.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master - :target: https://travis-ci.com/certbot/certbot - :alt: Travis CI status - -.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg - :target: https://codecov.io/gh/certbot/certbot - :alt: Coverage status - -.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ - :target: https://readthedocs.org/projects/letsencrypt/ - :alt: Documentation status - -.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status - :target: https://quay.io/repository/letsencrypt/letsencrypt - :alt: Docker Repository on Quay.io - -.. Do not modify this comment unless you know what you're doing. tag:links-end - -System Requirements -=================== - -See https://certbot.eff.org/docs/install.html#system-requirements. - -.. Do not modify this comment unless you know what you're doing. tag:intro-end - -.. Do not modify this comment unless you know what you're doing. tag:features-begin - -Current Features -===================== - -* Supports multiple web servers: - - - apache/2.x - - nginx/0.8.48+ - - webroot (adds files to webroot directories in order to prove control of - domains and obtain certs) - - standalone (runs its own simple webserver to prove you control a domain) - - other server software via `third party plugins `_ - -* The private key is generated locally on your system. -* Can talk to the Let's Encrypt CA or optionally to other ACME - compliant services. -* Can get domain-validated (DV) certificates. -* Can revoke certificates. -* Adjustable RSA key bit-length (2048 (default), 4096, ...). -* Can optionally install a http -> https redirect, so your site effectively - runs https only (Apache only) -* Fully automated. -* Configuration changes are logged and can be reverted. -* Supports an interactive text UI, or can be driven entirely from the - command line. -* Free and Open Source Software, made with Python. - -.. Do not modify this comment unless you know what you're doing. tag:features-end - -For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide `_. diff --git a/README.rst b/README.rst new file mode 120000 index 000000000..645fd4c78 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +certbot/README.rst \ No newline at end of file diff --git a/acme/readthedocs.org.requirements.txt b/acme/readthedocs.org.requirements.txt index 65e6c7cf3..168af8013 100644 --- a/acme/readthedocs.org.requirements.txt +++ b/acme/readthedocs.org.requirements.txt @@ -1,10 +1,10 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e acme[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install acme[docs]" does not work as +# expected and "pip install -e acme[docs]" must be used instead -e acme[docs] diff --git a/certbot-compatibility-test/Dockerfile b/certbot-compatibility-test/Dockerfile index 2716d6fcb..c32bc0bd6 100644 --- a/certbot-compatibility-test/Dockerfile +++ b/certbot-compatibility-test/Dockerfile @@ -14,7 +14,7 @@ RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only # the above is not likely to change, so by putting it further up the # Dockerfile we make sure we cache as much as possible -COPY setup.py README.rst CHANGELOG.md MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ +COPY certbot/setup.py certbot/README.rst CHANGELOG.md certbot/MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ # all above files are necessary for setup.py, however, package source # code directory has to be copied separately to a subdirectory... @@ -38,7 +38,7 @@ ENV PATH /opt/certbot/venv/bin:$PATH RUN /opt/certbot/venv/bin/python \ /opt/certbot/src/tools/pip_install_editable.py \ /opt/certbot/src/acme \ - /opt/certbot/src \ + /opt/certbot/src/certbot \ /opt/certbot/src/certbot-apache \ /opt/certbot/src/certbot-nginx \ /opt/certbot/src/certbot-compatibility-test diff --git a/certbot-dns-cloudflare/readthedocs.org.requirements.txt b/certbot-dns-cloudflare/readthedocs.org.requirements.txt index b18901111..f1df15227 100644 --- a/certbot-dns-cloudflare/readthedocs.org.requirements.txt +++ b/certbot-dns-cloudflare/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-cloudflare[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-cloudflare[docs]" does not work as +# expected and "pip install -e certbot-dns-cloudflare[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-cloudflare[docs] diff --git a/certbot-dns-cloudxns/readthedocs.org.requirements.txt b/certbot-dns-cloudxns/readthedocs.org.requirements.txt index ae2ff8165..a9a4d068b 100644 --- a/certbot-dns-cloudxns/readthedocs.org.requirements.txt +++ b/certbot-dns-cloudxns/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-cloudxns[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-cloudxns[docs]" does not work as +# expected and "pip install -e certbot-dns-cloudxns[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-cloudxns[docs] diff --git a/certbot-dns-digitalocean/readthedocs.org.requirements.txt b/certbot-dns-digitalocean/readthedocs.org.requirements.txt index 08d973ab3..d0cc2f74a 100644 --- a/certbot-dns-digitalocean/readthedocs.org.requirements.txt +++ b/certbot-dns-digitalocean/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-digitalocean[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-digitalocean[docs]" does not work as +# expected and "pip install -e certbot-dns-digitalocean[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-digitalocean[docs] diff --git a/certbot-dns-dnsimple/readthedocs.org.requirements.txt b/certbot-dns-dnsimple/readthedocs.org.requirements.txt index fef73916c..04163ff34 100644 --- a/certbot-dns-dnsimple/readthedocs.org.requirements.txt +++ b/certbot-dns-dnsimple/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-dnsimple[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-dnsimple[docs]" does not work as +# expected and "pip install -e certbot-dns-dnsimple[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-dnsimple[docs] diff --git a/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt b/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt index 8f8c6c731..eb205d8f2 100644 --- a/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt +++ b/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-dnsmadeeasy[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-dnsmadeeasy[docs]" does not work as +# expected and "pip install -e certbot-dns-dnsmadeeasy[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-dnsmadeeasy[docs] diff --git a/certbot-dns-gehirn/readthedocs.org.requirements.txt b/certbot-dns-gehirn/readthedocs.org.requirements.txt index d9f4f9823..97af343d9 100644 --- a/certbot-dns-gehirn/readthedocs.org.requirements.txt +++ b/certbot-dns-gehirn/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-gehirn[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-gehirn[docs]" does not work as +# expected and "pip install -e certbot-dns-gehirn[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-gehirn[docs] diff --git a/certbot-dns-google/readthedocs.org.requirements.txt b/certbot-dns-google/readthedocs.org.requirements.txt index 6ea393f86..fe97cee94 100644 --- a/certbot-dns-google/readthedocs.org.requirements.txt +++ b/certbot-dns-google/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-google[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-google[docs]" does not work as +# expected and "pip install -e certbot-dns-google[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-google[docs] diff --git a/certbot-dns-linode/readthedocs.org.requirements.txt b/certbot-dns-linode/readthedocs.org.requirements.txt index 47449454f..3d28f43bf 100644 --- a/certbot-dns-linode/readthedocs.org.requirements.txt +++ b/certbot-dns-linode/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-linode[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-linode[docs]" does not work as +# expected and "pip install -e certbot-dns-linode[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-linode[docs] diff --git a/certbot-dns-luadns/readthedocs.org.requirements.txt b/certbot-dns-luadns/readthedocs.org.requirements.txt index acb51e4ef..6f467dc7c 100644 --- a/certbot-dns-luadns/readthedocs.org.requirements.txt +++ b/certbot-dns-luadns/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-luadns[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-luadns[docs]" does not work as +# expected and "pip install -e certbot-dns-luadns[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-luadns[docs] diff --git a/certbot-dns-nsone/readthedocs.org.requirements.txt b/certbot-dns-nsone/readthedocs.org.requirements.txt index dbdee4480..bf17eae30 100644 --- a/certbot-dns-nsone/readthedocs.org.requirements.txt +++ b/certbot-dns-nsone/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-nsone[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-nsone[docs]" does not work as +# expected and "pip install -e certbot-dns-nsone[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-nsone[docs] diff --git a/certbot-dns-ovh/readthedocs.org.requirements.txt b/certbot-dns-ovh/readthedocs.org.requirements.txt index 0780e12a1..3c21ae0ce 100644 --- a/certbot-dns-ovh/readthedocs.org.requirements.txt +++ b/certbot-dns-ovh/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-ovh[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-ovh[docs]" does not work as +# expected and "pip install -e certbot-dns-ovh[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-ovh[docs] diff --git a/certbot-dns-rfc2136/readthedocs.org.requirements.txt b/certbot-dns-rfc2136/readthedocs.org.requirements.txt index df89018ce..2cf4f70f8 100644 --- a/certbot-dns-rfc2136/readthedocs.org.requirements.txt +++ b/certbot-dns-rfc2136/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-rfc2136[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-rfc2136[docs]" does not work as +# expected and "pip install -e certbot-dns-rfc2136[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-rfc2136[docs] diff --git a/certbot-dns-route53/readthedocs.org.requirements.txt b/certbot-dns-route53/readthedocs.org.requirements.txt index 660a90d0e..993225eac 100644 --- a/certbot-dns-route53/readthedocs.org.requirements.txt +++ b/certbot-dns-route53/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-route53[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-route53[docs]" does not work as +# expected and "pip install -e certbot-dns-route53[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-route53[docs] diff --git a/certbot-dns-sakuracloud/readthedocs.org.requirements.txt b/certbot-dns-sakuracloud/readthedocs.org.requirements.txt index 3f46d95ef..07bc8a289 100644 --- a/certbot-dns-sakuracloud/readthedocs.org.requirements.txt +++ b/certbot-dns-sakuracloud/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-sakuracloud[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-sakuracloud[docs]" does not work as +# expected and "pip install -e certbot-dns-sakuracloud[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-sakuracloud[docs] diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py index d05370c68..8e0450f6a 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/http_01_test.py @@ -1,6 +1,7 @@ """Tests for certbot_nginx.http_01""" import unittest +import josepy as jose import mock import six @@ -8,17 +9,19 @@ from acme import challenges from certbot import achallenges -from certbot.plugins import common_test from certbot.tests import acme_util +from certbot.tests import util as test_util from certbot_nginx.obj import Addr from certbot_nginx.tests import util +AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + class HttpPerformTest(util.NginxTest): """Test the NginxHttp01 challenge.""" - account_key = common_test.AUTH_KEY + account_key = AUTH_KEY achalls = [ achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 3192f8360..1782f15ba 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -e acme[dev] --e .[dev] +-e certbot[dev] diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md new file mode 100644 index 000000000..dfb6acde8 --- /dev/null +++ b/certbot/CHANGELOG.md @@ -0,0 +1,1852 @@ +# Certbot change log + +Certbot adheres to [Semantic Versioning](https://semver.org/). + +## 1.0.0 - master + +### Added + +* + +### Removed + +* The `docs` extras for the `certbot-apache` and `certbot-nginx` packages + have been removed. + +### Changed + +* certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are + not running on x86-64. This primarily affects RHEL 6 based systems. +* Certbot's `config_changes` subcommand has been removed +* `certbot.plugins.common.TLSSNI01` has been removed. +* Deprecated attributes related to the TLS-SNI-01 challenge in + `acme.challenges` and `acme.standalone` + have been removed. +* The functions `certbot.client.view_config_changes`, + `certbot.main.config_changes`, + `certbot.plugins.common.Installer.view_config_changes`, + `certbot.reverter.Reverter.view_config_changes`, and + `certbot.util.get_systemd_os_info` have been removed +* Certbot's `register --update-registration` subcommand has been removed + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 0.40.1 - 2019-11-05 + +### Changed + +* Added back support for Python 3.4 to Certbot components and certbot-auto due + to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. + +More details about these changes can be found on our GitHub repo. + +## 0.40.0 - 2019-11-05 + +### Added + +* + +### Changed + +* We deprecated support for Python 3.4 in Certbot and its ACME library. Support + for Python 3.4 will be removed in the next major release of Certbot. + certbot-auto users on RHEL 6 based systems will be asked to enable Software + Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can + enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based + systems will be asked to do this manually. +* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the + staging server instead of the live server when `--dry-run` is used. +* `--dry-run` now requests fresh authorizations every time, fixing the issue + where it was prone to falsely reporting success. +* Updated certbot-dns-google to depend on newer versions of + google-api-python-client and oauth2client. +* The OS detection logic again uses distro library for Linux OSes +* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a + future release. +* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. +* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no + longer accepted. +* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script` +* acme.standalone.BaseRequestHandlerWithLogging and + acme.standalone.simple_tls_sni_01_server have been deprecated and will be + removed in a future release of the library. +* certbot-dns-rfc2136 now use TCP to query SOA records. + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 0.39.0 - 2019-10-01 + +### Added + +* Support for Python 3.8 was added to Certbot and all of its components. +* Support for CentOS 8 was added to certbot-auto. + +### Changed + +* Don't send OCSP requests for expired certificates +* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 +* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11. + +### Fixed + +* Fixed OS detection in the Apache plugin on RHEL 6. + +More details about these changes can be found on our GitHub repo. + +## 0.38.0 - 2019-09-03 + +### Added + +* Disable session tickets for Nginx users when appropriate. + +### Changed + +* If Certbot fails to rollback your server configuration, the error message + links to the Let's Encrypt forum. Change the link to the Help category now + that the Server category has been closed. +* Replace platform.linux_distribution with distro.linux_distribution as a step + towards Python 3.8 support in Certbot. + +### Fixed + +* Fixed OS detection in the Apache plugin on Scientific Linux. + +More details about these changes can be found on our GitHub repo. + +## 0.37.2 - 2019-08-21 + +* Stop disabling TLS session tickets in Nginx as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + +## 0.37.1 - 2019-08-08 + +### Fixed + +* Stop disabling TLS session tickets in Apache as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + +## 0.37.0 - 2019-08-07 + +### Added + +* Turn off session tickets for apache plugin by default +* acme: Authz deactivation added to `acme` module. + +### Changed + +* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers, + and ssl_prefer_server_ciphers + +### Fixed + +* Fix certbot-auto failures on RHEL 8. + +More details about these changes can be found on our GitHub repo. + +## 0.36.0 - 2019-07-11 + +### Added + +* Turn off session tickets for nginx plugin by default +* Added missing error types from RFC8555 to acme + +### Changed + +* Support for Ubuntu 14.04 Trusty has been removed. +* Update the 'manage your account' help to be more generic. +* The error message when Certbot's Apache plugin is unable to modify your + Apache configuration has been improved. +* Certbot's config_changes subcommand has been deprecated and will be + removed in a future release. +* `certbot config_changes` no longer accepts a --num parameter. +* The functions `certbot.plugins.common.Installer.view_config_changes` and + `certbot.reverter.Reverter.view_config_changes` have been deprecated and will + be removed in a future release. + +### Fixed + +* Replace some unnecessary platform-specific line separation. + +More details about these changes can be found on our GitHub repo. + +## 0.35.1 - 2019-06-10 + +### Fixed + +* Support for specifying an authoritative base domain in our dns-rfc2136 plugin + has been removed. This feature was added in our last release but had a bug + which caused the plugin to fail so the feature has been removed until it can + be added properly. + +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-dns-rfc2136 + +More details about these changes can be found on our GitHub repo. + +## 0.35.0 - 2019-06-05 + +### Added + +* dns_rfc2136 plugin now supports explicitly specifing an authorative + base domain for cases when the automatic method does not work (e.g. + Split horizon DNS) + +### Changed + +* + +### Fixed + +* Renewal parameter `webroot_path` is always saved, avoiding some regressions + when `webroot` authenticator plugin is invoked with no challenge to perform. +* Certbot now accepts OCSP responses when an explicit authorized + responder, different from the issuer, is used to sign OCSP + responses. +* Scripts in Certbot hook directories are no longer executed when their + filenames end in a tilde. + +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 +* certbot-dns-rfc2136 + +More details about these changes can be found on our GitHub repo. + +## 0.34.2 - 2019-05-07 + +### Fixed + +* certbot-auto no longer writes a check_permissions.py script at the root + of the filesystem. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.34.1 - 2019-05-06 + +### Fixed + +* certbot-auto no longer prints a blank line when there are no permissions + problems. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.34.0 - 2019-05-01 + +### Changed + +* Apache plugin now tries to restart httpd on Fedora using systemctl if a + configuration test error is detected. This has to be done due to the way + Fedora now generates the self signed certificate files upon first + restart. +* Updated Certbot and its plugins to improve the handling of file system permissions + on Windows as a step towards adding proper Windows support to Certbot. +* Updated urllib3 to 1.24.2 in certbot-auto. +* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response + with a `keyAuthorization` if sending the response without this field caused a + `malformed` error to be received from the ACME server. +* Linode DNS plugin now supports api keys created from their new panel + at [cloud.linode.com](https://cloud.linode.com) + +### Fixed + +* Fixed Google DNS Challenge issues when private zones exist +* Adding a warning noting that future versions of Certbot will automatically configure the + webserver so that all requests redirect to secure HTTPS access. You can control this + behavior and disable this warning with the --redirect and --no-redirect flags. +* certbot-auto now prints warnings when run as root with insecure file system + permissions. If you see these messages, you should fix the problem by + following the instructions at + https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, + however, these warnings can be disabled as necessary with the flag + --no-permissions-check. +* `acme` module uses now a POST-as-GET request to retrieve the registration + from an ACME v2 server +* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to + all uppercase letters before validating. This makes the value in the config case + insensitive. + +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-cloudxns +* certbot-dns-digitalocean +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-google +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-rfc2136 +* certbot-dns-route53 +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.33.1 - 2019-04-04 + +### Fixed + +* A bug causing certbot-auto to print warnings or crash on some RHEL based + systems has been resolved. + +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 +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.33.0 - 2019-04-03 + +### Added + +* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation + path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. +* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces + `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while + setting up a new SSL vhost. By default the HTTPS port is 443. + +### Changed + +* Support for TLS-SNI-01 has been removed from all official Certbot plugins. +* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone` + modules are deprecated and will be removed soon. +* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will + generate a deprecation warning if used, and will be removed soon. +* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, + will generate a deprecation warning if used, and will be removed soon. +* CLI flag `--standalone-supported-challenges` has been removed. + +### Fixed + +* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 + is installed. We fixed a bug in Certbot causing it to interpret timestamps in + the OCSP response as being in the local timezone rather than UTC. +* Issue causing the default CentOS 6 TLS configuration to ignore some of the + HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main + http.conf for this environment where possible. + +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-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.32.0 - 2019-03-06 + +### Added + +* If possible, Certbot uses built-in support for OCSP from recent cryptography + versions instead of the OpenSSL binary: as a consequence Certbot does not need + the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. + +### Changed + +* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the + warnings described at https://github.com/certbot/josepy/issues/13. +* Apache plugin now respects CERTBOT_DOCS environment variable when adding + command line defaults. +* The running of manual plugin hooks is now always included in Certbot's log + output. +* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. +* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as + specified in the ACME protocol, to indicate when the next polling should occur. Certbot now + reads this header if set and respect its value. +* The `acme` module avoids sending the `keyAuthorization` field in the JWS + payload when responding to a challenge as the field is not included in the + current ACME protocol. To ease the migration path for ACME CA servers, + Certbot and its `acme` module will first try the request without the + `keyAuthorization` field but will temporarily retry the request with the + field included if a `malformed` error is received. This fallback will be + removed in version 0.34.0. + +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-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.31.0 - 2019-02-07 + +### Added + +* Avoid reprocessing challenges that are already validated + when a certificate is issued. +* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges + with the `acme` module. + +### Changed + +* Certbot's official Docker images are now based on Alpine Linux 3.9 rather + than 3.7. The new version comes with OpenSSL 1.1.1. +* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support + on 2.x branch is maintained). +* Apache plugin now attempts to configure all VirtualHosts matching requested + domain name instead of only a single one when answering the HTTP-01 challenge. + +### Fixed + +* Fixed accessing josepy contents through acme.jose when the full acme.jose + path is used. +* Clarify behavior for deleting certs as part of revocation. + +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-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-sakuracloud + +More details about these changes can be found on our GitHub repo. + +## 0.30.2 - 2019-01-25 + +### Fixed + +* Update the version of setuptools pinned in certbot-auto to 40.6.3 to + solve installation problems on newer OSes. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, this +release only affects certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.30.1 - 2019-01-24 + +### Fixed + +* Always download the pinned version of pip in pipstrap to address breakages +* Rename old,default.conf to old-and-default.conf to address commas in filenames + breaking recent versions of pip. +* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages + from venv downloading the latest pip + +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-apache + +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 + +## 0.28.0 - 2018-11-7 + +### Added + +* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`. +* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory. + +### Changed + +* Removed documentation mentions of `#letsencrypt` IRC on Freenode. +* Write README to the base of (config-dir)/live directory +* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges. +* Warn when using deprecated acme.challenges.TLSSNI01 +* Log warning about TLS-SNI deprecation in Certbot +* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins +* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies +* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. + +### Fixed + +* Match Nginx parser update in allowing variable names to start with `${`. +* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first +* Correct OVH integration tests on machines without internet access. +* Stop caching the results of ipv6_info in http01.py +* Test fix for Route53 plugin to prevent boto3 making outgoing connections. +* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors. +* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and + Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+. + +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-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-route53 +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/59?closed=1 + +## 0.27.1 - 2018-09-06 + +### Fixed + +* Fixed parameter name in OpenSUSE overrides for default parameters in the + Apache plugin. Certbot on OpenSUSE works again. + +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-apache + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/60?closed=1 + +## 0.27.0 - 2018-09-05 + +### Added + +* The Apache plugin now accepts the parameter --apache-ctl which can be + used to configure the path to the Apache control script. + +### Changed + +* When using `acme.client.ClientV2` (or + `acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a + newer version of the ACME protocol), an `acme.errors.ConflictError` will be + raised if you try to create an ACME account with a key that has already been + used. Previously, a JSON parsing error was raised in this scenario when using + the library with Let's Encrypt's ACMEv2 endpoint. + +### Fixed + +* When Apache is not installed, Certbot's Apache plugin no longer prints + messages about being unable to find apachectl to the terminal when the plugin + is not selected. +* If you're using the Apache plugin with the --apache-vhost-root flag set to a + directory containing a disabled virtual host for the domain you're requesting + a certificate for, the virtual host will now be temporarily enabled if + necessary to pass the HTTP challenge. +* The documentation for the Certbot package can now be built using Sphinx 1.6+. +* You can now call `query_registration` without having to first call + `new_account` on `acme.client.ClientV2` objects. +* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`. +* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura + Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on. + +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-ovh +* certbot-dns-sakuracloud + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/57?closed=1 + +## 0.26.1 - 2018-07-17 + +### Fixed + +* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs. + +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: +https://github.com/certbot/certbot/milestone/58?closed=1 + +## 0.26.0 - 2018-07-11 + +### Added + +* A new security enhancement which we're calling AutoHSTS has been added to + Certbot's Apache plugin. This enhancement configures your webserver to send a + HTTP Strict Transport Security header with a low max-age value that is slowly + increased over time. The max-age value is not increased to a large value + until you've successfully managed to renew your certificate. This enhancement + can be requested with the --auto-hsts flag. +* New official DNS plugins have been created for Gehirn Infrastracture Service, + Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub + page at https://hub.docker.com/u/certbot and on PyPI. +* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on + Let's Encrypt's ACMEv2 endpoint has been added. +* Certbot and its components now support Python 3.7. +* Certbot's install subcommand now allows you to interactively choose which + certificate to install from the list of certificates managed by Certbot. +* Certbot now accepts the flag `--no-autorenew` which causes any obtained + certificates to not be automatically renewed when it approaches expiration. +* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme + library. + +### Changed + +* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2 + endpoint. By default, this server will now be used for both new certificate + lineages and renewals. +* The Nginx plugin is no longer marked labeled as an "Alpha" version. +* The `prepare` method of Certbot's plugins is no longer called before running + "Updater" enhancements that are run on every invocation of `certbot renew`. + +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 +packages with functional changes were: + +* acme +* certbot +* certbot-apache +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-ovh +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/55?closed=1 + +## 0.25.1 - 2018-06-13 + +### Fixed + +* TLS-ALPN-01 support has been removed from our acme library. Using our current + dependencies, we are unable to provide a correct implementation of this + challenge so we decided to remove it from the library until we can provide + proper support. +* Issues causing test failures when running the tests in the acme package with + pytest<3.0 has been resolved. +* certbot-nginx now correctly depends on acme>=0.25.0. + +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 +packages with changes other than their version number were: + +* acme +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/56?closed=1 + +## 0.25.0 - 2018-06-06 + +### Added + +* Support for the ready status type was added to acme. Without this change, + Certbot and acme users will begin encountering errors when using Let's + Encrypt's ACMEv2 API starting on June 19th for the staging environment and + July 5th for production. See + https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more + information. +* Certbot now accepts the flag --reuse-key which will cause the same key to be + used in the certificate when the lineage is renewed rather than generating a + new key. +* You can now add multiple email addresses to your ACME account with Certbot by + providing a comma separated list of emails to the --email flag. +* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme. + For more information, see + https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1. +* acme now supports specifying the source address to bind to when sending + outgoing connections. You still cannot specify this address using Certbot. +* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't + already have an account registered at that server URL, Certbot will + automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint + if it exists. +* Interfaces were added to Certbot allowing plugins to be called at additional + points. The `GenericUpdater` interface allows plugins to perform actions + every time `certbot renew` is run, regardless of whether any certificates are + due for renewal, and the `RenewDeployer` interface allows plugins to perform + actions when a certificate is renewed. See `certbot.interfaces` for more + information. + +### Changed + +* When running Certbot with --dry-run and you don't already have a staging + account, the created account does not contain an email address even if one + was provided to avoid expiration emails from Let's Encrypt's staging server. +* certbot-nginx does a better job of automatically detecting the location of + Nginx's configuration files when run on BSD based systems. +* acme now requires and uses pytest when running tests with setuptools with + `python setup.py test`. +* `certbot config_changes` no longer waits for user input before exiting. + +### Fixed + +* Misleading log output that caused users to think that Certbot's standalone + plugin failed to bind to a port when performing a challenge has been + corrected. +* An issue where certbot-nginx would fail to enable HSTS if the server block + already had an `add_header` directive has been resolved. +* certbot-nginx now does a better job detecting the server block to base the + configuration for TLS-SNI challenges on. + +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 +packages with functional changes were: + +* acme +* certbot +* certbot-apache +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/54?closed=1 + +## 0.24.0 - 2018-05-02 + +### Added + +* certbot now has an enhance subcommand which allows you to configure security + enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without + reinstalling a certificate. +* certbot-dns-rfc2136 now allows the user to specify the port to use to reach + the DNS server in its credentials file. +* acme now parses the wildcard field included in authorizations so it can be + used by users of the library. + +### Changed + +* certbot-dns-route53 used to wait for each DNS update to propagate before + sending the next one, but now it sends all updates before waiting which + speeds up issuance for multiple domains dramatically. +* Certbot's official Docker images are now based on Alpine Linux 3.7 rather + than 3.4 because 3.4 has reached its end-of-life. +* We've doubled the time Certbot will spend polling authorizations before + timing out. +* The level of the message logged when Certbot is being used with + non-standard paths warning that crontabs for renewal included in Certbot + packages from OS package managers may not work has been reduced. This stops + the message from being written to stderr every time `certbot renew` runs. + +### Fixed + +* certbot-auto now works with Python 3.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 +packages with changes other than their version number were: + +* acme +* certbot +* certbot-apache +* certbot-dns-digitalocean (only style improvements to tests) +* certbot-dns-rfc2136 + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/52?closed=1 + +## 0.23.0 - 2018-04-04 + +### Added + +* Support for OpenResty was added to the Nginx plugin. + +### Changed + +* The timestamps in Certbot's logfiles now use the system's local time zone + rather than UTC. +* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able + to create and delete multiple TXT records on a single domain. +* certbot-dns-google's test suite now works without an internet connection. + +### Fixed + +* Removed a small window that if during which an error occurred, Certbot + wouldn't clean up performed challenges. +* The parameters `default` and `ipv6only` are now removed from `listen` + directives when creating a new server block in the Nginx plugin. +* `server_name` directives enclosed in quotation marks in Nginx are now properly + supported. +* Resolved an issue preventing the Apache plugin from starting Apache when it's + not currently running on RHEL and Gentoo based systems. + +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 +packages with changes other than their version number were: + +* certbot +* certbot-apache +* certbot-dns-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-google +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-rfc2136 +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/50?closed=1 + +## 0.22.2 - 2018-03-19 + +### Fixed + +* A type error introduced in 0.22.1 that would occur during challenge cleanup + when a Certbot plugin raises an exception while trying to complete the + challenge was 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 +packages with changes other than their version number were: + +* certbot + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/53?closed=1 + +## 0.22.1 - 2018-03-19 + +### Changed + +* The ACME server used with Certbot's --dry-run and --staging flags is now + Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2 + features with these flags. + +### Fixed + +* The HTTP Content-Type header is now set to the correct value during + certificate revocation with new versions of the ACME protocol. +* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank + line to the top of chain.pem and between the certificates in fullchain.pem + for each lineage. These blank lines have been removed. +* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to + work. +* Fixed a regression in acme.client.Client that caused the class to not work + when it was initialized without a ClientNetwork which is done by some of the + other projects using our ACME library. + +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 +packages with changes other than their version number were: + +* acme +* certbot + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/51?closed=1 + +## 0.22.0 - 2018-03-07 + +### Added + +* Support for obtaining wildcard certificates and a newer version of the ACME + protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2 + endpoint was added to Certbot and its ACME library. Certbot still works with + older ACME versions and will automatically change the version of the protocol + used based on the version the ACME CA implements. +* The Apache and Nginx plugins are now able to automatically install a wildcard + certificate to multiple virtual hosts that you select from your server + configuration. +* The `certbot install` command now accepts the `--cert-name` flag for + selecting a certificate. +* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library + which automatically handles most of the differences between new and old ACME + versions. `acme.client.ClientV2` is also available for people who only want + to support one version of the protocol or want to handle the differences + between versions themselves. +* certbot-auto now supports the flag --install-only which has the script + install Certbot and its dependencies and exit without invoking Certbot. +* Support for issuing a single certificate for a wildcard and base domain was + added to our Google Cloud DNS plugin. To do this, we now require your API + credentials have additional permissions, however, your credentials will + already have these permissions unless you defined a custom role with fewer + permissions than the standard DNS administrator role provided by Google. + These permissions are also only needed for the case described above so it + will continue to work for existing users. For more information about the + permissions changes, see the documentation in the plugin. + +### Changed + +* We have broken lockstep between our ACME library, Certbot, and its plugins. + This means that the different components do not need to be the same version + to work together like they did previously. This makes packaging easier + because not every piece of Certbot needs to be repackaged to ship a change to + a subset of its components. +* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot, + Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL + 6 based system, it will walk you through the process of installing Certbot + with Python 3 and refuse to upgrade to a newer version of Certbot until you + have done so. +* Certbot's components now work with older versions of setuptools to simplify + packaging for EPEL 7. + +### Fixed + +* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives + has been resolved. +* A problem where Certbot's Apache plugin would add redundant include + directives for the TLS configuration managed by Certbot has been fixed. +* Certbot's webroot plugin now properly deletes any directories it creates. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/48?closed=1 + +## 0.21.1 - 2018-01-25 + +### Fixed + +* When creating an HTTP to HTTPS redirect in Nginx, we now ensure the Host + header of the request is set to an expected value before redirecting users to + the domain found in the header. The previous way Certbot configured Nginx + redirects was a potential security issue which you can read more about at + https://community.letsencrypt.org/t/security-issue-with-redirects-added-by-certbots-nginx-plugin/51493. +* Fixed a problem where Certbot's Apache plugin could fail HTTP-01 challenges + if basic authentication is configured for the domain you request a + certificate for. +* certbot-auto --no-bootstrap now properly tries to use Python 3.4 on RHEL 6 + based systems rather than Python 2.6. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/49?closed=1 + +## 0.21.0 - 2018-01-17 + +### Added + +* Support for the HTTP-01 challenge type was added to our Apache and Nginx + plugins. For those not aware, Let's Encrypt disabled the TLS-SNI-01 challenge + type which was what was previously being used by our Apache and Nginx plugins + last week due to a security issue. For more information about Let's Encrypt's + change, click + [here](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188). + Our Apache and Nginx plugins will automatically switch to use HTTP-01 so no + changes need to be made to your Certbot configuration, however, you should + make sure your server is accessible on port 80 and isn't behind an external + proxy doing things like redirecting all traffic from HTTP to HTTPS. HTTP to + HTTPS redirects inside Apache and Nginx are fine. +* IPv6 support was added to the Nginx plugin. +* Support for automatically creating server blocks based on the default server + block was added to the Nginx plugin. +* The flags --delete-after-revoke and --no-delete-after-revoke were added + allowing users to control whether the revoke subcommand also deletes the + certificates it is revoking. + +### Changed + +* We deprecated support for Python 2.6 and Python 3.3 in Certbot and its ACME + library. Support for these versions of Python will be removed in the next + major release of Certbot. If you are using certbot-auto on a RHEL 6 based + system, it will guide you through the process of installing Python 3. +* We split our implementation of JOSE (Javascript Object Signing and + Encryption) out of our ACME library and into a separate package named josepy. + This package is available on [PyPI](https://pypi.python.org/pypi/josepy) and + on [GitHub](https://github.com/certbot/josepy). +* We updated the ciphersuites used in Apache to the new [values recommended by + Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29). + The major change here is adding ChaCha20 to the list of supported + ciphersuites. + +### Fixed + +* An issue with our Apache plugin on Gentoo due to differences in their + apache2ctl command have been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/47?closed=1 + +## 0.20.0 - 2017-12-06 + +### Added + +* Certbot's ACME library now recognizes URL fields in challenge objects in + preparation for Let's Encrypt's new ACME endpoint. The value is still + accessible in our ACME library through the name "uri". + +### Changed + +* The Apache plugin now parses some distro specific Apache configuration files + on non-Debian systems allowing it to get a clearer picture on the running + configuration. Internally, these changes were structured so that external + contributors can easily write patches to make the plugin work in new Apache + configurations. +* Certbot better reports network failures by removing information about + connection retries from the error output. +* An unnecessary question when using Certbot's webroot plugin interactively has + been removed. + +### Fixed + +* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was + unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a + redirect for multiple domains. +* Problems where the Apache plugin was failing to find directives and + duplicating existing directives on openSUSE have been resolved. +* An issue running the test shipped with Certbot and some our DNS plugins with + older versions of mock have been resolved. +* On some systems, users reported strangely interleaved output depending on + when stdout and stderr were flushed. This problem was resolved by having + Certbot regularly flush these streams. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/44?closed=1 + +## 0.19.0 - 2017-10-04 + +### Added + +* Certbot now has renewal hook directories where executable files can be placed + for Certbot to run with the renew subcommand. Pre-hooks, deploy-hooks, and + post-hooks can be specified in the renewal-hooks/pre, renewal-hooks/deploy, + and renewal-hooks/post directories respectively in Certbot's configuration + directory (which is /etc/letsencrypt by default). Certbot will automatically + create these directories when it is run if they do not already exist. +* After revoking a certificate with the revoke subcommand, Certbot will offer + to delete the lineage associated with the certificate. When Certbot is run + with --non-interactive, it will automatically try to delete the associated + lineage. +* When using Certbot's Google Cloud DNS plugin on Google Compute Engine, you no + longer have to provide a credential file to Certbot if you have configured + sufficient permissions for the instance which Certbot can automatically + obtain using Google's metadata service. + +### Changed + +* When deleting certificates interactively using the delete subcommand, Certbot + will now allow you to select multiple lineages to be deleted at once. +* Certbot's Apache plugin no longer always parses Apache's sites-available on + Debian based systems and instead only parses virtual hosts included in your + Apache configuration. You can provide an additional directory for Certbot to + parse using the command line flag --apache-vhost-root. + +### Fixed + +* The plugins subcommand can now be run without root access. +* certbot-auto now includes a timeout when updating itself so it no longer + hangs indefinitely when it is unable to connect to the external server. +* An issue where Certbot's Apache plugin would sometimes fail to deploy a + certificate on Debian based systems if mod_ssl wasn't already enabled has + been resolved. +* A bug in our Docker image where the certificates subcommand could not report + if certificates maintained by Certbot had been revoked has been fixed. +* Certbot's RFC 2136 DNS plugin (for use with software like BIND) now properly + performs DNS challenges when the domain being verified contains a CNAME + record. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/43?closed=1 + +## 0.18.2 - 2017-09-20 + +### Fixed + +* An issue where Certbot's ACME module would raise an AttributeError trying to + create self-signed certificates when used with pyOpenSSL 17.3.0 has been + resolved. For Certbot users with this version of pyOpenSSL, this caused + Certbot to crash when performing a TLS SNI challenge or when the Nginx plugin + tried to create an SSL server block. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/46?closed=1 + +## 0.18.1 - 2017-09-08 + +### Fixed + +* If certbot-auto was running as an unprivileged user and it upgraded from + 0.17.0 to 0.18.0, it would crash with a permissions error and would need to + be run again to successfully complete the upgrade. This has been fixed and + certbot-auto should upgrade cleanly to 0.18.1. +* Certbot usually uses "certbot-auto" or "letsencrypt-auto" in error messages + and the User-Agent string instead of "certbot" when you are using one of + these wrapper scripts. Proper detection of this was broken with Certbot's new + installation path in /opt in 0.18.0 but this problem has been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/45?closed=1 + +## 0.18.0 - 2017-09-06 + +### Added + +* The Nginx plugin now configures Nginx to use 2048-bit Diffie-Hellman + parameters. Java 6 clients do not support Diffie-Hellman parameters larger + than 1024 bits, so if you need to support these clients you will need to + manually modify your Nginx configuration after using the Nginx installer. + +### Changed + +* certbot-auto now installs Certbot in directories under `/opt/eff.org`. If you + had an existing installation from certbot-auto, a symlink is created to the + new directory. You can configure certbot-auto to use a different path by + setting the environment variable VENV_PATH. +* The Nginx plugin can now be selected in Certbot's interactive output. +* Output verbosity of renewal failures when running with `--quiet` has been + reduced. +* The default revocation reason shown in Certbot help output now is a human + readable string instead of a numerical code. +* Plugin selection is now included in normal terminal output. + +### Fixed + +* A newer version of ConfigArgParse is now installed when using certbot-auto + causing values set to false in a Certbot INI configuration file to be handled + intuitively. Setting a boolean command line flag to false is equivalent to + not including it in the configuration file at all. +* New naming conventions preventing certbot-auto from installing OS + dependencies on Fedora 26 have been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/42?closed=1 + +## 0.17.0 - 2017-08-02 + +### Added + +* Support in our nginx plugin for modifying SSL server blocks that do + not contain certificate or key directives. +* A `--max-log-backups` flag to allow users to configure or even completely + disable Certbot's built in log rotation. +* A `--user-agent-comment` flag to allow people who build tools around Certbot + to differentiate their user agent string by adding a comment to its default + value. + +### Changed + +* Due to some awesome work by + [cryptography project](https://github.com/pyca/cryptography), compilation can + now be avoided on most systems when using certbot-auto. This eliminates many + problems people have had in the past such as running out of memory, having + invalid headers/libraries, and changes to the OS packages on their system + after compilation breaking Certbot. +* The `--renew-hook` flag has been hidden in favor of `--deploy-hook`. This new + flag works exactly the same way except it is always run when a certificate is + issued rather than just when it is renewed. +* We have started printing deprecation warnings in certbot-auto for + experimentally supported systems with OS packages available. +* A certificate lineage's name is included in error messages during renewal. + +### Fixed + +* Encoding errors that could occur when parsing error messages from the ACME + server containing Unicode have been resolved. +* certbot-auto no longer prints misleading messages about there being a newer + pip version available when installation fails. +* Certbot's ACME library now properly extracts domains from critical SAN + extensions. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.17.0+is%3Aclosed + +## 0.16.0 - 2017-07-05 + +### Added + +* A plugin for performing DNS challenges using dynamic DNS updates as defined + in RFC 2316. This plugin is packaged separately from Certbot and is available + at https://pypi.python.org/pypi/certbot-dns-rfc2136. It supports Python 2.6, + 2.7, and 3.3+. At this time, there isn't a good way to install this plugin + when using certbot-auto, but this should change in the near future. +* Plugins for performing DNS challenges for the providers + [DNS Made Easy](https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy) and + [LuaDNS](https://pypi.python.org/pypi/certbot-dns-luadns). These plugins are + packaged separately from Certbot and support Python 2.7 and 3.3+. Currently, + there isn't a good way to install these plugins when using certbot-auto, + but that should change soon. +* Support for performing TLS-SNI-01 challenges when using the manual plugin. +* Automatic detection of Arch Linux in the Apache plugin providing better + default settings for the plugin. + +### Changed + +* The text of the interactive question about whether a redirect from HTTP to + HTTPS should be added by Certbot has been rewritten to better explain the + choices to the user. +* Simplified HTTP challenge instructions in the manual plugin. + +### Fixed + +* Problems performing a dry run when using the Nginx plugin have been fixed. +* Resolved an issue where certbot-dns-digitalocean's test suite would sometimes + fail when ran using Python 3. +* On some systems, previous versions of certbot-auto would error out with a + message about a missing hash for setuptools. This has been fixed. +* A bug where Certbot would sometimes not print a space at the end of an + interactive prompt has been resolved. +* Nonfatal tracebacks are no longer shown in rare cases where Certbot + encounters an exception trying to close its TCP connection with the ACME + server. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.16.0+is%3Aclosed + +## 0.15.0 - 2017-06-08 + +### Added + +* Plugins for performing DNS challenges for popular providers. Like the Apache + and Nginx plugins, these plugins are packaged separately and not included in + Certbot by default. So far, we have plugins for + [Amazon Route 53](https://pypi.python.org/pypi/certbot-dns-route53), + [Cloudflare](https://pypi.python.org/pypi/certbot-dns-cloudflare), + [DigitalOcean](https://pypi.python.org/pypi/certbot-dns-digitalocean), and + [Google Cloud](https://pypi.python.org/pypi/certbot-dns-google) which all + work on Python 2.6, 2.7, and 3.3+. Additionally, we have plugins for + [CloudXNS](https://pypi.python.org/pypi/certbot-dns-cloudxns), + [DNSimple](https://pypi.python.org/pypi/certbot-dns-dnsimple), + [NS1](https://pypi.python.org/pypi/certbot-dns-nsone) which work on Python + 2.7 and 3.3+ (and not 2.6). Currently, there isn't a good way to install + these plugins when using `certbot-auto`, but that should change soon. +* IPv6 support in the standalone plugin. When performing a challenge, the + standalone plugin automatically handles listening for IPv4/IPv6 traffic based + on the configuration of your system. +* A mechanism for keeping your Apache and Nginx SSL/TLS configuration up to + date. When the Apache or Nginx plugins are used, they place SSL/TLS + configuration options in the root of Certbot's config directory + (`/etc/letsencrypt` by default). Now when a new version of these plugins run + on your system, they will automatically update the file to the newest + version if it is unmodified. If you manually modified the file, Certbot will + display a warning giving you a path to the updated file which you can use as + a reference to manually update your modified copy. +* `--http-01-address` and `--tls-sni-01-address` flags for controlling the + address Certbot listens on when using the standalone plugin. +* The command `certbot certificates` that lists certificates managed by Certbot + now performs additional validity checks to notify you if your files have + become corrupted. + +### Changed + +* Messages custom hooks print to `stdout` are now displayed by Certbot when not + running in `--quiet` mode. +* `jwk` and `alg` fields in JWS objects have been moved into the protected + header causing Certbot to more closely follow the latest version of the ACME + spec. + +### Fixed + +* Permissions on renewal configuration files are now properly preserved when + they are updated. +* A bug causing Certbot to display strange defaults in its help output when + using Python <= 2.7.4 has been fixed. +* Certbot now properly handles mixed case domain names found in custom CSRs. +* A number of poorly worded prompts and error messages. + +### Removed + +* Support for OpenSSL 1.0.0 in `certbot-auto` has been removed as we now pin a + newer version of `cryptography` which dropped support for this version. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.15.0+is%3Aclosed + +## 0.14.2 - 2017-05-25 + +### Fixed + +* Certbot 0.14.0 included a bug where Certbot would create a temporary log file +(usually in /tmp) if the program exited during argument parsing. If a user +provided -h/--help/help, --version, or an invalid command line argument, +Certbot would create this temporary log file. This was especially bothersome to +certbot-auto users as certbot-auto runs `certbot --version` internally to see +if the script needs to upgrade causing it to create at least one of these files +on every run. This problem has been resolved. + +More details about this change can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed + +## 0.14.1 - 2017-05-16 + +### Fixed + +* Certbot now works with configargparse 0.12.0. +* Issues with the Apache plugin and Augeas 1.7+ have been resolved. +* A problem where the Nginx plugin would fail to install certificates on +systems that had the plugin's SSL/TLS options file from 7+ months ago has been +fixed. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.1+is%3Aclosed + +## 0.14.0 - 2017-05-04 + +### Added + +* Python 3.3+ support for all Certbot packages. `certbot-auto` still currently +only supports Python 2, but the `acme`, `certbot`, `certbot-apache`, and +`certbot-nginx` packages on PyPI now fully support Python 2.6, 2.7, and 3.3+. +* Certbot's Apache plugin now handles multiple virtual hosts per file. +* Lockfiles to prevent multiple versions of Certbot running simultaneously. + +### Changed + +* When converting an HTTP virtual host to HTTPS in Apache, Certbot only copies +the virtual host rather than the entire contents of the file it's contained +in. +* The Nginx plugin now includes SSL/TLS directives in a separate file located +in Certbot's configuration directory rather than copying the contents of the +file into every modified `server` block. + +### Fixed + +* Ensure logging is configured before parts of Certbot attempt to log any +messages. +* Support for the `--quiet` flag in `certbot-auto`. +* Reverted a change made in a previous release to make the `acme` and `certbot` +packages always depend on `argparse`. This dependency is conditional again on +the user's Python version. +* Small bugs in the Nginx plugin such as properly handling empty `server` +blocks and setting `server_names_hash_bucket_size` during challenges. + +As always, a more complete list of changes can be found on GitHub: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.0+is%3Aclosed + +## 0.13.0 - 2017-04-06 + +### Added + +* `--debug-challenges` now pauses Certbot after setting up challenges for debugging. +* The Nginx parser can now handle all valid directives in configuration files. +* Nginx ciphersuites have changed to Mozilla Intermediate. +* `certbot-auto --no-bootstrap` provides the option to not install OS dependencies. + +### Fixed + +* `--register-unsafely-without-email` now respects `--quiet`. +* Hyphenated renewal parameters are now saved in renewal config files. +* `--dry-run` no longer persists keys and csrs. +* Certbot no longer hangs when trying to start Nginx in Arch Linux. +* Apache rewrite rules no longer double-encode characters. + +A full list of changes is available on GitHub: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.13.0%20is%3Aclosed%20 + +## 0.12.0 - 2017-03-02 + +### Added + +* Certbot now allows non-camelcase Apache VirtualHost names. +* Certbot now allows more log messages to be silenced. + +### Fixed + +* Fixed a regression around using `--cert-name` when getting new certificates + +More information about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0 + +## 0.11.1 - 2017-02-01 + +### Fixed + +* Resolved a problem where Certbot would crash while parsing command line +arguments in some cases. +* Fixed a typo. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed + +## 0.11.0 - 2017-02-01 + +### Added + +* When using the standalone plugin while running Certbot interactively +and a required port is bound by another process, Certbot will give you +the option to retry to grab the port rather than immediately exiting. +* You are now able to deactivate your account with the Let's Encrypt +server using the `unregister` subcommand. +* When revoking a certificate using the `revoke` subcommand, you now +have the option to provide the reason the certificate is being revoked +to Let's Encrypt with `--reason`. + +### Changed + +* Providing `--quiet` to `certbot-auto` now silences package manager output. + +### Removed + +* Removed the optional `dnspython` dependency in our `acme` package. +Now the library does not support client side verification of the DNS +challenge. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed + +## 0.10.2 - 2017-01-25 + +### Added + +* If Certbot receives a request with a `badNonce` error, it now +automatically retries the request. Since nonces from Let's Encrypt expire, +this helps people performing the DNS challenge with the `manual` plugin +who may have to wait an extended period of time for their DNS changes to +propagate. + +### Fixed + +* Certbot now saves the `--preferred-challenges` values for renewal. Previously +these values were discarded causing a different challenge type to be used when +renewing certs in some cases. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed + +## 0.10.1 - 2017-01-13 + +### Fixed + +* Resolve problems where when asking Certbot to update a certificate at +an existing path to include different domain names, the old names would +continue to be used. +* Fix issues successfully running our unit test suite on some systems. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed + +## 0.10.0 - 2017-01-11 + +## Added + +* Added the ability to customize and automatically complete DNS and HTTP +domain validation challenges with the manual plugin. The flags +`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided +when using the manual plugin to execute commands provided by the user to +perform and clean up challenges provided by the CA. This is best used in +complicated setups where the DNS challenge must be used or Certbot's +existing plugins cannot be used to perform HTTP challenges. For more +information on how this works, see `certbot --help manual`. +* Added a `--cert-name` flag for specifying the name to use for the +certificate in Certbot's configuration directory. Using this flag in +combination with `-d/--domains`, a user can easily request a new +certificate with different domains and save it with the name provided by +`--cert-name`. Additionally, `--cert-name` can be used to select a +certificate with the `certonly` and `run` subcommands so a full list of +domains in the certificate does not have to be provided. +* Added subcommand `certificates` for listing the certificates managed by +Certbot and their properties. +* Added the `delete` subcommand for removing certificates managed by Certbot +from the configuration directory. +* Certbot now supports requesting internationalized domain names (IDNs). +* Hooks provided to Certbot are now saved to be reused during renewal. +If you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook` +flags when obtaining a certificate, the provided commands will +automatically be saved and executed again when renewing the certificate. +A pre-hook and/or post-hook can also be given to the `certbot renew` +command either on the command line or in a [configuration +file](https://certbot.eff.org/docs/using.html#configuration-file) to run +an additional command before/after any certificate is renewed. Hooks +will only be run if a certificate is renewed. +* Support Busybox in certbot-auto. + +### Changed + +* Recategorized `-h/--help` output to improve documentation and +discoverability. + +### Removed + +* Removed the ncurses interface. This change solves problems people +were having on many systems, reduces the number of Certbot +dependencies, and simplifies our code. Certbot's only interface now is +the text interface which was available by providing `-t/--text` to +earlier versions of Certbot. + +### Fixed + +* Many small bug fixes. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed + +## 0.9.3 - 2016-10-13 + +### Added + +* The Apache plugin uses information about your OS to help determine the +layout of your Apache configuration directory. We added a patch to +ensure this code behaves the same way when testing on different systems +as the tests were failing in some cases. + +### Changed + +* Certbot adopted more conservative behavior about reporting a needed port as +unavailable when using the standalone plugin. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/27?closed=1 + +## 0.9.2 - 2016-10-12 + +### Added + +* Certbot stopped requiring that all possibly required ports are available when +using the standalone plugin. It now only verifies that the ports are available +when they are necessary. + +### Fixed + +* Certbot now verifies that our optional dependencies version matches what is +required by Certbot. +* Certnot now properly copies the `ssl on;` directives as necessary when +performing domain validation in the Nginx plugin. +* Fixed problem where symlinks were becoming files when they were +packaged, causing errors during testing and OS packaging. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/26?closed=1 + +## 0.9.1 - 2016-10-06 + +### Fixed + +* Fixed a bug that was introduced in version 0.9.0 where the command +line flag -q/--quiet wasn't respected in some cases. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/25?closed=1 + +## 0.9.0 - 2016-10-05 + +### Added + +* Added an alpha version of the Nginx plugin. This plugin fully automates the +process of obtaining and installing certificates with Nginx. +Additionally, it is able to automatically configure security +enhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use +this plugin, you must have the `certbot-nginx` package installed (which +is installed automatically when using `certbot-auto`) and provide +`--nginx` on the command line. This plugin is still in its early stages +so we recommend you use it with some caution and make sure you have a +backup of your Nginx configuration. +* Added support for the `DNS` challenge in the `acme` library and `DNS` in +Certbot's `manual` plugin. This allows you to create DNS records to +prove to Let's Encrypt you control the requested domain name. To use +this feature, include `--manual --preferred-challenges dns` on the +command line. +* Certbot now helps with enabling Extra Packages for Enterprise Linux (EPEL) on +CentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6, +the EPEL repository has to be enabled. `certbot-auto` will now prompt +users asking them if they would like the script to enable this for them +automatically. This is done without prompting users when using +`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is +included on the command line. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed + +## 0.8.1 - 2016-06-14 + +### Added + +* Certbot now preserves a certificate's common name when using `renew`. +* Certbot now saves webroot values for renewal when they are entered interactively. +* Certbot now gracefully reports that the Apache plugin isn't usable when Augeas is not installed. +* Added experimental support for Mageia has been added to `certbot-auto`. + +### Fixed + +* Fixed problems with an invalid user-agent string on OS X. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+ + +## 0.8.0 - 2016-06-02 + +### Added + +* Added the `register` subcommand which can be used to register an account +with the Let's Encrypt CA. +* You can now run `certbot register --update-registration` to +change the e-mail address associated with your registration. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+ + +## 0.7.0 - 2016-05-27 + +### Added + +* Added `--must-staple` to request certificates from Let's Encrypt +with the OCSP must staple extension. +* Certbot now automatically configures OSCP stapling for Apache. +* Certbot now allows requesting certificates for domains found in the common name +of a custom CSR. + +### Fixed + +* Fixed a number of miscellaneous bugs + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue + +## 0.6.0 - 2016-05-12 + +### Added + +* Versioned the datetime dependency in setup.py. + +### Changed + +* Renamed the client from `letsencrypt` to `certbot`. + +### Fixed + +* Fixed a small json deserialization error. +* Certbot now preserves domain order in generated CSRs. +* Fixed some minor bugs. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20 + +## 0.5.0 - 2016-04-05 + +### Added + +* Added the ability to use the webroot plugin interactively. +* Added the flags --pre-hook, --post-hook, and --renew-hook which can be used with +the renew subcommand to register shell commands to run in response to +renewal events. Pre-hook commands will be run before any certs are +renewed, post-hook commands will be run after any certs are renewed, +and renew-hook commands will be run after each cert is renewed. If no +certs are due for renewal, no command is run. +* Added a -q/--quiet flag which silences all output except errors. +* Added an --allow-subset-of-domains flag which can be used with the renew +command to prevent renewal failures for a subset of the requested +domains from causing the client to exit. + +### Changed + +* Certbot now uses renewal configuration files. In /etc/letsencrypt/renewal +by default, these files can be used to control what parameters are +used when renewing a specific certificate. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue + +## 0.4.2 - 2016-03-03 + +### Fixed + +* Resolved problems encountered when compiling letsencrypt +against the new OpenSSL release. +* Fixed problems encountered when using `letsencrypt renew` with configuration files +from the private beta. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2 + +## 0.4.1 - 2016-02-29 + +### Fixed + +* Fixed Apache parsing errors encountered with some configurations. +* Fixed Werkzeug dependency problems encountered on some Red Hat systems. +* Fixed bootstrapping failures when using letsencrypt-auto with --no-self-upgrade. +* Fixed problems with parsing renewal config files from private beta. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1 + +## 0.4.0 - 2016-02-10 + +### Added + +* Added the verb/subcommand `renew` which can be used to renew your existing +certificates as they approach expiration. Running `letsencrypt renew` +will examine all existing certificate lineages and determine if any are +less than 30 days from expiration. If so, the client will use the +settings provided when you previously obtained the certificate to renew +it. The subcommand finishes by printing a summary of which renewals were +successful, failed, or not yet due. +* Added a `--dry-run` flag to help with testing configuration +without affecting production rate limits. Currently supported by the +`renew` and `certonly` subcommands, providing `--dry-run` on the command +line will obtain certificates from the staging server without saving the +resulting certificates to disk. +* Added major improvements to letsencrypt-auto. This script +has been rewritten to include full support for Python 2.6, the ability +for letsencrypt-auto to update itself, and improvements to the +stability, security, and performance of the script. +* Added support for Apache 2.2 to the Apache plugin. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0 + +## 0.3.0 - 2016-01-27 + +### Added + +* Added a non-interactive mode which can be enabled by including `-n` or +`--non-interactive` on the command line. This can be used to guarantee +the client will not prompt when run automatically using cron/systemd. +* Added preparation for the new letsencrypt-auto script. Over the past +couple months, we've been working on increasing the reliability and +security of letsencrypt-auto. A number of changes landed in this +release to prepare for the new version of this script. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0 + +## 0.2.0 - 2016-01-14 + +### Added + +* Added Apache plugin support for non-Debian based systems. Support has been +added for modern Red Hat based systems such as Fedora 23, Red Hat 7, +and CentOS 7 running Apache 2.4. In theory, this plugin should be +able to be configured to run on any Unix-like OS running Apache 2.4. +* Relaxed PyOpenSSL version requirements. This adds support for systems +with PyOpenSSL versions 0.13 or 0.14. +* Improved error messages from the client. + +### Fixed + +* Resolved issues with the Apache plugin enabling an HTTP to HTTPS +redirect on some systems. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0 + +## 0.1.1 - 2015-12-15 + +### Added + +* Added a check that avoids attempting to issue for unqualified domain names like +"localhost". + +### Fixed + +* Fixed a confusing UI path that caused some users to repeatedly renew +their certs while experimenting with the client, in some cases hitting +issuance rate limits. +* Fixed numerous Apache configuration parser problems +* Fixed --webroot permission handling for non-root users + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1 diff --git a/certbot/LICENSE.txt b/certbot/LICENSE.txt new file mode 100644 index 000000000..b905dd120 --- /dev/null +++ b/certbot/LICENSE.txt @@ -0,0 +1,205 @@ +Certbot ACME Client +Copyright (c) Electronic Frontier Foundation and others +Licensed Apache Version 2.0 + +The nginx plugin incorporates code from nginxparser +Copyright (c) 2014 Fatih Erikli +Licensed MIT + + +Text of Apache License +====================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + +Text of MIT License +=================== +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/certbot/MANIFEST.in b/certbot/MANIFEST.in new file mode 100644 index 000000000..3b743ee1b --- /dev/null +++ b/certbot/MANIFEST.in @@ -0,0 +1,8 @@ +include README.rst +include CHANGELOG.md +include LICENSE.txt +recursive-include docs * +recursive-include examples * +recursive-include certbot/tests/testdata * +recursive-include tests *.py +include certbot/ssl-dhparams.pem diff --git a/certbot/README.rst b/certbot/README.rst new file mode 100644 index 000000000..5f5ea17a1 --- /dev/null +++ b/certbot/README.rst @@ -0,0 +1,131 @@ +.. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin + +Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server. + +Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment. + +How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide `_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access `_ to your web server to run Certbot. + +Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt. + +Certbot is a fully-featured, extensible client for the Let's +Encrypt CA (or any other CA that speaks the `ACME +`_ +protocol) that can automate the tasks of obtaining certificates and +configuring webservers to use them. This client runs on Unix-based operating +systems. + +To see the changes made to Certbot between versions please refer to our +`changelog `_. + +Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``, +depending on install method. Instructions on the Internet, and some pieces of the +software, may still refer to this older name. + +Contributing +------------ + +If you'd like to contribute to this project please read `Developer Guide +`_. + +This project is governed by `EFF's Public Projects Code of Conduct `_. + +.. _installation: + +How to run the client +--------------------- + +The easiest way to install and run Certbot is by visiting `certbot.eff.org`_, +where you can find the correct instructions for many web server and OS +combinations. For more information, see `Get Certbot +`_. + +.. _certbot.eff.org: https://certbot.eff.org/ + +Understanding the client in more depth +-------------------------------------- + +To understand what the client is doing in detail, it's important to +understand the way it uses plugins. Please see the `explanation of +plugins `_ in +the User Guide. + +Links +===== + +.. Do not modify this comment unless you know what you're doing. tag:links-begin + +Documentation: https://certbot.eff.org/docs + +Software project: https://github.com/certbot/certbot + +Notes for developers: https://certbot.eff.org/docs/contributing.html + +Main Website: https://certbot.eff.org + +Let's Encrypt Website: https://letsencrypt.org + +Community: https://community.letsencrypt.org + +ACME spec: http://ietf-wg-acme.github.io/acme/ + +ACME working area in github: https://github.com/ietf-wg-acme/acme + +|build-status| |coverage| |docs| |container| + +.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master + :target: https://travis-ci.com/certbot/certbot + :alt: Travis CI status + +.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg + :target: https://codecov.io/gh/certbot/certbot + :alt: Coverage status + +.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ + :target: https://readthedocs.org/projects/letsencrypt/ + :alt: Documentation status + +.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status + :target: https://quay.io/repository/letsencrypt/letsencrypt + :alt: Docker Repository on Quay.io + +.. Do not modify this comment unless you know what you're doing. tag:links-end + +System Requirements +=================== + +See https://certbot.eff.org/docs/install.html#system-requirements. + +.. Do not modify this comment unless you know what you're doing. tag:intro-end + +.. Do not modify this comment unless you know what you're doing. tag:features-begin + +Current Features +===================== + +* Supports multiple web servers: + + - apache/2.x + - nginx/0.8.48+ + - webroot (adds files to webroot directories in order to prove control of + domains and obtain certs) + - standalone (runs its own simple webserver to prove you control a domain) + - other server software via `third party plugins `_ + +* The private key is generated locally on your system. +* Can talk to the Let's Encrypt CA or optionally to other ACME + compliant services. +* Can get domain-validated (DV) certificates. +* Can revoke certificates. +* Adjustable RSA key bit-length (2048 (default), 4096, ...). +* Can optionally install a http -> https redirect, so your site effectively + runs https only (Apache only) +* Fully automated. +* Configuration changes are logged and can be reverted. +* Supports an interactive text UI, or can be driven entirely from the + command line. +* Free and Open Source Software, made with Python. + +.. Do not modify this comment unless you know what you're doing. tag:features-end + +For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide `_. diff --git a/certbot/__init__.py b/certbot/__init__.py deleted file mode 100644 index 30b52be1a..000000000 --- a/certbot/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Certbot client.""" - -# version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.0.0.dev0' diff --git a/certbot/_internal/__init__.py b/certbot/_internal/__init__.py deleted file mode 100644 index 45ec6ac9c..000000000 --- a/certbot/_internal/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Modules internal to Certbot. - -This package contains modules that are not considered part of Certbot's public -API. They may be changed without updating Certbot's major version. -""" diff --git a/certbot/_internal/account.py b/certbot/_internal/account.py deleted file mode 100644 index 6060cbd71..000000000 --- a/certbot/_internal/account.py +++ /dev/null @@ -1,344 +0,0 @@ -"""Creates ACME accounts for server.""" -import datetime -import functools -import hashlib -import logging -import shutil -import socket - -import josepy as jose -import pyrfc3339 -import pytz -import six -import zope.component -from cryptography.hazmat.primitives import serialization - -from acme import fields as acme_fields -from acme import messages - -from certbot._internal import constants -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -class Account(object): - """ACME protocol registration. - - :ivar .RegistrationResource regr: Registration Resource - :ivar .JWK key: Authorized Account Key - :ivar .Meta: Account metadata - :ivar str id: Globally unique account identifier. - - """ - - class Meta(jose.JSONObjectWithFields): - """Account metadata - - :ivar datetime.datetime creation_dt: Creation date and time (UTC). - :ivar str creation_host: FQDN of host, where account has been created. - - .. note:: ``creation_dt`` and ``creation_host`` are useful in - cross-machine migration scenarios. - - """ - creation_dt = acme_fields.RFC3339Field("creation_dt") - creation_host = jose.Field("creation_host") - - def __init__(self, regr, key, meta=None): - self.key = key - self.regr = regr - self.meta = self.Meta( - # pyrfc3339 drops microseconds, make sure __eq__ is sane - creation_dt=datetime.datetime.now( - tz=pytz.UTC).replace(microsecond=0), - creation_host=socket.getfqdn()) if meta is None else meta - - self.id = hashlib.md5( - self.key.key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo) - ).hexdigest() - # Implementation note: Email? Multiple accounts can have the - # same email address. Registration URI? Assigned by the - # server, not guaranteed to be stable over time, nor - # canonical URI can be generated. ACME protocol doesn't allow - # account key (and thus its fingerprint) to be updated... - - @property - def slug(self): - """Short account identification string, useful for UI.""" - return "{1}@{0} ({2})".format(pyrfc3339.generate( - self.meta.creation_dt), self.meta.creation_host, self.id[:4]) - - def __repr__(self): - return "<{0}({1}, {2}, {3})>".format( - self.__class__.__name__, self.regr, self.id, self.meta) - - def __eq__(self, other): - return (isinstance(other, self.__class__) and - self.key == other.key and self.regr == other.regr and - self.meta == other.meta) - - -def report_new_account(config): - """Informs the user about their new ACME account.""" - reporter = zope.component.queryUtility(interfaces.IReporter) - if reporter is None: - return - reporter.add_message( - "Your account credentials have been saved in your Certbot " - "configuration directory at {0}. You should make a secure backup " - "of this folder now. This configuration directory will also " - "contain certificates and private keys obtained by Certbot " - "so making regular backups of this folder is ideal.".format( - config.config_dir), - reporter.MEDIUM_PRIORITY) - - -class AccountMemoryStorage(interfaces.AccountStorage): - """In-memory account storage.""" - - def __init__(self, initial_accounts=None): - self.accounts = initial_accounts if initial_accounts is not None else {} - - def find_all(self): - return list(six.itervalues(self.accounts)) - - def save(self, account, client): - if account.id in self.accounts: - logger.debug("Overwriting account: %s", account.id) - self.accounts[account.id] = account - - def load(self, account_id): - try: - return self.accounts[account_id] - except KeyError: - raise errors.AccountNotFound(account_id) - -class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): - """A backwards-compatible RegistrationResource with a new-authz URI. - - Hack: Certbot versions pre-0.11.1 expect to load - new_authzr_uri as part of the account. Because people - sometimes switch between old and new versions, we will - continue to write out this field for some time so older - clients don't crash in that scenario. - """ - new_authzr_uri = jose.Field('new_authzr_uri') - -class AccountFileStorage(interfaces.AccountStorage): - """Accounts file storage. - - :ivar .IConfig config: Client configuration - - """ - def __init__(self, config): - self.config = config - util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) - - def _account_dir_path(self, account_id): - return self._account_dir_path_for_server_path(account_id, self.config.server_path) - - def _account_dir_path_for_server_path(self, account_id, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - return os.path.join(accounts_dir, account_id) - - @classmethod - def _regr_path(cls, account_dir_path): - return os.path.join(account_dir_path, "regr.json") - - @classmethod - def _key_path(cls, account_dir_path): - return os.path.join(account_dir_path, "private_key.json") - - @classmethod - def _metadata_path(cls, account_dir_path): - return os.path.join(account_dir_path, "meta.json") - - def _find_all_for_server_path(self, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - try: - candidates = os.listdir(accounts_dir) - except OSError: - return [] - - accounts = [] - for account_id in candidates: - try: - accounts.append(self._load_for_server_path(account_id, server_path)) - except errors.AccountStorageError: - logger.debug("Account loading problem", exc_info=True) - - if not accounts and server_path in constants.LE_REUSE_SERVERS: - # find all for the next link down - prev_server_path = constants.LE_REUSE_SERVERS[server_path] - prev_accounts = self._find_all_for_server_path(prev_server_path) - # if we found something, link to that - if prev_accounts: - try: - self._symlink_to_accounts_dir(prev_server_path, server_path) - except OSError: - return [] - accounts = prev_accounts - return accounts - - def find_all(self): - return self._find_all_for_server_path(self.config.server_path) - - def _symlink_to_account_dir(self, prev_server_path, server_path, account_id): - prev_account_dir = self._account_dir_path_for_server_path(account_id, prev_server_path) - new_account_dir = self._account_dir_path_for_server_path(account_id, server_path) - os.symlink(prev_account_dir, new_account_dir) - - def _symlink_to_accounts_dir(self, prev_server_path, server_path): - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - if os.path.islink(accounts_dir): - os.unlink(accounts_dir) - else: - os.rmdir(accounts_dir) - prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path) - os.symlink(prev_account_dir, accounts_dir) - - def _load_for_server_path(self, account_id, server_path): - account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) - if not os.path.isdir(account_dir_path): # isdir is also true for symlinks - if server_path in constants.LE_REUSE_SERVERS: - prev_server_path = constants.LE_REUSE_SERVERS[server_path] - prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) - # we didn't error so we found something, so create a symlink to that - accounts_dir = self.config.accounts_dir_for_server_path(server_path) - # If accounts_dir isn't empty, make an account specific symlink - if os.listdir(accounts_dir): - self._symlink_to_account_dir(prev_server_path, server_path, account_id) - else: - self._symlink_to_accounts_dir(prev_server_path, server_path) - return prev_loaded_account - else: - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) - - try: - with open(self._regr_path(account_dir_path)) as regr_file: - regr = messages.RegistrationResource.json_loads(regr_file.read()) - with open(self._key_path(account_dir_path)) as key_file: - key = jose.JWK.json_loads(key_file.read()) - with open(self._metadata_path(account_dir_path)) as metadata_file: - meta = Account.Meta.json_loads(metadata_file.read()) - except IOError as error: - raise errors.AccountStorageError(error) - - return Account(regr, key, meta) - - def load(self, account_id): - return self._load_for_server_path(account_id, self.config.server_path) - - def save(self, account, client): - self._save(account, client, regr_only=False) - - def save_regr(self, account, acme): - """Save the registration resource. - - :param Account account: account whose regr should be saved - - """ - self._save(account, acme, regr_only=True) - - def delete(self, account_id): - """Delete registration info from disk - - :param account_id: id of account which should be deleted - - """ - account_dir_path = self._account_dir_path(account_id) - if not os.path.isdir(account_dir_path): - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) - # Step 1: Delete account specific links and the directory - self._delete_account_dir_for_server_path(account_id, self.config.server_path) - - # Step 2: Remove any accounts links and directories that are now empty - if not os.listdir(self.config.accounts_dir): - self._delete_accounts_dir_for_server_path(self.config.server_path) - - def _delete_account_dir_for_server_path(self, account_id, server_path): - link_func = functools.partial(self._account_dir_path_for_server_path, account_id) - nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) - shutil.rmtree(nonsymlinked_dir) - - def _delete_accounts_dir_for_server_path(self, server_path): - link_func = self.config.accounts_dir_for_server_path - nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) - os.rmdir(nonsymlinked_dir) - - def _delete_links_and_find_target_dir(self, server_path, link_func): - """Delete symlinks and return the nonsymlinked directory path. - - :param str server_path: file path based on server - :param callable link_func: callable that returns possible links - given a server_path - - :returns: the final, non-symlinked target - :rtype: str - - """ - dir_path = link_func(server_path) - - # does an appropriate directory link to me? if so, make sure that's gone - reused_servers = {} - for k in constants.LE_REUSE_SERVERS: - reused_servers[constants.LE_REUSE_SERVERS[k]] = k - - # is there a next one up? - possible_next_link = True - while possible_next_link: - possible_next_link = False - if server_path in reused_servers: - next_server_path = reused_servers[server_path] - next_dir_path = link_func(next_server_path) - if os.path.islink(next_dir_path) and os.readlink(next_dir_path) == dir_path: - possible_next_link = True - server_path = next_server_path - dir_path = next_dir_path - - # if there's not a next one up to delete, then delete me - # and whatever I link to - while os.path.islink(dir_path): - target = os.readlink(dir_path) - os.unlink(dir_path) - dir_path = target - - return dir_path - - def _save(self, account, acme, regr_only): - account_dir_path = self._account_dir_path(account.id) - util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) - try: - with open(self._regr_path(account_dir_path), "w") as regr_file: - regr = account.regr - # If we have a value for new-authz, save it for forwards - # compatibility with older versions of Certbot. If we don't - # have a value for new-authz, this is an ACMEv2 directory where - # an older version of Certbot won't work anyway. - if hasattr(acme.directory, "new-authz"): - regr = RegistrationResourceWithNewAuthzrURI( - new_authzr_uri=acme.directory.new_authz, - body={}, - uri=regr.uri) - else: - regr = messages.RegistrationResource( - body={}, - uri=regr.uri) - regr_file.write(regr.json_dumps()) - if not regr_only: - with util.safe_open(self._key_path(account_dir_path), - "w", chmod=0o400) as key_file: - key_file.write(account.key.json_dumps()) - with open(self._metadata_path( - account_dir_path), "w") as metadata_file: - metadata_file.write(account.meta.json_dumps()) - except IOError as error: - raise errors.AccountStorageError(error) diff --git a/certbot/_internal/auth_handler.py b/certbot/_internal/auth_handler.py deleted file mode 100644 index 5c037e8dc..000000000 --- a/certbot/_internal/auth_handler.py +++ /dev/null @@ -1,470 +0,0 @@ -"""ACME AuthHandler.""" -import logging -import time -import datetime - -import zope.component - -from acme import challenges -from acme import messages -from acme import errors as acme_errors -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Tuple -# pylint: enable=unused-import, no-name-in-module -from certbot import achallenges -from certbot import errors -from certbot._internal import error_handler -from certbot import interfaces - -logger = logging.getLogger(__name__) - - -class AuthHandler(object): - """ACME Authorization Handler for a client. - - :ivar auth: Authenticator capable of solving - :class:`~acme.challenges.Challenge` types - :type auth: :class:`certbot.interfaces.IAuthenticator` - - :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API. - - :ivar account: Client's Account - :type account: :class:`certbot._internal.account.Account` - - :ivar list pref_challs: sorted user specified preferred challenges - type strings with the most preferred challenge listed first - - """ - def __init__(self, auth, acme_client, account, pref_challs): - self.auth = auth - self.acme = acme_client - - self.account = account - self.pref_challs = pref_challs - - def handle_authorizations(self, orderr, best_effort=False, max_retries=30): - """ - Retrieve all authorizations, perform all challenges required to validate - these authorizations, then poll and wait for the authorization to be checked. - :param acme.messages.OrderResource orderr: must have authorizations filled in - :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) - :param int max_retries: maximum number of retries to poll authorizations - :returns: list of all validated authorizations - :rtype: List - - :raises .AuthorizationError: If unable to retrieve all authorizations - """ - authzrs = orderr.authorizations[:] - if not authzrs: - raise errors.AuthorizationError('No authorization to handle.') - - # Retrieve challenges that need to be performed to validate authorizations. - achalls = self._choose_challenges(authzrs) - if not achalls: - return authzrs - - # Starting now, challenges will be cleaned at the end no matter what. - with error_handler.ExitHandler(self._cleanup_challenges, achalls): - # To begin, let's ask the authenticator plugin to perform all challenges. - try: - resps = self.auth.perform(achalls) - - # If debug is on, wait for user input before starting the verification process. - logger.info('Waiting for verification...') - config = zope.component.getUtility(interfaces.IConfig) - if config.debug_challenges: - notify = zope.component.getUtility(interfaces.IDisplay).notification - notify('Challenges loaded. Press continue to submit to CA. ' - 'Pass "-v" for more info about challenges.', pause=True) - except errors.AuthorizationError as error: - logger.critical('Failure in setting up challenges.') - logger.info('Attempting to clean up outstanding challenges...') - raise error - # All challenges should have been processed by the authenticator. - assert len(resps) == len(achalls), 'Some challenges have not been performed.' - - # Inform the ACME CA server that challenges are available for validation. - for achall, resp in zip(achalls, resps): - self.acme.answer_challenge(achall.challb, resp) - - # Wait for authorizations to be checked. - self._poll_authorizations(authzrs, max_retries, best_effort) - - # Keep validated authorizations only. If there is none, no certificate can be issued. - authzrs_validated = [authzr for authzr in authzrs - if authzr.body.status == messages.STATUS_VALID] - if not authzrs_validated: - raise errors.AuthorizationError('All challenges have failed.') - - return authzrs_validated - - def deactivate_valid_authorizations(self, orderr): - # type: (messages.OrderResource) -> Tuple[List, List] - """ - Deactivate all `valid` authorizations in the order, so that they cannot be re-used - in subsequent orders. - :param messages.OrderResource orderr: must have authorizations filled in - :returns: tuple of list of successfully deactivated authorizations, and - list of unsuccessfully deactivated authorizations. - :rtype: tuple - """ - to_deactivate = [authzr for authzr in orderr.authorizations - if authzr.body.status == messages.STATUS_VALID] - deactivated = [] - failed = [] - - for authzr in to_deactivate: - try: - authzr = self.acme.deactivate_authorization(authzr) - deactivated.append(authzr) - except acme_errors.Error as e: - failed.append(authzr) - logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) - - return (deactivated, failed) - - def _poll_authorizations(self, authzrs, max_retries, best_effort): - """ - Poll the ACME CA server, to wait for confirmation that authorizations have their challenges - all verified. The poll may occur several times, until all authorizations are checked - (valid or invalid), or after a maximum of retries. - """ - authzrs_to_check = {index: (authzr, None) - for index, authzr in enumerate(authzrs)} - authzrs_failed_to_report = [] - # Give an initial second to the ACME CA server to check the authorizations - sleep_seconds = 1 - for _ in range(max_retries): - # Wait for appropriate time (from Retry-After, initial wait, or no wait) - if sleep_seconds > 0: - time.sleep(sleep_seconds) - # Poll all updated authorizations. - authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _) - in authzrs_to_check.items()} - # Update the original list of authzr with the updated authzrs from server. - for index, (authzr, _) in authzrs_to_check.items(): - authzrs[index] = authzr - - # Gather failed authorizations - authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values() - if authzr.body.status == messages.STATUS_INVALID] - for authzr_failed in authzrs_failed: - logger.warning('Challenge failed for domain %s', - authzr_failed.body.identifier.value) - # Accumulating all failed authzrs to build a consolidated report - # on them at the end of the polling. - authzrs_failed_to_report.extend(authzrs_failed) - - # Extract out the authorization already checked for next poll iteration. - # Poll may stop here because there is no pending authorizations anymore. - authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp) - in authzrs_to_check.items() - if authzr.body.status == messages.STATUS_PENDING} - if not authzrs_to_check: - # Polling process is finished, we can leave the loop - break - - # Be merciful with the ACME server CA, check the Retry-After header returned, - # and wait this time before polling again in next loop iteration. - # From all the pending authorizations, we take the greatest Retry-After value - # to avoid polling an authorization before its relevant Retry-After value. - retry_after = max(self.acme.retry_after(resp, 3) - for _, resp in authzrs_to_check.values()) - sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() - - # In case of failed authzrs, create a report to the user. - if authzrs_failed_to_report: - _report_failed_authzrs(authzrs_failed_to_report, self.account.key) - if not best_effort: - # Without best effort, having failed authzrs is critical and fail the process. - raise errors.AuthorizationError('Some challenges have failed.') - - if authzrs_to_check: - # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. - raise errors.AuthorizationError('All authorizations were not finalized by the CA.') - - def _choose_challenges(self, authzrs): - """ - Retrieve necessary and pending challenges to satisfy server. - NB: Necessary and already validated challenges are not retrieved, - as they can be reused for a certificate issuance. - """ - pending_authzrs = [authzr for authzr in authzrs - if authzr.body.status != messages.STATUS_VALID] - achalls = [] # type: List[achallenges.AnnotatedChallenge] - if pending_authzrs: - logger.info("Performing the following challenges:") - for authzr in pending_authzrs: - authzr_challenges = authzr.body.challenges - if self.acme.acme_version == 1: - combinations = authzr.body.combinations - else: - combinations = tuple((i,) for i in range(len(authzr_challenges))) - - path = gen_challenge_path( - authzr_challenges, - self._get_chall_pref(authzr.body.identifier.value), - combinations) - - achalls.extend(self._challenge_factory(authzr, path)) - - return achalls - - def _get_chall_pref(self, domain): - """Return list of challenge preferences. - - :param str domain: domain for which you are requesting preferences - - """ - chall_prefs = [] - # Make sure to make a copy... - plugin_pref = self.auth.get_chall_pref(domain) - if self.pref_challs: - plugin_pref_types = set(chall.typ for chall in plugin_pref) - for typ in self.pref_challs: - if typ in plugin_pref_types: - chall_prefs.append(challenges.Challenge.TYPES[typ]) - if chall_prefs: - return chall_prefs - raise errors.AuthorizationError( - "None of the preferred challenges " - "are supported by the selected plugin") - chall_prefs.extend(plugin_pref) - return chall_prefs - - def _cleanup_challenges(self, achalls): - """Cleanup challenges. - - :param achalls: annotated challenges to cleanup - :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge` - - """ - logger.info("Cleaning up challenges") - self.auth.cleanup(achalls) - - def _challenge_factory(self, authzr, path): - """Construct Namedtuple Challenges - - :param messages.AuthorizationResource authzr: authorization - - :param list path: List of indices from `challenges`. - - :returns: achalls, list of challenge type - :class:`certbot.achallenges.Indexed` - :rtype: list - - :raises .errors.Error: if challenge type is not recognized - - """ - achalls = [] - - for index in path: - challb = authzr.body.challenges[index] - achalls.append(challb_to_achall( - challb, self.account.key, authzr.body.identifier.value)) - - return achalls - - -def challb_to_achall(challb, account_key, domain): - """Converts a ChallengeBody object to an AnnotatedChallenge. - - :param .ChallengeBody challb: ChallengeBody - :param .JWK account_key: Authorized Account Key - :param str domain: Domain of the challb - - :returns: Appropriate AnnotatedChallenge - :rtype: :class:`certbot.achallenges.AnnotatedChallenge` - - """ - chall = challb.chall - logger.info("%s challenge for %s", chall.typ, domain) - - if isinstance(chall, challenges.KeyAuthorizationChallenge): - return achallenges.KeyAuthorizationAnnotatedChallenge( - challb=challb, domain=domain, account_key=account_key) - elif isinstance(chall, challenges.DNS): - return achallenges.DNS(challb=challb, domain=domain) - else: - raise errors.Error( - "Received unsupported challenge of type: {0}".format(chall.typ)) - - -def gen_challenge_path(challbs, preferences, combinations): - """Generate a plan to get authority over the identity. - - .. todo:: This can be possibly be rewritten to use resolved_combinations. - - :param tuple challbs: A tuple of challenges - (:class:`acme.messages.Challenge`) from - :class:`acme.messages.AuthorizationResource` to be - fulfilled by the client in order to prove possession of the - identifier. - - :param list preferences: List of challenge preferences for domain - (:class:`acme.challenges.Challenge` subclasses) - - :param tuple combinations: A collection of sets of challenges from - :class:`acme.messages.Challenge`, each of which would - be sufficient to prove possession of the identifier. - - :returns: tuple of indices from ``challenges``. - :rtype: tuple - - :raises certbot.errors.AuthorizationError: If a - path cannot be created that satisfies the CA given the preferences and - combinations. - - """ - if combinations: - return _find_smart_path(challbs, preferences, combinations) - return _find_dumb_path(challbs, preferences) - - -def _find_smart_path(challbs, preferences, combinations): - """Find challenge path with server hints. - - Can be called if combinations is included. Function uses a simple - ranking system to choose the combo with the lowest cost. - - """ - chall_cost = {} - max_cost = 1 - for i, chall_cls in enumerate(preferences): - chall_cost[chall_cls] = i - max_cost += i - - # max_cost is now equal to sum(indices) + 1 - - best_combo = None - # Set above completing all of the available challenges - best_combo_cost = max_cost - - combo_total = 0 - for combo in combinations: - for challenge_index in combo: - combo_total += chall_cost.get(challbs[ - challenge_index].chall.__class__, max_cost) - - if combo_total < best_combo_cost: - best_combo = combo - best_combo_cost = combo_total - - combo_total = 0 - - if not best_combo: - _report_no_chall_path(challbs) - - return best_combo - - -def _find_dumb_path(challbs, preferences): - """Find challenge path without server hints. - - Should be called if the combinations hint is not included by the - server. This function either returns a path containing all - challenges provided by the CA or raises an exception. - - """ - path = [] - for i, challb in enumerate(challbs): - # supported is set to True if the challenge type is supported - supported = next((True for pref_c in preferences - if isinstance(challb.chall, pref_c)), False) - if supported: - path.append(i) - else: - _report_no_chall_path(challbs) - - return path - - -def _report_no_chall_path(challbs): - """Logs and raises an error that no satisfiable chall path exists. - - :param challbs: challenges from the authorization that can't be satisfied - - """ - msg = ("Client with the currently selected authenticator does not support " - "any combination of challenges that will satisfy the CA.") - if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): - msg += ( - " You may need to use an authenticator " - "plugin that can do challenges over DNS.") - logger.critical(msg) - raise errors.AuthorizationError(msg) - - -_ERROR_HELP_COMMON = ( - "To fix these errors, please make sure that your domain name was entered " - "correctly and the DNS A/AAAA record(s) for that domain contain(s) the " - "right IP address.") - - -_ERROR_HELP = { - "connection": - _ERROR_HELP_COMMON + " Additionally, please check that your computer " - "has a publicly routable IP address and that no firewalls are preventing " - "the server from communicating with the client. If you're using the " - "webroot plugin, you should also verify that you are serving files " - "from the webroot path you provided.", - "dnssec": - _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for " - "your domain, please ensure that the signature is valid.", - "malformed": - "To fix these errors, please make sure that you did not provide any " - "invalid information to the client, and try running Certbot " - "again.", - "serverInternal": - "Unfortunately, an error on the ACME server prevented you from completing " - "authorization. Please try again later.", - "tls": - _ERROR_HELP_COMMON + " Additionally, please check that you have an " - "up-to-date TLS configuration that allows the server to communicate " - "with the Certbot client.", - "unauthorized": _ERROR_HELP_COMMON, - "unknownHost": _ERROR_HELP_COMMON, -} - - -def _report_failed_authzrs(failed_authzrs, account_key): - """Notifies the user about failed authorizations.""" - problems = {} # type: Dict[str, List[achallenges.AnnotatedChallenge]] - failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value) - for authzr in failed_authzrs for challb in authzr.body.challenges - if challb.error] - - for achall in failed_achalls: - problems.setdefault(achall.error.typ, []).append(achall) - - reporter = zope.component.getUtility(interfaces.IReporter) - for achalls in problems.values(): - reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) - - -def _generate_failed_chall_msg(failed_achalls): - """Creates a user friendly error message about failed challenges. - - :param list failed_achalls: A list of failed - :class:`certbot.achallenges.AnnotatedChallenge` with the same error - type. - - :returns: A formatted error message for the client. - :rtype: str - - """ - error = failed_achalls[0].error - typ = error.typ - if messages.is_acme_error(error): - typ = error.code - msg = ["The following errors were reported by the server:"] - - for achall in failed_achalls: - msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % ( - achall.domain, typ, achall.error.detail)) - - if typ in _ERROR_HELP: - msg.append("\n\n") - msg.append(_ERROR_HELP[typ]) - - return "".join(msg) diff --git a/certbot/_internal/cert_manager.py b/certbot/_internal/cert_manager.py deleted file mode 100644 index 329b6cdff..000000000 --- a/certbot/_internal/cert_manager.py +++ /dev/null @@ -1,388 +0,0 @@ -"""Tools for managing certificates.""" -import datetime -import logging -import re -import traceback - -import pytz -import zope.component - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot._internal import ocsp -from certbot._internal import storage -from certbot import util -from certbot.compat import os -from certbot.display import util as display_util - -logger = logging.getLogger(__name__) - -################### -# Commands -################### - -def update_live_symlinks(config): - """Update the certificate file family symlinks to use archive_dir. - - Use the information in the config file to make symlinks point to - the correct archive directory. - - .. note:: This assumes that the installation is using a Reverter object. - - :param config: Configuration. - :type config: :class:`certbot._internal.configuration.NamespaceConfig` - - """ - for renewal_file in storage.renewal_conf_files(config): - storage.RenewableCert(renewal_file, config, update_symlinks=True) - -def rename_lineage(config): - """Rename the specified lineage to the new name. - - :param config: Configuration. - :type config: :class:`certbot._internal.configuration.NamespaceConfig` - - """ - disp = zope.component.getUtility(interfaces.IDisplay) - - certname = get_certnames(config, "rename")[0] - - new_certname = config.new_certname - if not new_certname: - code, new_certname = disp.input( - "Enter the new name for certificate {0}".format(certname), - flag="--updated-cert-name", force_interactive=True) - if code != display_util.OK or not new_certname: - raise errors.Error("User ended interaction.") - - lineage = lineage_for_certname(config, certname) - if not lineage: - raise errors.ConfigurationError("No existing certificate with name " - "{0} found.".format(certname)) - storage.rename_renewal_config(certname, new_certname, config) - disp.notification("Successfully renamed {0} to {1}." - .format(certname, new_certname), pause=False) - -def certificates(config): - """Display information about certs configured with Certbot - - :param config: Configuration. - :type config: :class:`certbot._internal.configuration.NamespaceConfig` - """ - parsed_certs = [] - parse_failures = [] - for renewal_file in storage.renewal_conf_files(config): - try: - renewal_candidate = storage.RenewableCert(renewal_file, config) - crypto_util.verify_renewable_cert(renewal_candidate) - parsed_certs.append(renewal_candidate) - except Exception as e: # pylint: disable=broad-except - logger.warning("Renewal configuration file %s produced an " - "unexpected error: %s. Skipping.", renewal_file, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - parse_failures.append(renewal_file) - - # Describe all the certs - _describe_certs(config, parsed_certs, parse_failures) - -def delete(config): - """Delete Certbot files associated with a certificate lineage.""" - certnames = get_certnames(config, "delete", allow_multiple=True) - for certname in certnames: - storage.delete_files(config, certname) - disp = zope.component.getUtility(interfaces.IDisplay) - disp.notification("Deleted all files relating to certificate {0}." - .format(certname), pause=False) - -################### -# Public Helpers -################### - -def lineage_for_certname(cli_config, certname): - """Find a lineage object with name certname.""" - configs_dir = cli_config.renewal_configs_dir - # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755) - try: - renewal_file = storage.renewal_file_for_certname(cli_config, certname) - except errors.CertStorageError: - return None - try: - return storage.RenewableCert(renewal_file, cli_config) - except (errors.CertStorageError, IOError): - logger.debug("Renewal conf file %s is broken.", renewal_file) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - -def domains_for_certname(config, certname): - """Find the domains in the cert with name certname.""" - lineage = lineage_for_certname(config, certname) - return lineage.names() if lineage else None - -def find_duplicative_certs(config, domains): - """Find existing certs that match the given domain names. - - This function searches for certificates whose domains are equal to - the `domains` parameter and certificates whose domains are a subset - of the domains in the `domains` parameter. If multiple certificates - are found whose names are a subset of `domains`, the one whose names - are the largest subset of `domains` is returned. - - If multiple certificates' domains are an exact match or equally - sized subsets, which matching certificates are returned is - undefined. - - :param config: Configuration. - :type config: :class:`certbot._internal.configuration.NamespaceConfig` - :param domains: List of domain names - :type domains: `list` of `str` - - :returns: lineages representing the identically matching cert and the - largest subset if they exist - :rtype: `tuple` of `storage.RenewableCert` or `None` - - """ - def update_certs_for_domain_matches(candidate_lineage, rv): - """Return cert as identical_names_cert if it matches, - or subset_names_cert if it matches as subset - """ - # TODO: Handle these differently depending on whether they are - # expired or still valid? - identical_names_cert, subset_names_cert = rv - candidate_names = set(candidate_lineage.names()) - if candidate_names == set(domains): - identical_names_cert = candidate_lineage - elif candidate_names.issubset(set(domains)): - # This logic finds and returns the largest subset-names cert - # in the case where there are several available. - if subset_names_cert is None: - subset_names_cert = candidate_lineage - elif len(candidate_names) > len(subset_names_cert.names()): - subset_names_cert = candidate_lineage - return (identical_names_cert, subset_names_cert) - - return _search_lineages(config, update_certs_for_domain_matches, (None, None)) - -def _archive_files(candidate_lineage, filetype): - """ In order to match things like: - /etc/letsencrypt/archive/example.com/chain1.pem. - - Anonymous functions which call this function are eventually passed (in a list) to - `match_and_check_overlaps` to help specify the acceptable_matches. - - :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to - be searched. - :param str filetype: main file name prefix e.g. "fullchain" or "chain". - - :returns: Files in candidate_lineage's archive dir that match the provided filetype. - :rtype: list of str or None - """ - archive_dir = candidate_lineage.archive_dir - pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) - if re.match("{0}[0-9]*.pem".format(filetype), f)] - if pattern: - return pattern - return None - -def _acceptable_matches(): - """ Generates the list that's passed to match_and_check_overlaps. Is its own function to - make unit testing easier. - - :returns: list of functions - :rtype: list - """ - return [lambda x: x.fullchain_path, lambda x: x.cert_path, - lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")] - -def cert_path_to_lineage(cli_config): - """ If config.cert_path is defined, try to find an appropriate value for config.certname. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - - :returns: a lineage name - :rtype: str - - :raises `errors.Error`: If the specified cert path can't be matched to a lineage name. - :raises `errors.OverlappingMatchFound`: If the matched lineage's archive is shared. - """ - acceptable_matches = _acceptable_matches() - match = match_and_check_overlaps(cli_config, acceptable_matches, - lambda x: cli_config.cert_path[0], lambda x: x.lineagename) - return match[0] - -def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func): - """ Searches through all lineages for a match, and checks for duplicates. - If a duplicate is found, an error is raised, as performing operations on lineages - that have their properties incorrectly duplicated elsewhere is probably a bad idea. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - :param list acceptable_matches: a list of functions that specify acceptable matches - :param function match_func: specifies what to match - :param function rv_func: specifies what to return - - """ - def find_matches(candidate_lineage, return_value, acceptable_matches): - """Returns a list of matches using _search_lineages.""" - acceptable_matches = [func(candidate_lineage) for func in acceptable_matches] - acceptable_matches_rv = [] # type: List[str] - for item in acceptable_matches: - if isinstance(item, list): - acceptable_matches_rv += item - else: - acceptable_matches_rv.append(item) - match = match_func(candidate_lineage) - if match in acceptable_matches_rv: - return_value.append(rv_func(candidate_lineage)) - return return_value - - matched = _search_lineages(cli_config, find_matches, [], acceptable_matches) - if not matched: - raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) - elif len(matched) > 1: - raise errors.OverlappingMatchFound() - else: - return matched - -def human_readable_cert_info(config, cert, skip_filter_checks=False): - """ Returns a human readable description of info about a RenewableCert object""" - certinfo = [] - checker = ocsp.RevocationChecker() - - if config.certname and cert.lineagename != config.certname and not skip_filter_checks: - return "" - if config.domains and not set(config.domains).issubset(cert.names()): - return "" - now = pytz.UTC.fromutc(datetime.datetime.utcnow()) - - reasons = [] - if cert.is_test_cert: - reasons.append('TEST_CERT') - if cert.target_expiry <= now: - reasons.append('EXPIRED') - elif checker.ocsp_revoked(cert): - reasons.append('REVOKED') - - if reasons: - status = "INVALID: " + ", ".join(reasons) - else: - diff = cert.target_expiry - now - if diff.days == 1: - status = "VALID: 1 day" - elif diff.days < 1: - status = "VALID: {0} hour(s)".format(diff.seconds // 3600) - else: - status = "VALID: {0} days".format(diff.days) - - valid_string = "{0} ({1})".format(cert.target_expiry, status) - certinfo.append(" Certificate Name: {0}\n" - " Domains: {1}\n" - " Expiry Date: {2}\n" - " Certificate Path: {3}\n" - " Private Key Path: {4}".format( - cert.lineagename, - " ".join(cert.names()), - valid_string, - cert.fullchain, - cert.privkey)) - return "".join(certinfo) - -def get_certnames(config, verb, allow_multiple=False, custom_prompt=None): - """Get certname from flag, interactively, or error out. - """ - certname = config.certname - if certname: - certnames = [certname] - else: - disp = zope.component.getUtility(interfaces.IDisplay) - filenames = storage.renewal_conf_files(config) - choices = [storage.lineagename_for_filename(name) for name in filenames] - if not choices: - raise errors.Error("No existing certificates found.") - if allow_multiple: - if not custom_prompt: - prompt = "Which certificate(s) would you like to {0}?".format(verb) - else: - prompt = custom_prompt - code, certnames = disp.checklist( - prompt, choices, cli_flag="--cert-name", force_interactive=True) - if code != display_util.OK: - raise errors.Error("User ended interaction.") - else: - if not custom_prompt: - prompt = "Which certificate would you like to {0}?".format(verb) - else: - prompt = custom_prompt - - code, index = disp.menu( - prompt, choices, cli_flag="--cert-name", force_interactive=True) - - if code != display_util.OK or index not in range(0, len(choices)): - raise errors.Error("User ended interaction.") - certnames = [choices[index]] - return certnames - -################### -# Private Helpers -################### - -def _report_lines(msgs): - """Format a results report for a category of single-line renewal outcomes""" - return " " + "\n ".join(str(msg) for msg in msgs) - -def _report_human_readable(config, parsed_certs): - """Format a results report for a parsed cert""" - certinfo = [] - for cert in parsed_certs: - certinfo.append(human_readable_cert_info(config, cert)) - return "\n".join(certinfo) - -def _describe_certs(config, parsed_certs, parse_failures): - """Print information about the certs we know about""" - out = [] # type: List[str] - - notify = out.append - - if not parsed_certs and not parse_failures: - notify("No certs found.") - else: - if parsed_certs: - match = "matching " if config.certname or config.domains else "" - notify("Found the following {0}certs:".format(match)) - notify(_report_human_readable(config, parsed_certs)) - if parse_failures: - notify("\nThe following renewal configurations " - "were invalid:") - notify(_report_lines(parse_failures)) - - disp = zope.component.getUtility(interfaces.IDisplay) - disp.notification("\n".join(out), pause=False, wrap=False) - -def _search_lineages(cli_config, func, initial_rv, *args): - """Iterate func over unbroken lineages, allowing custom return conditions. - - Allows flexible customization of return values, including multiple - return values and complex checks. - - :param `configuration.NamespaceConfig` cli_config: parsed command line arguments - :param function func: function used while searching over lineages - :param initial_rv: initial return value of the function (any type) - - :returns: Whatever was specified by `func` if a match is found. - """ - configs_dir = cli_config.renewal_configs_dir - # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755) - - rv = initial_rv - for renewal_file in storage.renewal_conf_files(cli_config): - try: - candidate_lineage = storage.RenewableCert(renewal_file, cli_config) - except (errors.CertStorageError, IOError): - logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - continue - rv = func(candidate_lineage, rv, *args) - return rv diff --git a/certbot/_internal/cli.py b/certbot/_internal/cli.py deleted file mode 100644 index 7eabeeee6..000000000 --- a/certbot/_internal/cli.py +++ /dev/null @@ -1,1583 +0,0 @@ -"""Certbot command line argument & config processing.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import argparse -import copy -import glob -import logging.handlers -import sys - -import configargparse -import six -import zope.component -import zope.interface -from zope.interface import interfaces as zope_interfaces - -from acme import challenges -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Dict, Optional -# pylint: enable=unused-import, no-name-in-module - -import certbot -import certbot.plugins.enhancements as enhancements -import certbot._internal.plugins.selection as plugin_selection -from certbot._internal import constants -from certbot import crypto_util -from certbot import errors -from certbot._internal import hooks -from certbot import interfaces -from certbot import util -from certbot.compat import os -from certbot.display import util as display_util -from certbot._internal.plugins import disco as plugins_disco - -logger = logging.getLogger(__name__) - -# Global, to save us from a lot of argument passing within the scope of this module -helpful_parser = None # type: Optional[HelpfulArgumentParser] - -# For help strings, figure out how the user ran us. -# When invoked from letsencrypt-auto, sys.argv[0] is something like: -# "/home/user/.local/share/certbot/bin/certbot" -# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before -# running letsencrypt-auto (and sudo stops us from seeing if they did), so it -# should only be used for purposes where inability to detect letsencrypt-auto -# fails safely - -LEAUTO = "letsencrypt-auto" -if "CERTBOT_AUTO" in os.environ: - # if we're here, this is probably going to be certbot-auto, unless the - # user saved the script under a different name - LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) - -old_path_fragment = os.path.join(".local", "share", "letsencrypt") -new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", - "eff.org", "certbot", "venv")) -if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): - cli_command = LEAUTO -else: - cli_command = "certbot" - -# Argparse's help formatting has a lot of unhelpful peculiarities, so we want -# to replace as much of it as we can... - -# This is the stub to include in help generated by argparse -SHORT_USAGE = """ - {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... - -Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, -it will attempt to use a webserver both for obtaining and installing the -certificate. """.format(cli_command) - -# This section is used for --help and --help all ; it needs information -# about installed plugins to be fully formatted -COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: - -obtain, install, and renew certificates: - (default) run Obtain & install a certificate in your current webserver - certonly Obtain or renew a certificate, but do not install it - renew Renew all previously obtained certificates that are near expiry - enhance Add security enhancements to your existing configuration - -d DOMAINS Comma-separated list of domains to obtain a certificate for - - %s - --standalone Run a standalone webserver for authentication - %s - --webroot Place files in a server's webroot folder for authentication - --manual Obtain certificates interactively, or using shell script hooks - - -n Run non-interactively - --test-cert Obtain a test certificate from a staging server - --dry-run Test "renew" or "certonly" without saving any certificates to disk - -manage certificates: - certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate - -manage your account: - register Create an ACME account - unregister Deactivate an ACME account - update_account Update an ACME account - --agree-tos Agree to the ACME server's Subscriber Agreement - -m EMAIL Email address for important account notifications -""" - -# This is the short help for certbot --help, where we disable argparse -# altogether -HELP_AND_VERSION_USAGE = """ -More detailed help: - - -h, --help [TOPIC] print this message, or detailed help on a topic; - the available TOPICS are: - - all, automation, commands, paths, security, testing, or any of the - subcommands or plugins (certonly, renew, install, register, nginx, - apache, standalone, webroot, etc.) - -h all print a detailed help page including all topics - --version print the version number -""" - - -# These argparse parameters should be removed when detecting defaults. -ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) - - -# These sets are used when to help detect options set by the user. -EXIT_ACTIONS = set(("help", "version",)) - - -ZERO_ARG_ACTIONS = set(("store_const", "store_true", - "store_false", "append_const", "count",)) - - -# Maps a config option to a set of config options that may have modified it. -# This dictionary is used recursively, so if A modifies B and B modifies C, -# it is determined that C was modified by the user if A was modified. -VAR_MODIFIERS = {"account": set(("server",)), - "renew_hook": set(("deploy_hook",)), - "server": set(("dry_run", "staging",)), - "webroot_map": set(("webroot_path",))} - - -def report_config_interaction(modified, modifiers): - """Registers config option interaction to be checked by set_by_cli. - - This function can be called by during the __init__ or - add_parser_arguments methods of plugins to register interactions - between config options. - - :param modified: config options that can be modified by modifiers - :type modified: iterable or str (string_types) - :param modifiers: config options that modify modified - :type modifiers: iterable or str (string_types) - - """ - if isinstance(modified, six.string_types): - modified = (modified,) - if isinstance(modifiers, six.string_types): - modifiers = (modifiers,) - - for var in modified: - VAR_MODIFIERS.setdefault(var, set()).update(modifiers) - - -class _Default(object): - """A class to use as a default to detect if a value is set by a user""" - - def __bool__(self): - return False - - def __eq__(self, other): - return isinstance(other, _Default) - - def __hash__(self): - return id(_Default) - - def __nonzero__(self): - return self.__bool__() - - -def set_by_cli(var): - """ - Return True if a particular config variable has been set by the user - (CLI or config file) including if the user explicitly set it to the - default. Returns False if the variable was assigned a default value. - """ - detector = set_by_cli.detector # type: ignore - if detector is None and helpful_parser is not None: - # Setup on first run: `detector` is a weird version of config in which - # the default value of every attribute is wrangled to be boolean-false - plugins = plugins_disco.PluginsRegistry.find_all() - # reconstructed_args == sys.argv[1:], or whatever was passed to main() - reconstructed_args = helpful_parser.args + [helpful_parser.verb] - detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore - plugins, reconstructed_args, detect_defaults=True) - # propagate plugin requests: eg --standalone modifies config.authenticator - detector.authenticator, detector.installer = ( # type: ignore - plugin_selection.cli_plugin_requests(detector)) - - if not isinstance(getattr(detector, var), _Default): - logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) - return True - - for modifier in VAR_MODIFIERS.get(var, []): - if set_by_cli(modifier): - logger.debug("Var %s=%s (set by user).", - var, VAR_MODIFIERS.get(var, [])) - return True - - return False - -# static housekeeping var -# functions attributed are not supported by mypy -# https://github.com/python/mypy/issues/2087 -set_by_cli.detector = None # type: ignore - - -def has_default_value(option, value): - """Does option have the default value? - - If the default value of option is not known, False is returned. - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if option has the default value, otherwise, False - :rtype: bool - - """ - if helpful_parser is not None: - return (option in helpful_parser.defaults and - helpful_parser.defaults[option] == value) - return False - - -def option_was_set(option, value): - """Was option set by the user or does it differ from the default? - - :param str option: configuration variable being considered - :param value: value of the configuration variable named option - - :returns: True if the option was set, otherwise, False - :rtype: bool - - """ - return set_by_cli(option) or not has_default_value(option, value) - - -def argparse_type(variable): - """Return our argparse type function for a config variable (default: str)""" - # pylint: disable=protected-access - if helpful_parser is not None: - for action in helpful_parser.parser._actions: - if action.type is not None and action.dest == variable: - return action.type - return str - -def read_file(filename, mode="rb"): - """Returns the given file's contents. - - :param str filename: path to file - :param str mode: open mode (see `open`) - - :returns: absolute path of filename and its contents - :rtype: tuple - - :raises argparse.ArgumentTypeError: File does not exist or is not readable. - - """ - try: - filename = os.path.abspath(filename) - with open(filename, mode) as the_file: - contents = the_file.read() - return filename, contents - except IOError as exc: - raise argparse.ArgumentTypeError(exc.strerror) - - -def flag_default(name): - """Default value for CLI flag.""" - # XXX: this is an internal housekeeping notion of defaults before - # argparse has been set up; it is not accurate for all flags. Call it - # with caution. Plugin defaults are missing, and some things are using - # defaults defined in this file, not in constants.py :( - return copy.deepcopy(constants.CLI_DEFAULTS[name]) - - -def config_help(name, hidden=False): - """Extract the help message for an `.IConfig` attribute.""" - # pylint: disable=no-member - if hidden: - return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter - return field.__doc__ - - -class HelpfulArgumentGroup(object): - """Emulates an argparse group for use with HelpfulArgumentParser. - - This class is used in the add_group method of HelpfulArgumentParser. - Command line arguments can be added to the group, but help - suppression and default detection is applied by - HelpfulArgumentParser when necessary. - - """ - def __init__(self, helpful_arg_parser, topic): - self._parser = helpful_arg_parser - self._topic = topic - - def add_argument(self, *args, **kwargs): - """Add a new command line argument to the argument group.""" - self._parser.add(self._topic, *args, **kwargs) - -class CustomHelpFormatter(argparse.HelpFormatter): - """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. - - In particular we fix https://bugs.python.org/issue28742 - """ - - def _get_help_string(self, action): - helpstr = action.help - if '%(default)' not in action.help and '(default:' not in action.help: - if action.default != argparse.SUPPRESS: - defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - helpstr += ' (default: %(default)s)' - return helpstr - -# The attributes here are: -# short: a string that will be displayed by "certbot -h commands" -# opts: a string that heads the section of flags with which this command is documented, -# both for "certbot -h SUBCOMMAND" and "certbot -h all" -# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" -VERB_HELP = [ - ("run (default)", { - "short": "Obtain/renew a certificate, and install it", - "opts": "Options for obtaining & installing certificates", - "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), - "realname": "run" - }), - ("certonly", { - "short": "Obtain or renew a certificate, but do not install it", - "opts": "Options for modifying how a certificate is obtained", - "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" - "This command obtains a TLS/SSL certificate without installing it anywhere.") - }), - ("renew", { - "short": "Renew all certificates (or one specified with --cert-name)", - "opts": ("The 'renew' subcommand will attempt to renew all" - " certificates (or more precisely, certificate lineages) you have" - " previously obtained if they are close to expiry, and print a" - " summary of the results. By default, 'renew' will reuse the options" - " used to create obtain or most recently successfully renew each" - " certificate lineage. You can try it with `--dry-run` first. For" - " more fine-grained control, you can renew individual lineages with" - " the `certonly` subcommand. Hooks are available to run commands" - " before and after renewal; see" - " https://certbot.eff.org/docs/using.html#renewal for more" - " information on these."), - "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" - }), - ("certificates", { - "short": "List certificates managed by Certbot", - "opts": "List certificates managed by Certbot", - "usage": ("\n\n certbot certificates [options] ...\n\n" - "Print information about the status of certificates managed by Certbot.") - }), - ("delete", { - "short": "Clean up all files related to a certificate", - "opts": "Options for deleting a certificate", - "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" - }), - ("revoke", { - "short": "Revoke a certificate specified with --cert-path or --cert-name", - "opts": "Options for revocation of certificates", - "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " - "--cert-name example.com] [options]\n\n" - }), - ("register", { - "short": "Register for account with Let's Encrypt / other ACME server", - "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.", - "usage": "\n\n certbot unregister [options]\n\n" - }), - ("install", { - "short": "Install an arbitrary certificate in a server", - "opts": "Options for modifying how a certificate is deployed", - "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " - " --key-path /path/to/private-key [options]\n\n" - }), - ("rollback", { - "short": "Roll back server conf changes made during certificate installation", - "opts": "Options for rolling back server configuration changes", - "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" - }), - ("plugins", { - "short": "List plugins that are installed and available on your system", - "opts": 'Options for the "plugins" subcommand', - "usage": "\n\n certbot plugins [options]\n\n" - }), - ("update_symlinks", { - "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", - "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " - "or edited a renewal configuration file".format( - os.path.join(flag_default("config_dir"), "live"))), - "usage": "\n\n certbot update_symlinks [options]\n\n" - }), - ("enhance", { - "short": "Add security enhancements to your existing configuration", - "opts": ("Helps to harden the TLS configuration by adding security enhancements " - "to already existing configuration."), - "usage": "\n\n certbot enhance [options]\n\n" - }), - -] -# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful -VERB_HELP_MAP = dict(VERB_HELP) - - -class HelpfulArgumentParser(object): - """Argparse Wrapper. - - This class wraps argparse, adding the ability to make --help less - verbose, and request help on specific subcategories at a time, eg - 'certbot --help security' for security options. - - """ - - - def __init__(self, args, plugins, detect_defaults=False): - from certbot._internal import main - self.VERBS = { - "auth": main.certonly, - "certonly": main.certonly, - "run": main.run, - "install": main.install, - "plugins": main.plugins_cmd, - "register": main.register, - "update_account": main.update_account, - "unregister": main.unregister, - "renew": main.renew, - "revoke": main.revoke, - "rollback": main.rollback, - "everything": main.run, - "update_symlinks": main.update_symlinks, - "certificates": main.certificates, - "delete": main.delete, - "enhance": main.enhance, - } - - # Get notification function for printing - try: - self.notify = zope.component.getUtility( - interfaces.IDisplay).notification - except zope_interfaces.ComponentLookupError: - self.notify = display_util.NoninteractiveDisplay( - sys.stdout).notification - - - # List of topics for which additional help can be provided - HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] - HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] - - plugin_names = list(plugins) - self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore - - self.detect_defaults = detect_defaults - self.args = args - - if self.args and self.args[0] == 'help': - self.args[0] = '--help' - - self.determine_verb() - help1 = self.prescan_for_flag("-h", self.help_topics) - help2 = self.prescan_for_flag("--help", self.help_topics) - if isinstance(help1, bool) and isinstance(help2, bool): - self.help_arg = help1 or help2 - else: - self.help_arg = help1 if isinstance(help1, six.string_types) else help2 - - short_usage = self._usage_string(plugins, self.help_arg) - - self.visible_topics = self.determine_help_topics(self.help_arg) - - # elements are added by .add_group() - self.groups = {} # type: Dict[str, argparse._ArgumentGroup] - # elements are added by .parse_args() - self.defaults = {} # type: Dict[str, Any] - - self.parser = configargparse.ArgParser( - prog="certbot", - usage=short_usage, - formatter_class=CustomHelpFormatter, - args_for_setting_config_path=["-c", "--config"], - default_config_files=flag_default("config_files"), - config_arg_help_message="path to config file (default: {0})".format( - " and ".join(flag_default("config_files")))) - - # This is the only way to turn off overly verbose config flag documentation - self.parser._add_config_file_help = False # pylint: disable=protected-access - - # Help that are synonyms for --help subcommands - COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] - def _list_subcommands(self): - longest = max(len(v) for v in VERB_HELP_MAP) - - text = "The full list of available SUBCOMMANDS is:\n\n" - for verb, props in sorted(VERB_HELP): - doc = props.get("short", "") - text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) - - text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" - return text - - def _usage_string(self, plugins, help_arg): - """Make usage strings late so that plugins can be initialised late - - :param plugins: all discovered plugins - :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC - :rtype: str - :returns: a short usage string for the top of --help TOPIC) - """ - if "nginx" in plugins: - nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" - else: - nginx_doc = "(the certbot nginx plugin is not installed)" - if "apache" in plugins: - apache_doc = "--apache Use the Apache plugin for authentication & installation" - else: - apache_doc = "(the certbot apache plugin is not installed)" - - usage = SHORT_USAGE - if help_arg is True: - self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) - sys.exit(0) - elif help_arg in self.COMMANDS_TOPICS: - self.notify(usage + self._list_subcommands()) - sys.exit(0) - elif help_arg == "all": - # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at - # the top; if we're doing --help someothertopic, it's OT so it's not - usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) - else: - custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) - usage = custom if custom else usage - - return usage - - def remove_config_file_domains_for_renewal(self, parsed_args): - """Make "certbot renew" safe if domains are set in cli.ini.""" - # Works around https://github.com/certbot/certbot/issues/4096 - if self.verb == "renew": - for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access - if source.startswith("config_file") and "domains" in flags: - parsed_args.domains = _Default() if self.detect_defaults else [] - - def parse_args(self): - """Parses command line arguments and returns the result. - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - parsed_args = self.parser.parse_args(self.args) - parsed_args.func = self.VERBS[self.verb] - parsed_args.verb = self.verb - - self.remove_config_file_domains_for_renewal(parsed_args) - - if self.detect_defaults: - return parsed_args - - self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) - for key in vars(parsed_args)) - - # Do any post-parsing homework here - - if self.verb == "renew": - if parsed_args.force_interactive: - raise errors.Error( - "{0} cannot be used with renew".format( - constants.FORCE_INTERACTIVE_FLAG)) - parsed_args.noninteractive_mode = True - - if parsed_args.force_interactive and parsed_args.noninteractive_mode: - raise errors.Error( - "Flag for non-interactive mode and {0} conflict".format( - constants.FORCE_INTERACTIVE_FLAG)) - - if parsed_args.staging or parsed_args.dry_run: - self.set_test_server(parsed_args) - - if parsed_args.csr: - self.handle_csr(parsed_args) - - if parsed_args.must_staple: - parsed_args.staple = True - - if parsed_args.validate_hooks: - hooks.validate_hooks(parsed_args) - - if parsed_args.allow_subset_of_names: - if any(util.is_wildcard_domain(d) for d in parsed_args.domains): - raise errors.Error("Using --allow-subset-of-names with a" - " wildcard domain is not supported.") - - if parsed_args.hsts and parsed_args.auto_hsts: - raise errors.Error( - "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - - return parsed_args - - def set_test_server(self, parsed_args): - """We have --staging/--dry-run; perform sanity check and set config.server""" - - # Flag combinations should produce these results: - # | --staging | --dry-run | - # ------------------------------------------------------------ - # | --server acme-v02 | Use staging | Use staging | - # | --server acme-staging-v02 | Use staging | Use staging | - # | --server | Conflict error | Use | - - default_servers = (flag_default("server"), constants.STAGING_URI) - - if parsed_args.staging and parsed_args.server not in default_servers: - raise errors.Error("--server value conflicts with --staging") - - if parsed_args.server in default_servers: - parsed_args.server = constants.STAGING_URI - - if parsed_args.dry_run: - if self.verb not in ["certonly", "renew"]: - raise errors.Error("--dry-run currently only works with the " - "'certonly' or 'renew' subcommands (%r)" % self.verb) - parsed_args.break_my_certs = parsed_args.staging = True - if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): - # The user has a prod account, but might not have a staging - # one; we don't want to start trying to perform interactive registration - parsed_args.tos = True - parsed_args.register_unsafely_without_email = True - - def handle_csr(self, parsed_args): - """Process a --csr flag.""" - if parsed_args.verb != "certonly": - raise errors.Error("Currently, a CSR file may only be specified " - "when obtaining a new or replacement " - "via the certonly command. Please try the " - "certonly command instead.") - if parsed_args.allow_subset_of_names: - raise errors.Error("--allow-subset-of-names cannot be used with --csr") - - csrfile, contents = parsed_args.csr[0:2] - typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) - - # This is not necessary for webroot to work, however, - # obtain_certificate_from_csr requires parsed_args.domains to be set - for domain in domains: - add_domains(parsed_args, domain) - - if not domains: - # TODO: add CN to domains instead: - raise errors.Error( - "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" - % parsed_args.csr[0]) - - parsed_args.actual_csr = (csr, typ) - - csr_domains = set([d.lower() for d in domains]) - config_domains = set(parsed_args.domains) - if csr_domains != config_domains: - raise errors.ConfigurationError( - "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" - .format(", ".join(csr_domains), ", ".join(config_domains))) - - - def determine_verb(self): - """Determines the verb/subcommand provided by the user. - - This function works around some of the limitations of argparse. - - """ - if "-h" in self.args or "--help" in self.args: - # all verbs double as help arguments; don't get them confused - self.verb = "help" - return - - for i, token in enumerate(self.args): - if token in self.VERBS: - verb = token - if verb == "auth": - verb = "certonly" - if verb == "everything": - verb = "run" - self.verb = verb - self.args.pop(i) - return - - self.verb = "run" - - def prescan_for_flag(self, flag, possible_arguments): - """Checks cli input for flags. - - Check for a flag, which accepts a fixed set of possible arguments, in - the command line; we will use this information to configure argparse's - help correctly. Return the flag's argument, if it has one that matches - the sequence @possible_arguments; otherwise return whether the flag is - present. - - """ - if flag not in self.args: - return False - pos = self.args.index(flag) - try: - nxt = self.args[pos + 1] - if nxt in possible_arguments: - return nxt - except IndexError: - pass - return True - - def add(self, topics, *args, **kwargs): - """Add a new command line argument. - - :param topics: str or [str] help topic(s) this should be listed under, - or None for options that don't fit under a specific - topic which will only be shown in "--help all" output. - The first entry determines where the flag lives in the - "--help all" output (None -> "optional arguments"). - :param list *args: the names of this argument flag - :param dict **kwargs: various argparse settings for this argument - - """ - - if isinstance(topics, list): - # if this flag can be listed in multiple sections, try to pick the one - # that the user has asked for help about - topic = self.help_arg if self.help_arg in topics else topics[0] - else: - topic = topics # there's only one - - if self.detect_defaults: - kwargs = self.modify_kwargs_for_default_detection(**kwargs) - - if self.visible_topics[topic]: - if topic in self.groups: - group = self.groups[topic] - group.add_argument(*args, **kwargs) - else: - self.parser.add_argument(*args, **kwargs) - else: - kwargs["help"] = argparse.SUPPRESS - self.parser.add_argument(*args, **kwargs) - - def modify_kwargs_for_default_detection(self, **kwargs): - """Modify an arg so we can check if it was set by the user. - - Changes the parameters given to argparse when adding an argument - so we can properly detect if the value was set by the user. - - :param dict kwargs: various argparse settings for this argument - - :returns: a modified versions of kwargs - :rtype: dict - - """ - action = kwargs.get("action", None) - if action not in EXIT_ACTIONS: - kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else - "store") - kwargs["default"] = _Default() - for param in ARGPARSE_PARAMS_TO_REMOVE: - kwargs.pop(param, None) - - return kwargs - - def add_deprecated_argument(self, argument_name, num_args): - """Adds a deprecated argument with the name argument_name. - - Deprecated arguments are not shown in the help. If they are used - on the command line, a warning is shown stating that the - argument is deprecated and no other action is taken. - - :param str argument_name: Name of deprecated argument. - :param int nargs: Number of arguments the option takes. - - """ - util.add_deprecated_argument( - self.parser.add_argument, argument_name, num_args) - - def add_group(self, topic, verbs=(), **kwargs): - """Create a new argument group. - - This method must be called once for every topic, however, calls - to this function are left next to the argument definitions for - clarity. - - :param str topic: Name of the new argument group. - :param str verbs: List of subcommands that should be documented as part of - this help group / topic - - :returns: The new argument group. - :rtype: `HelpfulArgumentGroup` - - """ - if self.visible_topics[topic]: - self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) - if self.help_arg: - for v in verbs: - self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) - return HelpfulArgumentGroup(self, topic) - - def add_plugin_args(self, plugins): - """ - - Let each of the plugins add its own command line arguments, which - may or may not be displayed as help topics. - - """ - for name, plugin_ep in six.iteritems(plugins): - parser_or_group = self.add_group(name, - description=plugin_ep.long_description) - plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) - - def determine_help_topics(self, chosen_topic): - """ - - The user may have requested help on a topic, return a dict of which - topics to display. @chosen_topic has prescan_for_flag's return type - - :returns: dict - - """ - # topics maps each topic to whether it should be documented by - # argparse on the command line - if chosen_topic == "auth": - chosen_topic = "certonly" - if chosen_topic == "everything": - chosen_topic = "run" - if chosen_topic == "all": - # 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]) - return dict([(t, t == chosen_topic) for t in self.help_topics]) - -def _add_all_groups(helpful): - helpful.add_group("automation", description="Flags for automating execution & other tweaks") - helpful.add_group("security", description="Security parameters & server settings") - helpful.add_group("testing", - description="The following flags are meant for testing and integration purposes only.") - helpful.add_group("paths", description="Flags for changing execution paths & servers") - helpful.add_group("manage", - description="Various subcommands and flags are available for managing your certificates:", - verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) - - # VERBS - for verb, docs in VERB_HELP: - name = docs.get("realname", verb) - helpful.add_group(name, description=docs["opts"]) - - -def prepare_and_parse_args(plugins, args, detect_defaults=False): - """Returns parsed command line arguments. - - :param .PluginsRegistry plugins: available plugins - :param list args: command line arguments with the program name removed - - :returns: parsed command line arguments - :rtype: argparse.Namespace - - """ - - - helpful = HelpfulArgumentParser(args, plugins, detect_defaults) - _add_all_groups(helpful) - - # --help is automatically provided by argparse - helpful.add( - None, "-v", "--verbose", dest="verbose_count", action="count", - default=flag_default("verbose_count"), help="This flag can be used " - "multiple times to incrementally increase the verbosity of output, " - "e.g. -vvv.") - helpful.add( - None, "-t", "--text", dest="text_mode", action="store_true", - default=flag_default("text_mode"), help=argparse.SUPPRESS) - helpful.add( - None, "--max-log-backups", type=nonnegative_int, - default=flag_default("max_log_backups"), - help="Specifies the maximum number of backup logs that should " - "be kept by Certbot's built in log rotation. Setting this " - "flag to 0 disables log rotation entirely, causing " - "Certbot to always append to the same log file.") - helpful.add( - [None, "automation", "run", "certonly", "enhance"], - "-n", "--non-interactive", "--noninteractive", - dest="noninteractive_mode", action="store_true", - default=flag_default("noninteractive_mode"), - help="Run without ever asking for user input. This may require " - "additional command line flags; the client will try to explain " - "which ones are required if it finds one missing") - helpful.add( - [None, "register", "run", "certonly", "enhance"], - constants.FORCE_INTERACTIVE_FLAG, action="store_true", - default=flag_default("force_interactive"), - help="Force Certbot to be interactive even if it detects it's not " - "being run in a terminal. This flag cannot be used with the " - "renew subcommand.") - helpful.add( - [None, "run", "certonly", "certificates", "enhance"], - "-d", "--domains", "--domain", dest="domains", - metavar="DOMAIN", action=_DomainsAction, - default=flag_default("domains"), - help="Domain names to apply. For multiple domains you can use " - "multiple -d flags or enter a comma separated list of domains " - "as a parameter. The first domain provided will be the " - "subject CN of the certificate, and all domains will be " - "Subject Alternative Names on the certificate. " - "The first domain will also be used in " - "some software user interfaces and as the file paths for the " - "certificate and related material unless otherwise " - "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", - metavar="CERTNAME", default=flag_default("certname"), - help="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 certificate names, run 'certbot certificates'. " - "When creating a new certificate, specifies the new certificate's name. " - "(default: the first provided domain or the name of an existing " - "certificate on your system for the same domains)") - helpful.add( - [None, "testing", "renew", "certonly"], - "--dry-run", action="store_true", dest="dry_run", - default=flag_default("dry_run"), - help="Perform a test run of the client, obtaining test (invalid) certificates" - " but not saving them to disk. This can currently only be used" - " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" - " tries to avoid making any persistent changes on a system, it " - " is not completely side-effect free: if used with webserver authenticator plugins" - " like apache and nginx, it makes and then reverts temporary config changes" - " in order to obtain test certificates, and reloads webservers to deploy and then" - " roll back those changes. It also calls --pre-hook and --post-hook commands" - " if they are defined because they may be necessary to accurately simulate" - " renewal. --deploy-hook commands are not called.") - helpful.add( - ["register", "automation"], "--register-unsafely-without-email", action="store_true", - default=flag_default("register_unsafely_without_email"), - help="Specifying this flag enables registering an account with no " - "email address. This is strongly discouraged, because in the " - "event of key loss or account compromise you will irrevocably " - "lose access to your account. You will also be unable to receive " - "notice about impending expiration or revocation of your " - "certificates. Updates to the Subscriber Agreement will still " - "affect you, and will be effective 14 days after posting an " - "update to the web site.") - helpful.add( - ["register", "update_account", "unregister", "automation"], "-m", "--email", - default=flag_default("email"), - help=config_help("email")) - 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", "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"], - "--keep-until-expiring", "--keep", "--reinstall", - dest="reinstall", action="store_true", default=flag_default("reinstall"), - help="If the requested certificate matches an existing certificate, always keep the " - "existing one until it is due for renewal (for the " - "'run' subcommand this means reinstall the existing certificate). (default: Ask)") - helpful.add( - "automation", "--expand", action="store_true", default=flag_default("expand"), - help="If an existing certificate is a strict subset of the requested names, " - "always expand and replace it with the additional names. (default: Ask)") - helpful.add( - "automation", "--version", action="version", - version="%(prog)s {0}".format(certbot.__version__), - help="show program's version number and exit") - helpful.add( - ["automation", "renew"], - "--force-renewal", "--renew-by-default", dest="renew_by_default", - action="store_true", default=flag_default("renew_by_default"), - help="If a certificate " - "already exists for the requested domains, renew it now, " - "regardless of whether it is near expiry. (Often " - "--keep-until-expiring is more appropriate). Also implies " - "--expand.") - helpful.add( - "automation", "--renew-with-new-domains", dest="renew_with_new_domains", - action="store_true", default=flag_default("renew_with_new_domains"), - help="If a " - "certificate already exists for the requested certificate name " - "but does not match the requested domains, renew it now, " - "regardless of whether it is near expiry.") - helpful.add( - "automation", "--reuse-key", dest="reuse_key", - action="store_true", default=flag_default("reuse_key"), - help="When renewing, use the same private key as the existing " - "certificate.") - - helpful.add( - ["automation", "renew", "certonly"], - "--allow-subset-of-names", action="store_true", - default=flag_default("allow_subset_of_names"), - help="When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This may be useful for allowing renewals for " - "multiple domains to succeed even if some domains no longer point " - "at this system. This option cannot be used with --csr.") - helpful.add( - "automation", "--agree-tos", dest="tos", action="store_true", - default=flag_default("tos"), - help="Agree to the ACME Subscriber Agreement (default: Ask)") - helpful.add( - ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", - default=flag_default("account"), - help="Account ID to use") - helpful.add( - "automation", "--duplicate", dest="duplicate", action="store_true", - default=flag_default("duplicate"), - help="Allow making a certificate lineage that duplicates an existing one " - "(both can be renewed in parallel)") - helpful.add( - "automation", "--os-packages-only", action="store_true", - default=flag_default("os_packages_only"), - help="(certbot-auto only) install OS package dependencies and then stop") - helpful.add( - "automation", "--no-self-upgrade", action="store_true", - default=flag_default("no_self_upgrade"), - help="(certbot-auto only) prevent the certbot-auto script from" - " upgrading itself to newer released versions (default: Upgrade" - " automatically)") - helpful.add( - "automation", "--no-bootstrap", action="store_true", - default=flag_default("no_bootstrap"), - help="(certbot-auto only) prevent the certbot-auto script from" - " installing OS-level dependencies (default: Prompt to install " - " OS-wide dependencies, but exit if the user says 'No')") - helpful.add( - "automation", "--no-permissions-check", action="store_true", - default=flag_default("no_permissions_check"), - help="(certbot-auto only) skip the check on the file system" - " permissions of the certbot-auto script") - helpful.add( - ["automation", "renew", "certonly", "run"], - "-q", "--quiet", dest="quiet", action="store_true", - default=flag_default("quiet"), - help="Silence all output except errors. Useful for automation via cron." - " Implies --non-interactive.") - # overwrites server, handled in HelpfulArgumentParser.parse_args() - helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", - dest="staging", action="store_true", default=flag_default("staging"), - help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" - " to --server " + constants.STAGING_URI) - helpful.add( - "testing", "--debug", action="store_true", default=flag_default("debug"), - help="Show tracebacks in case of errors, and allow certbot-auto " - "execution on experimental platforms") - helpful.add( - [None, "certonly", "run"], "--debug-challenges", action="store_true", - default=flag_default("debug_challenges"), - help="After setting up challenges, wait for user input before " - "submitting to CA") - helpful.add( - "testing", "--no-verify-ssl", action="store_true", - help=config_help("no_verify_ssl"), - default=flag_default("no_verify_ssl")) - helpful.add( - ["testing", "standalone", "manual"], "--http-01-port", type=int, - dest="http01_port", - default=flag_default("http01_port"), help=config_help("http01_port")) - helpful.add( - ["testing", "standalone"], "--http-01-address", - dest="http01_address", - default=flag_default("http01_address"), help=config_help("http01_address")) - helpful.add( - ["testing", "nginx"], "--https-port", type=int, - default=flag_default("https_port"), - help=config_help("https_port")) - helpful.add( - "testing", "--break-my-certs", action="store_true", - default=flag_default("break_my_certs"), - help="Be willing to replace or renew valid certificates with invalid " - "(testing/staging) certificates") - helpful.add( - "security", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) - helpful.add( - "security", "--must-staple", action="store_true", - dest="must_staple", default=flag_default("must_staple"), - help=config_help("must_staple")) - helpful.add( - ["security", "enhance"], - "--redirect", action="store_true", dest="redirect", - default=flag_default("redirect"), - help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - "security", "--no-redirect", action="store_false", dest="redirect", - default=flag_default("redirect"), - help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost. (default: Ask)") - helpful.add( - ["security", "enhance"], - "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), - help="Add the Strict-Transport-Security header to every HTTP response." - " Forcing browser to always use SSL for the domain." - " Defends against SSL Stripping.") - helpful.add( - "security", "--no-hsts", action="store_false", dest="hsts", - default=flag_default("hsts"), help=argparse.SUPPRESS) - helpful.add( - ["security", "enhance"], - "--uir", action="store_true", dest="uir", default=flag_default("uir"), - help='Add the "Content-Security-Policy: upgrade-insecure-requests"' - ' header to every HTTP response. Forcing the browser to use' - ' https:// for every http:// resource.') - helpful.add( - "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), - help=argparse.SUPPRESS) - helpful.add( - "security", "--staple-ocsp", action="store_true", dest="staple", - default=flag_default("staple"), - help="Enables OCSP Stapling. A valid OCSP response is stapled to" - " the certificate that the server offers during TLS.") - helpful.add( - "security", "--no-staple-ocsp", action="store_false", dest="staple", - default=flag_default("staple"), help=argparse.SUPPRESS) - helpful.add( - "security", "--strict-permissions", action="store_true", - default=flag_default("strict_permissions"), - help="Require that all configuration files are owned by the current " - "user; only needed if your config is somewhere unsafe like /tmp/") - helpful.add( - ["manual", "standalone", "certonly", "renew"], - "--preferred-challenges", dest="pref_challs", - action=_PrefChallAction, default=flag_default("pref_challs"), - help='A sorted, comma delimited list of the preferred challenge to ' - 'use during authorization with the most preferred challenge ' - 'listed first (Eg, "dns" or "http,dns"). ' - 'Not all plugins support all challenges. See ' - 'https://certbot.eff.org/docs/using.html#plugins for details. ' - 'ACME Challenges are versioned, but if you pick "http" rather ' - 'than "http-01", Certbot will select the latest version ' - 'automatically.') - helpful.add( - "renew", "--pre-hook", - help="Command to be run in a shell before obtaining any certificates." - " Intended primarily for renewal, where it can be used to temporarily" - " shut down a webserver that might conflict with the standalone" - " plugin. This will only be called if a certificate is actually to be" - " obtained/renewed. When renewing several certificates that have" - " identical pre-hooks, only the first will be executed.") - helpful.add( - "renew", "--post-hook", - help="Command to be run in a shell after attempting to obtain/renew" - " certificates. Can be used to deploy renewed certificates, or to" - " restart any servers that were stopped by --pre-hook. This is only" - " run if an attempt was made to obtain/renew a certificate. If" - " multiple renewed certificates have identical post-hooks, only" - " 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' - ' issued certificate. For this command, the shell variable' - ' $RENEWED_LINEAGE will point to the config live subdirectory' - ' (for example, "/etc/letsencrypt/live/example.com") containing' - ' the new certificates and keys; the shell variable' - ' $RENEWED_DOMAINS will contain a space-delimited list of' - ' renewed certificate domains (for example, "example.com' - ' www.example.com"') - helpful.add( - "renew", "--disable-hook-validation", - action="store_false", dest="validate_hooks", - default=flag_default("validate_hooks"), - help="Ordinarily the commands specified for" - " --pre-hook/--post-hook/--deploy-hook will be checked for" - " validity, to see if the programs being run are in the $PATH," - " so that mistakes can be caught early, even when the hooks" - " aren't being run just yet. The validation is rather" - " simplistic and fails if you use more advanced shell" - " constructs, so you can use this switch to disable it." - " (default: False)") - helpful.add( - "renew", "--no-directory-hooks", action="store_false", - default=flag_default("directory_hooks"), dest="directory_hooks", - help="Disable running executables found in Certbot's hook directories" - " during renewal. (default: False)") - helpful.add( - "renew", "--disable-renew-updates", action="store_true", - default=flag_default("disable_renew_updates"), dest="disable_renew_updates", - help="Disable automatic updates to your server configuration that" - " would otherwise be done by the selected installer plugin, and triggered" - " when the user executes \"certbot renew\", regardless of if the certificate" - " is renewed. This setting does not apply to important TLS configuration" - " updates.") - helpful.add( - "renew", "--no-autorenew", action="store_false", - default=flag_default("autorenew"), dest="autorenew", - help="Disable auto renewal of certificates.") - - # Populate the command line parameters for new style enhancements - enhancements.populate_cli(helpful.add) - - _create_subparsers(helpful) - _paths_parser(helpful) - # _plugins_parsing should be the last thing to act upon the main - # parser (--help should display plugin-specific options last) - _plugins_parsing(helpful, plugins) - - if not detect_defaults: - global helpful_parser # pylint: disable=global-statement - helpful_parser = helpful - return helpful.parse_args() - - -def _create_subparsers(helpful): - from certbot._internal.client import sample_user_agent # avoid import loops - helpful.add( - None, "--user-agent", default=flag_default("user_agent"), - help='Set a custom user agent string for the client. User agent strings allow ' - 'the CA to collect high level statistics about success rates by OS, ' - 'plugin and use 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: {0}). The flags encoded in the user agent are: ' - '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' - 'whether any hooks are set.'.format(sample_user_agent())) - helpful.add( - None, "--user-agent-comment", default=flag_default("user_agent_comment"), - type=_user_agent_comment_type, - help="Add a comment to the default user agent string. May be used when repackaging Certbot " - "or calling it from another tool to allow additional statistical data to be collected." - " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") - helpful.add("certonly", - "--csr", default=flag_default("csr"), type=read_file, - help="Path to a Certificate Signing Request (CSR) in DER or PEM format." - " Currently --csr only works with the 'certonly' subcommand.") - helpful.add("revoke", - "--reason", dest="reason", - choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, - key=constants.REVOCATION_REASONS.get)), - action=_EncodeReasonAction, default=flag_default("reason"), - help="Specify reason for revoking certificate. (default: unspecified)") - helpful.add("revoke", - "--delete-after-revoke", action="store_true", - default=flag_default("delete_after_revoke"), - help="Delete certificates after revoking them, along with all previous and later " - "versions of those certificates.") - helpful.add("revoke", - "--no-delete-after-revoke", action="store_false", - dest="delete_after_revoke", - default=flag_default("delete_after_revoke"), - help="Do not delete certificates after revoking them. This " - "option should be used with caution because the 'renew' " - "subcommand will attempt to renew undeleted revoked " - "certificates.") - helpful.add("rollback", - "--checkpoints", type=int, metavar="N", - default=flag_default("rollback_checkpoints"), - help="Revert configuration N number of checkpoints.") - helpful.add("plugins", - "--init", action="store_true", default=flag_default("init"), - help="Initialize plugins.") - helpful.add("plugins", - "--prepare", action="store_true", default=flag_default("prepare"), - help="Initialize and prepare plugins.") - helpful.add("plugins", - "--authenticators", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") - helpful.add("plugins", - "--installers", action="append_const", dest="ifaces", - default=flag_default("ifaces"), - const=interfaces.IInstaller, help="Limit to installer plugins only.") - - -class CaseInsensitiveList(list): - """A list that will ignore case when searching. - - This class is passed to the `choices` argument of `argparse.add_arguments` - through the `helpful` wrapper. It is necessary due to special handling of - command line arguments by `set_by_cli` in which the `type_func` is not applied.""" - def __contains__(self, element): - return super(CaseInsensitiveList, self).__contains__(element.lower()) - - -def _paths_parser(helpful): - add = helpful.add - verb = helpful.verb - if verb == "help": - verb = helpful.help_arg - - cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." - sections = ["paths", "install", "revoke", "certonly", "manage"] - if verb == "certonly": - add(sections, "--cert-path", type=os.path.abspath, - default=flag_default("auth_cert_path"), help=cph) - elif verb == "revoke": - add(sections, "--cert-path", type=read_file, required=False, help=cph) - else: - add(sections, "--cert-path", type=os.path.abspath, help=cph) - - section = "paths" - if verb in ("install", "revoke"): - section = verb - # revoke --key-path reads a file, install --key-path takes a string - add(section, "--key-path", - type=((verb == "revoke" and read_file) or os.path.abspath), - help="Path to private key for certificate installation " - "or revocation (if account key is missing)") - - default_cp = None - if verb == "certonly": - default_cp = flag_default("auth_chain_path") - add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a full certificate chain (certificate plus chain).") - add("paths", "--chain-path", default=default_cp, type=os.path.abspath, - help="Accompanying path to a certificate chain.") - add("paths", "--config-dir", default=flag_default("config_dir"), - help=config_help("config_dir")) - add("paths", "--work-dir", default=flag_default("work_dir"), - help=config_help("work_dir")) - add("paths", "--logs-dir", default=flag_default("logs_dir"), - help="Logs directory.") - add("paths", "--server", default=flag_default("server"), - help=config_help("server")) - - -def _plugins_parsing(helpful, plugins): - # It's nuts, but there are two "plugins" topics. Somehow this works - helpful.add_group( - "plugins", description="Plugin Selection: Certbot client supports an " - "extensible plugins architecture. See '%(prog)s plugins' for a " - "list of all installed plugins and their names. You can force " - "a particular plugin by setting options provided below. Running " - "--help will list flags specific to that plugin.") - - helpful.add("plugins", "--configurator", default=flag_default("configurator"), - help="Name of the plugin that is both an authenticator and an installer." - " Should not be used together with --authenticator or --installer. " - "(default: Ask)") - helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), - help="Authenticator plugin name.") - helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), - help="Installer plugin name (also used to find domains).") - helpful.add(["plugins", "certonly", "run", "install"], - "--apache", action="store_true", default=flag_default("apache"), - help="Obtain and install certificates using Apache") - helpful.add(["plugins", "certonly", "run", "install"], - "--nginx", action="store_true", default=flag_default("nginx"), - help="Obtain and install certificates using Nginx") - helpful.add(["plugins", "certonly"], "--standalone", action="store_true", - default=flag_default("standalone"), - help='Obtain certificates using a "standalone" webserver.') - helpful.add(["plugins", "certonly"], "--manual", action="store_true", - default=flag_default("manual"), - help="Provide laborious manual instructions for obtaining a certificate") - helpful.add(["plugins", "certonly"], "--webroot", action="store_true", - default=flag_default("webroot"), - help="Obtain certificates by placing files in a webroot directory.") - helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", - default=flag_default("dns_cloudflare"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Cloudflare for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", - default=flag_default("dns_cloudxns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using CloudXNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", - default=flag_default("dns_digitalocean"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DigitalOcean for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", - default=flag_default("dns_dnsimple"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNSimple for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", - default=flag_default("dns_dnsmadeeasy"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using DNS Made Easy for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", - default=flag_default("dns_gehirn"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastracture Service for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", - default=flag_default("dns_google"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Google Cloud DNS).")) - helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", - default=flag_default("dns_linode"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using Linode for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", - default=flag_default("dns_luadns"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using LuaDNS for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", - default=flag_default("dns_nsone"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using NS1 for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", - default=flag_default("dns_ovh"), - help=("Obtain certificates using a DNS TXT record (if you are " - "using OVH for DNS).")) - helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", - default=flag_default("dns_rfc2136"), - help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") - helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", - default=flag_default("dns_route53"), - help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " - "DNS).")) - helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", - default=flag_default("dns_sakuracloud"), - help=("Obtain certificates using a DNS TXT record " - "(if you are using Sakura Cloud for DNS).")) - - # things should not be reorder past/pre this comment: - # plugins_group should be displayed in --help before plugin - # specific groups (so that plugins_group.description makes sense) - - helpful.add_plugin_args(plugins) - - -class _EncodeReasonAction(argparse.Action): - """Action class for parsing revocation reason.""" - - def __call__(self, parser, namespace, reason, option_string=None): - """Encodes the reason for certificate revocation.""" - code = constants.REVOCATION_REASONS[reason.lower()] - setattr(namespace, self.dest, code) - - -class _DomainsAction(argparse.Action): - """Action class for parsing domains.""" - - def __call__(self, parser, namespace, domain, option_string=None): - """Just wrap add_domains in argparseese.""" - add_domains(namespace, domain) - -def add_domains(args_or_config, domains): - """Registers new domains to be used during the current client run. - - Domains are not added to the list of requested domains if they have - already been registered. - - :param args_or_config: parsed command line arguments - :type args_or_config: argparse.Namespace or - configuration.NamespaceConfig - :param str domain: one or more comma separated domains - - :returns: domains after they have been normalized and validated - :rtype: `list` of `str` - - """ - validated_domains = [] - for domain in domains.split(","): - domain = util.enforce_domain_sanity(domain.strip()) - validated_domains.append(domain) - if domain not in args_or_config.domains: - args_or_config.domains.append(domain) - - return validated_domains - -class _PrefChallAction(argparse.Action): - """Action class for parsing preferred challenges.""" - - def __call__(self, parser, namespace, pref_challs, option_string=None): - try: - challs = parse_preferred_challenges(pref_challs.split(",")) - except errors.Error as error: - raise argparse.ArgumentError(self, str(error)) - namespace.pref_challs.extend(challs) - - -def parse_preferred_challenges(pref_challs): - """Translate and validate preferred challenges. - - :param pref_challs: list of preferred challenge types - :type pref_challs: `list` of `str` - - :returns: validated list of preferred challenge types - :rtype: `list` of `str` - - :raises errors.Error: if pref_challs is invalid - - """ - aliases = {"dns": "dns-01", "http": "http-01"} - challs = [c.strip() for c in pref_challs] - challs = [aliases.get(c, c) for c in challs] - - unrecognized = ", ".join(name for name in challs - if name not in challenges.Challenge.TYPES) - if unrecognized: - raise errors.Error( - "Unrecognized challenges: {0}".format(unrecognized)) - return challs - - -def _user_agent_comment_type(value): - if "(" in value or ")" in value: - raise argparse.ArgumentTypeError("may not contain parentheses") - return value - - -class _DeployHookAction(argparse.Action): - """Action class for parsing deploy hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - renew_hook_set = namespace.deploy_hook != namespace.renew_hook - if renew_hook_set and namespace.renew_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --renew-hook value") - namespace.deploy_hook = namespace.renew_hook = values - - -class _RenewHookAction(argparse.Action): - """Action class for parsing renew hooks.""" - - def __call__(self, parser, namespace, values, option_string=None): - deploy_hook_set = namespace.deploy_hook is not None - if deploy_hook_set and namespace.deploy_hook != values: - raise argparse.ArgumentError( - self, "conflicts with --deploy-hook value") - namespace.renew_hook = values - - -def nonnegative_int(value): - """Converts value to an int and checks that it is not negative. - - This function should used as the type parameter for argparse - arguments. - - :param str value: value provided on the command line - - :returns: integer representation of value - :rtype: int - - :raises argparse.ArgumentTypeError: if value isn't a non-negative integer - - """ - try: - int_value = int(value) - except ValueError: - raise argparse.ArgumentTypeError("value must be an integer") - - if int_value < 0: - raise argparse.ArgumentTypeError("value must be non-negative") - return int_value diff --git a/certbot/_internal/client.py b/certbot/_internal/client.py deleted file mode 100644 index 2a9a52e73..000000000 --- a/certbot/_internal/client.py +++ /dev/null @@ -1,742 +0,0 @@ -"""Certbot client API.""" -import datetime -import logging -import platform - -import OpenSSL -import josepy as jose -import zope.component -from cryptography.hazmat.backends import default_backend -# https://github.com/python/typeshed/blob/master/third_party/ -# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi -from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore - -from acme import client as acme_client -from acme import crypto_util as acme_crypto_util -from acme import errors as acme_errors -from acme import messages -from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module - -import certbot -from certbot._internal import account -from certbot._internal import auth_handler -from certbot._internal import cli -from certbot._internal import constants -from certbot import crypto_util -from certbot._internal import eff -from certbot._internal import error_handler -from certbot import errors -from certbot import interfaces -from certbot._internal import storage -from certbot import util -from certbot.compat import os -from certbot._internal.display import enhancements -from certbot.display import ops as display_ops -from certbot._internal.plugins import selection as plugin_selection - -logger = logging.getLogger(__name__) - - -def acme_from_config_key(config, key, regr=None): - "Wrangle ACME client construction" - # TODO: Allow for other alg types besides RS256 - net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl), - user_agent=determine_user_agent(config)) - return acme_client.BackwardsCompatibleClientV2(net, key, config.server) - - -def determine_user_agent(config): - """ - Set a user_agent string in the config based on the choice of plugins. - (this wasn't knowable at construction time) - - :returns: the client's User-Agent string - :rtype: `str` - """ - - # WARNING: To ensure changes are in line with Certbot's privacy - # policy, talk to a core Certbot team member before making any - # changes here. - if config.user_agent is None: - ua = ("CertbotACMEClient/{0} ({1}; {2}{8}) Authenticator/{3} Installer/{4} " - "({5}; flags: {6}) Py/{7}") - if os.environ.get("CERTBOT_DOCS") == "1": - cli_command = "certbot(-auto)" - os_info = "OS_NAME OS_VERSION" - python_version = "major.minor.patchlevel" - else: - cli_command = cli.cli_command - os_info = util.get_os_info_ua() - python_version = platform.python_version() - ua = ua.format(certbot.__version__, cli_command, os_info, - config.authenticator, config.installer, config.verb, - ua_flags(config), python_version, - "; " + config.user_agent_comment if config.user_agent_comment else "") - else: - ua = config.user_agent - return ua - -def ua_flags(config): - "Turn some very important CLI flags into clues in the user agent." - if isinstance(config, DummyConfig): - return "FLAGS" - flags = [] - if config.duplicate: - flags.append("dup") - if config.renew_by_default: - flags.append("frn") - if config.allow_subset_of_names: - flags.append("asn") - if config.noninteractive_mode: - flags.append("n") - hook_names = ("pre", "post", "renew", "manual_auth", "manual_cleanup") - hooks = [getattr(config, h + "_hook") for h in hook_names] - if any(hooks): - flags.append("hook") - return " ".join(flags) - -class DummyConfig(object): - "Shim for computing a sample user agent." - def __init__(self): - self.authenticator = "XXX" - self.installer = "YYY" - self.user_agent = None - self.verb = "SUBCOMMAND" - - def __getattr__(self, name): - "Any config properties we might have are None." - return None - -def sample_user_agent(): - "Document what this Certbot's user agent string will be like." - - return determine_user_agent(DummyConfig()) - - -def register(config, account_storage, tos_cb=None): - """Register new account with an ACME CA. - - This function takes care of generating fresh private key, - registering the account, optionally accepting CA Terms of Service - and finally saving the account. It should be called prior to - initialization of `Client`, unless account has already been created. - - :param .IConfig config: Client configuration. - - :param .AccountStorage account_storage: Account storage where newly - registered account will be saved to. Save happens only after TOS - acceptance step, so any account private keys or - `.RegistrationResource` will not be persisted if `tos_cb` - returns ``False``. - - :param tos_cb: If ACME CA requires the user to accept a Terms of - Service before registering account, client action is - necessary. For example, a CLI tool would prompt the user - acceptance. `tos_cb` must be a callable that should accept - `.RegistrationResource` and return a `bool`: ``True`` iff the - Terms of Service present in the contained - `.Registration.terms_of_service` is accepted by the client, and - ``False`` otherwise. ``tos_cb`` will be called only if the - client action is necessary, i.e. when ``terms_of_service is not - None``. This argument is optional, if not supplied it will - default to automatic acceptance! - - :raises certbot.errors.Error: In case of any client problems, in - particular registration failure, or unaccepted Terms of Service. - :raises acme.errors.Error: In case of any protocol problems. - - :returns: Newly registered and saved account, as well as protocol - API handle (should be used in `Client` initialization). - :rtype: `tuple` of `.Account` and `acme.client.Client` - - """ - # Log non-standard actions, potentially wrong API calls - if account_storage.find_all(): - logger.info("There are already existing accounts for %s", config.server) - if config.email is None: - if not config.register_unsafely_without_email: - msg = ("No email was provided and " - "--register-unsafely-without-email was not present.") - logger.warning(msg) - raise errors.Error(msg) - if not config.dry_run: - logger.info("Registering without email!") - - # If --dry-run is used, and there is no staging account, create one with no email. - if config.dry_run: - config.email = None - - # Each new registration shall use a fresh new key - rsa_key = generate_private_key( - public_exponent=65537, - key_size=config.rsa_key_size, - backend=default_backend()) - key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key)) - acme = acme_from_config_key(config, key) - # TODO: add phone? - regr = perform_registration(acme, config, tos_cb) - - acc = account.Account(regr, key) - account.report_new_account(config) - account_storage.save(acc, acme) - - eff.handle_subscription(config) - - return acc, acme - - -def perform_registration(acme, config, tos_cb): - """ - Actually register new account, trying repeatedly if there are email - problems - - :param acme.client.Client client: ACME client object. - :param .IConfig config: Client configuration. - :param Callable tos_cb: a callback to handle Term of Service agreement. - - :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: - 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: - msg = ("The ACME server believes %s is an invalid email address. " - "Please ensure it is a valid email and attempt " - "registration again." % config.email) - raise errors.Error(msg) - else: - config.email = display_ops.get_email(invalid=True) - return perform_registration(acme, config, tos_cb) - else: - raise - - -class Client(object): - """Certbot's client. - - :ivar .IConfig config: Client configuration. - :ivar .Account account: Account registered with `register`. - :ivar .AuthHandler auth_handler: Authorizations handler that will - dispatch DV challenges to appropriate authenticators - (providing `.IAuthenticator` interface). - :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) - authenticator that can solve ACME challenges. - :ivar .IInstaller installer: Installer. - :ivar acme.client.BackwardsCompatibleClientV2 acme: Optional ACME - client API handle. You might already have one from `register`. - - """ - - def __init__(self, config, account_, auth, installer, acme=None): - """Initialize a client.""" - self.config = config - self.account = account_ - self.auth = auth - self.installer = installer - - # Initialize ACME if account is provided - if acme is None and self.account is not None: - acme = acme_from_config_key(config, self.account.key, self.account.regr) - self.acme = acme - - if auth is not None: - self.auth_handler = auth_handler.AuthHandler( - auth, self.acme, self.account, self.config.pref_challs) - else: - self.auth_handler = None - - def obtain_certificate_from_csr(self, csr, orderr=None): - """Obtain certificate. - - :param .util.CSR csr: PEM-encoded Certificate Signing - Request. The key used to generate this CSR can be different - than `authkey`. - :param acme.messages.OrderResource orderr: contains authzrs - - :returns: certificate and chain as PEM byte strings - :rtype: tuple - - """ - if self.auth_handler is None: - msg = ("Unable to obtain certificate because authenticator is " - "not set.") - logger.warning(msg) - raise errors.Error(msg) - if self.account.regr is None: - raise errors.Error("Please register with the ACME server first.") - - logger.debug("CSR: %s", csr) - - if orderr is None: - orderr = self._get_order_and_authorizations(csr.data, best_effort=False) - - deadline = datetime.datetime.now() + datetime.timedelta(seconds=90) - orderr = self.acme.finalize_order(orderr, deadline) - cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem) - return cert.encode(), chain.encode() - - def obtain_certificate(self, domains, old_keypath=None): - """Obtains a certificate from the ACME server. - - `.register` must be called before `.obtain_certificate` - - :param list domains: domains to get a certificate - - :returns: certificate as PEM string, chain as PEM string, - newly generated private key (`.util.Key`), and DER-encoded - Certificate Signing Request (`.util.CSR`). - :rtype: tuple - - """ - - # We need to determine the key path, key PEM data, CSR path, - # and CSR PEM data. For a dry run, the paths are None because - # they aren't permanently saved to disk. For a lineage with - # --reuse-key, the key path and PEM data are derived from an - # existing file. - - if old_keypath is not None: - # We've been asked to reuse a specific existing private key. - # Therefore, we'll read it now and not generate a new one in - # either case below. - # - # We read in bytes here because the type of `key.pem` - # created below is also bytes. - with open(old_keypath, "rb") as f: - keypath = old_keypath - keypem = f.read() - key = util.Key(file=keypath, pem=keypem) # type: Optional[util.Key] - logger.info("Reusing existing private key from %s.", old_keypath) - else: - # The key is set to None here but will be created below. - key = None - - # Create CSR from names - if self.config.dry_run: - key = key or util.Key(file=None, - pem=crypto_util.make_key(self.config.rsa_key_size)) - csr = util.CSR(file=None, form="pem", - data=acme_crypto_util.make_csr( - key.pem, domains, self.config.must_staple)) - else: - key = key or crypto_util.init_save_key(self.config.rsa_key_size, - self.config.key_dir) - csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir) - - orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) - authzr = orderr.authorizations - auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable - successful_domains = [d for d in domains if d in auth_domains] - - # allow_subset_of_names is currently disabled for wildcard - # certificates. The reason for this and checking allow_subset_of_names - # below is because successful_domains == domains is never true if - # domains contains a wildcard because the ACME spec forbids identifiers - # in authzs from containing a wildcard character. - if self.config.allow_subset_of_names and successful_domains != domains: - if not self.config.dry_run: - os.remove(key.file) - os.remove(csr.file) - return self.obtain_certificate(successful_domains) - else: - cert, chain = self.obtain_certificate_from_csr(csr, orderr) - - return cert, chain, key, csr - - def _get_order_and_authorizations(self, csr_pem, best_effort): - # type: (str, bool) -> List[messages.OrderResource] - """Request a new order and complete its authorizations. - - :param str csr_pem: A CSR in PEM format. - :param bool best_effort: True if failing to complete all - authorizations should not raise an exception - - :returns: order resource containing its completed authorizations - :rtype: acme.messages.OrderResource - - """ - try: - orderr = self.acme.new_order(csr_pem) - except acme_errors.WildcardUnsupportedError: - raise errors.Error("The currently selected ACME CA endpoint does" - " not support issuing wildcard certificates.") - - # For a dry run, ensure we have an order with fresh authorizations - if orderr and self.config.dry_run: - deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) - if deactivated: - logger.debug("Recreating order after authz deactivations") - orderr = self.acme.new_order(csr_pem) - if failed: - logger.warning("Certbot was unable to obtain fresh authorizations for every domain" - ". The dry run will continue, but results may not be accurate.") - - authzr = self.auth_handler.handle_authorizations(orderr, best_effort) - return orderr.update(authorizations=authzr) - - # pylint: disable=no-member - def obtain_and_enroll_certificate(self, domains, certname): - """Obtain and enroll certificate. - - Get a new certificate for the specified domains using the specified - authenticator and installer, and then create a new renewable lineage - containing it. - - :param domains: domains to request a certificate for - :type domains: `list` of `str` - :param certname: requested name of lineage - :type certname: `str` or `None` - - :returns: A new :class:`certbot._internal.storage.RenewableCert` instance - referred to the enrolled cert lineage, False if the cert could not - be obtained, or None if doing a successful dry run. - - """ - cert, chain, key, _ = self.obtain_certificate(domains) - - if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or - self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): - logger.info( - "Non-standard path(s), might not work with crontab installed " - "by your operating system package manager") - - new_name = self._choose_lineagename(domains, certname) - - if self.config.dry_run: - logger.debug("Dry run: Skipping creating new lineage for %s", - new_name) - return None - return storage.RenewableCert.new_lineage( - new_name, cert, - key.pem, chain, - self.config) - - def _choose_lineagename(self, domains, certname): - """Chooses a name for the new lineage. - - :param domains: domains in certificate request - :type domains: `list` of `str` - :param certname: requested name of lineage - :type certname: `str` or `None` - - :returns: lineage name that should be used - :rtype: str - - """ - if certname: - return certname - elif util.is_wildcard_domain(domains[0]): - # Don't make files and directories starting with *. - return domains[0][2:] - return domains[0] - - def save_certificate(self, cert_pem, chain_pem, - cert_path, chain_path, fullchain_path): - """Saves the certificate received from the ACME server. - - :param str cert_pem: - :param str chain_pem: - :param str cert_path: Candidate path to a certificate. - :param str chain_path: Candidate path to a certificate chain. - :param str fullchain_path: Candidate path to a full cert chain. - - :returns: cert_path, chain_path, and fullchain_path as absolute - paths to the actual files - :rtype: `tuple` of `str` - - :raises IOError: If unable to find room to write the cert files - - """ - for path in cert_path, chain_path, fullchain_path: - util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) - - - cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) - - try: - cert_file.write(cert_pem) - finally: - cert_file.close() - logger.info("Server issued certificate; certificate written to %s", - abs_cert_path) - - chain_file, abs_chain_path =\ - _open_pem_file('chain_path', chain_path) - fullchain_file, abs_fullchain_path =\ - _open_pem_file('fullchain_path', fullchain_path) - - _save_chain(chain_pem, chain_file) - _save_chain(cert_pem + chain_pem, fullchain_file) - - return abs_cert_path, abs_chain_path, abs_fullchain_path - - def deploy_certificate(self, domains, privkey_path, - cert_path, chain_path, fullchain_path): - """Install certificate - - :param list domains: list of domains to install the certificate - :param str privkey_path: path to certificate private key - :param str cert_path: certificate file path (optional) - :param str chain_path: chain file path - - """ - if self.installer is None: - logger.warning("No installer specified, client is unable to deploy" - "the certificate") - raise errors.Error("No installer available") - - chain_path = None if chain_path is None else os.path.abspath(chain_path) - - msg = ("Unable to install the certificate") - with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): - for dom in domains: - self.installer.deploy_cert( - domain=dom, cert_path=os.path.abspath(cert_path), - key_path=os.path.abspath(privkey_path), - chain_path=chain_path, - fullchain_path=fullchain_path) - self.installer.save() # needed by the Apache plugin - - self.installer.save("Deployed ACME Certificate") - - msg = ("We were unable to install your certificate, " - "however, we successfully restored your " - "server to its prior configuration.") - with error_handler.ErrorHandler(self._rollback_and_restart, msg): - # sites may have been enabled / final cleanup - self.installer.restart() - - def enhance_config(self, domains, chain_path, ask_redirect=True): - """Enhance the configuration. - - :param list domains: list of domains to configure - :param chain_path: chain file path - :type chain_path: `str` or `None` - - :raises .errors.Error: if no installer is specified in the - client. - - """ - if self.installer is None: - logger.warning("No installer is specified, there isn't any " - "configuration to enhance.") - raise errors.Error("No installer available") - - enhanced = False - enhancement_info = ( - ("hsts", "ensure-http-header", "Strict-Transport-Security"), - ("redirect", "redirect", None), - ("staple", "staple-ocsp", chain_path), - ("uir", "ensure-http-header", "Upgrade-Insecure-Requests"),) - supported = self.installer.supported_enhancements() - - for config_name, enhancement_name, option in enhancement_info: - config_value = getattr(self.config, config_name) - if enhancement_name in supported: - if ask_redirect: - if config_name == "redirect" and config_value is None: - config_value = enhancements.ask(enhancement_name) - if not config_value: - logger.warning("Future versions of Certbot will automatically " - "configure the webserver so that all requests redirect to secure " - "HTTPS access. You can control this behavior and disable this " - "warning with the --redirect and --no-redirect flags.") - if config_value: - self.apply_enhancement(domains, enhancement_name, option) - enhanced = True - elif config_value: - logger.warning( - "Option %s is not supported by the selected installer. " - "Skipping enhancement.", config_name) - - msg = ("We were unable to restart web server") - if enhanced: - with error_handler.ErrorHandler(self._rollback_and_restart, msg): - self.installer.restart() - - def apply_enhancement(self, domains, enhancement, options=None): - """Applies an enhancement on all domains. - - :param list domains: list of ssl_vhosts (as strings) - :param str enhancement: name of enhancement, e.g. ensure-http-header - :param str options: options to enhancement, e.g. Strict-Transport-Security - - .. note:: When more `options` are needed, make options a list. - - :raises .errors.PluginError: If Enhancement is not supported, or if - there is any other problem with the enhancement. - - - """ - msg = ("We were unable to set up enhancement %s for your server, " - "however, we successfully installed your certificate." - % (enhancement)) - with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): - for dom in domains: - try: - self.installer.enhance(dom, enhancement, options) - except errors.PluginEnhancementAlreadyPresent: - if enhancement == "ensure-http-header": - logger.warning("Enhancement %s was already set.", - options) - else: - logger.warning("Enhancement %s was already set.", - enhancement) - except errors.PluginError: - logger.warning("Unable to set enhancement %s for %s", - enhancement, dom) - raise - - self.installer.save("Add enhancement %s" % (enhancement)) - - def _recovery_routine_with_msg(self, success_msg): - """Calls the installer's recovery routine and prints success_msg - - :param str success_msg: message to show on successful recovery - - """ - self.installer.recovery_routine() - reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message(success_msg, reporter.HIGH_PRIORITY) - - def _rollback_and_restart(self, success_msg): - """Rollback the most recent checkpoint and restart the webserver - - :param str success_msg: message to show on successful rollback - - """ - logger.critical("Rolling back to previous server configuration...") - reporter = zope.component.getUtility(interfaces.IReporter) - try: - self.installer.rollback_checkpoints() - self.installer.restart() - except: - reporter.add_message( - "An error occurred and we failed to restore your config and " - "restart your server. Please post to " - "https://community.letsencrypt.org/c/help " - "with details about your configuration and this error you received.", - reporter.HIGH_PRIORITY) - raise - reporter.add_message(success_msg, reporter.HIGH_PRIORITY) - - -def validate_key_csr(privkey, csr=None): - """Validate Key and CSR files. - - Verifies that the client key and csr arguments are valid and correspond to - one another. This does not currently check the names in the CSR due to - the inability to read SANs from CSRs in python crypto libraries. - - If csr is left as None, only the key will be validated. - - :param privkey: Key associated with CSR - :type privkey: :class:`certbot.util.Key` - - :param .util.CSR csr: CSR - - :raises .errors.Error: when validation fails - - """ - # TODO: Handle all of these problems appropriately - # The client can eventually do things like prompt the user - # and allow the user to take more appropriate actions - - # Key must be readable and valid. - if privkey.pem and not crypto_util.valid_privkey(privkey.pem): - raise errors.Error("The provided key is not a valid key") - - if csr: - if csr.form == "der": - csr_obj = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_ASN1, csr.data) - cert_buffer = OpenSSL.crypto.dump_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_obj - ) - csr = util.CSR(csr.file, cert_buffer, "pem") - - # If CSR is provided, it must be readable and valid. - if csr.data and not crypto_util.valid_csr(csr.data): - raise errors.Error("The provided CSR is not a valid CSR") - - # If both CSR and key are provided, the key must be the same key used - # in the CSR. - if csr.data and privkey.pem: - if not crypto_util.csr_matches_pubkey( - csr.data, privkey.pem): - raise errors.Error("The key and CSR do not match") - - -def rollback(default_installer, checkpoints, config, plugins): - """Revert configuration the specified number of checkpoints. - - :param int checkpoints: Number of checkpoints to revert. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - """ - # Misconfigurations are only a slight problems... allow the user to rollback - installer = plugin_selection.pick_installer( - config, default_installer, plugins, question="Which installer " - "should be used for rollback?") - - # No Errors occurred during init... proceed normally - # If installer is None... couldn't find an installer... there shouldn't be - # anything to rollback - if installer is not None: - installer.rollback_checkpoints(checkpoints) - installer.restart() - -def _open_pem_file(cli_arg_path, pem_path): - """Open a pem file. - - If cli_arg_path was set by the client, open that. - Otherwise, uniquify the file path. - - :param str cli_arg_path: the cli arg name, e.g. cert_path - :param str pem_path: the pem file path to open - - :returns: a tuple of file object and its absolute file path - - """ - if cli.set_by_cli(cli_arg_path): - return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ - os.path.abspath(pem_path) - uniq = util.unique_file(pem_path, 0o644, "wb") - return uniq[0], os.path.abspath(uniq[1]) - -def _save_chain(chain_pem, chain_file): - """Saves chain_pem at a unique path based on chain_path. - - :param str chain_pem: certificate chain in PEM format - :param str chain_file: chain file object - - """ - try: - chain_file.write(chain_pem) - finally: - chain_file.close() - - logger.info("Cert chain written to %s", chain_file.name) diff --git a/certbot/_internal/configuration.py b/certbot/_internal/configuration.py deleted file mode 100644 index 48579eb1c..000000000 --- a/certbot/_internal/configuration.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Certbot user-supplied configuration.""" -import copy - -import zope.interface -from six.moves.urllib import parse # pylint: disable=relative-import - -from certbot._internal import constants -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import misc -from certbot.compat import os - - -@zope.interface.implementer(interfaces.IConfig) -class NamespaceConfig(object): - """Configuration wrapper around :class:`argparse.Namespace`. - - For more documentation, including available attributes, please see - :class:`certbot.interfaces.IConfig`. However, note that - the following attributes are dynamically resolved using - :attr:`~certbot.interfaces.IConfig.work_dir` and relative - paths defined in :py:mod:`certbot._internal.constants`: - - - `accounts_dir` - - `csr_dir` - - `in_progress_dir` - - `key_dir` - - `temp_checkpoint_dir` - - And the following paths are dynamically resolved using - :attr:`~certbot.interfaces.IConfig.config_dir` and relative - paths defined in :py:mod:`certbot._internal.constants`: - - - `default_archive_dir` - - `live_dir` - - `renewal_configs_dir` - - :ivar namespace: Namespace typically produced by - :meth:`argparse.ArgumentParser.parse_args`. - :type namespace: :class:`argparse.Namespace` - - """ - - def __init__(self, namespace): - object.__setattr__(self, 'namespace', namespace) - - self.namespace.config_dir = os.path.abspath(self.namespace.config_dir) - self.namespace.work_dir = os.path.abspath(self.namespace.work_dir) - self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir) - - # Check command line parameters sanity, and error out in case of problem. - check_config_sanity(self) - - def __getattr__(self, name): - return getattr(self.namespace, name) - - def __setattr__(self, name, value): - setattr(self.namespace, name, value) - - @property - def server_path(self): - """File path based on ``server``.""" - parsed = parse.urlparse(self.namespace.server) - return (parsed.netloc + parsed.path).replace('/', os.path.sep) - - @property - def accounts_dir(self): # pylint: disable=missing-docstring - return self.accounts_dir_for_server_path(self.server_path) - - def accounts_dir_for_server_path(self, server_path): - """Path to accounts directory based on server_path""" - server_path = misc.underscores_for_unsupported_characters_in_path(server_path) - return os.path.join( - self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) - - @property - def backup_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) - - @property - def csr_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.CSR_DIR) - - @property - def in_progress_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) - - @property - def key_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.KEY_DIR) - - @property - def temp_checkpoint_dir(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) - - def __deepcopy__(self, _memo): - # Work around https://bugs.python.org/issue1515 for py26 tests :( :( - # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276 - new_ns = copy.deepcopy(self.namespace) - return type(self)(new_ns) - - @property - def default_archive_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) - - @property - def live_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) - - @property - def renewal_configs_dir(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) - - @property - def renewal_hooks_dir(self): - """Path to directory with hooks to run with the renew subcommand.""" - return os.path.join(self.namespace.config_dir, - constants.RENEWAL_HOOKS_DIR) - - @property - def renewal_pre_hooks_dir(self): - """Path to the pre-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_PRE_HOOKS_DIR) - - @property - def renewal_deploy_hooks_dir(self): - """Path to the deploy-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_DEPLOY_HOOKS_DIR) - - @property - def renewal_post_hooks_dir(self): - """Path to the post-hook directory for the renew subcommand.""" - return os.path.join(self.renewal_hooks_dir, - constants.RENEWAL_POST_HOOKS_DIR) - - -def check_config_sanity(config): - """Validate command line options and display error message if - requirements are not met. - - :param config: IConfig instance holding user configuration - :type args: :class:`certbot.interfaces.IConfig` - - """ - # Port check - if config.http01_port == config.https_port: - raise errors.ConfigurationError( - "Trying to run http-01 and https-port " - "on the same port ({0})".format(config.https_port)) - - # Domain checks - if config.namespace.domains is not None: - for domain in config.namespace.domains: - # This may be redundant, but let's be paranoid - util.enforce_domain_sanity(domain) diff --git a/certbot/_internal/constants.py b/certbot/_internal/constants.py deleted file mode 100644 index 5ac7ee72d..000000000 --- a/certbot/_internal/constants.py +++ /dev/null @@ -1,219 +0,0 @@ -"""Certbot constants.""" -import logging - -import pkg_resources - -from acme import challenges - -from certbot.compat import misc -from certbot.compat import os - -SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" -"""Setuptools entry point group name for plugins.""" - -OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" -"""Plugins Setuptools entry point before rename.""" - -CLI_DEFAULTS = dict( - config_files=[ - os.path.join(misc.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"), - ], - - # Main parser - verbose_count=-int(logging.INFO / 10), - text_mode=False, - max_log_backups=1000, - noninteractive_mode=False, - force_interactive=False, - domains=[], - certname=None, - dry_run=False, - register_unsafely_without_email=False, - email=None, - eff_email=None, - reinstall=False, - expand=False, - renew_by_default=False, - renew_with_new_domains=False, - autorenew=True, - allow_subset_of_names=False, - tos=False, - account=None, - duplicate=False, - os_packages_only=False, - no_self_upgrade=False, - no_permissions_check=False, - no_bootstrap=False, - quiet=False, - staging=False, - debug=False, - debug_challenges=False, - no_verify_ssl=False, - http01_port=challenges.HTTP01Response.PORT, - http01_address="", - https_port=443, - break_my_certs=False, - rsa_key_size=2048, - must_staple=False, - redirect=None, - auto_hsts=False, - hsts=None, - uir=None, - staple=None, - strict_permissions=False, - pref_challs=[], - validate_hooks=True, - 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, - user_agent=None, - user_agent_comment=None, - csr=None, - reason=0, - delete_after_revoke=None, - rollback_checkpoints=1, - init=False, - prepare=False, - ifaces=None, - - # Path parsers - auth_cert_path="./cert.pem", - auth_chain_path="./chain.pem", - key_path=None, - config_dir=misc.get_default_folder('config'), - work_dir=misc.get_default_folder('work'), - logs_dir=misc.get_default_folder('logs'), - server="https://acme-v02.api.letsencrypt.org/directory", - - # Plugins parsers - configurator=None, - authenticator=None, - installer=None, - apache=False, - nginx=False, - standalone=False, - manual=False, - webroot=False, - dns_cloudflare=False, - dns_cloudxns=False, - dns_digitalocean=False, - dns_dnsimple=False, - dns_dnsmadeeasy=False, - dns_gehirn=False, - dns_google=False, - dns_linode=False, - dns_luadns=False, - dns_nsone=False, - dns_ovh=False, - dns_rfc2136=False, - dns_route53=False, - dns_sakuracloud=False - -) -STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory" - -# The set of reasons for revoking a certificate is defined in RFC 5280 in -# section 5.3.1. The reasons that users are allowed to submit are restricted to -# those accepted by the ACME server implementation. They are listed in -# `letsencrypt.boulder.revocation.reasons.go`. -REVOCATION_REASONS = { - "unspecified": 0, - "keycompromise": 1, - "affiliationchanged": 3, - "superseded": 4, - "cessationofoperation": 5} - -"""Defaults for CLI flags and `.IConfig` attributes.""" - -QUIET_LOGGING_LEVEL = logging.WARNING -"""Logging level to use in quiet mode.""" - -RENEWER_DEFAULTS = dict( - renewer_enabled="yes", - renew_before_expiry="30 days", - # This value should ensure that there is never a deployment delay by - # default. - deploy_before_expiry="99 years", -) -"""Defaults for renewer script.""" - -ARCHIVE_DIR = "archive" -"""Archive directory, relative to `IConfig.config_dir`.""" - -CONFIG_DIRS_MODE = 0o755 -"""Directory mode for ``.IConfig.config_dir`` et al.""" - -ACCOUNTS_DIR = "accounts" -"""Directory where all accounts are saved.""" - -LE_REUSE_SERVERS = { - os.path.normpath('acme-v02.api.letsencrypt.org/directory'): - os.path.normpath('acme-v01.api.letsencrypt.org/directory'), - os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): - os.path.normpath('acme-staging.api.letsencrypt.org/directory') -} -"""Servers that can reuse accounts from other servers.""" - -BACKUP_DIR = "backups" -"""Directory (relative to `IConfig.work_dir`) where backups are kept.""" - -CSR_DIR = "csr" -"""See `.IConfig.csr_dir`.""" - -IN_PROGRESS_DIR = "IN_PROGRESS" -"""Directory used before a permanent checkpoint is finalized (relative to -`IConfig.work_dir`).""" - -KEY_DIR = "keys" -"""Directory (relative to `IConfig.config_dir`) where keys are saved.""" - -LIVE_DIR = "live" -"""Live directory, relative to `IConfig.config_dir`.""" - -TEMP_CHECKPOINT_DIR = "temp_checkpoint" -"""Temporary checkpoint directory (relative to `IConfig.work_dir`).""" - -RENEWAL_CONFIGS_DIR = "renewal" -"""Renewal configs directory, relative to `IConfig.config_dir`.""" - -RENEWAL_HOOKS_DIR = "renewal-hooks" -"""Basename of directory containing hooks to run with the renew command.""" - -RENEWAL_PRE_HOOKS_DIR = "pre" -"""Basename of directory containing pre-hooks to run with the renew command.""" - -RENEWAL_DEPLOY_HOOKS_DIR = "deploy" -"""Basename of directory containing deploy-hooks to run with the renew command.""" - -RENEWAL_POST_HOOKS_DIR = "post" -"""Basename of directory containing post-hooks to run with the renew command.""" - -FORCE_INTERACTIVE_FLAG = "--force-interactive" -"""Flag to disable TTY checking in IDisplay.""" - -EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot" -"""EFF URI used to submit the e-mail address of users who opt-in.""" - -SSL_DHPARAMS_DEST = "ssl-dhparams.pem" -"""Name of the ssl_dhparams file as saved in `IConfig.config_dir`.""" - -SSL_DHPARAMS_SRC = pkg_resources.resource_filename( - "certbot", "ssl-dhparams.pem") -"""Path to the nginx ssl_dhparams file found in the Certbot distribution.""" - -UPDATED_SSL_DHPARAMS_DIGEST = ".updated-ssl-dhparams-pem-digest.txt" -"""Name of the hash of the updated or informed ssl_dhparams as saved in `IConfig.config_dir`.""" - -ALL_SSL_DHPARAMS_HASHES = [ - '9ba6429597aeed2d8617a7705b56e96d044f64b07971659382e426675105654b', -] -"""SHA256 hashes of the contents of all versions of SSL_DHPARAMS_SRC""" diff --git a/certbot/_internal/display/__init__.py b/certbot/_internal/display/__init__.py deleted file mode 100644 index 9d39dce92..000000000 --- a/certbot/_internal/display/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot display utilities.""" diff --git a/certbot/_internal/display/completer.py b/certbot/_internal/display/completer.py deleted file mode 100644 index 3be06bec1..000000000 --- a/certbot/_internal/display/completer.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Provides Tab completion when prompting users for a path.""" -import glob -# readline module is not available on all systems -try: - import readline -except ImportError: - import certbot._internal.display.dummy_readline as readline # type: ignore - - -class Completer(object): - """Provides Tab completion when prompting users for a path. - - This class is meant to be used with readline to provide Tab - completion for users entering paths. The complete method can be - passed to readline.set_completer directly, however, this function - works best as a context manager. For example: - - with Completer(): - raw_input() - - In this example, Tab completion will be available during the call to - raw_input above, however, readline will be restored to its previous - state when exiting the body of the with statement. - - """ - - def __init__(self): - self._iter = self._original_completer = self._original_delims = None - - def complete(self, text, state): - """Provides path completion for use with readline. - - :param str text: text to offer completions for - :param int state: which completion to return - - :returns: possible completion for text or ``None`` if all - completions have been returned - :rtype: str - - """ - if state == 0: - self._iter = glob.iglob(text + '*') - return next(self._iter, None) - - def __enter__(self): - self._original_completer = readline.get_completer() - self._original_delims = readline.get_completer_delims() - - readline.set_completer(self.complete) - readline.set_completer_delims(' \t\n;') - - # readline can be implemented using GNU readline, pyreadline or libedit - # which have different configuration syntax - if readline.__doc__ is not None and 'libedit' in readline.__doc__: - readline.parse_and_bind('bind ^I rl_complete') - else: - readline.parse_and_bind('tab: complete') - - def __exit__(self, unused_type, unused_value, unused_traceback): - readline.set_completer_delims(self._original_delims) - readline.set_completer(self._original_completer) diff --git a/certbot/_internal/display/dummy_readline.py b/certbot/_internal/display/dummy_readline.py deleted file mode 100644 index fb3d807bb..000000000 --- a/certbot/_internal/display/dummy_readline.py +++ /dev/null @@ -1,21 +0,0 @@ -"""A dummy module with no effect for use on systems without readline.""" - - -def get_completer(): - """An empty implementation of readline.get_completer.""" - - -def get_completer_delims(): - """An empty implementation of readline.get_completer_delims.""" - - -def parse_and_bind(unused_command): - """An empty implementation of readline.parse_and_bind.""" - - -def set_completer(unused_function=None): - """An empty implementation of readline.set_completer.""" - - -def set_completer_delims(unused_delims): - """An empty implementation of readline.set_completer_delims.""" diff --git a/certbot/_internal/display/enhancements.py b/certbot/_internal/display/enhancements.py deleted file mode 100644 index 5498b9547..000000000 --- a/certbot/_internal/display/enhancements.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Certbot Enhancement Display""" -import logging - -import zope.component - -from certbot import errors -from certbot import interfaces -from certbot.display import util as display_util - - -logger = logging.getLogger(__name__) - -# Define a helper function to avoid verbose code -util = zope.component.getUtility - - -def ask(enhancement): - """Display the enhancement to the user. - - :param str enhancement: One of the - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` enhancements - - :returns: True if feature is desired, False otherwise - :rtype: bool - - :raises .errors.Error: if the enhancement provided is not supported - - """ - try: - # Call the appropriate function based on the enhancement - return DISPATCH[enhancement]() - except KeyError: - logger.error("Unsupported enhancement given to ask(): %s", enhancement) - raise errors.Error("Unsupported Enhancement") - - -def redirect_by_default(): - """Determines whether the user would like to redirect to HTTPS. - - :returns: True if redirect is desired, False otherwise - :rtype: bool - - """ - choices = [ - ("No redirect", "Make no further changes to the webserver configuration."), - ("Redirect", "Make all requests redirect to secure HTTPS access. " - "Choose this for new sites, or if you're confident your site works on HTTPS. " - "You can undo this change by editing your web server's configuration."), - ] - - code, selection = util(interfaces.IDisplay).menu( - "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", - choices, default=0, - cli_flag="--redirect / --no-redirect", force_interactive=True) - - if code != display_util.OK: - return False - - return selection == 1 - - -DISPATCH = { - "redirect": redirect_by_default -} diff --git a/certbot/_internal/eff.py b/certbot/_internal/eff.py deleted file mode 100644 index a0692009f..000000000 --- a/certbot/_internal/eff.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Subscribes users to the EFF newsletter.""" -import logging - -import requests -import zope.component - -from certbot._internal import constants -from certbot import interfaces - - -logger = logging.getLogger(__name__) - - -def handle_subscription(config): - """High level function to take care of EFF newsletter subscriptions. - - The user may be asked if they want to sign up for the newsletter if - they have not already specified. - - :param .IConfig config: Client configuration. - - """ - if config.email is None: - if config.eff_email: - _report_failure("you didn't provide an e-mail address") - return - if config.eff_email is None: - config.eff_email = _want_subscription() - if config.eff_email: - subscribe(config.email) - - -def _want_subscription(): - """Does the user want to be subscribed to the EFF newsletter? - - :returns: True if we should subscribe the user, otherwise, False - :rtype: bool - - """ - prompt = ( - 'Would you be willing to share your email address with the ' - "Electronic Frontier Foundation, a founding partner of the Let's " - 'Encrypt project and the non-profit organization that develops ' - "Certbot? We'd like to send you email about our work encrypting " - "the web, EFF news, campaigns, and ways to support digital freedom. ") - display = zope.component.getUtility(interfaces.IDisplay) - return display.yesno(prompt, default=False) - - -def subscribe(email): - """Subscribe the user to the EFF mailing list. - - :param str email: the e-mail address to subscribe - - """ - url = constants.EFF_SUBSCRIBE_URI - data = {'data_type': 'json', - 'email': email, - 'form_id': 'eff_supporters_library_subscribe_form'} - logger.debug('Sending POST request to %s:\n%s', url, data) - _check_response(requests.post(url, data=data)) - - -def _check_response(response): - """Check for errors in the server's response. - - If an error occurred, it will be reported to the user. - - :param requests.Response response: the server's response to the - subscription request - - """ - logger.debug('Received response:\n%s', response.content) - try: - response.raise_for_status() - if not response.json()['status']: - _report_failure('your e-mail address appears to be invalid') - except requests.exceptions.HTTPError: - _report_failure() - except (ValueError, KeyError): - _report_failure('there was a problem with the server response') - - -def _report_failure(reason=None): - """Notify the user of failing to sign them up for the newsletter. - - :param reason: a phrase describing what the problem was - beginning with a lowercase letter and no closing punctuation - :type reason: `str` or `None` - - """ - msg = ['We were unable to subscribe you the EFF mailing list'] - if reason is not None: - msg.append(' because ') - msg.append(reason) - msg.append('. You can try again later by visiting https://act.eff.org.') - reporter = zope.component.getUtility(interfaces.IReporter) - reporter.add_message(''.join(msg), reporter.LOW_PRIORITY) diff --git a/certbot/_internal/error_handler.py b/certbot/_internal/error_handler.py deleted file mode 100644 index 1a570e48e..000000000 --- a/certbot/_internal/error_handler.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Registers functions to be called if an exception or signal occurs.""" -import functools -import logging -import signal -import traceback - -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Callable, Dict, List, Union -# pylint: enable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -# _SIGNALS stores the signals that will be handled by the ErrorHandler. These -# signals were chosen as their default handler terminates the process and could -# potentially occur from inside Python. Signals such as SIGILL were not -# included as they could be a sign of something devious and we should terminate -# immediately. -if os.name != "nt": - _SIGNALS = [signal.SIGTERM] - for signal_code in [signal.SIGHUP, signal.SIGQUIT, - signal.SIGXCPU, signal.SIGXFSZ]: - # Adding only those signals that their default action is not Ignore. - # This is platform-dependent, so we check it dynamically. - if signal.getsignal(signal_code) != signal.SIG_IGN: - _SIGNALS.append(signal_code) -else: - # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. - # As consumed by CPython, most of handlers on theses signals are useless, in particular - # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops - # immediately the process without calling the attached handler. Besides, non-POSIX signals - # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the - # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable - # behavior in fact, and maps to the handler to SIGINT. However in this case, a - # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager - # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. - # - # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, - # https://docs.python.org/2/library/os.html#os.kill, - # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question - _SIGNALS = [] - - -class ErrorHandler(object): - """Context manager for running code that must be cleaned up on failure. - - The context manager allows you to register functions that will be called - when an exception (excluding SystemExit) or signal is encountered. - Usage:: - - handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs) - handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs) - - with handler: - do_something() - - Or for one cleanup function:: - - with ErrorHandler(func, args, kwargs): - do_something() - - If an exception is raised out of do_something, the cleanup functions will - be called in last in first out order. Then the exception is raised. - Similarly, if a signal is encountered, the cleanup functions are called - followed by the previously received signal handler. - - Each registered cleanup function is called exactly once. If a registered - function raises an exception, it is logged and the next function is called. - Signals received while the registered functions are executing are - deferred until they finish. - - """ - def __init__(self, func, *args, **kwargs): - self.call_on_regular_exit = False - self.body_executed = False - self.funcs = [] # type: List[Callable[[], Any]] - self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]] - self.received_signals = [] # type: List[int] - if func is not None: - self.register(func, *args, **kwargs) - - def __enter__(self): - self.body_executed = False - self._set_signal_handlers() - - def __exit__(self, exec_type, exec_value, trace): - self.body_executed = True - retval = False - # SystemExit is ignored to properly handle forks that don't exec - if exec_type is SystemExit: - return retval - elif exec_type is None: - if not self.call_on_regular_exit: - return retval - elif exec_type is errors.SignalExit: - logger.debug("Encountered signals: %s", self.received_signals) - retval = True - else: - logger.debug("Encountered exception:\n%s", "".join( - traceback.format_exception(exec_type, exec_value, trace))) - - self._call_registered() - self._reset_signal_handlers() - self._call_signals() - return retval - - def register(self, func, *args, **kwargs): - # type: (Callable, *Any, **Any) -> None - """Sets func to be run with the given arguments during cleanup. - - :param function func: function to be called in case of an error - - """ - self.funcs.append(functools.partial(func, *args, **kwargs)) - - def _call_registered(self): - """Calls all registered functions""" - logger.debug("Calling registered functions") - while self.funcs: - try: - self.funcs[-1]() - except Exception: # pylint: disable=broad-except - logger.error("Encountered exception during recovery: ", exc_info=True) - self.funcs.pop() - - def _set_signal_handlers(self): - """Sets signal handlers for signals in _SIGNALS.""" - for signum in _SIGNALS: - prev_handler = signal.getsignal(signum) - # If prev_handler is None, the handler was set outside of Python - if prev_handler is not None: - self.prev_handlers[signum] = prev_handler - signal.signal(signum, self._signal_handler) - - def _reset_signal_handlers(self): - """Resets signal handlers for signals in _SIGNALS.""" - for signum in self.prev_handlers: - signal.signal(signum, self.prev_handlers[signum]) - self.prev_handlers.clear() - - def _signal_handler(self, signum, unused_frame): - """Replacement function for handling received signals. - - Store the received signal. If we are executing the code block in - the body of the context manager, stop by raising signal exit. - - :param int signum: number of current signal - - """ - self.received_signals.append(signum) - if not self.body_executed: - raise errors.SignalExit - - def _call_signals(self): - """Finally call the deferred signals.""" - for signum in self.received_signals: - logger.debug("Calling signal %s", signum) - os.kill(os.getpid(), signum) - -class ExitHandler(ErrorHandler): - """Context manager for running code that must be cleaned up. - - Subclass of ErrorHandler, with the same usage and parameters. - In addition to cleaning up on all signals, also cleans up on - regular exit. - """ - def __init__(self, func, *args, **kwargs): - ErrorHandler.__init__(self, func, *args, **kwargs) - self.call_on_regular_exit = True diff --git a/certbot/_internal/hooks.py b/certbot/_internal/hooks.py deleted file mode 100644 index 1bb3a2eab..000000000 --- a/certbot/_internal/hooks.py +++ /dev/null @@ -1,272 +0,0 @@ -"""Facilities for implementing hooks that call shell commands.""" -from __future__ import print_function - -import logging -from subprocess import Popen, PIPE - -from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot import util -from certbot.compat import filesystem -from certbot.compat import os -from certbot.plugins import util as plug_util - -logger = logging.getLogger(__name__) - - -def validate_hooks(config): - """Check hook commands are executable.""" - validate_hook(config.pre_hook, "pre") - validate_hook(config.post_hook, "post") - validate_hook(config.deploy_hook, "deploy") - validate_hook(config.renew_hook, "renew") - - -def _prog(shell_cmd): - """Extract the program run by a shell command. - - :param str shell_cmd: command to be executed - - :returns: basename of command or None if the command isn't found - :rtype: str or None - - """ - if not util.exe_exists(shell_cmd): - plug_util.path_surgery(shell_cmd) - if not util.exe_exists(shell_cmd): - return None - return os.path.basename(shell_cmd) - - -def validate_hook(shell_cmd, hook_name): - """Check that a command provided as a hook is plausibly executable. - - :raises .errors.HookCommandNotFound: if the command is not found - """ - if shell_cmd: - cmd = shell_cmd.split(None, 1)[0] - if not _prog(cmd): - path = os.environ["PATH"] - if os.path.exists(cmd): - msg = "{1}-hook command {0} exists, but is not executable.".format(cmd, hook_name) - else: - msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format( - cmd, path, hook_name) - - raise errors.HookCommandNotFound(msg) - - -def pre_hook(config): - """Run pre-hooks if they exist and haven't already been run. - - When Certbot is running with the renew subcommand, this function - runs any hooks found in the config.renewal_pre_hooks_dir (if they - have not already been run) followed by any pre-hook in the config. - If hooks in config.renewal_pre_hooks_dir are run and the pre-hook in - the config is a path to one of these scripts, it is not run twice. - - :param configuration.NamespaceConfig config: Certbot settings - - """ - if config.verb == "renew" and config.directory_hooks: - for hook in list_hooks(config.renewal_pre_hooks_dir): - _run_pre_hook_if_necessary(hook) - - cmd = config.pre_hook - if cmd: - _run_pre_hook_if_necessary(cmd) - - -executed_pre_hooks = set() # type: Set[str] - - -def _run_pre_hook_if_necessary(command): - """Run the specified pre-hook if we haven't already. - - If we've already run this exact command before, a message is logged - saying the pre-hook was skipped. - - :param str command: pre-hook to be run - - """ - if command in executed_pre_hooks: - logger.info("Pre-hook command already run, skipping: %s", command) - else: - _run_hook("pre-hook", command) - executed_pre_hooks.add(command) - - -def post_hook(config): - """Run post-hooks if defined. - - This function also registers any executables found in - config.renewal_post_hooks_dir to be run when Certbot is used with - the renew subcommand. - - If the verb is renew, we delay executing any post-hooks until - :func:`run_saved_post_hooks` is called. In this case, this function - registers all hooks found in config.renewal_post_hooks_dir to be - called followed by any post-hook in the config. If the post-hook in - the config is a path to an executable in the post-hook directory, it - is not scheduled to be run twice. - - :param configuration.NamespaceConfig config: Certbot settings - - """ - - cmd = config.post_hook - # In the "renew" case, we save these up to run at the end - if config.verb == "renew": - if config.directory_hooks: - for hook in list_hooks(config.renewal_post_hooks_dir): - _run_eventually(hook) - if cmd: - _run_eventually(cmd) - # certonly / run - elif cmd: - _run_hook("post-hook", cmd) - - -post_hooks = [] # type: List[str] - - -def _run_eventually(command): - """Registers a post-hook to be run eventually. - - All commands given to this function will be run exactly once in the - order they were given when :func:`run_saved_post_hooks` is called. - - :param str command: post-hook to register to be run - - """ - if command not in post_hooks: - post_hooks.append(command) - - -def run_saved_post_hooks(): - """Run any post hooks that were saved up in the course of the 'renew' verb""" - for cmd in post_hooks: - _run_hook("post-hook", cmd) - - -def deploy_hook(config, domains, lineage_path): - """Run post-issuance hook if defined. - - :param configuration.NamespaceConfig config: Certbot settings - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - - """ - if config.deploy_hook: - _run_deploy_hook(config.deploy_hook, domains, - lineage_path, config.dry_run) - - -def renew_hook(config, domains, lineage_path): - """Run post-renewal hooks. - - This function runs any hooks found in - config.renewal_deploy_hooks_dir followed by any renew-hook in the - config. If the renew-hook in the config is a path to a script in - config.renewal_deploy_hooks_dir, it is not run twice. - - If Certbot is doing a dry run, no hooks are run and messages are - logged saying that they were skipped. - - :param configuration.NamespaceConfig config: Certbot settings - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - - """ - executed_dir_hooks = set() - if config.directory_hooks: - for hook in list_hooks(config.renewal_deploy_hooks_dir): - _run_deploy_hook(hook, domains, lineage_path, config.dry_run) - executed_dir_hooks.add(hook) - - if config.renew_hook: - if config.renew_hook in executed_dir_hooks: - logger.info("Skipping deploy-hook '%s' as it was already run.", - config.renew_hook) - else: - _run_deploy_hook(config.renew_hook, domains, - lineage_path, config.dry_run) - - -def _run_deploy_hook(command, domains, lineage_path, dry_run): - """Run the specified deploy-hook (if not doing a dry run). - - If dry_run is True, command is not run and a message is logged - saying that it was skipped. If dry_run is False, the hook is run - after setting the appropriate environment variables. - - :param str command: command to run as a deploy-hook - :param domains: domains in the obtained certificate - :type domains: `list` of `str` - :param str lineage_path: live directory path for the new cert - :param bool dry_run: True iff Certbot is doing a dry run - - """ - if dry_run: - logger.warning("Dry run: skipping deploy hook command: %s", - command) - return - - os.environ["RENEWED_DOMAINS"] = " ".join(domains) - os.environ["RENEWED_LINEAGE"] = lineage_path - _run_hook("deploy-hook", command) - - -def _run_hook(cmd_name, shell_cmd): - """Run a hook command. - - :param str cmd_name: the user facing name of the hook being run - :param shell_cmd: shell command to execute - :type shell_cmd: `list` of `str` or `str` - - :returns: stderr if there was any""" - err, _ = execute(cmd_name, shell_cmd) - return err - - -def execute(cmd_name, shell_cmd): - """Run a command. - - :param str cmd_name: the user facing name of the hook being run - :param shell_cmd: shell command to execute - :type shell_cmd: `list` of `str` or `str` - - :returns: `tuple` (`str` stderr, `str` stdout)""" - logger.info("Running %s command: %s", cmd_name, shell_cmd) - - # universal_newlines causes Popen.communicate() - # to return str objects instead of bytes in Python 3 - cmd = Popen(shell_cmd, shell=True, stdout=PIPE, - stderr=PIPE, universal_newlines=True) - out, err = cmd.communicate() - base_cmd = os.path.basename(shell_cmd.split(None, 1)[0]) - if out: - logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out) - if cmd.returncode != 0: - logger.error('%s command "%s" returned error code %d', - cmd_name, shell_cmd, cmd.returncode) - if err: - logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) - return err, out - - -def list_hooks(dir_path): - """List paths to all hooks found in dir_path in sorted order. - - :param str dir_path: directory to search - - :returns: `list` of `str` - :rtype: sorted list of absolute paths to executables in dir_path - - """ - allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) - hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] - return sorted(hooks) diff --git a/certbot/_internal/lock.py b/certbot/_internal/lock.py deleted file mode 100644 index eda2a72a1..000000000 --- a/certbot/_internal/lock.py +++ /dev/null @@ -1,261 +0,0 @@ -"""Implements file locks compatible with Linux and Windows for locking files and directories.""" -import errno -import logging -try: - import fcntl # pylint: disable=import-error -except ImportError: - import msvcrt # pylint: disable=import-error - POSIX_MODE = False -else: - POSIX_MODE = True - -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem - -logger = logging.getLogger(__name__) - - -def lock_dir(dir_path): - # type: (str) -> LockFile - """Place a lock file on the directory at dir_path. - - The lock file is placed in the root of dir_path with the name - .certbot.lock. - - :param str dir_path: path to directory - - :returns: the locked LockFile object - :rtype: LockFile - - :raises errors.LockError: if unable to acquire the lock - - """ - return LockFile(os.path.join(dir_path, '.certbot.lock')) - - -class LockFile(object): - """ - Platform independent file lock system. - LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, - instance is created, the associated file is 'locked from the point of view of the OS, - meaning that if another instance of Certbot try at the same time to acquire the same lock, - it will raise an Exception. Calling release method will release the lock, and make it - available to every other instance. - Upon exit, Certbot will also release all the locks. - This allows us to protect a file or directory from being concurrently accessed - or modified by two Certbot instances. - LockFile is platform independent: it will proceed to the appropriate OS lock mechanism - depending on Linux or Windows. - """ - def __init__(self, path): - # type: (str) -> None - """ - Create a LockFile instance on the given file path, and acquire lock. - :param str path: the path to the file that will hold a lock - """ - self._path = path - mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism - self._lock_mechanism = mechanism(path) - - self.acquire() - - def __repr__(self): - # type: () -> str - repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) - if self.is_locked(): - repr_str += 'acquired>' - else: - repr_str += 'released>' - return repr_str - - def acquire(self): - # type: () -> None - """ - Acquire the lock on the file, forbidding any other Certbot instance to acquire it. - :raises errors.LockError: if unable to acquire the lock - """ - self._lock_mechanism.acquire() - - def release(self): - # type: () -> None - """ - Release the lock on the file, allowing any other Certbot instance to acquire it. - """ - self._lock_mechanism.release() - - def is_locked(self): - # type: () -> bool - """ - Check if the file is currently locked. - :return: True if the file is locked, False otherwise - """ - return self._lock_mechanism.is_locked() - - -class _BaseLockMechanism(object): - def __init__(self, path): - # type: (str) -> None - """ - Create a lock file mechanism for Unix. - :param str path: the path to the lock file - """ - self._path = path - self._fd = None # type: Optional[int] - - def is_locked(self): - # type: () -> bool - """Check if lock file is currently locked. - :return: True if the lock file is locked - :rtype: bool - """ - return self._fd is not None - - def acquire(self): # pylint: disable=missing-docstring - pass # pragma: no cover - - def release(self): # pylint: disable=missing-docstring - pass # pragma: no cover - - -class _UnixLockMechanism(_BaseLockMechanism): - """ - A UNIX lock file mechanism. - This lock file is released when the locked file is closed or the - process exits. It cannot be used to provide synchronization between - threads. It is based on the lock_file package by Martin Horcicka. - """ - def acquire(self): - # type: () -> None - """Acquire the lock.""" - while self._fd is None: - # Open the file - fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) - try: - self._try_lock(fd) - if self._lock_success(fd): - self._fd = fd - finally: - # Close the file if it is not the required one - if self._fd is None: - os.close(fd) - - def _try_lock(self, fd): - # type: (int) -> None - """ - Try to acquire the lock file without blocking. - :param int fd: file descriptor of the opened file to lock - """ - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError as err: - if err.errno in (errno.EACCES, errno.EAGAIN): - logger.debug('A lock on %s is held by another process.', self._path) - raise errors.LockError('Another instance of Certbot is already running.') - raise - - def _lock_success(self, fd): - # type: (int) -> bool - """ - Did we successfully grab the lock? - Because this class deletes the locked file when the lock is - released, it is possible another process removed and recreated - the file between us opening the file and acquiring the lock. - :param int fd: file descriptor of the opened file to lock - :returns: True if the lock was successfully acquired - :rtype: bool - """ - # Normally os module should not be imported in certbot codebase except in certbot.compat - # for the sake of compatibility over Windows and Linux. - # We make an exception here, since _lock_success is private and called only on Linux. - from os import stat, fstat # pylint: disable=os-module-forbidden - try: - stat1 = stat(self._path) - except OSError as err: - if err.errno == errno.ENOENT: - return False - raise - - stat2 = fstat(fd) - # If our locked file descriptor and the file on disk refer to - # the same device and inode, they're the same file. - return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino - - def release(self): - # type: () -> None - """Remove, close, and release the lock file.""" - # It is important the lock file is removed before it's released, - # otherwise: - # - # process A: open lock file - # process B: release lock file - # process A: lock file - # process A: check device and inode - # process B: delete file - # process C: open and lock a different file at the same path - try: - os.remove(self._path) - finally: - # Following check is done to make mypy happy: it ensure that self._fd, marked - # as Optional[int] is effectively int to make it compatible with os.close signature. - if self._fd is None: # pragma: no cover - raise TypeError('Error, self._fd is None.') - try: - os.close(self._fd) - finally: - self._fd = None - - -class _WindowsLockMechanism(_BaseLockMechanism): - """ - A Windows lock file mechanism. - By default on Windows, acquiring a file handler gives exclusive access to the process - and results in an effective lock. However, it is possible to explicitly acquire the - file handler in shared access in terms of read and write, and this is done by os.open - and io.open in Python. So an explicit lock needs to be done through the call of - msvcrt.locking, that will lock the first byte of the file. In theory, it is also - possible to access a file in shared delete access, allowing other processes to delete an - opened file. But this needs also to be done explicitly by all processes using the Windows - low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers - state that deleting a file opened by a process from another process is not possible with - os.open and io.open. - Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race - condition encountered on Linux is not possible on Windows, leading to a simpler workflow. - """ - def acquire(self): - """Acquire the lock""" - open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC - - fd = None - try: - # Under Windows, filesystem.open will raise directly an EACCES error - # if the lock file is already locked. - fd = filesystem.open(self._path, open_mode, 0o600) - msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) - except (IOError, OSError) as err: - if fd: - os.close(fd) - # Anything except EACCES is unexpected. Raise directly the error in that case. - if err.errno != errno.EACCES: - raise - logger.debug('A lock on %s is held by another process.', self._path) - raise errors.LockError('Another instance of Certbot is already running.') - - self._fd = fd - - def release(self): - """Release the lock.""" - try: - msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) - os.close(self._fd) - - try: - os.remove(self._path) - except OSError as e: - # If the lock file cannot be removed, it is not a big deal. - # Likely another instance is acquiring the lock we just released. - logger.debug(str(e)) - finally: - self._fd = None diff --git a/certbot/_internal/log.py b/certbot/_internal/log.py deleted file mode 100644 index 2109e0427..000000000 --- a/certbot/_internal/log.py +++ /dev/null @@ -1,353 +0,0 @@ -"""Logging utilities for Certbot. - -The best way to use this module is through `pre_arg_parse_setup` and -`post_arg_parse_setup`. `pre_arg_parse_setup` configures a minimal -terminal logger and ensures a detailed log is written to a secure -temporary file if Certbot exits before `post_arg_parse_setup` is called. -`post_arg_parse_setup` relies on the parsed command line arguments and -does the full logging setup with terminal and rotating file handling as -configured by the user. Any logged messages before -`post_arg_parse_setup` is called are sent to the rotating file handler. -Special care is taken by both methods to ensure all errors are logged -and properly flushed before program exit. - -""" -from __future__ import print_function - -import functools -import logging -import logging.handlers -import shutil -import sys -import tempfile -import traceback - -from acme import messages - -from certbot._internal import constants -from certbot import errors -from certbot import util -from certbot.compat import os - -# Logging format -CLI_FMT = "%(message)s" -FILE_FMT = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" - - -logger = logging.getLogger(__name__) - - -def pre_arg_parse_setup(): - """Setup logging before command line arguments are parsed. - - Terminal logging is setup using - `certbot._internal.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as - possible. File logging is setup so that logging messages are - buffered in memory. If Certbot exits before `post_arg_parse_setup` - is called, these buffered messages are written to a temporary file. - If Certbot doesn't exit, `post_arg_parse_setup` writes the messages - to the normal log files. - - This function also sets `logging.shutdown` to be called on program - exit which automatically flushes logging handlers and - `sys.excepthook` to properly log/display fatal exceptions. - - """ - temp_handler = TempHandler() - temp_handler.setFormatter(logging.Formatter(FILE_FMT)) - temp_handler.setLevel(logging.DEBUG) - memory_handler = MemoryHandler(temp_handler) - - stream_handler = ColoredStreamHandler() - stream_handler.setFormatter(logging.Formatter(CLI_FMT)) - stream_handler.setLevel(constants.QUIET_LOGGING_LEVEL) - - root_logger = logging.getLogger() - root_logger.setLevel(logging.DEBUG) # send all records to handlers - root_logger.addHandler(memory_handler) - root_logger.addHandler(stream_handler) - - # logging.shutdown will flush the memory handler because flush() and - # close() are explicitly called - util.atexit_register(logging.shutdown) - sys.excepthook = functools.partial( - pre_arg_parse_except_hook, memory_handler, - debug='--debug' in sys.argv, log_path=temp_handler.path) - - -def post_arg_parse_setup(config): - """Setup logging after command line arguments are parsed. - - This function assumes `pre_arg_parse_setup` was called earlier and - the root logging configuration has not been modified. A rotating - file logging handler is created and the buffered log messages are - sent to that handler. Terminal logging output is set to the level - requested by the user. - - :param certbot.interface.IConfig config: Configuration object - - """ - file_handler, file_path = setup_log_file_handler( - config, 'letsencrypt.log', FILE_FMT) - logs_dir = os.path.dirname(file_path) - - root_logger = logging.getLogger() - memory_handler = stderr_handler = None - for handler in root_logger.handlers: - if isinstance(handler, ColoredStreamHandler): - stderr_handler = handler - elif isinstance(handler, MemoryHandler): - memory_handler = handler - msg = 'Previously configured logging handlers have been removed!' - assert memory_handler is not None and stderr_handler is not None, msg - - root_logger.addHandler(file_handler) - root_logger.removeHandler(memory_handler) - temp_handler = memory_handler.target - memory_handler.setTarget(file_handler) - memory_handler.flush(force=True) - memory_handler.close() - temp_handler.close() - - if config.quiet: - level = constants.QUIET_LOGGING_LEVEL - else: - level = -config.verbose_count * 10 - stderr_handler.setLevel(level) - logger.debug('Root logging level set at %d', level) - logger.info('Saving debug log to %s', file_path) - - sys.excepthook = functools.partial( - post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir) - - -def setup_log_file_handler(config, logfile, fmt): - """Setup file debug logging. - - :param certbot.interface.IConfig config: Configuration object - :param str logfile: basename for the log file - :param str fmt: logging format string - - :returns: file handler and absolute path to the log file - :rtype: tuple - - """ - # TODO: logs might contain sensitive data such as contents of the - # private key! #525 - util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) - log_file_path = os.path.join(config.logs_dir, logfile) - try: - handler = logging.handlers.RotatingFileHandler( - log_file_path, maxBytes=2 ** 20, - backupCount=config.max_log_backups) - except IOError as error: - raise errors.Error(util.PERM_ERR_FMT.format(error)) - # rotate on each invocation, rollover only possible when maxBytes - # is nonzero and backupCount is nonzero, so we set maxBytes as big - # as possible not to overrun in single CLI invocation (1MB). - handler.doRollover() # TODO: creates empty letsencrypt.log.1 file - handler.setLevel(logging.DEBUG) - handler_formatter = logging.Formatter(fmt=fmt) - handler.setFormatter(handler_formatter) - return handler, log_file_path - - -class ColoredStreamHandler(logging.StreamHandler): - """Sends colored logging output to a stream. - - If the specified stream is not a tty, the class works like the - standard `logging.StreamHandler`. Default red_level is - `logging.WARNING`. - - :ivar bool colored: True if output should be colored - :ivar bool red_level: The level at which to output - - """ - def __init__(self, stream=None): - super(ColoredStreamHandler, self).__init__(stream) - self.colored = (sys.stderr.isatty() if stream is None else - stream.isatty()) - self.red_level = logging.WARNING - - def format(self, record): - """Formats the string representation of record. - - :param logging.LogRecord record: Record to be formatted - - :returns: Formatted, string representation of record - :rtype: str - - """ - out = super(ColoredStreamHandler, self).format(record) - if self.colored and record.levelno >= self.red_level: - return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) - return out - - -class MemoryHandler(logging.handlers.MemoryHandler): - """Buffers logging messages in memory until the buffer is flushed. - - This differs from `logging.handlers.MemoryHandler` in that flushing - only happens when flush(force=True) is called. - - """ - def __init__(self, target=None, capacity=10000): - # capacity doesn't matter because should_flush() is overridden - super(MemoryHandler, self).__init__(capacity, target=target) - - def close(self): - """Close the memory handler, but don't set the target to None.""" - # This allows the logging module which may only have a weak - # reference to the target handler to properly flush and close it. - target = self.target - super(MemoryHandler, self).close() - self.target = target - - def flush(self, force=False): # pylint: disable=arguments-differ - """Flush the buffer if force=True. - - If force=False, this call is a noop. - - :param bool force: True if the buffer should be flushed. - - """ - # This method allows flush() calls in logging.shutdown to be a - # noop so we can control when this handler is flushed. - if force: - super(MemoryHandler, self).flush() - - def shouldFlush(self, record): - """Should the buffer be automatically flushed? - - :param logging.LogRecord record: log record to be considered - - :returns: False because the buffer should never be auto-flushed - :rtype: bool - - """ - return False - - -class TempHandler(logging.StreamHandler): - """Safely logs messages to a temporary file. - - The file is created with permissions 600. If no log records are sent - to this handler, the temporary file is deleted when the handler is - closed. - - :ivar str path: file system path to the temporary log file - - """ - def __init__(self): - self._workdir = tempfile.mkdtemp() - self.path = os.path.join(self._workdir, 'log') - stream = util.safe_open(self.path, mode='w', chmod=0o600) - super(TempHandler, self).__init__(stream) - self._delete = True - - def emit(self, record): - """Log the specified logging record. - - :param logging.LogRecord record: Record to be formatted - - """ - self._delete = False - super(TempHandler, self).emit(record) - - def close(self): - """Close the handler and the temporary log file. - - The temporary log file is deleted if it wasn't used. - - """ - self.acquire() - try: - # StreamHandler.close() doesn't close the stream to allow a - # stream like stderr to be used - self.stream.close() - if self._delete: - shutil.rmtree(self._workdir) - self._delete = False - super(TempHandler, self).close() - finally: - self.release() - - -def pre_arg_parse_except_hook(memory_handler, *args, **kwargs): - """A simple wrapper around post_arg_parse_except_hook. - - The additional functionality provided by this wrapper is the memory - handler will be flushed before Certbot exits. This allows us to - write logging messages to a temporary file if we crashed before - logging was fully configured. - - Since sys.excepthook isn't called on SystemExit exceptions, the - memory handler will not be flushed in this case which prevents us - from creating temporary log files when argparse exits because a - command line argument was invalid or -h, --help, or --version was - provided on the command line. - - :param MemoryHandler memory_handler: memory handler to flush - :param tuple args: args for post_arg_parse_except_hook - :param dict kwargs: kwargs for post_arg_parse_except_hook - - """ - try: - post_arg_parse_except_hook(*args, **kwargs) - finally: - # flush() is called here so messages logged during - # post_arg_parse_except_hook are also flushed. - memory_handler.flush(force=True) - - -def post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path): - """Logs fatal exceptions and reports them to the user. - - If debug is True, the full exception and traceback is shown to the - user, otherwise, it is suppressed. sys.exit is always called with a - nonzero status. - - :param type exc_type: type of the raised exception - :param BaseException exc_value: raised exception - :param traceback trace: traceback of where the exception was raised - :param bool debug: True if the traceback should be shown to the user - :param str log_path: path to file or directory containing the log - - """ - exc_info = (exc_type, exc_value, trace) - # constants.QUIET_LOGGING_LEVEL or higher should be used to - # display message the user, otherwise, a lower level like - # logger.DEBUG should be used - if debug or not issubclass(exc_type, Exception): - assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR - logger.error('Exiting abnormally:', exc_info=exc_info) - else: - logger.debug('Exiting abnormally:', exc_info=exc_info) - if issubclass(exc_type, errors.Error): - sys.exit(exc_value) - logger.error('An unexpected error occurred:') - if messages.is_acme_error(exc_value): - # Remove the ACME error prefix from the exception - _, _, exc_str = str(exc_value).partition(':: ') - logger.error(exc_str) - else: - traceback.print_exception(exc_type, exc_value, None) - exit_with_log_path(log_path) - - -def exit_with_log_path(log_path): - """Print a message about the log location and exit. - - The message is printed to stderr and the program will exit with a - nonzero status. - - :param str log_path: path to file or directory containing the log - - """ - msg = 'Please see the ' - if os.path.isdir(log_path): - msg += 'logfiles in {0} '.format(log_path) - else: - msg += "logfile '{0}' ".format(log_path) - msg += 'for more details.' - sys.exit(msg) diff --git a/certbot/_internal/main.py b/certbot/_internal/main.py deleted file mode 100644 index d697dc5b7..000000000 --- a/certbot/_internal/main.py +++ /dev/null @@ -1,1357 +0,0 @@ -"""Certbot main entry point.""" -# pylint: disable=too-many-lines -from __future__ import print_function - -import functools -import logging.handlers -import sys - -import configobj -import josepy as jose -import zope.component - -from acme import errors as acme_errors -from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module - -import certbot -from certbot._internal import account -from certbot._internal import cert_manager -from certbot._internal import cli -from certbot._internal import client -from certbot._internal import configuration -from certbot._internal import constants -from certbot import crypto_util -from certbot._internal import eff -from certbot import errors -from certbot._internal import hooks -from certbot import interfaces -from certbot._internal import log -from certbot._internal import renewal -from certbot._internal import reporter -from certbot._internal import storage -from certbot._internal import updater -from certbot import util -from certbot.compat import filesystem -from certbot.compat import misc -from certbot.compat import os -from certbot.display import util as display_util, ops as display_ops -from certbot._internal.plugins import disco as plugins_disco -from certbot.plugins import enhancements -from certbot._internal.plugins import selection as plug_sel - -USER_CANCELLED = ("User chose to cancel the operation and may " - "reinvoke the client.") - - -logger = logging.getLogger(__name__) - - -def _suggest_donation_if_appropriate(config): - """Potentially suggest a donation to support Certbot. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - assert config.verb != "renew" - if config.staging: - # --dry-run implies --staging - return - reporter_util = zope.component.getUtility(interfaces.IReporter) - msg = ("If you like Certbot, please consider supporting our work by:\n\n" - "Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n" - "Donating to EFF: https://eff.org/donate-le\n\n") - reporter_util.add_message(msg, reporter_util.LOW_PRIORITY) - -def _report_successful_dry_run(config): - """Reports on successful dry run - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - reporter_util = zope.component.getUtility(interfaces.IReporter) - assert config.verb != "renew" - reporter_util.add_message("The dry run was successful.", - reporter_util.HIGH_PRIORITY, on_crash=False) - - -def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None): - """Authenticate and enroll certificate. - - This method finds the relevant lineage, figures out what to do with it, - then performs that action. Includes calls to hooks, various reports, - checks, and requests for user input. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names to get a certificate. Defaults to `None` - :type domains: `list` of `str` - - :param certname: Name of new certificate. Defaults to `None` - :type certname: str - - :param lineage: Certificate lineage object. Defaults to `None` - :type lineage: storage.RenewableCert - - :returns: the issued certificate or `None` if doing a dry run - :rtype: storage.RenewableCert or None - - :raises errors.Error: if certificate could not be obtained - - """ - hooks.pre_hook(config) - try: - if lineage is not None: - # Renewal, where we already know the specific lineage we're - # interested in - logger.info("Renewing an existing certificate") - renewal.renew_cert(config, domains, le_client, lineage) - else: - # TREAT AS NEW REQUEST - assert domains is not None - logger.info("Obtaining a new certificate") - lineage = le_client.obtain_and_enroll_certificate(domains, certname) - if lineage is False: - raise errors.Error("Certificate could not be obtained") - elif lineage is not None: - hooks.deploy_hook(config, lineage.names(), lineage.live_dir) - finally: - hooks.post_hook(config) - - return lineage - - -def _handle_subset_cert_request(config, domains, cert): - """Figure out what to do if a previous cert had a subset of the names now requested - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param cert: Certificate object - :type cert: storage.RenewableCert - - :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname - action can be: "newcert" | "renew" | "reinstall" - :rtype: `tuple` of `str` - - """ - existing = ", ".join(cert.names()) - question = ( - "You have an existing certificate that contains a portion of " - "the domains you requested (ref: {0}){br}{br}It contains these " - "names: {1}{br}{br}You requested these names for the new " - "certificate: {2}.{br}{br}Do you want to expand and replace this existing " - "certificate with the new certificate?" - ).format(cert.configfile.filename, - existing, - ", ".join(domains), - br=os.linesep) - if config.expand or config.renew_by_default or zope.component.getUtility( - interfaces.IDisplay).yesno(question, "Expand", "Cancel", - cli_flag="--expand", - force_interactive=True): - return "renew", cert - else: - reporter_util = zope.component.getUtility(interfaces.IReporter) - reporter_util.add_message( - "To obtain a new certificate that contains these names without " - "replacing your existing certificate for {0}, you must use the " - "--duplicate option.{br}{br}" - "For example:{br}{br}{1} --duplicate {2}".format( - existing, - sys.argv[0], " ".join(sys.argv[1:]), - br=os.linesep - ), - reporter_util.HIGH_PRIORITY) - raise errors.Error(USER_CANCELLED) - - -def _handle_identical_cert_request(config, lineage): - """Figure out what to do if a lineage has the same names as a previously obtained one - - :param config: Configuration object - :type config: interfaces.IConfig - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname - action can be: "newcert" | "renew" | "reinstall" - :rtype: `tuple` of `str` - - """ - if not lineage.ensure_deployed(): - return "reinstall", lineage - if renewal.should_renew(config, lineage): - return "renew", lineage - if config.reinstall: - # Set with --reinstall, force an identical certificate to be - # reinstalled without further prompting. - return "reinstall", lineage - question = ( - "You have an existing certificate that has exactly the same " - "domains or certificate name you requested and isn't close to expiry." - "{br}(ref: {0}){br}{br}What would you like to do?" - ).format(lineage.configfile.filename, br=os.linesep) - - if config.verb == "run": - keep_opt = "Attempt to reinstall this existing certificate" - elif config.verb == "certonly": - keep_opt = "Keep the existing certificate for now" - choices = [keep_opt, - "Renew & replace the cert (limit ~5 per 7 days)"] - - display = zope.component.getUtility(interfaces.IDisplay) - response = display.menu(question, choices, - default=0, force_interactive=True) - if response[0] == display_util.CANCEL: - # TODO: Add notification related to command-line options for - # skipping the menu for this case. - raise errors.Error( - "Operation canceled. You may re-run the client.") - elif response[1] == 0: - return "reinstall", lineage - elif response[1] == 1: - return "renew", lineage - raise AssertionError('This is impossible') - - -def _find_lineage_for_domains(config, domains): - """Determine whether there are duplicated names and how to handle - them (renew, reinstall, newcert, or raising an error to stop - the client run if the user chooses to cancel the operation when - prompted). - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :returns: Two-element tuple containing desired new-certificate behavior as - a string token ("reinstall", "renew", or "newcert"), plus either - a RenewableCert instance or `None` if renewal shouldn't occur. - :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` - - :raises errors.Error: If the user would like to rerun the client again. - - """ - # Considering the possibility that the requested certificate is - # related to an existing certificate. (config.duplicate, which - # is set with --duplicate, skips all of this logic and forces any - # kind of certificate to be obtained with renewal = False.) - if config.duplicate: - return "newcert", None - # TODO: Also address superset case - ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, domains) - # XXX ^ schoen is not sure whether that correctly reads the systemwide - # configuration file. - if ident_names_cert is None and subset_names_cert is None: - return "newcert", None - - if ident_names_cert is not None: - return _handle_identical_cert_request(config, ident_names_cert) - elif subset_names_cert is not None: - return _handle_subset_cert_request(config, domains, subset_names_cert) - return None, None - -def _find_cert(config, domains, certname): - """Finds an existing certificate object given domains and/or a certificate name. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :returns: Two-element tuple of a boolean that indicates if this function should be - followed by a call to fetch a certificate from the server, and either a - RenewableCert instance or None. - :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None` - - """ - action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname) - if action == "reinstall": - logger.info("Keeping the existing certificate") - return (action != "reinstall"), lineage - -def _find_lineage_for_domains_and_certname(config, domains, certname): - """Find appropriate lineage based on given domains and/or certname. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param domains: List of domain names - :type domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :returns: Two-element tuple containing desired new-certificate behavior as - a string token ("reinstall", "renew", or "newcert"), plus either - a RenewableCert instance or None if renewal should not occur. - - :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` - - :raises errors.Error: If the user would like to rerun the client again. - - """ - if not certname: - return _find_lineage_for_domains(config, domains) - else: - lineage = cert_manager.lineage_for_certname(config, certname) - if lineage: - if domains: - if set(cert_manager.domains_for_certname(config, certname)) != set(domains): - _ask_user_to_confirm_new_names(config, domains, certname, - lineage.names()) # raises if no - return "renew", lineage - # unnecessarily specified domains or no domains specified - return _handle_identical_cert_request(config, lineage) - else: - if domains: - return "newcert", None - else: - raise errors.ConfigurationError("No certificate with name {0} found. " - "Use -d to specify domains, or run certbot certificates to see " - "possible certificate names.".format(certname)) - -def _get_added_removed(after, before): - """Get lists of items removed from `before` - and a lists of items added to `after` - """ - added = list(set(after) - set(before)) - removed = list(set(before) - set(after)) - added.sort() - removed.sort() - return added, removed - -def _format_list(character, strings): - """Format list with given character - """ - if not strings: - formatted = "{br}(None)" - else: - formatted = "{br}{ch} " + "{br}{ch} ".join(strings) - return formatted.format( - ch=character, - br=os.linesep - ) - -def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains): - """Ask user to confirm update cert certname to contain new_domains. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param new_domains: List of new domain names - :type new_domains: `list` of `str` - - :param certname: Name of certificate - :type certname: str - - :param old_domains: List of old domain names - :type old_domains: `list` of `str` - - :returns: None - :rtype: None - - :raises errors.ConfigurationError: if cert name and domains mismatch - - """ - if config.renew_with_new_domains: - return - - added, removed = _get_added_removed(new_domains, old_domains) - - msg = ("You are updating certificate {0} to include new domain(s): {1}{br}{br}" - "You are also removing previously included domain(s): {2}{br}{br}" - "Did you intend to make this change?".format( - certname, - _format_list("+", added), - _format_list("-", removed), - br=os.linesep)) - obj = zope.component.getUtility(interfaces.IDisplay) - if not obj.yesno(msg, "Update cert", "Cancel", default=True): - raise errors.ConfigurationError("Specified mismatched cert name and domains.") - -def _find_domains_or_certname(config, installer, question=None): - """Retrieve domains and certname from config or user input. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :param `str` question: Overriding dialog question to ask the user if asked - to choose from domain names. - - :returns: Two-part tuple of domains and certname - :rtype: `tuple` of list of `str` and `str` - - :raises errors.Error: Usage message, if parameters are not used correctly - - """ - domains = None - certname = config.certname - # first, try to get domains from the config - if config.domains: - domains = config.domains - # if we can't do that but we have a certname, get the domains - # with that certname - elif certname: - domains = cert_manager.domains_for_certname(config, certname) - - # that certname might not have existed, or there was a problem. - # try to get domains from the user. - if not domains: - domains = display_ops.choose_names(installer, question) - - if not domains and not certname: - raise errors.Error("Please specify --domains, or --installer that " - "will help in domain names autodiscovery, or " - "--cert-name for an existing certificate name.") - - return domains, certname - - -def _report_new_cert(config, cert_path, fullchain_path, key_path=None): - """Reports the creation of a new certificate to the user. - - :param cert_path: path to certificate - :type cert_path: str - - :param fullchain_path: path to full chain - :type fullchain_path: str - - :param key_path: path to private key, if available - :type key_path: str - - :returns: `None` - :rtype: None - - """ - if config.dry_run: - _report_successful_dry_run(config) - return - - assert cert_path and fullchain_path, "No certificates saved to report." - - expiry = crypto_util.notAfter(cert_path).date() - reporter_util = zope.component.getUtility(interfaces.IReporter) - # Print the path to fullchain.pem because that's what modern webservers - # (Nginx and Apache2.4) will want. - - verbswitch = ' with the "certonly" option' if config.verb == "run" else "" - privkey_statement = 'Your key file has been saved at:{br}{0}{br}'.format( - key_path, br=os.linesep) if key_path else "" - # XXX Perhaps one day we could detect the presence of known old webservers - # and say something more informative here. - msg = ('Congratulations! Your certificate and chain have been saved at:{br}' - '{0}{br}{1}' - 'Your cert will expire on {2}. To obtain a new or tweaked version of this ' - 'certificate in the future, simply run {3} again{4}. ' - 'To non-interactively renew *all* of your certificates, run "{3} renew"' - .format(fullchain_path, privkey_statement, expiry, cli.cli_command, verbswitch, - br=os.linesep)) - reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY) - - -def _determine_account(config): - """Determine which account to use. - - If ``config.account`` is ``None``, it will be updated based on the - user input. Same for ``config.email``. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: Account and optionally ACME client API (biproduct of new - registration). - :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client` - - :raises errors.Error: If unable to register an account with ACME server - - """ - def _tos_cb(terms_of_service): - if config.tos: - return True - msg = ("Please read the Terms of Service at {0}. You " - "must agree in order to register with the ACME " - "server at {1}".format( - terms_of_service, config.server)) - obj = zope.component.getUtility(interfaces.IDisplay) - result = obj.yesno(msg, "Agree", "Cancel", - cli_flag="--agree-tos", force_interactive=True) - if not result: - raise errors.Error( - "Registration cannot proceed without accepting " - "Terms of Service.") - return None - - account_storage = account.AccountFileStorage(config) - acme = None - - if config.account is not None: - acc = account_storage.load(config.account) - else: - accounts = account_storage.find_all() - if len(accounts) > 1: - acc = display_ops.choose_account(accounts) - elif len(accounts) == 1: - acc = accounts[0] - else: # no account registered yet - if config.email is None and not config.register_unsafely_without_email: - config.email = display_ops.get_email() - try: - acc, acme = client.register( - config, account_storage, tos_cb=_tos_cb) - except errors.MissingCommandlineFlag: - raise - except errors.Error: - logger.debug("", exc_info=True) - raise errors.Error( - "Unable to register an account with ACME server") - - config.account = acc.id - return acc, acme - - -def _delete_if_appropriate(config): - """Does the user want to delete their now-revoked certs? If run in non-interactive mode, - deleting happens automatically. - - :param config: parsed command line arguments - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping - archive dir is found for the specified lineage, etc ... - """ - display = zope.component.getUtility(interfaces.IDisplay) - reporter_util = zope.component.getUtility(interfaces.IReporter) - - attempt_deletion = config.delete_after_revoke - if attempt_deletion is None: - msg = ("Would you like to delete the cert(s) you just revoked, along with all earlier and " - "later versions of the cert?") - attempt_deletion = display.yesno(msg, yes_label="Yes (recommended)", no_label="No", - force_interactive=True, default=True) - - if not attempt_deletion: - reporter_util.add_message("Not deleting revoked certs.", reporter_util.LOW_PRIORITY) - return - - # config.cert_path must have been set - # config.certname may have been set - assert config.cert_path - - if not config.certname: - config.certname = cert_manager.cert_path_to_lineage(config) - - # don't delete if the archive_dir is used by some other lineage - archive_dir = storage.full_archive_path( - configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)), - config, config.certname) - try: - cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir], - lambda x: x.archive_dir, lambda x: x) - except errors.OverlappingMatchFound: - msg = ('Not deleting revoked certs due to overlapping archive dirs. More than ' - 'one lineage is using {0}'.format(archive_dir)) - reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY) - return - except Exception as e: - msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},' - 'original exception: {3}') - msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e) - raise errors.Error(msg) - - cert_manager.delete(config) - - -def _init_le_client(config, authenticator, installer): - """Initialize Let's Encrypt Client - - :param config: Configuration object - :type config: interfaces.IConfig - - :param authenticator: Acme authentication handler - :type authenticator: interfaces.IAuthenticator - :param installer: Installer object - :type installer: interfaces.IInstaller - - :returns: client: Client object - :rtype: client.Client - - """ - if authenticator is not None: - # if authenticator was given, then we will need account... - acc, acme = _determine_account(config) - logger.debug("Picked account: %r", acc) - # XXX - #crypto_util.validate_key_csr(acc.key) - else: - acc, acme = None, None - - return client.Client(config, acc, authenticator, installer, acme=acme) - - -def unregister(config, unused_plugins): - """Deactivate account on server - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - account_storage = account.AccountFileStorage(config) - accounts = account_storage.find_all() - reporter_util = zope.component.getUtility(interfaces.IReporter) - - if not accounts: - return "Could not find existing account to deactivate." - yesno = zope.component.getUtility(interfaces.IDisplay).yesno - prompt = ("Are you sure you would like to irrevocably deactivate " - "your account?") - wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort', - default=True) - - if not wants_deactivate: - return "Deactivation aborted." - - acc, acme = _determine_account(config) - cb_client = client.Client(config, acc, None, None, acme=acme) - - # delete on boulder - cb_client.acme.deactivate_registration(acc.regr) - account_files = account.AccountFileStorage(config) - # delete local account files - account_files.delete(config.account) - - reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY) - return None - - -def register(config, unused_plugins): - """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 - - """ - # Portion of _determine_account logic to see whether accounts already - # exist or not. - account_storage = account.AccountFileStorage(config) - accounts = account_storage.find_all() - - if accounts: - # 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 None - - -def update_account(config, unused_plugins): - """Modify 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 - - """ - # Portion of _determine_account logic to see whether accounts already - # exist or not. - account_storage = account.AccountFileStorage(config) - accounts = account_storage.find_all() - reporter_util = zope.component.getUtility(interfaces.IReporter) - add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY) - - if not accounts: - return "Could not find an existing account to update." - if config.email is None: - if config.register_unsafely_without_email: - return ("--register-unsafely-without-email provided, however, a " - "new e-mail address must\ncurrently be provided when " - "updating a registration.") - config.email = display_ops.get_email(optional=False) - - acc, acme = _determine_account(config) - cb_client = client.Client(config, acc, None, None, acme=acme) - # We rely on an exception to interrupt this process if it didn't work. - acc_contacts = ['mailto:' + email for email in config.email.split(',')] - prev_regr_uri = acc.regr.uri - acc.regr = cb_client.acme.update_registration(acc.regr.update( - body=acc.regr.body.update(contact=acc_contacts))) - # A v1 account being used as a v2 account will result in changing the uri to - # the v2 uri. Since it's the same object on disk, put it back to the v1 uri - # so that we can also continue to use the account object with acmev1. - acc.regr = acc.regr.update(uri=prev_regr_uri) - account_storage.save_regr(acc, cb_client.acme) - eff.handle_subscription(config) - add_msg("Your e-mail address was updated to {0}.".format(config.email)) - return None - -def _install_cert(config, le_client, domains, lineage=None): - """Install a cert - - :param config: Configuration object - :type config: interfaces.IConfig - - :param le_client: Client object - :type le_client: client.Client - - :param domains: List of domains - :type domains: `list` of `str` - - :param lineage: Certificate lineage object. Defaults to `None` - :type lineage: storage.RenewableCert - - :returns: `None` - :rtype: None - - """ - path_provider = lineage if lineage else config - assert path_provider.cert_path is not None - - le_client.deploy_certificate(domains, path_provider.key_path, - path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path) - le_client.enhance_config(domains, path_provider.chain_path) - - -def install(config, plugins): - """Install a previously obtained cert in a server. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - # XXX: Update for renewer/RenewableCert - # FIXME: be consistent about whether errors are raised or returned from - # this function ... - - try: - installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "install") - except errors.PluginSelectionError as e: - return str(e) - - custom_cert = (config.key_path and config.cert_path) - if not config.certname and not custom_cert: - certname_question = "Which certificate would you like to install?" - config.certname = cert_manager.get_certnames( - config, "install", allow_multiple=False, - custom_prompt=certname_question)[0] - - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - # If cert-path is defined, populate missing (ie. not overridden) values. - # Unfortunately this can't be done in argument parser, as certificate - # manager needs the access to renewal directory paths - if config.certname: - config = _populate_from_certname(config) - elif enhancements.are_requested(config): - # Preflight config check - raise errors.ConfigurationError("One or more of the requested enhancements " - "require --cert-name to be provided") - - if config.key_path and config.cert_path: - _check_certificate_and_key(config) - domains, _ = _find_domains_or_certname(config, installer) - le_client = _init_le_client(config, authenticator=None, installer=installer) - _install_cert(config, le_client, domains) - else: - raise errors.ConfigurationError("Path to certificate or key was not defined. " - "If your certificate is managed by Certbot, please use --cert-name " - "to define which certificate you would like to install.") - - if enhancements.are_requested(config): - # In the case where we don't have certname, we have errored out already - lineage = cert_manager.lineage_for_certname(config, config.certname) - enhancements.enable(lineage, domains, installer, config) - - return None - -def _populate_from_certname(config): - """Helper function for install to populate missing config values from lineage - defined by --cert-name.""" - - lineage = cert_manager.lineage_for_certname(config, config.certname) - if not lineage: - return config - if not config.key_path: - config.namespace.key_path = lineage.key_path - if not config.cert_path: - config.namespace.cert_path = lineage.cert_path - if not config.chain_path: - config.namespace.chain_path = lineage.chain_path - if not config.fullchain_path: - config.namespace.fullchain_path = lineage.fullchain_path - return config - -def _check_certificate_and_key(config): - if not os.path.isfile(filesystem.realpath(config.cert_path)): - raise errors.ConfigurationError("Error while reading certificate from path " - "{0}".format(config.cert_path)) - if not os.path.isfile(filesystem.realpath(config.key_path)): - raise errors.ConfigurationError("Error while reading private key from path " - "{0}".format(config.key_path)) -def plugins_cmd(config, plugins): - """List server software plugins. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - logger.debug("Expected interfaces: %s", config.ifaces) - - ifaces = [] if config.ifaces is None else config.ifaces - filtered = plugins.visible().ifaces(ifaces) - logger.debug("Filtered plugins: %r", filtered) - - notify = functools.partial(zope.component.getUtility( - interfaces.IDisplay).notification, pause=False) - if not config.init and not config.prepare: - notify(str(filtered)) - return - - filtered.init(config) - verified = filtered.verify(ifaces) - logger.debug("Verified plugins: %r", verified) - - if not config.prepare: - notify(str(verified)) - return - - verified.prepare() - available = verified.available() - logger.debug("Prepared plugins: %s", available) - notify(str(available)) - - -def enhance(config, plugins): - """Add security enhancements to existing configuration - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - supported_enhancements = ["hsts", "redirect", "uir", "staple"] - # Check that at least one enhancement was requested on command line - oldstyle_enh = any([getattr(config, enh) for enh in supported_enhancements]) - if not enhancements.are_requested(config) and not oldstyle_enh: - msg = ("Please specify one or more enhancement types to configure. To list " - "the available enhancement types, run:\n\n%s --help enhance\n") - logger.warning(msg, sys.argv[0]) - raise errors.MisconfigurationError("No enhancements requested, exiting.") - - try: - installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance") - except errors.PluginSelectionError as e: - return str(e) - - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - - certname_question = ("Which certificate would you like to use to enhance " - "your configuration?") - config.certname = cert_manager.get_certnames( - config, "enhance", allow_multiple=False, - custom_prompt=certname_question)[0] - cert_domains = cert_manager.domains_for_certname(config, config.certname) - if config.noninteractive_mode: - domains = cert_domains - else: - domain_question = ("Which domain names would you like to enable the " - "selected enhancements for?") - domains = display_ops.choose_values(cert_domains, domain_question) - if not domains: - raise errors.Error("User cancelled the domain selection. No domains " - "defined, exiting.") - - lineage = cert_manager.lineage_for_certname(config, config.certname) - if not config.chain_path: - config.chain_path = lineage.chain_path - if oldstyle_enh: - le_client = _init_le_client(config, authenticator=None, installer=installer) - le_client.enhance_config(domains, config.chain_path, ask_redirect=False) - if enhancements.are_requested(config): - enhancements.enable(lineage, domains, installer, config) - - return None - - -def rollback(config, plugins): - """Rollback server configuration changes made during install. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - client.rollback(config.installer, config.checkpoints, config, plugins) - -def update_symlinks(config, unused_plugins): - """Update the certificate file family symlinks - - Use the information in the config file to make symlinks point to - the correct archive directory. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.update_live_symlinks(config) - -def rename(config, unused_plugins): - """Rename a certificate - - Use the information in the config file to rename an existing - lineage. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.rename_lineage(config) - -def delete(config, unused_plugins): - """Delete a certificate - - Use the information in the config file to delete an existing - lineage. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.delete(config) - -def certificates(config, unused_plugins): - """Display information about certs configured with Certbot - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - cert_manager.certificates(config) - -# TODO: coop with renewal config -def revoke(config, unused_plugins): - """Revoke a previously obtained certificate. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` or string indicating error in case of error - :rtype: None or str - - """ - # For user-agent construction - config.installer = config.authenticator = None - - if config.cert_path is None and config.certname: - config.cert_path = storage.cert_path_for_cert_name(config, config.certname) - elif not config.cert_path or (config.cert_path and config.certname): - # intentionally not supporting --cert-path & --cert-name together, - # to avoid dealing with mismatched values - raise errors.Error("Error! Exactly one of --cert-path or --cert-name must be specified!") - - if config.key_path is not None: # revocation by cert key - logger.debug("Revoking %s using cert key %s", - config.cert_path[0], config.key_path[0]) - crypto_util.verify_cert_matches_priv_key(config.cert_path[0], config.key_path[0]) - key = jose.JWK.load(config.key_path[1]) - acme = client.acme_from_config_key(config, key) - else: # revocation by account key - logger.debug("Revoking %s using Account Key", config.cert_path[0]) - acc, _ = _determine_account(config) - acme = client.acme_from_config_key(config, acc.key, acc.regr) - cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0] - logger.debug("Reason code for revocation: %s", config.reason) - try: - acme.revoke(jose.ComparableX509(cert), config.reason) - _delete_if_appropriate(config) - except acme_errors.ClientError as e: - return str(e) - - display_ops.success_revocation(config.cert_path[0]) - return None - - -def run(config, plugins): - """Obtain a certificate and install. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - # TODO: Make run as close to auth + install as possible - # Possible difficulties: config.csr was hacked into auth - try: - installer, authenticator = plug_sel.choose_configurator_plugins(config, plugins, "run") - except errors.PluginSelectionError as e: - return str(e) - - # Preflight check for enhancement support by the selected installer - if not enhancements.are_supported(config, installer): - raise errors.NotSupportedError("One ore more of the requested enhancements " - "are not supported by the selected installer") - - # TODO: Handle errors from _init_le_client? - le_client = _init_le_client(config, authenticator, installer) - - domains, certname = _find_domains_or_certname(config, installer) - should_get_cert, lineage = _find_cert(config, domains, certname) - - new_lineage = lineage - if should_get_cert: - new_lineage = _get_and_save_cert(le_client, config, domains, - certname, lineage) - - cert_path = new_lineage.cert_path if new_lineage else None - fullchain_path = new_lineage.fullchain_path if new_lineage else None - key_path = new_lineage.key_path if new_lineage else None - _report_new_cert(config, cert_path, fullchain_path, key_path) - - _install_cert(config, le_client, domains, new_lineage) - - if enhancements.are_requested(config) and new_lineage: - enhancements.enable(new_lineage, domains, installer, config) - - if lineage is None or not should_get_cert: - display_ops.success_installation(domains) - else: - display_ops.success_renewal(domains) - - _suggest_donation_if_appropriate(config) - return None - - -def _csr_get_and_save_cert(config, le_client): - """Obtain a cert using a user-supplied CSR - - This works differently in the CSR case (for now) because we don't - have the privkey, and therefore can't construct the files for a lineage. - So we just save the cert & chain to disk :/ - - :param config: Configuration object - :type config: interfaces.IConfig - - :param client: Client object - :type client: client.Client - - :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files - :rtype: `tuple` of `str` - - """ - csr, _ = config.actual_csr - cert, chain = le_client.obtain_certificate_from_csr(csr) - if config.dry_run: - logger.debug( - "Dry run: skipping saving certificate to %s", config.cert_path) - return None, None - cert_path, _, fullchain_path = le_client.save_certificate( - cert, chain, os.path.normpath(config.cert_path), - os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path)) - return cert_path, fullchain_path - -def renew_cert(config, plugins, lineage): - """Renew & save an existing cert. Do not install it. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :returns: `None` - :rtype: None - - :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass - - """ - try: - # installers are used in auth mode to determine domain names - installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") - except errors.PluginSelectionError as e: - logger.info("Could not choose appropriate plugin: %s", e) - raise - le_client = _init_le_client(config, auth, installer) - - renewed_lineage = _get_and_save_cert(le_client, config, lineage=lineage) - - notify = zope.component.getUtility(interfaces.IDisplay).notification - if installer is None: - notify("new certificate deployed without reload, fullchain is {0}".format( - lineage.fullchain), pause=False) - else: - # In case of a renewal, reload server to pick up new certificate. - # In principle we could have a configuration option to inhibit this - # from happening. - # Run deployer - updater.run_renewal_deployer(config, renewed_lineage, installer) - installer.restart() - notify("new certificate deployed with reload of {0} server; fullchain is {1}".format( - config.installer, lineage.fullchain), pause=False) - -def certonly(config, plugins): - """Authenticate & obtain cert, but do not install it. - - This implements the 'certonly' subcommand. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - - :raises errors.Error: If specified plugin could not be used - - """ - # SETUP: Select plugins and construct a client instance - try: - # installers are used in auth mode to determine domain names - installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") - except errors.PluginSelectionError as e: - logger.info("Could not choose appropriate plugin: %s", e) - raise - - le_client = _init_le_client(config, auth, installer) - - if config.csr: - cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client) - _report_new_cert(config, cert_path, fullchain_path) - _suggest_donation_if_appropriate(config) - return - - domains, certname = _find_domains_or_certname(config, installer) - should_get_cert, lineage = _find_cert(config, domains, certname) - - if not should_get_cert: - notify = zope.component.getUtility(interfaces.IDisplay).notification - notify("Certificate not yet due for renewal; no action taken.", pause=False) - return - - lineage = _get_and_save_cert(le_client, config, domains, certname, lineage) - - cert_path = lineage.cert_path if lineage else None - fullchain_path = lineage.fullchain_path if lineage else None - key_path = lineage.key_path if lineage else None - _report_new_cert(config, cert_path, fullchain_path, key_path) - _suggest_donation_if_appropriate(config) - -def renew(config, unused_plugins): - """Renew previously-obtained certificates. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - try: - renewal.handle_renewal_request(config) - finally: - hooks.run_saved_post_hooks() - - -def make_or_verify_needed_dirs(config): - """Create or verify existence of config, work, and hook directories. - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) - util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) - - hook_dirs = (config.renewal_pre_hooks_dir, - config.renewal_deploy_hooks_dir, - config.renewal_post_hooks_dir,) - for hook_dir in hook_dirs: - util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) - - -def set_displayer(config): - """Set the displayer - - :param config: Configuration object - :type config: interfaces.IConfig - - :returns: `None` - :rtype: None - - """ - if config.quiet: - config.noninteractive_mode = True - displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) \ - # type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay] - elif config.noninteractive_mode: - displayer = display_util.NoninteractiveDisplay(sys.stdout) - else: - displayer = display_util.FileDisplay(sys.stdout, - config.force_interactive) - zope.component.provideUtility(displayer) - - -def main(cli_args=None): - """Command line argument parsing and main script execution. - - :returns: result of requested command - - :raises errors.Error: OS errors triggered by wrong permissions - :raises errors.Error: error if plugin command is not supported - - """ - if not cli_args: - cli_args = sys.argv[1:] - - log.pre_arg_parse_setup() - - plugins = plugins_disco.PluginsRegistry.find_all() - logger.debug("certbot version: %s", certbot.__version__) - # do not log `config`, as it contains sensitive data (e.g. revoke --key)! - logger.debug("Arguments: %r", cli_args) - logger.debug("Discovered plugins: %r", plugins) - - # note: arg parser internally handles --help (and exits afterwards) - args = cli.prepare_and_parse_args(plugins, cli_args) - config = configuration.NamespaceConfig(args) - zope.component.provideUtility(config) - - # On windows, shell without administrative right cannot create symlinks required by certbot. - # So we check the rights before continuing. - misc.raise_for_non_administrative_windows_rights() - - try: - log.post_arg_parse_setup(config) - make_or_verify_needed_dirs(config) - except errors.Error: - # Let plugins_cmd be run as un-privileged user. - if config.func != plugins_cmd: - raise - - set_displayer(config) - - # Reporter - report = reporter.Reporter(config) - zope.component.provideUtility(report) - util.atexit_register(report.print_messages) - - return config.func(config, plugins) - - -if __name__ == "__main__": - err_string = main() - if err_string: - logger.warning("Exiting with message %s", err_string) - sys.exit(err_string) # pragma: no cover diff --git a/certbot/_internal/notify.py b/certbot/_internal/notify.py deleted file mode 100644 index dda0a85af..000000000 --- a/certbot/_internal/notify.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Send e-mail notification to system administrators.""" - -import email -import smtplib -import socket -import subprocess - - -def notify(subject, whom, what): - """Send email notification. - - Try to notify the addressee (``whom``) by e-mail, with Subject: - defined by ``subject`` and message body by ``what``. - - """ - msg = email.message_from_string(what) - msg.add_header("From", "Certbot renewal agent ") - msg.add_header("To", whom) - msg.add_header("Subject", subject) - msg = msg.as_string() - try: - lmtp = smtplib.LMTP() - lmtp.connect() - lmtp.sendmail("root", [whom], msg) - except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused, - smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error): - # We should try using /usr/sbin/sendmail in this case - try: - proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"], - stdin=subprocess.PIPE) - proc.communicate(msg) - except OSError: - return False - return True diff --git a/certbot/_internal/ocsp.py b/certbot/_internal/ocsp.py deleted file mode 100644 index 2a63412a0..000000000 --- a/certbot/_internal/ocsp.py +++ /dev/null @@ -1,292 +0,0 @@ -"""Tools for checking certificate revocation.""" -import logging -import re -from datetime import datetime, timedelta -from subprocess import Popen, PIPE - -try: - # Only cryptography>=2.5 has ocsp module - # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error - getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') -except (ImportError, AttributeError): # pragma: no cover - ocsp = None # type: ignore -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -import pytz -import requests - -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module -from certbot import crypto_util -from certbot import errors -from certbot._internal.storage import RenewableCert # pylint: disable=unused-import -from certbot import util - -logger = logging.getLogger(__name__) - - -class RevocationChecker(object): - """This class figures out OCSP checking on this system, and performs it.""" - - def __init__(self, enforce_openssl_binary_usage=False): - self.broken = False - self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp - - if self.use_openssl_binary: - if not util.exe_exists("openssl"): - logger.info("openssl not installed, can't check revocation") - self.broken = True - return - - # New versions of openssl want -header var=val, old ones want -header var val - test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) - _out, err = test_host_format.communicate() - if "Missing =" in err: - self.host_args = lambda host: ["Host=" + host] - else: - self.host_args = lambda host: ["Host", host] - - def ocsp_revoked(self, cert): - # type: (RenewableCert) -> bool - """Get revoked status for a particular cert version. - - .. todo:: Make this a non-blocking call - - :param `.storage.RenewableCert` cert: Certificate object - :returns: True if revoked; False if valid or the check failed or cert is expired. - :rtype: bool - - """ - cert_path, chain_path = cert.cert, cert.chain - - if self.broken: - return False - - # Let's Encrypt doesn't update OCSP for expired certificates, - # so don't check OCSP if the cert is expired. - # https://github.com/certbot/certbot/issues/7152 - now = pytz.UTC.fromutc(datetime.utcnow()) - if cert.target_expiry <= now: - return False - - url, host = _determine_ocsp_server(cert_path) - if not host or not url: - return False - - if self.use_openssl_binary: - return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) - return _check_ocsp_cryptography(cert_path, chain_path, url) - - def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): - # type: (str, str, str, str) -> bool - # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! - cmd = ["openssl", "ocsp", - "-no_nonce", - "-issuer", chain_path, - "-cert", cert_path, - "-url", url, - "-CAfile", chain_path, - "-verify_other", chain_path, - "-trust_other", - "-header"] + self.host_args(host) - logger.debug("Querying OCSP for %s", cert_path) - logger.debug(" ".join(cmd)) - try: - output, err = util.run_script(cmd, log=logger.debug) - except errors.SubprocessError: - logger.info("OCSP check failed for %s (are we offline?)", cert_path) - return False - return _translate_ocsp_query(cert_path, output, err) - - -def _determine_ocsp_server(cert_path): - # type: (str) -> Tuple[Optional[str], Optional[str]] - """Extract the OCSP server host from a certificate. - - :param str cert_path: Path to the cert we're checking OCSP for - :rtype tuple: - :returns: (OCSP server URL or None, OCSP server host or None) - - """ - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - try: - extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) - ocsp_oid = x509.AuthorityInformationAccessOID.OCSP - descriptions = [description for description in extension.value - if description.access_method == ocsp_oid] - - url = descriptions[0].access_location.value - except (x509.ExtensionNotFound, IndexError): - logger.info("Cannot extract OCSP URI from %s", cert_path) - return None, None - - url = url.rstrip() - host = url.partition("://")[2].rstrip("/") - - if host: - return url, host - logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) - return None, None - - -def _check_ocsp_cryptography(cert_path, chain_path, url): - # type: (str, str, str) -> bool - # Retrieve OCSP response - with open(chain_path, 'rb') as file_handler: - issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - with open(cert_path, 'rb') as file_handler: - cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) - builder = ocsp.OCSPRequestBuilder() - builder = builder.add_certificate(cert, issuer, hashes.SHA1()) - request = builder.build() - request_binary = request.public_bytes(serialization.Encoding.DER) - try: - response = requests.post(url, data=request_binary, - headers={'Content-Type': 'application/ocsp-request'}) - except requests.exceptions.RequestException: - logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True) - return False - if response.status_code != 200: - logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code) - return False - - response_ocsp = ocsp.load_der_ocsp_response(response.content) - - # Check OCSP response validity - if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: - logger.error("Invalid OCSP response status for %s: %s", - cert_path, response_ocsp.response_status) - return False - - # Check OCSP signature - try: - _check_ocsp_response(response_ocsp, request, issuer, cert_path) - except UnsupportedAlgorithm as e: - logger.error(str(e)) - except errors.Error as e: - logger.error(str(e)) - except InvalidSignature: - logger.error('Invalid signature on OCSP response for %s', cert_path) - except AssertionError as error: - logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) - else: - # Check OCSP certificate status - logger.debug("OCSP certificate status for %s is: %s", - cert_path, response_ocsp.certificate_status) - return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED - - return False - - -def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): - """Verify that the OCSP is valid for serveral criterias""" - # Assert OCSP response corresponds to the certificate we are talking about - if response_ocsp.serial_number != request_ocsp.serial_number: - raise AssertionError('the certificate in response does not correspond ' - 'to the certificate in request') - - # Assert signature is valid - _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path) - - # Assert issuer in response is the expected one - if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) - or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash - or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): - raise AssertionError('the issuer does not correspond to issuer of the certificate.') - - # In following checks, two situations can occur: - # * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate - # * nextUpdate is not set, and requirement is thisUpdate < now - # NB1: We add a validity period tolerance to handle clock time inconsistencies, - # value is 5 min like for OpenSSL. - # NB2: Another check is to verify that thisUpdate is not too old, it is optional - # for OpenSSL, so we do not do it here. - # See OpenSSL implementation as a reference: - # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 - now = datetime.utcnow() # thisUpdate/nextUpdate are expressed in UTC/GMT time zone - if not response_ocsp.this_update: - raise AssertionError('param thisUpdate is not set.') - if response_ocsp.this_update > now + timedelta(minutes=5): - raise AssertionError('param thisUpdate is in the future.') - if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5): - raise AssertionError('param nextUpdate is in the past.') - - -def _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path): - """Verify an OCSP response signature against certificate issuer or responder""" - if response_ocsp.responder_name == issuer_cert.subject: - # Case where the OCSP responder is also the certificate issuer - logger.debug('OCSP response for certificate %s is signed by the certificate\'s issuer.', - cert_path) - responder_cert = issuer_cert - else: - # Case where the OCSP responder is not the certificate issuer - logger.debug('OCSP response for certificate %s is delegated to an external responder.', - cert_path) - - responder_certs = [cert for cert in response_ocsp.certificates - if cert.subject == response_ocsp.responder_name] - if not responder_certs: - raise AssertionError('no matching responder certificate could be found') - - # We suppose here that the ACME server support only one certificate in the OCSP status - # request. This is currently the case for LetsEncrypt servers. - # See https://github.com/letsencrypt/boulder/issues/2331 - responder_cert = responder_certs[0] - - if responder_cert.issuer != issuer_cert.subject: - raise AssertionError('responder certificate is not signed ' - 'by the certificate\'s issuer') - - try: - extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) - delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value - except (x509.ExtensionNotFound, IndexError): - delegate_authorized = False - if not delegate_authorized: - raise AssertionError('responder is not authorized by issuer to sign OCSP responses') - - # Following line may raise UnsupportedAlgorithm - chosen_hash = responder_cert.signature_hash_algorithm - # For a delegate OCSP responder, we need first check that its certificate is effectively - # signed by the certificate issuer. - crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, - responder_cert.tbs_certificate_bytes, chosen_hash) - - # Following line may raise UnsupportedAlgorithm - chosen_hash = response_ocsp.signature_hash_algorithm - # We check that the OSCP response is effectively signed by the responder - # (an authorized delegate one or the certificate issuer itself). - crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature, - response_ocsp.tbs_response_bytes, chosen_hash) - - -def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): - """Parse openssl's weird output to work out what it means.""" - - states = ("good", "revoked", "unknown") - patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states] - good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns) - - warning = good.group(1) if good else None - - if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: - logger.info("Revocation status for %s is unknown", cert_path) - logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) - return False - elif good and not warning: - return False - elif revoked: - warning = revoked.group(1) - if warning: - logger.info("OCSP revocation warning: %s", warning) - return True - else: - logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) - return False diff --git a/certbot/_internal/plugins/__init__.py b/certbot/_internal/plugins/__init__.py deleted file mode 100644 index 7831eab61..000000000 --- a/certbot/_internal/plugins/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot plugins.""" diff --git a/certbot/_internal/plugins/disco.py b/certbot/_internal/plugins/disco.py deleted file mode 100644 index 0bee88ae1..000000000 --- a/certbot/_internal/plugins/disco.py +++ /dev/null @@ -1,289 +0,0 @@ -"""Utilities for plugins discovery and selection.""" -import collections -import itertools -import logging - -import pkg_resources -import six - -import zope.interface -import zope.interface.verify - -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from certbot._internal import constants -from certbot import errors -from certbot import interfaces - - -logger = logging.getLogger(__name__) - - -class PluginEntryPoint(object): - """Plugin entry point.""" - - PREFIX_FREE_DISTRIBUTIONS = [ - "certbot", - "certbot-apache", - "certbot-dns-cloudflare", - "certbot-dns-cloudxns", - "certbot-dns-digitalocean", - "certbot-dns-dnsimple", - "certbot-dns-dnsmadeeasy", - "certbot-dns-gehirn", - "certbot-dns-google", - "certbot-dns-linode", - "certbot-dns-luadns", - "certbot-dns-nsone", - "certbot-dns-ovh", - "certbot-dns-rfc2136", - "certbot-dns-route53", - "certbot-dns-sakuracloud", - "certbot-nginx", - ] - """Distributions for which prefix will be omitted.""" - - # this object is mutable, don't allow it to be hashed! - __hash__ = None # type: ignore - - def __init__(self, entry_point): - self.name = self.entry_point_to_plugin_name(entry_point) - self.plugin_cls = entry_point.load() - self.entry_point = entry_point - self._initialized = None - self._prepared = None - - @classmethod - def entry_point_to_plugin_name(cls, entry_point): - """Unique plugin name for an ``entry_point``""" - if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: - return entry_point.name - return entry_point.dist.key + ":" + entry_point.name - - @property - def description(self): - """Description of the plugin.""" - return self.plugin_cls.description - - @property - def description_with_name(self): - """Description with name. Handy for UI.""" - return "{0} ({1})".format(self.description, self.name) - - @property - def long_description(self): - """Long description of the plugin.""" - try: - return self.plugin_cls.long_description - except AttributeError: - return self.description - - @property - def hidden(self): - """Should this plugin be hidden from UI?""" - return getattr(self.plugin_cls, "hidden", False) - - def ifaces(self, *ifaces_groups): - """Does plugin implements specified interface groups?""" - return not ifaces_groups or any( - all(iface.implementedBy(self.plugin_cls) - for iface in ifaces) - for ifaces in ifaces_groups) - - @property - def initialized(self): - """Has the plugin been initialized already?""" - return self._initialized is not None - - def init(self, config=None): - """Memoized plugin initialization.""" - if not self.initialized: - self.entry_point.require() # fetch extras! - self._initialized = self.plugin_cls(config, self.name) - return self._initialized - - def verify(self, ifaces): - """Verify that the plugin conforms to the specified interfaces.""" - assert self.initialized - for iface in ifaces: # zope.interface.providedBy(plugin) - try: - zope.interface.verify.verifyObject(iface, self.init()) - except zope.interface.exceptions.BrokenImplementation as error: - if iface.implementedBy(self.plugin_cls): - logger.debug( - "%s implements %s but object does not verify: %s", - self.plugin_cls, iface.__name__, error, exc_info=True) - return False - return True - - @property - def prepared(self): - """Has the plugin been prepared already?""" - if not self.initialized: - logger.debug(".prepared called on uninitialized %r", self) - return self._prepared is not None - - def prepare(self): - """Memoized plugin preparation.""" - assert self.initialized - if self._prepared is None: - try: - self._initialized.prepare() - except errors.MisconfigurationError as error: - logger.debug("Misconfigured %r: %s", self, error, exc_info=True) - self._prepared = error - except errors.NoInstallationError as error: - logger.debug( - "No installation (%r): %s", self, error, exc_info=True) - self._prepared = error - except errors.PluginError as error: - logger.debug("Other error:(%r): %s", self, error, exc_info=True) - self._prepared = error - else: - self._prepared = True - return self._prepared - - @property - def misconfigured(self): - """Is plugin misconfigured?""" - return isinstance(self._prepared, errors.MisconfigurationError) - - @property - def problem(self): - """Return the Exception raised during plugin setup, or None if all is well""" - if isinstance(self._prepared, Exception): - return self._prepared - return None - - @property - def available(self): - """Is plugin available, i.e. prepared or misconfigured?""" - return self._prepared is True or self.misconfigured - - def __repr__(self): - return "PluginEntryPoint#{0}".format(self.name) - - def __str__(self): - lines = [ - "* {0}".format(self.name), - "Description: {0}".format(self.plugin_cls.description), - "Interfaces: {0}".format(", ".join( - iface.__name__ for iface in zope.interface.implementedBy( - self.plugin_cls))), - "Entry point: {0}".format(self.entry_point), - ] - - if self.initialized: - lines.append("Initialized: {0}".format(self.init())) - if self.prepared: - lines.append("Prep: {0}".format(self.prepare())) - - return "\n".join(lines) - - -class PluginsRegistry(collections.Mapping): - """Plugins registry.""" - - def __init__(self, plugins): - # plugins are sorted so the same order is used between runs. - # This prevents deadlock caused by plugins acquiring a lock - # and ensures at least one concurrent Certbot instance will run - # successfully. - - # Pylint checks for super init, but also claims the super - # has no __init__member - # pylint: disable=super-init-not-called - self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) - - @classmethod - def find_all(cls): - """Find plugins using setuptools entry points.""" - plugins = {} # type: Dict[str, PluginEntryPoint] - # pylint: disable=not-callable - entry_points = itertools.chain( - pkg_resources.iter_entry_points( - constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), - pkg_resources.iter_entry_points( - constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),) - for entry_point in entry_points: - plugin_ep = PluginEntryPoint(entry_point) - assert plugin_ep.name not in plugins, ( - "PREFIX_FREE_DISTRIBUTIONS messed up") - # providedBy | pylint: disable=no-member - if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): - plugins[plugin_ep.name] = plugin_ep - else: # pragma: no cover - logger.warning( - "%r does not provide IPluginFactory, skipping", plugin_ep) - return cls(plugins) - - def __getitem__(self, name): - return self._plugins[name] - - def __iter__(self): - return iter(self._plugins) - - def __len__(self): - return len(self._plugins) - - def init(self, config): - """Initialize all plugins in the registry.""" - return [plugin_ep.init(config) for plugin_ep - in six.itervalues(self._plugins)] - - def filter(self, pred): - """Filter plugins based on predicate.""" - return type(self)(dict((name, plugin_ep) for name, plugin_ep - in six.iteritems(self._plugins) if pred(plugin_ep))) - - def visible(self): - """Filter plugins based on visibility.""" - return self.filter(lambda plugin_ep: not plugin_ep.hidden) - - def ifaces(self, *ifaces_groups): - """Filter plugins based on interfaces.""" - return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) - - def verify(self, ifaces): - """Filter plugins based on verification.""" - return self.filter(lambda p_ep: p_ep.verify(ifaces)) - - def prepare(self): - """Prepare all plugins in the registry.""" - return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)] - - def available(self): - """Filter plugins based on availability.""" - return self.filter(lambda p_ep: p_ep.available) - # successfully prepared + misconfigured - - def find_init(self, plugin): - """Find an initialized plugin. - - This is particularly useful for finding a name for the plugin - (although `.IPluginFactory.__call__` takes ``name`` as one of - the arguments, ``IPlugin.name`` is not part of the interface):: - - # plugin is an instance providing IPlugin, initialized - # somewhere else in the code - plugin_registry.find_init(plugin).name - - Returns ``None`` if ``plugin`` is not found in the registry. - - """ - # use list instead of set because PluginEntryPoint is not hashable - candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins) - if plugin_ep.initialized and plugin_ep.init() is plugin] - assert len(candidates) <= 1 - if candidates: - return candidates[0] - return None - - def __repr__(self): - return "{0}({1})".format( - self.__class__.__name__, ','.join( - repr(p_ep) for p_ep in six.itervalues(self._plugins))) - - def __str__(self): - if not self._plugins: - return "No plugins" - return "\n\n".join(str(p_ep) for p_ep in six.itervalues(self._plugins)) diff --git a/certbot/_internal/plugins/manual.py b/certbot/_internal/plugins/manual.py deleted file mode 100644 index 43f70d650..000000000 --- a/certbot/_internal/plugins/manual.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Manual authenticator plugin""" -import zope.component -import zope.interface - -from acme import challenges -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - -from certbot import achallenges # pylint: disable=unused-import -from certbot import errors -from certbot._internal import hooks -from certbot import interfaces -from certbot import reverter -from certbot.compat import os -from certbot.plugins import common - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Manual authenticator - - This plugin allows the user to perform the domain validation - challenge(s) themselves. This either be done manually by the user or - through shell scripts provided to Certbot. - - """ - - description = 'Manual configuration or run your own shell scripts' - hidden = True - long_description = ( - 'Authenticate through manual configuration or custom shell scripts. ' - 'When using shell scripts, an authenticator script must be provided. ' - 'The environment variables available to this script depend on the ' - 'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' - 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' - 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' - 'resource requested when performing an HTTP-01 challenge. An additional ' - 'cleanup script can also be provided and can use the additional variable ' - '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') - _DNS_INSTRUCTIONS = """\ -Please deploy a DNS TXT record under the name -{domain} with the following value: - -{validation} - -Before continuing, verify the record is deployed.""" - _HTTP_INSTRUCTIONS = """\ -Create a file containing just this data: - -{validation} - -And make it available on your web server at this URL: - -{uri} -""" - _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ -(This must be set up in addition to the previous challenges; do not remove, -replace, or undo the previous challenge tasks yet.) -""" - _SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS = """ -(This must be set up in addition to the previous challenges; do not remove, -replace, or undo the previous challenge tasks yet. Note that you might be -asked to create multiple distinct TXT records with the same name. This is -permitted by DNS standards.) -""" - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.reverter = reverter.Reverter(self.config) - self.reverter.recovery_routine() - self.env = dict() \ - # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] - self.subsequent_dns_challenge = False - self.subsequent_any_challenge = False - - @classmethod - def add_parser_arguments(cls, add): - add('auth-hook', - help='Path or command to execute for the authentication script') - add('cleanup-hook', - help='Path or command to execute for the cleanup script') - add('public-ip-logging-ok', action='store_true', - help='Automatically allows public IP logging (default: Ask)') - - def prepare(self): # pylint: disable=missing-docstring - if self.config.noninteractive_mode and not self.conf('auth-hook'): - raise errors.PluginError( - 'An authentication script must be provided with --{0} when ' - 'using the manual plugin non-interactively.'.format( - self.option_name('auth-hook'))) - self._validate_hooks() - - def _validate_hooks(self): - if self.config.validate_hooks: - for name in ('auth-hook', 'cleanup-hook'): - hook = self.conf(name) - if hook is not None: - hook_prefix = self.option_name(name)[:-len('-hook')] - hooks.validate_hook(hook, hook_prefix) - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return ( - 'This plugin allows the user to customize setup for domain ' - 'validation challenges either through shell scripts provided by ' - 'the user or by performing the setup manually.') - - def get_chall_pref(self, domain): - # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.HTTP01, challenges.DNS01] - - def perform(self, achalls): # pylint: disable=missing-docstring - self._verify_ip_logging_ok() - if self.conf('auth-hook'): - perform_achall = self._perform_achall_with_script - else: - perform_achall = self._perform_achall_manually - - responses = [] - for achall in achalls: - perform_achall(achall) - responses.append(achall.response(achall.account_key)) - return responses - - def _verify_ip_logging_ok(self): - if not self.conf('public-ip-logging-ok'): - cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok')) - msg = ('NOTE: The IP of this machine will be publicly logged as ' - "having requested this certificate. If you're running " - 'certbot in manual mode on a machine that is not your ' - "server, please ensure you're okay with that.\n\n" - 'Are you OK with your IP being logged?') - display = zope.component.getUtility(interfaces.IDisplay) - if display.yesno(msg, cli_flag=cli_flag, force_interactive=True): - setattr(self.config, self.dest('public-ip-logging-ok'), True) - else: - raise errors.PluginError('Must agree to IP logging to proceed') - - def _perform_achall_with_script(self, achall): - env = dict(CERTBOT_DOMAIN=achall.domain, - CERTBOT_VALIDATION=achall.validation(achall.account_key)) - if isinstance(achall.chall, challenges.HTTP01): - env['CERTBOT_TOKEN'] = achall.chall.encode('token') - else: - os.environ.pop('CERTBOT_TOKEN', None) - os.environ.update(env) - _, out = self._execute_hook('auth-hook') - env['CERTBOT_AUTH_OUTPUT'] = out.strip() - self.env[achall] = env - - def _perform_achall_manually(self, achall): - validation = achall.validation(achall.account_key) - if isinstance(achall.chall, challenges.HTTP01): - msg = self._HTTP_INSTRUCTIONS.format( - achall=achall, encoded_token=achall.chall.encode('token'), - port=self.config.http01_port, - uri=achall.chall.uri(achall.domain), validation=validation) - else: - assert isinstance(achall.chall, challenges.DNS01) - msg = self._DNS_INSTRUCTIONS.format( - domain=achall.validation_domain_name(achall.domain), - validation=validation) - if isinstance(achall.chall, challenges.DNS01): - if self.subsequent_dns_challenge: - # 2nd or later dns-01 challenge - msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS - self.subsequent_dns_challenge = True - elif self.subsequent_any_challenge: - # 2nd or later challenge of another type - msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS - display = zope.component.getUtility(interfaces.IDisplay) - display.notification(msg, wrap=False, force_interactive=True) - self.subsequent_any_challenge = True - - def cleanup(self, achalls): # pylint: disable=missing-docstring - if self.conf('cleanup-hook'): - for achall in achalls: - env = self.env.pop(achall) - if 'CERTBOT_TOKEN' not in env: - os.environ.pop('CERTBOT_TOKEN', None) - os.environ.update(env) - self._execute_hook('cleanup-hook') - self.reverter.recovery_routine() - - def _execute_hook(self, hook_name): - return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) diff --git a/certbot/_internal/plugins/null.py b/certbot/_internal/plugins/null.py deleted file mode 100644 index 6deb358f1..000000000 --- a/certbot/_internal/plugins/null.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Null plugin.""" -import logging - -import zope.component -import zope.interface - -from certbot import interfaces -from certbot.plugins import common - - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class Installer(common.Plugin): - """Null installer.""" - - description = "Null Installer" - hidden = True - - # pylint: disable=missing-docstring,no-self-use - - def prepare(self): - pass # pragma: no cover - - def more_info(self): - return "Installer that doesn't do anything (for testing)." - - def get_all_names(self): - return [] - - def deploy_cert(self, domain, cert_path, key_path, - chain_path=None, fullchain_path=None): - pass # pragma: no cover - - def enhance(self, domain, enhancement, options=None): - pass # pragma: no cover - - def supported_enhancements(self): - return [] - - def save(self, title=None, temporary=False): - pass # pragma: no cover - - def rollback_checkpoints(self, rollback=1): - pass # pragma: no cover - - def recovery_routine(self): - pass # pragma: no cover - - def config_test(self): - pass # pragma: no cover - - def restart(self): - pass # pragma: no cover diff --git a/certbot/_internal/plugins/selection.py b/certbot/_internal/plugins/selection.py deleted file mode 100644 index de1d27227..000000000 --- a/certbot/_internal/plugins/selection.py +++ /dev/null @@ -1,338 +0,0 @@ -"""Decide which plugins to use for authentication & installation""" -from __future__ import print_function - -import logging - -import six -import zope.component - -from certbot import errors -from certbot import interfaces -from certbot.compat import os -from certbot.display import util as display_util - -logger = logging.getLogger(__name__) -z_util = zope.component.getUtility - -def pick_configurator( - config, default, plugins, - question="How would you like to authenticate and install " - "certificates?"): - """Pick configurator plugin.""" - return pick_plugin( - config, default, plugins, question, - (interfaces.IAuthenticator, interfaces.IInstaller)) - - -def pick_installer(config, default, plugins, - question="How would you like to install certificates?"): - """Pick installer plugin.""" - return pick_plugin( - config, default, plugins, question, (interfaces.IInstaller,)) - - -def pick_authenticator( - config, default, plugins, question="How would you " - "like to authenticate with the ACME CA?"): - """Pick authentication plugin.""" - return pick_plugin( - config, default, plugins, question, (interfaces.IAuthenticator,)) - -def get_unprepared_installer(config, plugins): - """ - Get an unprepared interfaces.IInstaller object. - - :param certbot.interfaces.IConfig config: Configuration - :param certbot._internal.plugins.disco.PluginsRegistry plugins: - All plugins registered as entry points. - - :returns: Unprepared installer plugin or None - :rtype: IPlugin or None - """ - - _, req_inst = cli_plugin_requests(config) - if not req_inst: - return None - installers = plugins.filter(lambda p_ep: p_ep.name == req_inst) - installers.init(config) - installers = installers.verify((interfaces.IInstaller,)) - if len(installers) > 1: - raise errors.PluginSelectionError( - "Found multiple installers with the name %s, Certbot is unable to " - "determine which one to use. Skipping." % req_inst) - if installers: - inst = list(installers.values())[0] - logger.debug("Selecting plugin: %s", inst) - return inst.init(config) - else: - raise errors.PluginSelectionError( - "Could not select or initialize the requested installer %s." % req_inst) - -def pick_plugin(config, default, plugins, question, ifaces): - """Pick plugin. - - :param certbot.interfaces.IConfig: Configuration - :param str default: Plugin name supplied by user or ``None``. - :param certbot._internal.plugins.disco.PluginsRegistry plugins: - All plugins registered as entry points. - :param str question: Question to be presented to the user in case - multiple candidates are found. - :param list ifaces: Interfaces that plugins must provide. - - :returns: Initialized plugin. - :rtype: IPlugin - - """ - if default is not None: - # throw more UX-friendly error if default not in plugins - filtered = plugins.filter(lambda p_ep: p_ep.name == default) - else: - if config.noninteractive_mode: - # it's really bad to auto-select the single available plugin in - # non-interactive mode, because an update could later add a second - # available plugin - raise errors.MissingCommandlineFlag( - "Missing command line flags. For non-interactive execution, " - "you will need to specify a plugin on the command line. Run " - "with '--help plugins' to see a list of options, and see " - "https://eff.org/letsencrypt-plugins for more detail on what " - "the plugins do and how to use them.") - - filtered = plugins.visible().ifaces(ifaces) - - filtered.init(config) - verified = filtered.verify(ifaces) - verified.prepare() - prepared = verified.available() - - if len(prepared) > 1: - logger.debug("Multiple candidate plugins: %s", prepared) - plugin_ep = choose_plugin(list(six.itervalues(prepared)), question) - if plugin_ep is None: - return None - return plugin_ep.init() - elif len(prepared) == 1: - plugin_ep = list(prepared.values())[0] - logger.debug("Single candidate plugin: %s", plugin_ep) - if plugin_ep.misconfigured: - return None - return plugin_ep.init() - else: - logger.debug("No candidate plugin") - return None - - -def choose_plugin(prepared, question): - """Allow the user to choose their plugin. - - :param list prepared: List of `~.PluginEntryPoint`. - :param str question: Question to be presented to the user. - - :returns: Plugin entry point chosen by the user. - :rtype: `~.PluginEntryPoint` - - """ - opts = [plugin_ep.description_with_name + - (" [Misconfigured]" if plugin_ep.misconfigured else "") - for plugin_ep in prepared] - names = set(plugin_ep.name for plugin_ep in prepared) - - while True: - disp = z_util(interfaces.IDisplay) - if "CERTBOT_AUTO" in os.environ and names == set(("apache", "nginx")): - # The possibility of being offered exactly apache and nginx here - # is new interactivity brought by https://github.com/certbot/certbot/issues/4079, - # so set apache as a default for those kinds of non-interactive use - # (the user will get a warning to set --non-interactive or --force-interactive) - apache_idx = [n for n, p in enumerate(prepared) if p.name == "apache"][0] - code, index = disp.menu(question, opts, default=apache_idx) - else: - code, index = disp.menu(question, opts, force_interactive=True) - - if code == display_util.OK: - plugin_ep = prepared[index] - if plugin_ep.misconfigured: - z_util(interfaces.IDisplay).notification( - "The selected plugin encountered an error while parsing " - "your server configuration and cannot be used. The error " - "was:\n\n{0}".format(plugin_ep.prepare()), pause=False) - else: - return plugin_ep - else: - return None - -noninstaller_plugins = ["webroot", "manual", "standalone", "dns-cloudflare", "dns-cloudxns", - "dns-digitalocean", "dns-dnsimple", "dns-dnsmadeeasy", "dns-gehirn", - "dns-google", "dns-linode", "dns-luadns", "dns-nsone", "dns-ovh", - "dns-rfc2136", "dns-route53", "dns-sakuracloud"] - -def record_chosen_plugins(config, plugins, auth, inst): - "Update the config entries to reflect the plugins we actually selected." - config.authenticator = plugins.find_init(auth).name if auth else None - config.installer = plugins.find_init(inst).name if inst else None - logger.info("Plugins selected: Authenticator %s, Installer %s", - config.authenticator, config.installer) - - -def choose_configurator_plugins(config, plugins, verb): - """ - Figure out which configurator we're going to use, modifies - config.authenticator and config.installer strings to reflect that choice if - necessary. - - :raises errors.PluginSelectionError if there was a problem - - :returns: (an `IAuthenticator` or None, an `IInstaller` or None) - :rtype: tuple - """ - - req_auth, req_inst = cli_plugin_requests(config) - installer_question = None - - if verb == "enhance": - installer_question = ("Which installer would you like to use to " - "configure the selected enhancements?") - - # Which plugins do we need? - if verb == "run": - need_inst = need_auth = True - from certbot._internal.cli import cli_command - if req_auth in noninstaller_plugins and not req_inst: - msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' - '{1} {2} certonly --{0}{1}{1}' - '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins' - '{1} and "--help plugins" for more information.)'.format( - req_auth, os.linesep, cli_command)) - - raise errors.MissingCommandlineFlag(msg) - else: - need_inst = need_auth = False - if verb == "certonly": - need_auth = True - if verb == "install" or verb == "enhance": - need_inst = True - if config.authenticator: - logger.warning("Specifying an authenticator doesn't make sense when " - "running Certbot with verb \"%s\"", verb) - # Try to meet the user's request and/or ask them to pick plugins - authenticator = installer = None - if verb == "run" and req_auth == req_inst: - # Unless the user has explicitly asked for different auth/install, - # only consider offering a single choice - authenticator = installer = pick_configurator(config, req_inst, plugins) - else: - if need_inst or req_inst: - installer = pick_installer(config, req_inst, plugins, installer_question) - if need_auth: - authenticator = pick_authenticator(config, req_auth, plugins) - logger.debug("Selected authenticator %s and installer %s", authenticator, installer) - - # Report on any failures - if need_inst and not installer: - diagnose_configurator_problem("installer", req_inst, plugins) - if need_auth and not authenticator: - diagnose_configurator_problem("authenticator", req_auth, plugins) - - record_chosen_plugins(config, plugins, authenticator, installer) - return installer, authenticator - - -def set_configurator(previously, now): - """ - Setting configurators multiple ways is okay, as long as they all agree - :param str previously: previously identified request for the installer/authenticator - :param str requested: the request currently being processed - """ - if not now: - # we're not actually setting anything - return previously - if previously: - if previously != now: - msg = "Too many flags setting configurators/installers/authenticators {0} -> {1}" - raise errors.PluginSelectionError(msg.format(repr(previously), repr(now))) - return now - - -def cli_plugin_requests(config): - """ - Figure out which plugins the user requested with CLI and config options - - :returns: (requested authenticator string or None, requested installer string or None) - :rtype: tuple - """ - req_inst = req_auth = config.configurator - req_inst = set_configurator(req_inst, config.installer) - req_auth = set_configurator(req_auth, config.authenticator) - - if config.nginx: - req_inst = set_configurator(req_inst, "nginx") - req_auth = set_configurator(req_auth, "nginx") - if config.apache: - req_inst = set_configurator(req_inst, "apache") - req_auth = set_configurator(req_auth, "apache") - if config.standalone: - req_auth = set_configurator(req_auth, "standalone") - if config.webroot: - req_auth = set_configurator(req_auth, "webroot") - if config.manual: - req_auth = set_configurator(req_auth, "manual") - if config.dns_cloudflare: - req_auth = set_configurator(req_auth, "dns-cloudflare") - if config.dns_cloudxns: - req_auth = set_configurator(req_auth, "dns-cloudxns") - if config.dns_digitalocean: - req_auth = set_configurator(req_auth, "dns-digitalocean") - if config.dns_dnsimple: - req_auth = set_configurator(req_auth, "dns-dnsimple") - if config.dns_dnsmadeeasy: - req_auth = set_configurator(req_auth, "dns-dnsmadeeasy") - if config.dns_gehirn: - req_auth = set_configurator(req_auth, "dns-gehirn") - if config.dns_google: - req_auth = set_configurator(req_auth, "dns-google") - if config.dns_linode: - req_auth = set_configurator(req_auth, "dns-linode") - if config.dns_luadns: - req_auth = set_configurator(req_auth, "dns-luadns") - if config.dns_nsone: - req_auth = set_configurator(req_auth, "dns-nsone") - if config.dns_ovh: - req_auth = set_configurator(req_auth, "dns-ovh") - if config.dns_rfc2136: - req_auth = set_configurator(req_auth, "dns-rfc2136") - if config.dns_route53: - req_auth = set_configurator(req_auth, "dns-route53") - if config.dns_sakuracloud: - req_auth = set_configurator(req_auth, "dns-sakuracloud") - logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst) - return req_auth, req_inst - - -def diagnose_configurator_problem(cfg_type, requested, plugins): - """ - Raise the most helpful error message about a plugin being unavailable - - :param str cfg_type: either "installer" or "authenticator" - :param str requested: the plugin that was requested - :param .PluginsRegistry plugins: available plugins - - :raises error.PluginSelectionError: if there was a problem - """ - - if requested: - if requested not in plugins: - msg = "The requested {0} plugin does not appear to be installed".format(requested) - else: - msg = ("The {0} plugin is not working; there may be problems with " - "your existing configuration.\nThe error was: {1!r}" - .format(requested, plugins[requested].problem)) - elif cfg_type == "installer": - from certbot._internal.cli import cli_command - msg = ('Certbot doesn\'t know how to automatically configure the web ' - 'server on this system. However, it can still get a certificate for ' - 'you. Please run "{0} certonly" to do so. You\'ll need to ' - 'manually configure your web server to use the resulting ' - 'certificate.').format(cli_command) - else: - msg = "{0} could not be determined or is not installed".format(cfg_type) - raise errors.PluginSelectionError(msg) diff --git a/certbot/_internal/plugins/standalone.py b/certbot/_internal/plugins/standalone.py deleted file mode 100644 index 9723116c1..000000000 --- a/certbot/_internal/plugins/standalone.py +++ /dev/null @@ -1,210 +0,0 @@ -"""Standalone Authenticator.""" -import collections -import logging -import socket -# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi -from socket import errno as socket_errors # type: ignore - -import OpenSSL # pylint: disable=unused-import -import six -import zope.interface - -from acme import challenges -from acme import standalone as acme_standalone -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING - -from certbot import achallenges # pylint: disable=unused-import -from certbot import errors -from certbot import interfaces - -from certbot.plugins import common - -logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - ServedType = DefaultDict[ - acme_standalone.BaseDualNetworkedServers, - Set[achallenges.KeyAuthorizationAnnotatedChallenge] - ] - -class ServerManager(object): - """Standalone servers manager. - - Manager for `ACMEServer` and `ACMETLSServer` instances. - - `certs` and `http_01_resources` correspond to - `acme.crypto_util.SSLSocket.certs` and - `acme.crypto_util.SSLSocket.http_01_resources` respectively. All - created servers share the same certificates and resources, so if - you're running both TLS and non-TLS instances, HTTP01 handlers - will serve the same URLs! - - """ - def __init__(self, certs, http_01_resources): - self._instances = {} # type: Dict[int, acme_standalone.BaseDualNetworkedServers] - self.certs = certs - self.http_01_resources = http_01_resources - - def run(self, port, challenge_type, listenaddr=""): - """Run ACME server on specified ``port``. - - This method is idempotent, i.e. all calls with the same pair of - ``(port, challenge_type)`` will reuse the same server. - - :param int port: Port to run the server on. - :param challenge_type: Subclass of `acme.challenges.Challenge`, - currently only `acme.challenge.HTTP01`. - :param str listenaddr: (optional) The address to listen on. Defaults to all addrs. - - :returns: DualNetworkedServers instance. - :rtype: ACMEServerMixin - - """ - assert challenge_type == challenges.HTTP01 - if port in self._instances: - return self._instances[port] - - address = (listenaddr, port) - try: - servers = acme_standalone.HTTP01DualNetworkedServers( - address, self.http_01_resources) - except socket.error as error: - raise errors.StandaloneBindError(error, port) - - servers.serve_forever() - - # if port == 0, then random free port on OS is taken - # pylint: disable=no-member - # both servers, if they exist, have the same port - real_port = servers.getsocknames()[0][1] - self._instances[real_port] = servers - return servers - - def stop(self, port): - """Stop ACME server running on the specified ``port``. - - :param int port: - - """ - instance = self._instances[port] - for sockname in instance.getsocknames(): - logger.debug("Stopping server at %s:%d...", - *sockname[:2]) - instance.shutdown_and_server_close() - del self._instances[port] - - def running(self): - """Return all running instances. - - Once the server is stopped using `stop`, it will not be - returned. - - :returns: Mapping from ``port`` to ``servers``. - :rtype: tuple - - """ - return self._instances.copy() - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Standalone Authenticator. - - This authenticator creates its own ephemeral TCP listener on the - necessary port in order to respond to incoming http-01 - challenges from the certificate authority. Therefore, it does not - rely on any existing server program. - """ - - description = "Spin up a temporary webserver" - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - - self.served = collections.defaultdict(set) # type: ServedType - - # Stuff below is shared across threads (i.e. servers read - # values, main thread writes). Due to the nature of CPython's - # GIL, the operations are safe, c.f. - # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe - self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] - self.http_01_resources = set() \ - # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] - - self.servers = ServerManager(self.certs, self.http_01_resources) - - @classmethod - def add_parser_arguments(cls, add): - pass # No additional argument for the standalone plugin parser - - def more_info(self): # pylint: disable=missing-docstring - return("This authenticator creates its own ephemeral TCP listener " - "on the necessary port in order to respond to incoming " - "http-01 challenges from the certificate authority. Therefore, " - "it does not rely on any existing server program.") - - def prepare(self): # pylint: disable=missing-docstring - pass - - def get_chall_pref(self, domain): - # pylint: disable=unused-argument,missing-docstring - return [challenges.HTTP01] - - def perform(self, achalls): # pylint: disable=missing-docstring - return [self._try_perform_single(achall) for achall in achalls] - - def _try_perform_single(self, achall): - while True: - try: - return self._perform_single(achall) - except errors.StandaloneBindError as error: - _handle_perform_error(error) - - def _perform_single(self, achall): - servers, response = self._perform_http_01(achall) - self.served[servers].add(achall) - return response - - def _perform_http_01(self, achall): - port = self.config.http01_port - addr = self.config.http01_address - servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr) - response, validation = achall.response_and_validation() - resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource( - chall=achall.chall, response=response, validation=validation) - self.http_01_resources.add(resource) - return servers, response - - def cleanup(self, achalls): # pylint: disable=missing-docstring - # reduce self.served and close servers if no challenges are served - for unused_servers, server_achalls in self.served.items(): - for achall in achalls: - if achall in server_achalls: - server_achalls.remove(achall) - for port, servers in six.iteritems(self.servers.running()): - if not self.served[servers]: - self.servers.stop(port) - - -def _handle_perform_error(error): - if error.socket_error.errno == socket_errors.EACCES: - raise errors.PluginError( - "Could not bind TCP port {0} because you don't have " - "the appropriate permissions (for example, you " - "aren't running this program as " - "root).".format(error.port)) - elif error.socket_error.errno == socket_errors.EADDRINUSE: - display = zope.component.getUtility(interfaces.IDisplay) - msg = ( - "Could not bind TCP port {0} because it is already in " - "use by another process on this system (such as a web " - "server). Please stop the program in question and " - "then try again.".format(error.port)) - should_retry = display.yesno(msg, "Retry", - "Cancel", default=False) - if not should_retry: - raise errors.PluginError(msg) - else: - raise error diff --git a/certbot/_internal/plugins/webroot.py b/certbot/_internal/plugins/webroot.py deleted file mode 100644 index b87b3092a..000000000 --- a/certbot/_internal/plugins/webroot.py +++ /dev/null @@ -1,288 +0,0 @@ -"""Webroot plugin.""" -import argparse -import collections -import errno -import json -import logging - -import six -import zope.component -import zope.interface - -from acme import challenges # pylint: disable=unused-import -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Set, DefaultDict, List -# pylint: enable=unused-import, no-name-in-module - -from certbot import achallenges # pylint: disable=unused-import -from certbot._internal import cli -from certbot import errors -from certbot import interfaces -from certbot.compat import os -from certbot.compat import filesystem -from certbot.display import ops -from certbot.display import util as display_util -from certbot.plugins import common -from certbot.plugins import util -from certbot.util import safe_open - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Webroot Authenticator.""" - - description = "Place files in webroot directory" - - MORE_INFO = """\ -Authenticator plugin that performs http-01 challenge by saving -necessary validation resources to appropriate paths on the file -system. It expects that there is some other HTTP server configured -to serve all files under specified web root ({0}).""" - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return self.MORE_INFO.format(self.conf("path")) - - @classmethod - def add_parser_arguments(cls, add): - add("path", "-w", default=[], action=_WebrootPathAction, - help="public_html / webroot path. This can be specified multiple " - "times to handle different domains; each domain will have " - "the webroot path that preceded it. For instance: `-w " - "/var/www/example -d example.com -d www.example.com -w " - "/var/www/thing -d thing.net -d m.thing.net` (default: Ask)") - add("map", default={}, action=_WebrootMapAction, - help="JSON dictionary mapping domains to webroot paths; this " - "implies -d for each entry. You may need to escape this from " - "your shell. E.g.: --webroot-map " - '\'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}\' ' - "This option is merged with, but takes precedence over, -w / " - "-d entries. At present, if you put webroot-map in a config " - "file, it needs to be on a single line, like: webroot-map = " - '{"example.com":"/var/www"}.') - - def get_chall_pref(self, domain): # pragma: no cover - # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.HTTP01] - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.full_roots = {} # type: Dict[str, str] - self.performed = collections.defaultdict(set) \ - # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] - # stack of dirs successfully created by this authenticator - self._created_dirs = [] # type: List[str] - - def prepare(self): # pylint: disable=missing-docstring - pass - - def perform(self, achalls): # pylint: disable=missing-docstring - self._set_webroots(achalls) - - self._create_challenge_dirs() - - return [self._perform_single(achall) for achall in achalls] - - def _set_webroots(self, achalls): - if self.conf("path"): - webroot_path = self.conf("path")[-1] - logger.info("Using the webroot path %s for all unmatched domains.", - webroot_path) - for achall in achalls: - self.conf("map").setdefault(achall.domain, webroot_path) - else: - known_webroots = list(set(six.itervalues(self.conf("map")))) - for achall in achalls: - if achall.domain not in self.conf("map"): - new_webroot = self._prompt_for_webroot(achall.domain, - known_webroots) - # Put the most recently input - # webroot first for easy selection - try: - known_webroots.remove(new_webroot) - except ValueError: - pass - known_webroots.insert(0, new_webroot) - self.conf("map")[achall.domain] = new_webroot - - def _prompt_for_webroot(self, domain, known_webroots): - webroot = None - - while webroot is None: - if known_webroots: - # Only show the menu if we have options for it - webroot = self._prompt_with_webroot_list(domain, known_webroots) - if webroot is None: - webroot = self._prompt_for_new_webroot(domain) - else: - # Allow prompt to raise PluginError instead of looping forever - webroot = self._prompt_for_new_webroot(domain, True) - - return webroot - - def _prompt_with_webroot_list(self, domain, known_webroots): - display = zope.component.getUtility(interfaces.IDisplay) - path_flag = "--" + self.option_name("path") - - while True: - code, index = display.menu( - "Select the webroot for {0}:".format(domain), - ["Enter a new webroot"] + known_webroots, - cli_flag=path_flag, force_interactive=True) - if code == display_util.CANCEL: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return None if index == 0 else known_webroots[index - 1] - - def _prompt_for_new_webroot(self, domain, allowraise=False): - code, webroot = ops.validated_directory( - _validate_webroot, - "Input the webroot for {0}:".format(domain), - force_interactive=True) - if code == display_util.CANCEL: - if not allowraise: - return None - else: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return _validate_webroot(webroot) - - def _create_challenge_dirs(self): - path_map = self.conf("map") - if not path_map: - raise errors.PluginError( - "Missing parts of webroot configuration; please set either " - "--webroot-path and --domains, or --webroot-map. Run with " - " --help webroot for examples.") - for name, path in path_map.items(): - self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) - logger.debug("Creating root challenges validation dir at %s", - self.full_roots[name]) - - # Change the permissions to be writable (GH #1389) - # Umask is used instead of chmod to ensure the client can also - # run as non-root (GH #1795) - old_umask = os.umask(0o022) - try: - # We ignore the last prefix in the next iteration, - # as it does not correspond to a folder path ('/' or 'C:') - for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): - try: - # Set owner as parent directory if possible, apply mode for Linux/Windows. - # For Linux, this is coupled with the "umask" call above because - # os.mkdir's "mode" parameter may not always work: - # https://docs.python.org/3/library/os.html#os.mkdir - filesystem.mkdir(prefix, 0o755) - self._created_dirs.append(prefix) - try: - filesystem.copy_ownership_and_apply_mode( - path, prefix, 0o755, copy_user=True, copy_group=True) - except (OSError, AttributeError) as exception: - logger.info("Unable to change owner and uid of webroot directory") - logger.debug("Error was: %s", exception) - except OSError as exception: - if exception.errno not in (errno.EEXIST, errno.EISDIR): - raise errors.PluginError( - "Couldn't create root for {0} http-01 " - "challenge responses: {1}".format(name, exception)) - finally: - os.umask(old_umask) - - def _get_validation_path(self, root_path, achall): - return os.path.join(root_path, achall.chall.encode("token")) - - def _perform_single(self, achall): - response, validation = achall.response_and_validation() - - root_path = self.full_roots[achall.domain] - validation_path = self._get_validation_path(root_path, achall) - logger.debug("Attempting to save validation to %s", validation_path) - - # Change permissions to be world-readable, owner-writable (GH #1795) - old_umask = os.umask(0o022) - - try: - with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: - validation_file.write(validation.encode()) - finally: - os.umask(old_umask) - - self.performed[root_path].add(achall) - return response - - def cleanup(self, achalls): # pylint: disable=missing-docstring - for achall in achalls: - root_path = self.full_roots.get(achall.domain, None) - if root_path is not None: - validation_path = self._get_validation_path(root_path, achall) - logger.debug("Removing %s", validation_path) - os.remove(validation_path) - self.performed[root_path].remove(achall) - - not_removed = [] # type: List[str] - while self._created_dirs: - path = self._created_dirs.pop() - try: - os.rmdir(path) - except OSError as exc: - not_removed.insert(0, path) - logger.info("Challenge directory %s was not empty, didn't remove", path) - logger.debug("Error was: %s", exc) - self._created_dirs = not_removed - logger.debug("All challenges cleaned up") - - -class _WebrootMapAction(argparse.Action): - """Action class for parsing webroot_map.""" - - def __call__(self, parser, namespace, webroot_map, option_string=None): - for domains, webroot_path in six.iteritems(json.loads(webroot_map)): - webroot_path = _validate_webroot(webroot_path) - namespace.webroot_map.update( - (d, webroot_path) for d in cli.add_domains(namespace, domains)) - - -class _WebrootPathAction(argparse.Action): - """Action class for parsing webroot_path.""" - - def __init__(self, *args, **kwargs): - super(_WebrootPathAction, self).__init__(*args, **kwargs) - self._domain_before_webroot = False - - def __call__(self, parser, namespace, webroot_path, option_string=None): - if self._domain_before_webroot: - raise errors.PluginError( - "If you specify multiple webroot paths, " - "one of them must precede all domain flags") - - if namespace.webroot_path: - # Apply previous webroot to all matched - # domains before setting the new webroot path - prev_webroot = namespace.webroot_path[-1] - for domain in namespace.domains: - namespace.webroot_map.setdefault(domain, prev_webroot) - elif namespace.domains: - self._domain_before_webroot = True - - namespace.webroot_path.append(_validate_webroot(webroot_path)) - - -def _validate_webroot(webroot_path): - """Validates and returns the absolute path of webroot_path. - - :param str webroot_path: path to the webroot directory - - :returns: absolute path of webroot_path - :rtype: str - - """ - if not os.path.isdir(webroot_path): - raise errors.PluginError(webroot_path + " does not exist or is not a directory") - - return os.path.abspath(webroot_path) diff --git a/certbot/_internal/renewal.py b/certbot/_internal/renewal.py deleted file mode 100644 index f96cd004f..000000000 --- a/certbot/_internal/renewal.py +++ /dev/null @@ -1,476 +0,0 @@ -"""Functionality for autorenewal and associated juggling of configurations""" -from __future__ import print_function - -import copy -import itertools -import logging -import random -import sys -import time -import traceback - -import OpenSSL -import six -import zope.component - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot._internal import cli -from certbot import crypto_util -from certbot import errors -from certbot._internal import hooks -from certbot import interfaces -from certbot._internal import storage -from certbot._internal import updater -from certbot import util -from certbot.compat import os -from certbot._internal.plugins import disco as plugins_disco - -logger = logging.getLogger(__name__) - -# These are the items which get pulled out of a renewal configuration -# file's renewalparams and actually used in the client configuration -# during the renewal process. We have to record their types here because -# the renewal configuration process loses this information. -STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent", - "server", "account", "authenticator", "installer", - "renew_hook", "pre_hook", "post_hook", "http01_address"] -INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"] -BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key", - "autorenew"] - -CONFIG_ITEMS = set(itertools.chain( - BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',))) - - -def _reconstitute(config, full_path): - """Try to instantiate a RenewableCert, updating config with relevant items. - - This is specifically for use in renewal and enforces several checks - and policies to ensure that we can try to proceed with the renewal - request. The config argument is modified by including relevant options - read from the renewal configuration file. - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param str full_path: Absolute path to the configuration file that - defines this lineage - - :returns: the RenewableCert object or None if a fatal error occurred - :rtype: `storage.RenewableCert` or NoneType - - """ - try: - renewal_candidate = storage.RenewableCert(full_path, config) - except (errors.CertStorageError, IOError): - logger.warning("", exc_info=True) - logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - if "renewalparams" not in renewal_candidate.configuration: - logger.warning("Renewal configuration file %s lacks " - "renewalparams. Skipping.", full_path) - return None - renewalparams = renewal_candidate.configuration["renewalparams"] - if "authenticator" not in renewalparams: - logger.warning("Renewal configuration file %s does not specify " - "an authenticator. Skipping.", full_path) - return None - # Now restore specific values along with their data types, if - # those elements are present. - try: - restore_required_config_elements(config, renewalparams) - _restore_plugin_configs(config, renewalparams) - except (ValueError, errors.Error) as error: - logger.warning( - "An error occurred while parsing %s. The error was %s. " - "Skipping the file.", full_path, str(error)) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - return None - - try: - config.domains = [util.enforce_domain_sanity(d) - for d in renewal_candidate.names()] - except errors.ConfigurationError as error: - logger.warning("Renewal configuration file %s references a cert " - "that contains an invalid domain name. The problem " - "was: %s. Skipping.", full_path, error) - return None - - return renewal_candidate - - -def _restore_webroot_config(config, renewalparams): - """ - webroot_map is, uniquely, a dict, and the general-purpose configuration - restoring logic is not able to correctly parse it from the serialized - form. - """ - if "webroot_map" in renewalparams and not cli.set_by_cli("webroot_map"): - config.webroot_map = renewalparams["webroot_map"] - # To understand why webroot_path and webroot_map processing are not mutually exclusive, - # see https://github.com/certbot/certbot/pull/7095 - if "webroot_path" in renewalparams and not cli.set_by_cli("webroot_path"): - wp = renewalparams["webroot_path"] - if isinstance(wp, six.string_types): # prior to 0.1.0, webroot_path was a string - wp = [wp] - config.webroot_path = wp - - -def _restore_plugin_configs(config, renewalparams): - """Sets plugin specific values in config from renewalparams - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param configobj.Section renewalparams: Parameters from the renewal - configuration file that defines this lineage - - """ - # Now use parser to get plugin-prefixed items with correct types - # XXX: the current approach of extracting only prefixed items - # related to the actually-used installer and authenticator - # works as long as plugins don't need to read plugin-specific - # variables set by someone else (e.g., assuming Apache - # configurator doesn't need to read webroot_ variables). - # Note: if a parameter that used to be defined in the parser is no - # longer defined, stored copies of that parameter will be - # deserialized as strings by this logic even if they were - # originally meant to be some other type. - plugin_prefixes = [] # type: List[str] - if renewalparams["authenticator"] == "webroot": - _restore_webroot_config(config, renewalparams) - else: - plugin_prefixes.append(renewalparams["authenticator"]) - - if renewalparams.get("installer") is not None: - plugin_prefixes.append(renewalparams["installer"]) - - for plugin_prefix in set(plugin_prefixes): - plugin_prefix = plugin_prefix.replace('-', '_') - for config_item, config_value in six.iteritems(renewalparams): - if config_item.startswith(plugin_prefix + "_") and not cli.set_by_cli(config_item): - # Values None, True, and False need to be treated specially, - # As their types aren't handled correctly by configobj - if config_value in ("None", "True", "False"): - # bool("False") == True - # pylint: disable=eval-used - setattr(config, config_item, eval(config_value)) - else: - cast = cli.argparse_type(config_item) - setattr(config, config_item, cast(config_value)) - - -def restore_required_config_elements(config, renewalparams): - """Sets non-plugin specific values in config from renewalparams - - :param configuration.NamespaceConfig config: configuration for the - current lineage - :param configobj.Section renewalparams: parameters from the renewal - configuration file that defines this lineage - - """ - - required_items = itertools.chain( - (("pref_challs", _restore_pref_challs),), - six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)), - six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)), - six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str))) - for item_name, restore_func in required_items: - if item_name in renewalparams and not cli.set_by_cli(item_name): - value = restore_func(item_name, renewalparams[item_name]) - setattr(config, item_name, value) - - -def _restore_pref_challs(unused_name, value): - """Restores preferred challenges from a renewal config file. - - If value is a `str`, it should be a single challenge type. - - :param str unused_name: option name - :param value: option value - :type value: `list` of `str` or `str` - - :returns: converted option value to be stored in the runtime config - :rtype: `list` of `str` - - :raises errors.Error: if value can't be converted to an bool - - """ - # If pref_challs has only one element, configobj saves the value - # with a trailing comma so it's parsed as a list. If this comma is - # removed by the user, the value is parsed as a str. - value = [value] if isinstance(value, six.string_types) else value - return cli.parse_preferred_challenges(value) - - -def _restore_bool(name, value): - """Restores an boolean key-value pair from a renewal config file. - - :param str name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: bool - - :raises errors.Error: if value can't be converted to an bool - - """ - lowercase_value = value.lower() - if lowercase_value not in ("true", "false"): - raise errors.Error( - "Expected True or False for {0} but found {1}".format(name, value)) - return lowercase_value == "true" - - -def _restore_int(name, value): - """Restores an integer key-value pair from a renewal config file. - - :param str name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: int - - :raises errors.Error: if value can't be converted to an int - - """ - if name == "http01_port" and value == "None": - logger.info("updating legacy http01_port value") - return cli.flag_default("http01_port") - - try: - return int(value) - except ValueError: - raise errors.Error("Expected a numeric value for {0}".format(name)) - - -def _restore_str(unused_name, value): - """Restores an string key-value pair from a renewal config file. - - :param str unused_name: option name - :param str value: option value - - :returns: converted option value to be stored in the runtime config - :rtype: str or None - - """ - return None if value == "None" else value - - -def should_renew(config, lineage): - "Return true if any of the circumstances for automatic renewal apply." - if config.renew_by_default: - logger.debug("Auto-renewal forced with --force-renewal...") - return True - if lineage.should_autorenew(): - logger.info("Cert is due for renewal, auto-renewing...") - return True - if config.dry_run: - logger.info("Cert not due for renewal, but simulating renewal for dry run") - return True - logger.info("Cert not yet due for renewal") - return False - - -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, 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() - - if util.is_staging(config.server): - if not util.is_staging(original_server) or now_valid: - if not config.break_my_certs: - names = ", ".join(lineage.names()) - raise errors.Error( - "You've asked to renew/replace a seemingly valid certificate with " - "a test certificate (domains: {0}). We will not do that " - "unless you use the --break-my-certs flag!".format(names)) - - -def renew_cert(config, domains, le_client, lineage): - "Renew a certificate lineage." - renewal_params = lineage.configuration["renewalparams"] - original_server = renewal_params.get("server", cli.flag_default("server")) - _avoid_invalidating_lineage(config, lineage, original_server) - if not domains: - domains = lineage.names() - # The private key is the existing lineage private key if reuse_key is set. - # Otherwise, generate a fresh private key by passing None. - new_key = os.path.normpath(lineage.privkey) if config.reuse_key else None - new_cert, new_chain, new_key, _ = le_client.obtain_certificate(domains, new_key) - if config.dry_run: - logger.debug("Dry run: skipping updating lineage at %s", - os.path.dirname(lineage.cert)) - else: - prior_version = lineage.latest_common_version() - # TODO: Check return value of save_successor - lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config) - lineage.update_all_links_to(lineage.latest_common_version()) - - hooks.renew_hook(config, domains, lineage.live_dir) - - -def report(msgs, category): - "Format a results report for a category of renewal outcomes" - lines = ("%s (%s)" % (m, category) for m in msgs) - return " " + "\n ".join(lines) - -def _renew_describe_results(config, renew_successes, renew_failures, - renew_skipped, parse_failures): - - out = [] # type: List[str] - notify = out.append - disp = zope.component.getUtility(interfaces.IDisplay) - - def notify_error(err): - """Notify and log errors.""" - notify(str(err)) - logger.error(err) - - if config.dry_run: - notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") - notify("** (The test certificates below have not been saved.)") - notify("") - if renew_skipped: - notify("The following certs are not due for renewal yet:") - notify(report(renew_skipped, "skipped")) - if not renew_successes and not renew_failures: - notify("No renewals were attempted.") - if (config.pre_hook is not None or - config.renew_hook is not None or config.post_hook is not None): - notify("No hooks were run.") - elif renew_successes and not renew_failures: - notify("Congratulations, all renewals succeeded. The following certs " - "have been renewed:") - notify(report(renew_successes, "success")) - elif renew_failures and not renew_successes: - notify_error("All renewal attempts failed. The following certs could " - "not be renewed:") - notify_error(report(renew_failures, "failure")) - elif renew_failures and renew_successes: - notify("The following certs were successfully renewed:") - notify(report(renew_successes, "success") + "\n") - notify_error("The following certs could not be renewed:") - notify_error(report(renew_failures, "failure")) - - if parse_failures: - notify("\nAdditionally, the following renewal configurations " - "were invalid: ") - notify(report(parse_failures, "parsefail")) - - if config.dry_run: - notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") - notify("** (The test certificates above have not been saved.)") - - disp.notification("\n".join(out), wrap=False) - - -def handle_renewal_request(config): - """Examine each lineage; renew if due and report results""" - - # This is trivially False if config.domains is empty - if any(domain not in config.webroot_map for domain in config.domains): - # If more plugins start using cli.add_domains, - # we may want to only log a warning here - raise errors.Error("Currently, the renew verb is capable of either " - "renewing all installed certificates that are due " - "to be renewed or renewing a single certificate specified " - "by its name. If you would like to renew specific " - "certificates by their domains, use the certonly command " - "instead. The renew verb may provide other options " - "for selecting certificates to renew in the future.") - - if config.certname: - conf_files = [storage.renewal_file_for_certname(config, config.certname)] - else: - conf_files = storage.renewal_conf_files(config) - - renew_successes = [] - 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) - lineage_config = copy.deepcopy(config) - lineagename = storage.lineagename_for_filename(renewal_file) - - # Note that this modifies config (to add back the configuration - # elements from within the renewal configuration file). - try: - renewal_candidate = _reconstitute(lineage_config, renewal_file) - except Exception as e: # pylint: disable=broad-except - logger.warning("Renewal configuration file %s (cert: %s) " - "produced an unexpected error: %s. Skipping.", - renewal_file, lineagename, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - parse_failures.append(renewal_file) - continue - - try: - if renewal_candidate is None: - parse_failures.append(renewal_file) - else: - # XXX: ensure that each call here replaces the previous one - zope.component.provideUtility(lineage_config) - renewal_candidate.ensure_deployed() - from certbot._internal 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.uniform(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 - # we already know it's time to renew based on should_renew - # and we have a lineage in renewal_candidate - main.renew_cert(lineage_config, plugins, renewal_candidate) - renew_successes.append(renewal_candidate.fullchain) - else: - expiry = crypto_util.notAfter(renewal_candidate.version( - "cert", renewal_candidate.latest_common_version())) - renew_skipped.append("%s expires on %s" % (renewal_candidate.fullchain, - expiry.strftime("%Y-%m-%d"))) - # Run updater interface methods - updater.run_generic_updaters(lineage_config, renewal_candidate, - plugins) - - except Exception as e: # pylint: disable=broad-except - # obtain_cert (presumably) encountered an unanticipated problem. - logger.warning("Attempting to renew cert (%s) from %s produced an " - "unexpected error: %s. Skipping.", lineagename, - renewal_file, e) - logger.debug("Traceback was:\n%s", traceback.format_exc()) - renew_failures.append(renewal_candidate.fullchain) - - # Describe all the results - _renew_describe_results(config, renew_successes, renew_failures, - renew_skipped, parse_failures) - - if renew_failures or parse_failures: - raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( - len(renew_failures), len(parse_failures))) - else: - logger.debug("no renewal failures") diff --git a/certbot/_internal/reporter.py b/certbot/_internal/reporter.py deleted file mode 100644 index e0063d8e5..000000000 --- a/certbot/_internal/reporter.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Collects and displays information to the user.""" -from __future__ import print_function - -import collections -import logging -import sys -import textwrap - -from six.moves import queue # type: ignore # pylint: disable=import-error -import zope.interface - -from certbot import interfaces -from certbot import util - - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IReporter) -class Reporter(object): - """Collects and displays information to the user. - - :ivar `queue.PriorityQueue` messages: Messages to be displayed to - the user. - - """ - - HIGH_PRIORITY = 0 - """High priority constant. See `add_message`.""" - MEDIUM_PRIORITY = 1 - """Medium priority constant. See `add_message`.""" - LOW_PRIORITY = 2 - """Low priority constant. See `add_message`.""" - - _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') - - def __init__(self, config): - self.messages = queue.PriorityQueue() - self.config = config - - def add_message(self, msg, priority, on_crash=True): - """Adds msg to the list of messages to be printed. - - :param str msg: Message to be displayed to the user. - - :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`, - or `LOW_PRIORITY`. - - :param bool on_crash: Whether or not the message should be - printed if the program exits abnormally. - - """ - assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY - self.messages.put(self._msg_type(priority, msg, on_crash)) - logger.debug("Reporting to user: %s", msg) - - def print_messages(self): - """Prints messages to the user and clears the message queue. - - If there is an unhandled exception, only messages for which - ``on_crash`` is ``True`` are printed. - - """ - bold_on = False - if not self.messages.empty(): - no_exception = sys.exc_info()[0] is None - bold_on = sys.stdout.isatty() - if not self.config.quiet: - if bold_on: - print(util.ANSI_SGR_BOLD) - print('IMPORTANT NOTES:') - first_wrapper = textwrap.TextWrapper( - initial_indent=' - ', - subsequent_indent=(' ' * 3), - break_long_words=False, - break_on_hyphens=False) - next_wrapper = textwrap.TextWrapper( - initial_indent=first_wrapper.subsequent_indent, - subsequent_indent=first_wrapper.subsequent_indent, - break_long_words=False, - break_on_hyphens=False) - while not self.messages.empty(): - msg = self.messages.get() - if self.config.quiet: - # In --quiet mode, we only print high priority messages that - # are flagged for crash cases - if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash): - continue - if no_exception or msg.on_crash: - if bold_on and msg.priority > self.HIGH_PRIORITY: - if not self.config.quiet: - sys.stdout.write(util.ANSI_SGR_RESET) - bold_on = False - lines = msg.text.splitlines() - print(first_wrapper.fill(lines[0])) - if len(lines) > 1: - print("\n".join( - next_wrapper.fill(line) for line in lines[1:])) - if bold_on and not self.config.quiet: - sys.stdout.write(util.ANSI_SGR_RESET) diff --git a/certbot/_internal/storage.py b/certbot/_internal/storage.py deleted file mode 100644 index bb36f462a..000000000 --- a/certbot/_internal/storage.py +++ /dev/null @@ -1,1128 +0,0 @@ -"""Renewable certificates storage.""" -import datetime -import glob -import logging -import re -import shutil -import stat - -import configobj -import parsedatetime -import pytz -import six - -import certbot -from certbot._internal import cli -from certbot._internal import constants -from certbot import crypto_util -from certbot._internal import error_handler -from certbot import errors -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem -from certbot.plugins import common as plugins_common -from certbot._internal.plugins import disco as plugins_disco - -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): - """Build a list of all renewal configuration files. - - :param certbot.interfaces.IConfig config: Configuration object - - :returns: list of renewal configuration files - :rtype: `list` of `str` - - """ - 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""" - path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname)) - if not os.path.exists(path): - raise errors.CertStorageError("No certificate found with name {0} (expected " - "{1}).".format(certname, path)) - return path - - -def cert_path_for_cert_name(config, cert_name): - """ If `--cert-name` was specified, but you need a value for `--cert-path`. - - :param `configuration.NamespaceConfig` config: parsed command line arguments - :param str cert_name: cert name. - - """ - cert_name_implied_conf = renewal_file_for_certname(config, cert_name) - fullchain_path = configobj.ConfigObj(cert_name_implied_conf)["fullchain"] - with open(fullchain_path) as f: - cert_path = (fullchain_path, f.read()) - return cert_path - - -def config_with_defaults(config=None): - """Merge supplied config, if provided, on top of builtin defaults.""" - defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS) - defaults_copy.merge(config if config is not None else configobj.ConfigObj()) - return defaults_copy - - -def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()): - """Parse the time specified time interval, and add it to the base_time - - The interval can be in the English-language format understood by - parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or - a sequence of such intervals like '6 months 1 week' or '3 days 12 - hours'. If an integer is found with no associated unit, it is - interpreted by default as a number of days. - - :param datetime.datetime base_time: The time to be added with the interval. - :param str interval: The time interval to parse. - - :returns: The base_time plus the interpretation of the time interval. - :rtype: :class:`datetime.datetime`""" - - if interval.strip().isdigit(): - interval += " days" - - # try to use the same timezone, but fallback to UTC - tzinfo = base_time.tzinfo or pytz.UTC - - return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0] - - -def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): - """Writes a renewal config file with the specified name and values. - - :param str o_filename: Absolute path to the previous version of config file - :param str n_filename: Absolute path to the new destination of config file - :param str archive_dir: Absolute path to the archive directory - :param dict target: Maps ALL_FOUR to their symlink paths - :param dict relevant_data: Renewal configuration options to save - - :returns: Configuration object for the new config file - :rtype: configobj.ConfigObj - - """ - config = configobj.ConfigObj(o_filename) - config["version"] = certbot.__version__ - config["archive_dir"] = archive_dir - for kind in ALL_FOUR: - config[kind] = target[kind] - - if "renewalparams" not in config: - config["renewalparams"] = {} - config.comments["renewalparams"] = ["", - "Options used in " - "the renewal process"] - - config["renewalparams"].update(relevant_data) - - for k in config["renewalparams"].keys(): - if k not in relevant_data: - del config["renewalparams"][k] - - if "renew_before_expiry" not in config: - default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] - config.initial_comment = ["renew_before_expiry = " + default_interval] - - # TODO: add human-readable comments explaining other available - # parameters - logger.debug("Writing new config %s.", n_filename) - - # Ensure that the file exists - open(n_filename, 'a').close() - - # Copy permissions from the old version of the file, if it exists. - if os.path.exists(o_filename): - current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) - filesystem.chmod(n_filename, current_permissions) - - with open(n_filename, "wb") as f: - config.write(outfile=f) - return config - - -def rename_renewal_config(prev_name, new_name, cli_config): - """Renames cli_config.certname's config to cli_config.new_certname. - - :param .NamespaceConfig cli_config: parsed command line - arguments - """ - prev_filename = renewal_filename_for_lineagename(cli_config, prev_name) - new_filename = renewal_filename_for_lineagename(cli_config, new_name) - if os.path.exists(new_filename): - raise errors.ConfigurationError("The new certificate name " - "is already in use.") - try: - filesystem.replace(prev_filename, new_filename) - except OSError: - raise errors.ConfigurationError("Please specify a valid filename " - "for the new certificate name.") - - -def update_configuration(lineagename, archive_dir, target, cli_config): - """Modifies lineagename's config to contain the specified values. - - :param str lineagename: Name of the lineage being modified - :param str archive_dir: Absolute path to the archive directory - :param dict target: Maps ALL_FOUR to their symlink paths - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: Configuration object for the updated config file - :rtype: configobj.ConfigObj - - """ - config_filename = renewal_filename_for_lineagename(cli_config, lineagename) - temp_filename = config_filename + ".new" - - # If an existing tempfile exists, delete it - if os.path.exists(temp_filename): - os.unlink(temp_filename) - - # Save only the config items that are relevant to renewal - values = relevant_values(vars(cli_config.namespace)) - write_renewal_config(config_filename, temp_filename, archive_dir, target, values) - filesystem.replace(temp_filename, config_filename) - - return configobj.ConfigObj(config_filename) - - -def get_link_target(link): - """Get an absolute path to the target of link. - - :param str link: Path to a symbolic link - - :returns: Absolute path to the target of link - :rtype: str - - :raises .CertStorageError: If link does not exists. - - """ - try: - target = os.readlink(link) - except OSError: - raise errors.CertStorageError( - "Expected {0} to be a symlink".format(link)) - - if not os.path.isabs(target): - target = os.path.join(os.path.dirname(link), target) - return os.path.abspath(target) - -def _write_live_readme_to(readme_path, is_base_dir=False): - prefix = "" - if is_base_dir: - prefix = "[cert name]/" - with open(readme_path, "w") as f: - logger.debug("Writing README to %s.", readme_path) - f.write("This directory contains your keys and certificates.\n\n" - "`{prefix}privkey.pem` : the private key for your certificate.\n" - "`{prefix}fullchain.pem`: the certificate file used in most server software.\n" - "`{prefix}chain.pem` : used for OCSP stapling in Nginx >=1.3.7.\n" - "`{prefix}cert.pem` : will break many server configurations, and " - "should not be used\n" - " without reading further documentation (see link below).\n\n" - "WARNING: DO NOT MOVE OR RENAME THESE FILES!\n" - " Certbot expects these files to remain in this location in order\n" - " to function properly!\n\n" - "We recommend not moving these files. For more information, see the Certbot\n" - "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-" - "certificates.\n".format(prefix=prefix)) - - -def _relevant(namespaces, option): - """ - Is this option one that could be restored for future renewal purposes? - - :param namespaces: plugin namespaces for configuration options - :type namespaces: `list` of `str` - :param str option: the name of the option - - :rtype: bool - """ - from certbot._internal import renewal - - return (option in renewal.CONFIG_ITEMS or - any(option.startswith(namespace) for namespace in namespaces)) - - -def relevant_values(all_values): - """Return a new dict containing only items relevant for renewal. - - :param dict all_values: The original values. - - :returns: A new dictionary containing items that can be used in renewal. - :rtype dict: - - """ - plugins = plugins_disco.PluginsRegistry.find_all() - namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] - - rv = dict( - (option, value) - for option, value in six.iteritems(all_values) - if _relevant(namespaces, option) and cli.option_was_set(option, value)) - # We always save the server value to help with forward compatibility - # and behavioral consistency when versions of Certbot with different - # server defaults are used. - rv["server"] = all_values["server"] - return rv - -def lineagename_for_filename(config_filename): - """Returns the lineagename for a configuration filename. - """ - if not config_filename.endswith(".conf"): - raise errors.CertStorageError( - "renewal config file name must end in .conf") - return os.path.basename(config_filename[:-len(".conf")]) - -def renewal_filename_for_lineagename(config, lineagename): - """Returns the lineagename for a configuration filename. - """ - return os.path.join(config.renewal_configs_dir, lineagename) + ".conf" - -def _relpath_from_file(archive_dir, from_file): - """Path to a directory from a file""" - return os.path.relpath(archive_dir, os.path.dirname(from_file)) - -def full_archive_path(config_obj, cli_config, lineagename): - """Returns the full archive path for a lineagename - - Uses cli_config to determine archive path if not available from config_obj. - - :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None) - :param configuration.NamespaceConfig cli_config: Main config file - :param str lineagename: Certificate name - """ - if config_obj and "archive_dir" in config_obj: - return config_obj["archive_dir"] - return os.path.join(cli_config.default_archive_dir, lineagename) - -def _full_live_path(cli_config, lineagename): - """Returns the full default live path for a lineagename""" - return os.path.join(cli_config.live_dir, lineagename) - -def delete_files(config, certname): - """Delete all files related to the certificate. - - If some files are not found, ignore them and continue. - """ - renewal_filename = renewal_file_for_certname(config, certname) - # file exists - full_default_archive_dir = full_archive_path(None, config, certname) - full_default_live_dir = _full_live_path(config, certname) - try: - renewal_config = configobj.ConfigObj(renewal_filename) - except configobj.ConfigObjError: - # config is corrupted - logger.warning("Could not parse %s. You may wish to manually " - "delete the contents of %s and %s.", renewal_filename, - full_default_live_dir, full_default_archive_dir) - raise errors.CertStorageError( - "error parsing {0}".format(renewal_filename)) - finally: - # we couldn't read it, but let's at least delete it - # if this was going to fail, it already would have. - os.remove(renewal_filename) - logger.debug("Removed %s", renewal_filename) - - # cert files and (hopefully) live directory - # it's not guaranteed that the files are in our default storage - # structure. so, first delete the cert files. - directory_names = set() - for kind in ALL_FOUR: - link = renewal_config.get(kind) - try: - os.remove(link) - logger.debug("Removed %s", link) - except OSError: - logger.debug("Unable to delete %s", link) - directory = os.path.dirname(link) - directory_names.add(directory) - - # if all four were in the same directory, and the only thing left - # is the README file (or nothing), delete that directory. - # this will be wrong in very few but some cases. - if len(directory_names) == 1: - # delete the README file - directory = directory_names.pop() - readme_path = os.path.join(directory, README) - try: - os.remove(readme_path) - logger.debug("Removed %s", readme_path) - except OSError: - logger.debug("Unable to delete %s", readme_path) - # if it's now empty, delete the directory - try: - os.rmdir(directory) # only removes empty directories - logger.debug("Removed %s", directory) - except OSError: - logger.debug("Unable to remove %s; may not be empty.", directory) - - # archive directory - try: - archive_path = full_archive_path(renewal_config, config, certname) - shutil.rmtree(archive_path) - logger.debug("Removed %s", archive_path) - except OSError: - logger.debug("Unable to remove %s", archive_path) - - -class RenewableCert(object): - """Renewable certificate. - - Represents a lineage of certificates that is under the management of - Certbot, indicated by the existence of an associated renewal - configuration file. - - Note that the notion of "current version" for a lineage is - maintained on disk in the structure of symbolic links, and is not - explicitly stored in any instance variable in this object. The - RenewableCert object is able to determine information about the - current (or other) version by accessing data on disk, but does not - inherently know any of this information except by examining the - symbolic links as needed. The instance variables mentioned below - point to symlinks that reflect the notion of "current version" of - each managed object, and it is these paths that should be used when - configuring servers to use the certificate managed in a lineage. - These paths are normally within the "live" directory, and their - symlink targets -- the actual cert files -- are normally found - within the "archive" directory. - - :ivar str cert: The path to the symlink representing the current - version of the certificate managed by this lineage. - :ivar str privkey: The path to the symlink representing the current - version of the private key managed by this lineage. - :ivar str chain: The path to the symlink representing the current version - of the chain managed by this lineage. - :ivar str fullchain: The path to the symlink representing the - current version of the fullchain (combined chain and cert) - managed by this lineage. - :ivar configobj.ConfigObj configuration: The renewal configuration - options associated with this lineage, obtained from parsing the - renewal configuration file and/or systemwide defaults. - - """ - def __init__(self, config_filename, cli_config, update_symlinks=False): - """Instantiate a RenewableCert object from an existing lineage. - - :param str config_filename: the path to the renewal config file - that defines this lineage. - :param .NamespaceConfig: parsed command line arguments - - :raises .CertStorageError: if the configuration file's name didn't end - in ".conf", or the file is missing or broken. - - """ - self.cli_config = cli_config - self.lineagename = lineagename_for_filename(config_filename) - - # self.configuration should be used to read parameters that - # may have been chosen based on default values from the - # systemwide renewal configuration; self.configfile should be - # used to make and save changes. - try: - self.configfile = configobj.ConfigObj(config_filename) - except configobj.ConfigObjError: - raise errors.CertStorageError( - "error parsing {0}".format(config_filename)) - # TODO: Do we actually use anything from defaults and do we want to - # read further defaults from the systemwide renewal configuration - # file at this stage? - self.configuration = config_with_defaults(self.configfile) - - if not all(x in self.configuration for x in ALL_FOUR): - raise errors.CertStorageError( - "renewal config file {0} is missing a required " - "file reference".format(self.configfile)) - - conf_version = self.configuration.get("version") - if (conf_version is not None and - util.get_strict_version(conf_version) > CURRENT_VERSION): - logger.info( - "Attempting to parse the version %s renewal configuration " - "file found at %s with version %s of Certbot. This might not " - "work.", conf_version, config_filename, certbot.__version__) - - self.cert = self.configuration["cert"] - self.privkey = self.configuration["privkey"] - self.chain = self.configuration["chain"] - self.fullchain = self.configuration["fullchain"] - self.live_dir = os.path.dirname(self.cert) - - self._fix_symlinks() - if update_symlinks: - self._update_symlinks() - self._check_symlinks() - - @property - def key_path(self): - """Duck type for self.privkey""" - return self.privkey - - @property - def cert_path(self): - """Duck type for self.cert""" - return self.cert - - @property - def chain_path(self): - """Duck type for self.chain""" - return self.chain - - @property - def fullchain_path(self): - """Duck type for self.fullchain""" - return self.fullchain - - @property - def target_expiry(self): - """The current target certificate's expiration datetime - - :returns: Expiration datetime of the current target certificate - :rtype: :class:`datetime.datetime` - """ - return crypto_util.notAfter(self.current_target("cert")) - - @property - def archive_dir(self): - """Returns the default or specified archive directory""" - return full_archive_path(self.configuration, - self.cli_config, self.lineagename) - - def relative_archive_dir(self, from_file): - """Returns the default or specified archive directory as a relative path - - Used for creating symbolic links. - """ - return _relpath_from_file(self.archive_dir, from_file) - - @property - def is_test_cert(self): - """Returns true if this is a test cert from a staging server.""" - server = self.configuration["renewalparams"].get("server", None) - if server: - return util.is_staging(server) - return False - - def _check_symlinks(self): - """Raises an exception if a symlink doesn't exist""" - for kind in ALL_FOUR: - link = getattr(self, kind) - if not os.path.islink(link): - raise errors.CertStorageError( - "expected {0} to be a symlink".format(link)) - target = get_link_target(link) - if not os.path.exists(target): - raise errors.CertStorageError("target {0} of symlink {1} does " - "not exist".format(target, link)) - - def _update_symlinks(self): - """Updates symlinks to use archive_dir""" - for kind in ALL_FOUR: - link = getattr(self, kind) - previous_link = get_link_target(link) - new_link = os.path.join(self.relative_archive_dir(link), - os.path.basename(previous_link)) - - os.unlink(link) - os.symlink(new_link, link) - - def _consistent(self): - """Are the files associated with this lineage self-consistent? - - :returns: Whether the files stored in connection with this - lineage appear to be correct and consistent with one - another. - :rtype: bool - - """ - # Each element must be referenced with an absolute path - for x in (self.cert, self.privkey, self.chain, self.fullchain): - if not os.path.isabs(x): - logger.debug("Element %s is not referenced with an " - "absolute path.", x) - return False - - # Each element must exist and be a symbolic link - for x in (self.cert, self.privkey, self.chain, self.fullchain): - if not os.path.islink(x): - logger.debug("Element %s is not a symbolic link.", x) - return False - for kind in ALL_FOUR: - link = getattr(self, kind) - target = get_link_target(link) - - # Each element's link must point within the cert lineage's - # directory within the official archive directory - if not os.path.samefile(os.path.dirname(target), self.archive_dir): - logger.debug("Element's link does not point within the " - "cert lineage's directory within the " - "official archive directory. Link: %s, " - "target directory: %s, " - "archive directory: %s. If you've specified " - "the archive directory in the renewal configuration " - "file, you may need to update links by running " - "certbot update_symlinks.", - link, os.path.dirname(target), self.archive_dir) - return False - - # The link must point to a file that exists - if not os.path.exists(target): - logger.debug("Link %s points to file %s that does not exist.", - link, target) - return False - - # The link must point to a file that follows the archive - # naming convention - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - if not pattern.match(os.path.basename(target)): - logger.debug("%s does not follow the archive naming " - "convention.", target) - return False - - # It is NOT required that the link's target be a regular - # file (it may itself be a symlink). But we should probably - # do a recursive check that ultimately the target does - # exist? - # XXX: Additional possible consistency checks (e.g. - # cryptographic validation of the chain being a chain, - # the chain matching the cert, and the cert matching - # the subject key) - # XXX: All four of the targets are in the same directory - # (This check is redundant with the check that they - # are all in the desired directory!) - # len(set(os.path.basename(self.current_target(x) - # for x in ALL_FOUR))) == 1 - return True - - def _fix(self): - """Attempt to fix defects or inconsistencies in this lineage. - - .. todo:: Currently unimplemented. - - """ - # TODO: Figure out what kinds of fixes are possible. For - # example, checking if there is a valid version that - # we can update the symlinks to. (Maybe involve - # parsing keys and certs to see if they exist and - # if a key corresponds to the subject key of a cert?) - - # TODO: In general, the symlink-reading functions below are not - # cautious enough about the possibility that links or their - # targets may not exist. (This shouldn't happen, but might - # happen as a result of random tampering by a sysadmin, or - # filesystem errors, or crashes.) - - def _previous_symlinks(self): - """Returns the kind and path of all symlinks used in recovery. - - :returns: list of (kind, symlink) tuples - :rtype: list - - """ - previous_symlinks = [] - for kind in ALL_FOUR: - link_dir = os.path.dirname(getattr(self, kind)) - link_base = "previous_{0}.pem".format(kind) - previous_symlinks.append((kind, os.path.join(link_dir, link_base))) - - return previous_symlinks - - def _fix_symlinks(self): - """Fixes symlinks in the event of an incomplete version update. - - If there is no problem with the current symlinks, this function - has no effect. - - """ - previous_symlinks = self._previous_symlinks() - if all(os.path.exists(link[1]) for link in previous_symlinks): - for kind, previous_link in previous_symlinks: - current_link = getattr(self, kind) - if os.path.lexists(current_link): - os.unlink(current_link) - os.symlink(os.readlink(previous_link), current_link) - - for _, link in previous_symlinks: - if os.path.exists(link): - os.unlink(link) - - def current_target(self, kind): - """Returns full path to which the specified item currently points. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - - :returns: The path to the current version of the specified - member. - :rtype: str or None - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - link = getattr(self, kind) - if not os.path.exists(link): - logger.debug("Expected symlink %s for %s does not exist.", - link, kind) - return None - return get_link_target(link) - - def current_version(self, kind): - """Returns numerical version of the specified item. - - For example, if kind is "chain" and the current chain link - points to a file named "chain7.pem", returns the integer 7. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - - :returns: the current version of the specified member. - :rtype: int - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - target = self.current_target(kind) - if target is None or not os.path.exists(target): - logger.debug("Current-version target for %s " - "does not exist at %s.", kind, target) - target = "" - matches = pattern.match(os.path.basename(target)) - if matches: - return int(matches.groups()[0]) - logger.debug("No matches for target %s.", kind) - return None - - def version(self, kind, version): - """The filename that corresponds to the specified version and kind. - - .. warning:: The specified version may not exist in this - lineage. There is no guarantee that the file path returned - by this method actually exists. - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - :param int version: the desired version - - :returns: The path to the specified version of the specified member. - :rtype: str - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - where = os.path.dirname(self.current_target(kind)) - return os.path.join(where, "{0}{1}.pem".format(kind, version)) - - def available_versions(self, kind): - """Which alternative versions of the specified kind of item exist? - - The archive directory where the current version is stored is - consulted to obtain the list of alternatives. - - :param str kind: the lineage member item ( - ``cert``, ``privkey``, ``chain``, or ``fullchain``) - - :returns: all of the version numbers that currently exist - :rtype: `list` of `int` - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - where = os.path.dirname(self.current_target(kind)) - files = os.listdir(where) - pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) - matches = [pattern.match(f) for f in files] - return sorted([int(m.groups()[0]) for m in matches if m]) - - def newest_available_version(self, kind): - """Newest available version of the specified kind of item? - - :param str kind: the lineage member item (``cert``, - ``privkey``, ``chain``, or ``fullchain``) - - :returns: the newest available version of this member - :rtype: int - - """ - return max(self.available_versions(kind)) - - def latest_common_version(self): - """Newest version for which all items are available? - - :returns: the newest available version for which all members - (``cert, ``privkey``, ``chain``, and ``fullchain``) exist - :rtype: int - - """ - # TODO: this can raise CertStorageError if there is no version overlap - # (it should probably return None instead) - # TODO: this can raise a spurious AttributeError if the current - # link for any kind is missing (it should probably return None) - versions = [self.available_versions(x) for x in ALL_FOUR] - return max(n for n in versions[0] if all(n in v for v in versions[1:])) - - def next_free_version(self): - """Smallest version newer than all full or partial versions? - - :returns: the smallest version number that is larger than any - version of any item currently stored in this lineage - :rtype: int - - """ - # TODO: consider locking/mutual exclusion between updating processes - # This isn't self.latest_common_version() + 1 because we don't want - # collide with a version that might exist for one file type but not - # for the others. - return max(self.newest_available_version(x) for x in ALL_FOUR) + 1 - - def ensure_deployed(self): - """Make sure we've deployed the latest version. - - :returns: False if a change was needed, True otherwise - :rtype: bool - - May need to recover from rare interrupted / crashed states.""" - - if self.has_pending_deployment(): - 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 - return True - - - def has_pending_deployment(self): - """Is there a later version of all of the managed items? - - :returns: ``True`` if there is a complete version of this - lineage with a larger version number than the current - version, and ``False`` otherwise - :rtype: bool - - """ - # TODO: consider whether to assume consistency or treat - # inconsistent/consistent versions differently - smallest_current = min(self.current_version(x) for x in ALL_FOUR) - return smallest_current < self.latest_common_version() - - def _update_link_to(self, kind, version): - """Make the specified item point at the specified version. - - (Note that this method doesn't verify that the specified version - exists.) - - :param str kind: the lineage member item ("cert", "privkey", - "chain", or "fullchain") - :param int version: the desired version - - """ - if kind not in ALL_FOUR: - raise errors.CertStorageError("unknown kind of item") - link = getattr(self, kind) - filename = "{0}{1}.pem".format(kind, version) - # Relative rather than absolute target directory - target_directory = os.path.dirname(os.readlink(link)) - # TODO: it could be safer to make the link first under a temporary - # filename, then unlink the old link, then rename the new link - # to the old link; this ensures that this process is able to - # create symlinks. - # TODO: we might also want to check consistency of related links - # for the other corresponding items - os.unlink(link) - os.symlink(os.path.join(target_directory, filename), link) - - def update_all_links_to(self, version): - """Change all member objects to point to the specified version. - - :param int version: the desired version - - """ - with error_handler.ErrorHandler(self._fix_symlinks): - previous_links = self._previous_symlinks() - for kind, link in previous_links: - os.symlink(self.current_target(kind), link) - - for kind in ALL_FOUR: - self._update_link_to(kind, version) - - for _, link in previous_links: - os.unlink(link) - - def names(self, version=None): - """What are the subject names of this certificate? - - (If no version is specified, use the current version.) - - :param int version: the desired version number - :returns: the subject names - :rtype: `list` of `str` - :raises .CertStorageError: if could not find cert file. - - """ - if version is None: - target = self.current_target("cert") - else: - target = self.version("cert", version) - if target is None: - raise errors.CertStorageError("could not find cert file") - with open(target) as f: - return crypto_util.get_names_from_cert(f.read()) - - def ocsp_revoked(self, version=None): - # pylint: disable=no-self-use,unused-argument - """Is the specified cert version revoked according to OCSP? - - Also returns True if the cert version is declared as intended - to be revoked according to Let's Encrypt OCSP extensions. - (If no version is specified, uses the current version.) - - This method is not yet implemented and currently always returns - False. - - :param int version: the desired version number - - :returns: whether the certificate is or will be revoked - :rtype: bool - - """ - # XXX: This query and its associated network service aren't - # implemented yet, so we currently return False (indicating that the - # certificate is not revoked). - return False - - def autorenewal_is_enabled(self): - """Is automatic renewal enabled for this cert? - - If autorenew is not specified, defaults to True. - - :returns: True if automatic renewal is enabled - :rtype: bool - - """ - return ("autorenew" not in self.configuration["renewalparams"] or - self.configuration["renewalparams"].as_bool("autorenew")) - - def should_autorenew(self): - """Should we now try to autorenew the most recent cert version? - - This is a policy question and does not only depend on whether - the cert is expired. (This considers whether autorenewal is - enabled, whether the cert is revoked, and whether the time - interval for autorenewal has been reached.) - - Note that this examines the numerically most recent cert version, - not the currently deployed version. - - :returns: whether an attempt should now be made to autorenew the - most current cert version in this lineage - :rtype: bool - - """ - if self.autorenewal_is_enabled(): - # Consider whether to attempt to autorenew this cert now - - # Renewals on the basis of revocation - if self.ocsp_revoked(self.latest_common_version()): - logger.debug("Should renew, certificate is revoked.") - return True - - # Renews some period before expiry time - default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] - interval = self.configuration.get("renew_before_expiry", default_interval) - expiry = crypto_util.notAfter(self.version( - "cert", self.latest_common_version())) - now = pytz.UTC.fromutc(datetime.datetime.utcnow()) - if expiry < add_time_interval(now, interval): - logger.debug("Should renew, less than %s before certificate " - "expiry %s.", interval, - expiry.strftime("%Y-%m-%d %H:%M:%S %Z")) - return True - return False - - @classmethod - def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): - """Create a new certificate lineage. - - Attempts to create a certificate lineage -- enrolled for - potential future renewal -- with the (suggested) lineage name - lineagename, and the associated cert, privkey, and chain (the - associated fullchain will be created automatically). Optional - configurator and renewalparams record the configuration that was - originally used to obtain this cert, so that it can be reused - later during automated renewal. - - Returns a new RenewableCert object referring to the created - lineage. (The actual lineage name, as well as all the relevant - file paths, will be available within this object.) - - :param str lineagename: the suggested name for this lineage - (normally the current cert's first subject DNS name) - :param str cert: the initial certificate version in PEM format - :param str privkey: the private key in PEM format - :param str chain: the certificate chain in PEM format - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: the newly-created RenewalCert object - :rtype: :class:`storage.renewableCert` - - """ - - # Examine the configuration and find the new lineage's name - for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, - cli_config.live_dir): - if not os.path.exists(i): - filesystem.makedirs(i, 0o700) - logger.debug("Creating directory %s.", i) - config_file, config_filename = util.unique_lineage_name( - cli_config.renewal_configs_dir, lineagename) - base_readme_path = os.path.join(cli_config.live_dir, README) - if not os.path.exists(base_readme_path): - _write_live_readme_to(base_readme_path, is_base_dir=True) - - # Determine where on disk everything will go - # lineagename will now potentially be modified based on which - # renewal configuration file could actually be created - lineagename = lineagename_for_filename(config_filename) - 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) - filesystem.mkdir(archive) - filesystem.mkdir(live_dir) - logger.debug("Archive directory %s and live " - "directory %s created.", archive, live_dir) - - # 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(_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 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 - with open(target["chain"], "wb") as f: - logger.debug("Writing chain to %s.", target["chain"]) - f.write(chain) - with open(target["fullchain"], "wb") as f: - # assumes that OpenSSL.crypto.dump_certificate includes - # ending newline character - logger.debug("Writing full chain to %s.", target["fullchain"]) - f.write(cert + chain) - - # Write a README file to the live directory - readme_path = os.path.join(live_dir, README) - _write_live_readme_to(readme_path) - - # Document what we've done in a new renewal config file - config_file.close() - - # Save only the config items that are relevant to renewal - values = relevant_values(vars(cli_config.namespace)) - - new_config = write_renewal_config(config_filename, config_filename, archive, - target, values) - return cls(new_config.filename, cli_config) - - def save_successor(self, prior_version, new_cert, - new_privkey, new_chain, cli_config): - """Save new cert and chain as a successor of a prior version. - - Returns the new version number that was created. - - .. note:: this function does NOT update links to deploy this - version - - :param int prior_version: the old version to which this version - is regarded as a successor (used to choose a privkey, if the - key has not changed, but otherwise this information is not - permanently recorded anywhere) - :param bytes new_cert: the new certificate, in PEM format - :param bytes new_privkey: the new private key, in PEM format, - or ``None``, if the private key has not changed - :param bytes new_chain: the new chain, in PEM format - :param .NamespaceConfig cli_config: parsed command line - arguments - - :returns: the new version number that was created - :rtype: int - - """ - # XXX: assumes official archive location rather than examining links - # XXX: consider using os.open for availability of os.O_EXCL - # XXX: ensure file permissions are correct; also create directories - # if needed (ensuring their permissions are correct) - # Figure out what the new version is and hence where to save things - - self.cli_config = cli_config - target_version = self.next_free_version() - target = dict( - [(kind, - 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. - if os.path.islink(old_privkey): - old_privkey = os.readlink(old_privkey) - else: - old_privkey = "privkey{0}.pem".format(prior_version) - logger.debug("Writing symlink to old private key, %s.", old_privkey) - os.symlink(old_privkey, target["privkey"]) - else: - 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 & MASK_FOR_PRIVATE_KEY_PERMISSIONS) - # from previous privkey in this lineage. - mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) - filesystem.copy_ownership_and_apply_mode( - old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) - - # Save everything else - with open(target["cert"], "wb") as f: - logger.debug("Writing certificate to %s.", target["cert"]) - f.write(new_cert) - with open(target["chain"], "wb") as f: - logger.debug("Writing chain to %s.", target["chain"]) - f.write(new_chain) - with open(target["fullchain"], "wb") as f: - logger.debug("Writing full chain to %s.", target["fullchain"]) - f.write(new_cert + new_chain) - - symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) - # Update renewal config file - self.configfile = update_configuration( - self.lineagename, self.archive_dir, symlinks, cli_config) - self.configuration = config_with_defaults(self.configfile) - - return target_version diff --git a/certbot/_internal/updater.py b/certbot/_internal/updater.py deleted file mode 100644 index 50db0e21c..000000000 --- a/certbot/_internal/updater.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Updaters run at renewal""" -import logging - -from certbot import errors -from certbot import interfaces - -from certbot._internal.plugins import selection as plug_sel -import certbot.plugins.enhancements as enhancements - -logger = logging.getLogger(__name__) - -def run_generic_updaters(config, lineage, plugins): - """Run updaters that the plugin supports - - :param config: Configuration object - :type config: interfaces.IConfig - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :param plugins: List of plugins - :type plugins: `list` of `str` - - :returns: `None` - :rtype: None - """ - if config.dry_run: - logger.debug("Skipping updaters in dry-run mode.") - return - try: - installer = plug_sel.get_unprepared_installer(config, plugins) - except errors.Error as e: - logger.warning("Could not choose appropriate plugin for updaters: %s", e) - return - if installer: - _run_updaters(lineage, installer, config) - _run_enhancement_updaters(lineage, installer, config) - -def run_renewal_deployer(config, lineage, installer): - """Helper function to run deployer interface method if supported by the used - installer plugin. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :returns: `None` - :rtype: None - """ - if config.dry_run: - logger.debug("Skipping renewal deployer in dry-run mode.") - return - - if not config.disable_renew_updates and isinstance(installer, - interfaces.RenewDeployer): - installer.renew_deploy(lineage) - _run_enhancement_deployers(lineage, installer, config) - -def _run_updaters(lineage, installer, config): - """Helper function to run the updater interface methods if supported by the - used installer plugin. - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :returns: `None` - :rtype: None - """ - if not config.disable_renew_updates: - if isinstance(installer, interfaces.GenericUpdater): - installer.generic_updates(lineage) - -def _run_enhancement_updaters(lineage, installer, config): - """Iterates through known enhancement interfaces. If the installer implements - an enhancement interface and the enhance interface has an updater method, the - updater method gets run. - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :param config: Configuration object - :type config: interfaces.IConfig - """ - - if config.disable_renew_updates: - return - for enh in enhancements._INDEX: # pylint: disable=protected-access - if isinstance(installer, enh["class"]) and enh["updater_function"]: - getattr(installer, enh["updater_function"])(lineage) - - -def _run_enhancement_deployers(lineage, installer, config): - """Iterates through known enhancement interfaces. If the installer implements - an enhancement interface and the enhance interface has an deployer method, the - deployer method gets run. - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :param config: Configuration object - :type config: interfaces.IConfig - """ - - if config.disable_renew_updates: - return - for enh in enhancements._INDEX: # pylint: disable=protected-access - if isinstance(installer, enh["class"]) and enh["deployer_function"]: - getattr(installer, enh["deployer_function"])(lineage) diff --git a/certbot/achallenges.py b/certbot/achallenges.py deleted file mode 100644 index 2f2e1f3bd..000000000 --- a/certbot/achallenges.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Client annotated ACME challenges. - -Please use names such as ``achall`` to distinguish from variables "of type" -:class:`acme.challenges.Challenge` (denoted by ``chall``) -and :class:`.ChallengeBody` (denoted by ``challb``):: - - from acme import challenges - from acme import messages - from certbot import achallenges - - chall = challenges.DNS(token='foo') - challb = messages.ChallengeBody(chall=chall) - achall = achallenges.DNS(chall=challb, domain='example.com') - -Note, that all annotated challenges act as a proxy objects:: - - achall.token == challb.token - -""" -import logging - -import josepy as jose - -from acme import challenges - - -logger = logging.getLogger(__name__) - - - -class AnnotatedChallenge(jose.ImmutableMap): - """Client annotated challenge. - - Wraps around server provided challenge and annotates with data - useful for the client. - - :ivar challb: Wrapped `~.ChallengeBody`. - - """ - __slots__ = ('challb',) - acme_type = NotImplemented - - def __getattr__(self, name): - return getattr(self.challb, name) - - -class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge): - """Client annotated `KeyAuthorizationChallenge` challenge.""" - __slots__ = ('challb', 'domain', 'account_key') - - def response_and_validation(self, *args, **kwargs): - """Generate response and validation.""" - return self.challb.chall.response_and_validation( - self.account_key, *args, **kwargs) - - -class DNS(AnnotatedChallenge): - """Client annotated "dns" ACME challenge.""" - __slots__ = ('challb', 'domain') - acme_type = challenges.DNS diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py new file mode 100644 index 000000000..30b52be1a --- /dev/null +++ b/certbot/certbot/__init__.py @@ -0,0 +1,4 @@ +"""Certbot client.""" + +# version number like 1.2.3a0, must have at least 2 parts, like 1.2 +__version__ = '1.0.0.dev0' diff --git a/certbot/certbot/_internal/__init__.py b/certbot/certbot/_internal/__init__.py new file mode 100644 index 000000000..45ec6ac9c --- /dev/null +++ b/certbot/certbot/_internal/__init__.py @@ -0,0 +1,6 @@ +""" +Modules internal to Certbot. + +This package contains modules that are not considered part of Certbot's public +API. They may be changed without updating Certbot's major version. +""" diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py new file mode 100644 index 000000000..6060cbd71 --- /dev/null +++ b/certbot/certbot/_internal/account.py @@ -0,0 +1,344 @@ +"""Creates ACME accounts for server.""" +import datetime +import functools +import hashlib +import logging +import shutil +import socket + +import josepy as jose +import pyrfc3339 +import pytz +import six +import zope.component +from cryptography.hazmat.primitives import serialization + +from acme import fields as acme_fields +from acme import messages + +from certbot._internal import constants +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +class Account(object): + """ACME protocol registration. + + :ivar .RegistrationResource regr: Registration Resource + :ivar .JWK key: Authorized Account Key + :ivar .Meta: Account metadata + :ivar str id: Globally unique account identifier. + + """ + + class Meta(jose.JSONObjectWithFields): + """Account metadata + + :ivar datetime.datetime creation_dt: Creation date and time (UTC). + :ivar str creation_host: FQDN of host, where account has been created. + + .. note:: ``creation_dt`` and ``creation_host`` are useful in + cross-machine migration scenarios. + + """ + creation_dt = acme_fields.RFC3339Field("creation_dt") + creation_host = jose.Field("creation_host") + + def __init__(self, regr, key, meta=None): + self.key = key + self.regr = regr + self.meta = self.Meta( + # pyrfc3339 drops microseconds, make sure __eq__ is sane + creation_dt=datetime.datetime.now( + tz=pytz.UTC).replace(microsecond=0), + creation_host=socket.getfqdn()) if meta is None else meta + + self.id = hashlib.md5( + self.key.key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + ).hexdigest() + # Implementation note: Email? Multiple accounts can have the + # same email address. Registration URI? Assigned by the + # server, not guaranteed to be stable over time, nor + # canonical URI can be generated. ACME protocol doesn't allow + # account key (and thus its fingerprint) to be updated... + + @property + def slug(self): + """Short account identification string, useful for UI.""" + return "{1}@{0} ({2})".format(pyrfc3339.generate( + self.meta.creation_dt), self.meta.creation_host, self.id[:4]) + + def __repr__(self): + return "<{0}({1}, {2}, {3})>".format( + self.__class__.__name__, self.regr, self.id, self.meta) + + def __eq__(self, other): + return (isinstance(other, self.__class__) and + self.key == other.key and self.regr == other.regr and + self.meta == other.meta) + + +def report_new_account(config): + """Informs the user about their new ACME account.""" + reporter = zope.component.queryUtility(interfaces.IReporter) + if reporter is None: + return + reporter.add_message( + "Your account credentials have been saved in your Certbot " + "configuration directory at {0}. You should make a secure backup " + "of this folder now. This configuration directory will also " + "contain certificates and private keys obtained by Certbot " + "so making regular backups of this folder is ideal.".format( + config.config_dir), + reporter.MEDIUM_PRIORITY) + + +class AccountMemoryStorage(interfaces.AccountStorage): + """In-memory account storage.""" + + def __init__(self, initial_accounts=None): + self.accounts = initial_accounts if initial_accounts is not None else {} + + def find_all(self): + return list(six.itervalues(self.accounts)) + + def save(self, account, client): + if account.id in self.accounts: + logger.debug("Overwriting account: %s", account.id) + self.accounts[account.id] = account + + def load(self, account_id): + try: + return self.accounts[account_id] + except KeyError: + raise errors.AccountNotFound(account_id) + +class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource): + """A backwards-compatible RegistrationResource with a new-authz URI. + + Hack: Certbot versions pre-0.11.1 expect to load + new_authzr_uri as part of the account. Because people + sometimes switch between old and new versions, we will + continue to write out this field for some time so older + clients don't crash in that scenario. + """ + new_authzr_uri = jose.Field('new_authzr_uri') + +class AccountFileStorage(interfaces.AccountStorage): + """Accounts file storage. + + :ivar .IConfig config: Client configuration + + """ + def __init__(self, config): + self.config = config + util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) + + def _account_dir_path(self, account_id): + return self._account_dir_path_for_server_path(account_id, self.config.server_path) + + def _account_dir_path_for_server_path(self, account_id, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + return os.path.join(accounts_dir, account_id) + + @classmethod + def _regr_path(cls, account_dir_path): + return os.path.join(account_dir_path, "regr.json") + + @classmethod + def _key_path(cls, account_dir_path): + return os.path.join(account_dir_path, "private_key.json") + + @classmethod + def _metadata_path(cls, account_dir_path): + return os.path.join(account_dir_path, "meta.json") + + def _find_all_for_server_path(self, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + try: + candidates = os.listdir(accounts_dir) + except OSError: + return [] + + accounts = [] + for account_id in candidates: + try: + accounts.append(self._load_for_server_path(account_id, server_path)) + except errors.AccountStorageError: + logger.debug("Account loading problem", exc_info=True) + + if not accounts and server_path in constants.LE_REUSE_SERVERS: + # find all for the next link down + prev_server_path = constants.LE_REUSE_SERVERS[server_path] + prev_accounts = self._find_all_for_server_path(prev_server_path) + # if we found something, link to that + if prev_accounts: + try: + self._symlink_to_accounts_dir(prev_server_path, server_path) + except OSError: + return [] + accounts = prev_accounts + return accounts + + def find_all(self): + return self._find_all_for_server_path(self.config.server_path) + + def _symlink_to_account_dir(self, prev_server_path, server_path, account_id): + prev_account_dir = self._account_dir_path_for_server_path(account_id, prev_server_path) + new_account_dir = self._account_dir_path_for_server_path(account_id, server_path) + os.symlink(prev_account_dir, new_account_dir) + + def _symlink_to_accounts_dir(self, prev_server_path, server_path): + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + if os.path.islink(accounts_dir): + os.unlink(accounts_dir) + else: + os.rmdir(accounts_dir) + prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path) + os.symlink(prev_account_dir, accounts_dir) + + def _load_for_server_path(self, account_id, server_path): + account_dir_path = self._account_dir_path_for_server_path(account_id, server_path) + if not os.path.isdir(account_dir_path): # isdir is also true for symlinks + if server_path in constants.LE_REUSE_SERVERS: + prev_server_path = constants.LE_REUSE_SERVERS[server_path] + prev_loaded_account = self._load_for_server_path(account_id, prev_server_path) + # we didn't error so we found something, so create a symlink to that + accounts_dir = self.config.accounts_dir_for_server_path(server_path) + # If accounts_dir isn't empty, make an account specific symlink + if os.listdir(accounts_dir): + self._symlink_to_account_dir(prev_server_path, server_path, account_id) + else: + self._symlink_to_accounts_dir(prev_server_path, server_path) + return prev_loaded_account + else: + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) + + try: + with open(self._regr_path(account_dir_path)) as regr_file: + regr = messages.RegistrationResource.json_loads(regr_file.read()) + with open(self._key_path(account_dir_path)) as key_file: + key = jose.JWK.json_loads(key_file.read()) + with open(self._metadata_path(account_dir_path)) as metadata_file: + meta = Account.Meta.json_loads(metadata_file.read()) + except IOError as error: + raise errors.AccountStorageError(error) + + return Account(regr, key, meta) + + def load(self, account_id): + return self._load_for_server_path(account_id, self.config.server_path) + + def save(self, account, client): + self._save(account, client, regr_only=False) + + def save_regr(self, account, acme): + """Save the registration resource. + + :param Account account: account whose regr should be saved + + """ + self._save(account, acme, regr_only=True) + + def delete(self, account_id): + """Delete registration info from disk + + :param account_id: id of account which should be deleted + + """ + account_dir_path = self._account_dir_path(account_id) + if not os.path.isdir(account_dir_path): + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) + # Step 1: Delete account specific links and the directory + self._delete_account_dir_for_server_path(account_id, self.config.server_path) + + # Step 2: Remove any accounts links and directories that are now empty + if not os.listdir(self.config.accounts_dir): + self._delete_accounts_dir_for_server_path(self.config.server_path) + + def _delete_account_dir_for_server_path(self, account_id, server_path): + link_func = functools.partial(self._account_dir_path_for_server_path, account_id) + nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) + shutil.rmtree(nonsymlinked_dir) + + def _delete_accounts_dir_for_server_path(self, server_path): + link_func = self.config.accounts_dir_for_server_path + nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func) + os.rmdir(nonsymlinked_dir) + + def _delete_links_and_find_target_dir(self, server_path, link_func): + """Delete symlinks and return the nonsymlinked directory path. + + :param str server_path: file path based on server + :param callable link_func: callable that returns possible links + given a server_path + + :returns: the final, non-symlinked target + :rtype: str + + """ + dir_path = link_func(server_path) + + # does an appropriate directory link to me? if so, make sure that's gone + reused_servers = {} + for k in constants.LE_REUSE_SERVERS: + reused_servers[constants.LE_REUSE_SERVERS[k]] = k + + # is there a next one up? + possible_next_link = True + while possible_next_link: + possible_next_link = False + if server_path in reused_servers: + next_server_path = reused_servers[server_path] + next_dir_path = link_func(next_server_path) + if os.path.islink(next_dir_path) and os.readlink(next_dir_path) == dir_path: + possible_next_link = True + server_path = next_server_path + dir_path = next_dir_path + + # if there's not a next one up to delete, then delete me + # and whatever I link to + while os.path.islink(dir_path): + target = os.readlink(dir_path) + os.unlink(dir_path) + dir_path = target + + return dir_path + + def _save(self, account, acme, regr_only): + account_dir_path = self._account_dir_path(account.id) + util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) + try: + with open(self._regr_path(account_dir_path), "w") as regr_file: + regr = account.regr + # If we have a value for new-authz, save it for forwards + # compatibility with older versions of Certbot. If we don't + # have a value for new-authz, this is an ACMEv2 directory where + # an older version of Certbot won't work anyway. + if hasattr(acme.directory, "new-authz"): + regr = RegistrationResourceWithNewAuthzrURI( + new_authzr_uri=acme.directory.new_authz, + body={}, + uri=regr.uri) + else: + regr = messages.RegistrationResource( + body={}, + uri=regr.uri) + regr_file.write(regr.json_dumps()) + if not regr_only: + with util.safe_open(self._key_path(account_dir_path), + "w", chmod=0o400) as key_file: + key_file.write(account.key.json_dumps()) + with open(self._metadata_path( + account_dir_path), "w") as metadata_file: + metadata_file.write(account.meta.json_dumps()) + except IOError as error: + raise errors.AccountStorageError(error) diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py new file mode 100644 index 000000000..5c037e8dc --- /dev/null +++ b/certbot/certbot/_internal/auth_handler.py @@ -0,0 +1,470 @@ +"""ACME AuthHandler.""" +import logging +import time +import datetime + +import zope.component + +from acme import challenges +from acme import messages +from acme import errors as acme_errors +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict, List, Tuple +# pylint: enable=unused-import, no-name-in-module +from certbot import achallenges +from certbot import errors +from certbot._internal import error_handler +from certbot import interfaces + +logger = logging.getLogger(__name__) + + +class AuthHandler(object): + """ACME Authorization Handler for a client. + + :ivar auth: Authenticator capable of solving + :class:`~acme.challenges.Challenge` types + :type auth: :class:`certbot.interfaces.IAuthenticator` + + :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API. + + :ivar account: Client's Account + :type account: :class:`certbot._internal.account.Account` + + :ivar list pref_challs: sorted user specified preferred challenges + type strings with the most preferred challenge listed first + + """ + def __init__(self, auth, acme_client, account, pref_challs): + self.auth = auth + self.acme = acme_client + + self.account = account + self.pref_challs = pref_challs + + def handle_authorizations(self, orderr, best_effort=False, max_retries=30): + """ + Retrieve all authorizations, perform all challenges required to validate + these authorizations, then poll and wait for the authorization to be checked. + :param acme.messages.OrderResource orderr: must have authorizations filled in + :param bool best_effort: if True, not all authorizations need to be validated (eg. renew) + :param int max_retries: maximum number of retries to poll authorizations + :returns: list of all validated authorizations + :rtype: List + + :raises .AuthorizationError: If unable to retrieve all authorizations + """ + authzrs = orderr.authorizations[:] + if not authzrs: + raise errors.AuthorizationError('No authorization to handle.') + + # Retrieve challenges that need to be performed to validate authorizations. + achalls = self._choose_challenges(authzrs) + if not achalls: + return authzrs + + # Starting now, challenges will be cleaned at the end no matter what. + with error_handler.ExitHandler(self._cleanup_challenges, achalls): + # To begin, let's ask the authenticator plugin to perform all challenges. + try: + resps = self.auth.perform(achalls) + + # If debug is on, wait for user input before starting the verification process. + logger.info('Waiting for verification...') + config = zope.component.getUtility(interfaces.IConfig) + if config.debug_challenges: + notify = zope.component.getUtility(interfaces.IDisplay).notification + notify('Challenges loaded. Press continue to submit to CA. ' + 'Pass "-v" for more info about challenges.', pause=True) + except errors.AuthorizationError as error: + logger.critical('Failure in setting up challenges.') + logger.info('Attempting to clean up outstanding challenges...') + raise error + # All challenges should have been processed by the authenticator. + assert len(resps) == len(achalls), 'Some challenges have not been performed.' + + # Inform the ACME CA server that challenges are available for validation. + for achall, resp in zip(achalls, resps): + self.acme.answer_challenge(achall.challb, resp) + + # Wait for authorizations to be checked. + self._poll_authorizations(authzrs, max_retries, best_effort) + + # Keep validated authorizations only. If there is none, no certificate can be issued. + authzrs_validated = [authzr for authzr in authzrs + if authzr.body.status == messages.STATUS_VALID] + if not authzrs_validated: + raise errors.AuthorizationError('All challenges have failed.') + + return authzrs_validated + + def deactivate_valid_authorizations(self, orderr): + # type: (messages.OrderResource) -> Tuple[List, List] + """ + Deactivate all `valid` authorizations in the order, so that they cannot be re-used + in subsequent orders. + :param messages.OrderResource orderr: must have authorizations filled in + :returns: tuple of list of successfully deactivated authorizations, and + list of unsuccessfully deactivated authorizations. + :rtype: tuple + """ + to_deactivate = [authzr for authzr in orderr.authorizations + if authzr.body.status == messages.STATUS_VALID] + deactivated = [] + failed = [] + + for authzr in to_deactivate: + try: + authzr = self.acme.deactivate_authorization(authzr) + deactivated.append(authzr) + except acme_errors.Error as e: + failed.append(authzr) + logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) + + return (deactivated, failed) + + def _poll_authorizations(self, authzrs, max_retries, best_effort): + """ + Poll the ACME CA server, to wait for confirmation that authorizations have their challenges + all verified. The poll may occur several times, until all authorizations are checked + (valid or invalid), or after a maximum of retries. + """ + authzrs_to_check = {index: (authzr, None) + for index, authzr in enumerate(authzrs)} + authzrs_failed_to_report = [] + # Give an initial second to the ACME CA server to check the authorizations + sleep_seconds = 1 + for _ in range(max_retries): + # Wait for appropriate time (from Retry-After, initial wait, or no wait) + if sleep_seconds > 0: + time.sleep(sleep_seconds) + # Poll all updated authorizations. + authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _) + in authzrs_to_check.items()} + # Update the original list of authzr with the updated authzrs from server. + for index, (authzr, _) in authzrs_to_check.items(): + authzrs[index] = authzr + + # Gather failed authorizations + authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values() + if authzr.body.status == messages.STATUS_INVALID] + for authzr_failed in authzrs_failed: + logger.warning('Challenge failed for domain %s', + authzr_failed.body.identifier.value) + # Accumulating all failed authzrs to build a consolidated report + # on them at the end of the polling. + authzrs_failed_to_report.extend(authzrs_failed) + + # Extract out the authorization already checked for next poll iteration. + # Poll may stop here because there is no pending authorizations anymore. + authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp) + in authzrs_to_check.items() + if authzr.body.status == messages.STATUS_PENDING} + if not authzrs_to_check: + # Polling process is finished, we can leave the loop + break + + # Be merciful with the ACME server CA, check the Retry-After header returned, + # and wait this time before polling again in next loop iteration. + # From all the pending authorizations, we take the greatest Retry-After value + # to avoid polling an authorization before its relevant Retry-After value. + retry_after = max(self.acme.retry_after(resp, 3) + for _, resp in authzrs_to_check.values()) + sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds() + + # In case of failed authzrs, create a report to the user. + if authzrs_failed_to_report: + _report_failed_authzrs(authzrs_failed_to_report, self.account.key) + if not best_effort: + # Without best effort, having failed authzrs is critical and fail the process. + raise errors.AuthorizationError('Some challenges have failed.') + + if authzrs_to_check: + # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt. + raise errors.AuthorizationError('All authorizations were not finalized by the CA.') + + def _choose_challenges(self, authzrs): + """ + Retrieve necessary and pending challenges to satisfy server. + NB: Necessary and already validated challenges are not retrieved, + as they can be reused for a certificate issuance. + """ + pending_authzrs = [authzr for authzr in authzrs + if authzr.body.status != messages.STATUS_VALID] + achalls = [] # type: List[achallenges.AnnotatedChallenge] + if pending_authzrs: + logger.info("Performing the following challenges:") + for authzr in pending_authzrs: + authzr_challenges = authzr.body.challenges + if self.acme.acme_version == 1: + combinations = authzr.body.combinations + else: + combinations = tuple((i,) for i in range(len(authzr_challenges))) + + path = gen_challenge_path( + authzr_challenges, + self._get_chall_pref(authzr.body.identifier.value), + combinations) + + achalls.extend(self._challenge_factory(authzr, path)) + + return achalls + + def _get_chall_pref(self, domain): + """Return list of challenge preferences. + + :param str domain: domain for which you are requesting preferences + + """ + chall_prefs = [] + # Make sure to make a copy... + plugin_pref = self.auth.get_chall_pref(domain) + if self.pref_challs: + plugin_pref_types = set(chall.typ for chall in plugin_pref) + for typ in self.pref_challs: + if typ in plugin_pref_types: + chall_prefs.append(challenges.Challenge.TYPES[typ]) + if chall_prefs: + return chall_prefs + raise errors.AuthorizationError( + "None of the preferred challenges " + "are supported by the selected plugin") + chall_prefs.extend(plugin_pref) + return chall_prefs + + def _cleanup_challenges(self, achalls): + """Cleanup challenges. + + :param achalls: annotated challenges to cleanup + :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge` + + """ + logger.info("Cleaning up challenges") + self.auth.cleanup(achalls) + + def _challenge_factory(self, authzr, path): + """Construct Namedtuple Challenges + + :param messages.AuthorizationResource authzr: authorization + + :param list path: List of indices from `challenges`. + + :returns: achalls, list of challenge type + :class:`certbot.achallenges.Indexed` + :rtype: list + + :raises .errors.Error: if challenge type is not recognized + + """ + achalls = [] + + for index in path: + challb = authzr.body.challenges[index] + achalls.append(challb_to_achall( + challb, self.account.key, authzr.body.identifier.value)) + + return achalls + + +def challb_to_achall(challb, account_key, domain): + """Converts a ChallengeBody object to an AnnotatedChallenge. + + :param .ChallengeBody challb: ChallengeBody + :param .JWK account_key: Authorized Account Key + :param str domain: Domain of the challb + + :returns: Appropriate AnnotatedChallenge + :rtype: :class:`certbot.achallenges.AnnotatedChallenge` + + """ + chall = challb.chall + logger.info("%s challenge for %s", chall.typ, domain) + + if isinstance(chall, challenges.KeyAuthorizationChallenge): + return achallenges.KeyAuthorizationAnnotatedChallenge( + challb=challb, domain=domain, account_key=account_key) + elif isinstance(chall, challenges.DNS): + return achallenges.DNS(challb=challb, domain=domain) + else: + raise errors.Error( + "Received unsupported challenge of type: {0}".format(chall.typ)) + + +def gen_challenge_path(challbs, preferences, combinations): + """Generate a plan to get authority over the identity. + + .. todo:: This can be possibly be rewritten to use resolved_combinations. + + :param tuple challbs: A tuple of challenges + (:class:`acme.messages.Challenge`) from + :class:`acme.messages.AuthorizationResource` to be + fulfilled by the client in order to prove possession of the + identifier. + + :param list preferences: List of challenge preferences for domain + (:class:`acme.challenges.Challenge` subclasses) + + :param tuple combinations: A collection of sets of challenges from + :class:`acme.messages.Challenge`, each of which would + be sufficient to prove possession of the identifier. + + :returns: tuple of indices from ``challenges``. + :rtype: tuple + + :raises certbot.errors.AuthorizationError: If a + path cannot be created that satisfies the CA given the preferences and + combinations. + + """ + if combinations: + return _find_smart_path(challbs, preferences, combinations) + return _find_dumb_path(challbs, preferences) + + +def _find_smart_path(challbs, preferences, combinations): + """Find challenge path with server hints. + + Can be called if combinations is included. Function uses a simple + ranking system to choose the combo with the lowest cost. + + """ + chall_cost = {} + max_cost = 1 + for i, chall_cls in enumerate(preferences): + chall_cost[chall_cls] = i + max_cost += i + + # max_cost is now equal to sum(indices) + 1 + + best_combo = None + # Set above completing all of the available challenges + best_combo_cost = max_cost + + combo_total = 0 + for combo in combinations: + for challenge_index in combo: + combo_total += chall_cost.get(challbs[ + challenge_index].chall.__class__, max_cost) + + if combo_total < best_combo_cost: + best_combo = combo + best_combo_cost = combo_total + + combo_total = 0 + + if not best_combo: + _report_no_chall_path(challbs) + + return best_combo + + +def _find_dumb_path(challbs, preferences): + """Find challenge path without server hints. + + Should be called if the combinations hint is not included by the + server. This function either returns a path containing all + challenges provided by the CA or raises an exception. + + """ + path = [] + for i, challb in enumerate(challbs): + # supported is set to True if the challenge type is supported + supported = next((True for pref_c in preferences + if isinstance(challb.chall, pref_c)), False) + if supported: + path.append(i) + else: + _report_no_chall_path(challbs) + + return path + + +def _report_no_chall_path(challbs): + """Logs and raises an error that no satisfiable chall path exists. + + :param challbs: challenges from the authorization that can't be satisfied + + """ + msg = ("Client with the currently selected authenticator does not support " + "any combination of challenges that will satisfy the CA.") + if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01): + msg += ( + " You may need to use an authenticator " + "plugin that can do challenges over DNS.") + logger.critical(msg) + raise errors.AuthorizationError(msg) + + +_ERROR_HELP_COMMON = ( + "To fix these errors, please make sure that your domain name was entered " + "correctly and the DNS A/AAAA record(s) for that domain contain(s) the " + "right IP address.") + + +_ERROR_HELP = { + "connection": + _ERROR_HELP_COMMON + " Additionally, please check that your computer " + "has a publicly routable IP address and that no firewalls are preventing " + "the server from communicating with the client. If you're using the " + "webroot plugin, you should also verify that you are serving files " + "from the webroot path you provided.", + "dnssec": + _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for " + "your domain, please ensure that the signature is valid.", + "malformed": + "To fix these errors, please make sure that you did not provide any " + "invalid information to the client, and try running Certbot " + "again.", + "serverInternal": + "Unfortunately, an error on the ACME server prevented you from completing " + "authorization. Please try again later.", + "tls": + _ERROR_HELP_COMMON + " Additionally, please check that you have an " + "up-to-date TLS configuration that allows the server to communicate " + "with the Certbot client.", + "unauthorized": _ERROR_HELP_COMMON, + "unknownHost": _ERROR_HELP_COMMON, +} + + +def _report_failed_authzrs(failed_authzrs, account_key): + """Notifies the user about failed authorizations.""" + problems = {} # type: Dict[str, List[achallenges.AnnotatedChallenge]] + failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value) + for authzr in failed_authzrs for challb in authzr.body.challenges + if challb.error] + + for achall in failed_achalls: + problems.setdefault(achall.error.typ, []).append(achall) + + reporter = zope.component.getUtility(interfaces.IReporter) + for achalls in problems.values(): + reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY) + + +def _generate_failed_chall_msg(failed_achalls): + """Creates a user friendly error message about failed challenges. + + :param list failed_achalls: A list of failed + :class:`certbot.achallenges.AnnotatedChallenge` with the same error + type. + + :returns: A formatted error message for the client. + :rtype: str + + """ + error = failed_achalls[0].error + typ = error.typ + if messages.is_acme_error(error): + typ = error.code + msg = ["The following errors were reported by the server:"] + + for achall in failed_achalls: + msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % ( + achall.domain, typ, achall.error.detail)) + + if typ in _ERROR_HELP: + msg.append("\n\n") + msg.append(_ERROR_HELP[typ]) + + return "".join(msg) diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py new file mode 100644 index 000000000..329b6cdff --- /dev/null +++ b/certbot/certbot/_internal/cert_manager.py @@ -0,0 +1,388 @@ +"""Tools for managing certificates.""" +import datetime +import logging +import re +import traceback + +import pytz +import zope.component + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot._internal import ocsp +from certbot._internal import storage +from certbot import util +from certbot.compat import os +from certbot.display import util as display_util + +logger = logging.getLogger(__name__) + +################### +# Commands +################### + +def update_live_symlinks(config): + """Update the certificate file family symlinks to use archive_dir. + + Use the information in the config file to make symlinks point to + the correct archive directory. + + .. note:: This assumes that the installation is using a Reverter object. + + :param config: Configuration. + :type config: :class:`certbot._internal.configuration.NamespaceConfig` + + """ + for renewal_file in storage.renewal_conf_files(config): + storage.RenewableCert(renewal_file, config, update_symlinks=True) + +def rename_lineage(config): + """Rename the specified lineage to the new name. + + :param config: Configuration. + :type config: :class:`certbot._internal.configuration.NamespaceConfig` + + """ + disp = zope.component.getUtility(interfaces.IDisplay) + + certname = get_certnames(config, "rename")[0] + + new_certname = config.new_certname + if not new_certname: + code, new_certname = disp.input( + "Enter the new name for certificate {0}".format(certname), + flag="--updated-cert-name", force_interactive=True) + if code != display_util.OK or not new_certname: + raise errors.Error("User ended interaction.") + + lineage = lineage_for_certname(config, certname) + if not lineage: + raise errors.ConfigurationError("No existing certificate with name " + "{0} found.".format(certname)) + storage.rename_renewal_config(certname, new_certname, config) + disp.notification("Successfully renamed {0} to {1}." + .format(certname, new_certname), pause=False) + +def certificates(config): + """Display information about certs configured with Certbot + + :param config: Configuration. + :type config: :class:`certbot._internal.configuration.NamespaceConfig` + """ + parsed_certs = [] + parse_failures = [] + for renewal_file in storage.renewal_conf_files(config): + try: + renewal_candidate = storage.RenewableCert(renewal_file, config) + crypto_util.verify_renewable_cert(renewal_candidate) + parsed_certs.append(renewal_candidate) + except Exception as e: # pylint: disable=broad-except + logger.warning("Renewal configuration file %s produced an " + "unexpected error: %s. Skipping.", renewal_file, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + parse_failures.append(renewal_file) + + # Describe all the certs + _describe_certs(config, parsed_certs, parse_failures) + +def delete(config): + """Delete Certbot files associated with a certificate lineage.""" + certnames = get_certnames(config, "delete", allow_multiple=True) + for certname in certnames: + storage.delete_files(config, certname) + disp = zope.component.getUtility(interfaces.IDisplay) + disp.notification("Deleted all files relating to certificate {0}." + .format(certname), pause=False) + +################### +# Public Helpers +################### + +def lineage_for_certname(cli_config, certname): + """Find a lineage object with name certname.""" + configs_dir = cli_config.renewal_configs_dir + # Verify the directory is there + util.make_or_verify_dir(configs_dir, mode=0o755) + try: + renewal_file = storage.renewal_file_for_certname(cli_config, certname) + except errors.CertStorageError: + return None + try: + return storage.RenewableCert(renewal_file, cli_config) + except (errors.CertStorageError, IOError): + logger.debug("Renewal conf file %s is broken.", renewal_file) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + +def domains_for_certname(config, certname): + """Find the domains in the cert with name certname.""" + lineage = lineage_for_certname(config, certname) + return lineage.names() if lineage else None + +def find_duplicative_certs(config, domains): + """Find existing certs that match the given domain names. + + This function searches for certificates whose domains are equal to + the `domains` parameter and certificates whose domains are a subset + of the domains in the `domains` parameter. If multiple certificates + are found whose names are a subset of `domains`, the one whose names + are the largest subset of `domains` is returned. + + If multiple certificates' domains are an exact match or equally + sized subsets, which matching certificates are returned is + undefined. + + :param config: Configuration. + :type config: :class:`certbot._internal.configuration.NamespaceConfig` + :param domains: List of domain names + :type domains: `list` of `str` + + :returns: lineages representing the identically matching cert and the + largest subset if they exist + :rtype: `tuple` of `storage.RenewableCert` or `None` + + """ + def update_certs_for_domain_matches(candidate_lineage, rv): + """Return cert as identical_names_cert if it matches, + or subset_names_cert if it matches as subset + """ + # TODO: Handle these differently depending on whether they are + # expired or still valid? + identical_names_cert, subset_names_cert = rv + candidate_names = set(candidate_lineage.names()) + if candidate_names == set(domains): + identical_names_cert = candidate_lineage + elif candidate_names.issubset(set(domains)): + # This logic finds and returns the largest subset-names cert + # in the case where there are several available. + if subset_names_cert is None: + subset_names_cert = candidate_lineage + elif len(candidate_names) > len(subset_names_cert.names()): + subset_names_cert = candidate_lineage + return (identical_names_cert, subset_names_cert) + + return _search_lineages(config, update_certs_for_domain_matches, (None, None)) + +def _archive_files(candidate_lineage, filetype): + """ In order to match things like: + /etc/letsencrypt/archive/example.com/chain1.pem. + + Anonymous functions which call this function are eventually passed (in a list) to + `match_and_check_overlaps` to help specify the acceptable_matches. + + :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to + be searched. + :param str filetype: main file name prefix e.g. "fullchain" or "chain". + + :returns: Files in candidate_lineage's archive dir that match the provided filetype. + :rtype: list of str or None + """ + archive_dir = candidate_lineage.archive_dir + pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir) + if re.match("{0}[0-9]*.pem".format(filetype), f)] + if pattern: + return pattern + return None + +def _acceptable_matches(): + """ Generates the list that's passed to match_and_check_overlaps. Is its own function to + make unit testing easier. + + :returns: list of functions + :rtype: list + """ + return [lambda x: x.fullchain_path, lambda x: x.cert_path, + lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")] + +def cert_path_to_lineage(cli_config): + """ If config.cert_path is defined, try to find an appropriate value for config.certname. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + + :returns: a lineage name + :rtype: str + + :raises `errors.Error`: If the specified cert path can't be matched to a lineage name. + :raises `errors.OverlappingMatchFound`: If the matched lineage's archive is shared. + """ + acceptable_matches = _acceptable_matches() + match = match_and_check_overlaps(cli_config, acceptable_matches, + lambda x: cli_config.cert_path[0], lambda x: x.lineagename) + return match[0] + +def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func): + """ Searches through all lineages for a match, and checks for duplicates. + If a duplicate is found, an error is raised, as performing operations on lineages + that have their properties incorrectly duplicated elsewhere is probably a bad idea. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + :param list acceptable_matches: a list of functions that specify acceptable matches + :param function match_func: specifies what to match + :param function rv_func: specifies what to return + + """ + def find_matches(candidate_lineage, return_value, acceptable_matches): + """Returns a list of matches using _search_lineages.""" + acceptable_matches = [func(candidate_lineage) for func in acceptable_matches] + acceptable_matches_rv = [] # type: List[str] + for item in acceptable_matches: + if isinstance(item, list): + acceptable_matches_rv += item + else: + acceptable_matches_rv.append(item) + match = match_func(candidate_lineage) + if match in acceptable_matches_rv: + return_value.append(rv_func(candidate_lineage)) + return return_value + + matched = _search_lineages(cli_config, find_matches, [], acceptable_matches) + if not matched: + raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) + elif len(matched) > 1: + raise errors.OverlappingMatchFound() + else: + return matched + +def human_readable_cert_info(config, cert, skip_filter_checks=False): + """ Returns a human readable description of info about a RenewableCert object""" + certinfo = [] + checker = ocsp.RevocationChecker() + + if config.certname and cert.lineagename != config.certname and not skip_filter_checks: + return "" + if config.domains and not set(config.domains).issubset(cert.names()): + return "" + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + + reasons = [] + if cert.is_test_cert: + reasons.append('TEST_CERT') + if cert.target_expiry <= now: + reasons.append('EXPIRED') + elif checker.ocsp_revoked(cert): + reasons.append('REVOKED') + + if reasons: + status = "INVALID: " + ", ".join(reasons) + else: + diff = cert.target_expiry - now + if diff.days == 1: + status = "VALID: 1 day" + elif diff.days < 1: + status = "VALID: {0} hour(s)".format(diff.seconds // 3600) + else: + status = "VALID: {0} days".format(diff.days) + + valid_string = "{0} ({1})".format(cert.target_expiry, status) + certinfo.append(" Certificate Name: {0}\n" + " Domains: {1}\n" + " Expiry Date: {2}\n" + " Certificate Path: {3}\n" + " Private Key Path: {4}".format( + cert.lineagename, + " ".join(cert.names()), + valid_string, + cert.fullchain, + cert.privkey)) + return "".join(certinfo) + +def get_certnames(config, verb, allow_multiple=False, custom_prompt=None): + """Get certname from flag, interactively, or error out. + """ + certname = config.certname + if certname: + certnames = [certname] + else: + disp = zope.component.getUtility(interfaces.IDisplay) + filenames = storage.renewal_conf_files(config) + choices = [storage.lineagename_for_filename(name) for name in filenames] + if not choices: + raise errors.Error("No existing certificates found.") + if allow_multiple: + if not custom_prompt: + prompt = "Which certificate(s) would you like to {0}?".format(verb) + else: + prompt = custom_prompt + code, certnames = disp.checklist( + prompt, choices, cli_flag="--cert-name", force_interactive=True) + if code != display_util.OK: + raise errors.Error("User ended interaction.") + else: + if not custom_prompt: + prompt = "Which certificate would you like to {0}?".format(verb) + else: + prompt = custom_prompt + + code, index = disp.menu( + prompt, choices, cli_flag="--cert-name", force_interactive=True) + + if code != display_util.OK or index not in range(0, len(choices)): + raise errors.Error("User ended interaction.") + certnames = [choices[index]] + return certnames + +################### +# Private Helpers +################### + +def _report_lines(msgs): + """Format a results report for a category of single-line renewal outcomes""" + return " " + "\n ".join(str(msg) for msg in msgs) + +def _report_human_readable(config, parsed_certs): + """Format a results report for a parsed cert""" + certinfo = [] + for cert in parsed_certs: + certinfo.append(human_readable_cert_info(config, cert)) + return "\n".join(certinfo) + +def _describe_certs(config, parsed_certs, parse_failures): + """Print information about the certs we know about""" + out = [] # type: List[str] + + notify = out.append + + if not parsed_certs and not parse_failures: + notify("No certs found.") + else: + if parsed_certs: + match = "matching " if config.certname or config.domains else "" + notify("Found the following {0}certs:".format(match)) + notify(_report_human_readable(config, parsed_certs)) + if parse_failures: + notify("\nThe following renewal configurations " + "were invalid:") + notify(_report_lines(parse_failures)) + + disp = zope.component.getUtility(interfaces.IDisplay) + disp.notification("\n".join(out), pause=False, wrap=False) + +def _search_lineages(cli_config, func, initial_rv, *args): + """Iterate func over unbroken lineages, allowing custom return conditions. + + Allows flexible customization of return values, including multiple + return values and complex checks. + + :param `configuration.NamespaceConfig` cli_config: parsed command line arguments + :param function func: function used while searching over lineages + :param initial_rv: initial return value of the function (any type) + + :returns: Whatever was specified by `func` if a match is found. + """ + configs_dir = cli_config.renewal_configs_dir + # Verify the directory is there + util.make_or_verify_dir(configs_dir, mode=0o755) + + rv = initial_rv + for renewal_file in storage.renewal_conf_files(cli_config): + try: + candidate_lineage = storage.RenewableCert(renewal_file, cli_config) + except (errors.CertStorageError, IOError): + logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + continue + rv = func(candidate_lineage, rv, *args) + return rv diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py new file mode 100644 index 000000000..7eabeeee6 --- /dev/null +++ b/certbot/certbot/_internal/cli.py @@ -0,0 +1,1583 @@ +"""Certbot command line argument & config processing.""" +# pylint: disable=too-many-lines +from __future__ import print_function + +import argparse +import copy +import glob +import logging.handlers +import sys + +import configargparse +import six +import zope.component +import zope.interface +from zope.interface import interfaces as zope_interfaces + +from acme import challenges +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Dict, Optional +# pylint: enable=unused-import, no-name-in-module + +import certbot +import certbot.plugins.enhancements as enhancements +import certbot._internal.plugins.selection as plugin_selection +from certbot._internal import constants +from certbot import crypto_util +from certbot import errors +from certbot._internal import hooks +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot.display import util as display_util +from certbot._internal.plugins import disco as plugins_disco + +logger = logging.getLogger(__name__) + +# Global, to save us from a lot of argument passing within the scope of this module +helpful_parser = None # type: Optional[HelpfulArgumentParser] + +# For help strings, figure out how the user ran us. +# When invoked from letsencrypt-auto, sys.argv[0] is something like: +# "/home/user/.local/share/certbot/bin/certbot" +# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before +# running letsencrypt-auto (and sudo stops us from seeing if they did), so it +# should only be used for purposes where inability to detect letsencrypt-auto +# fails safely + +LEAUTO = "letsencrypt-auto" +if "CERTBOT_AUTO" in os.environ: + # if we're here, this is probably going to be certbot-auto, unless the + # user saved the script under a different name + LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"]) + +old_path_fragment = os.path.join(".local", "share", "letsencrypt") +new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt", + "eff.org", "certbot", "venv")) +if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix): + cli_command = LEAUTO +else: + cli_command = "certbot" + +# Argparse's help formatting has a lot of unhelpful peculiarities, so we want +# to replace as much of it as we can... + +# This is the stub to include in help generated by argparse +SHORT_USAGE = """ + {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... + +Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, +it will attempt to use a webserver both for obtaining and installing the +certificate. """.format(cli_command) + +# This section is used for --help and --help all ; it needs information +# about installed plugins to be fully formatted +COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are: + +obtain, install, and renew certificates: + (default) run Obtain & install a certificate in your current webserver + certonly Obtain or renew a certificate, but do not install it + renew Renew all previously obtained certificates that are near expiry + enhance Add security enhancements to your existing configuration + -d DOMAINS Comma-separated list of domains to obtain a certificate for + + %s + --standalone Run a standalone webserver for authentication + %s + --webroot Place files in a server's webroot folder for authentication + --manual Obtain certificates interactively, or using shell script hooks + + -n Run non-interactively + --test-cert Obtain a test certificate from a staging server + --dry-run Test "renew" or "certonly" without saving any certificates to disk + +manage certificates: + certificates Display information about certificates you have from Certbot + revoke Revoke a certificate (supply --cert-path or --cert-name) + delete Delete a certificate + +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account + --agree-tos Agree to the ACME server's Subscriber Agreement + -m EMAIL Email address for important account notifications +""" + +# This is the short help for certbot --help, where we disable argparse +# altogether +HELP_AND_VERSION_USAGE = """ +More detailed help: + + -h, --help [TOPIC] print this message, or detailed help on a topic; + the available TOPICS are: + + all, automation, commands, paths, security, testing, or any of the + subcommands or plugins (certonly, renew, install, register, nginx, + apache, standalone, webroot, etc.) + -h all print a detailed help page including all topics + --version print the version number +""" + + +# These argparse parameters should be removed when detecting defaults. +ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",) + + +# These sets are used when to help detect options set by the user. +EXIT_ACTIONS = set(("help", "version",)) + + +ZERO_ARG_ACTIONS = set(("store_const", "store_true", + "store_false", "append_const", "count",)) + + +# Maps a config option to a set of config options that may have modified it. +# This dictionary is used recursively, so if A modifies B and B modifies C, +# it is determined that C was modified by the user if A was modified. +VAR_MODIFIERS = {"account": set(("server",)), + "renew_hook": set(("deploy_hook",)), + "server": set(("dry_run", "staging",)), + "webroot_map": set(("webroot_path",))} + + +def report_config_interaction(modified, modifiers): + """Registers config option interaction to be checked by set_by_cli. + + This function can be called by during the __init__ or + add_parser_arguments methods of plugins to register interactions + between config options. + + :param modified: config options that can be modified by modifiers + :type modified: iterable or str (string_types) + :param modifiers: config options that modify modified + :type modifiers: iterable or str (string_types) + + """ + if isinstance(modified, six.string_types): + modified = (modified,) + if isinstance(modifiers, six.string_types): + modifiers = (modifiers,) + + for var in modified: + VAR_MODIFIERS.setdefault(var, set()).update(modifiers) + + +class _Default(object): + """A class to use as a default to detect if a value is set by a user""" + + def __bool__(self): + return False + + def __eq__(self, other): + return isinstance(other, _Default) + + def __hash__(self): + return id(_Default) + + def __nonzero__(self): + return self.__bool__() + + +def set_by_cli(var): + """ + Return True if a particular config variable has been set by the user + (CLI or config file) including if the user explicitly set it to the + default. Returns False if the variable was assigned a default value. + """ + detector = set_by_cli.detector # type: ignore + if detector is None and helpful_parser is not None: + # Setup on first run: `detector` is a weird version of config in which + # the default value of every attribute is wrangled to be boolean-false + plugins = plugins_disco.PluginsRegistry.find_all() + # reconstructed_args == sys.argv[1:], or whatever was passed to main() + reconstructed_args = helpful_parser.args + [helpful_parser.verb] + detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore + plugins, reconstructed_args, detect_defaults=True) + # propagate plugin requests: eg --standalone modifies config.authenticator + detector.authenticator, detector.installer = ( # type: ignore + plugin_selection.cli_plugin_requests(detector)) + + if not isinstance(getattr(detector, var), _Default): + logger.debug("Var %s=%s (set by user).", var, getattr(detector, var)) + return True + + for modifier in VAR_MODIFIERS.get(var, []): + if set_by_cli(modifier): + logger.debug("Var %s=%s (set by user).", + var, VAR_MODIFIERS.get(var, [])) + return True + + return False + +# static housekeeping var +# functions attributed are not supported by mypy +# https://github.com/python/mypy/issues/2087 +set_by_cli.detector = None # type: ignore + + +def has_default_value(option, value): + """Does option have the default value? + + If the default value of option is not known, False is returned. + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if option has the default value, otherwise, False + :rtype: bool + + """ + if helpful_parser is not None: + return (option in helpful_parser.defaults and + helpful_parser.defaults[option] == value) + return False + + +def option_was_set(option, value): + """Was option set by the user or does it differ from the default? + + :param str option: configuration variable being considered + :param value: value of the configuration variable named option + + :returns: True if the option was set, otherwise, False + :rtype: bool + + """ + return set_by_cli(option) or not has_default_value(option, value) + + +def argparse_type(variable): + """Return our argparse type function for a config variable (default: str)""" + # pylint: disable=protected-access + if helpful_parser is not None: + for action in helpful_parser.parser._actions: + if action.type is not None and action.dest == variable: + return action.type + return str + +def read_file(filename, mode="rb"): + """Returns the given file's contents. + + :param str filename: path to file + :param str mode: open mode (see `open`) + + :returns: absolute path of filename and its contents + :rtype: tuple + + :raises argparse.ArgumentTypeError: File does not exist or is not readable. + + """ + try: + filename = os.path.abspath(filename) + with open(filename, mode) as the_file: + contents = the_file.read() + return filename, contents + except IOError as exc: + raise argparse.ArgumentTypeError(exc.strerror) + + +def flag_default(name): + """Default value for CLI flag.""" + # XXX: this is an internal housekeeping notion of defaults before + # argparse has been set up; it is not accurate for all flags. Call it + # with caution. Plugin defaults are missing, and some things are using + # defaults defined in this file, not in constants.py :( + return copy.deepcopy(constants.CLI_DEFAULTS[name]) + + +def config_help(name, hidden=False): + """Extract the help message for an `.IConfig` attribute.""" + # pylint: disable=no-member + if hidden: + return argparse.SUPPRESS + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter + return field.__doc__ + + +class HelpfulArgumentGroup(object): + """Emulates an argparse group for use with HelpfulArgumentParser. + + This class is used in the add_group method of HelpfulArgumentParser. + Command line arguments can be added to the group, but help + suppression and default detection is applied by + HelpfulArgumentParser when necessary. + + """ + def __init__(self, helpful_arg_parser, topic): + self._parser = helpful_arg_parser + self._topic = topic + + def add_argument(self, *args, **kwargs): + """Add a new command line argument to the argument group.""" + self._parser.add(self._topic, *args, **kwargs) + +class CustomHelpFormatter(argparse.HelpFormatter): + """This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes. + + In particular we fix https://bugs.python.org/issue28742 + """ + + def _get_help_string(self, action): + helpstr = action.help + if '%(default)' not in action.help and '(default:' not in action.help: + if action.default != argparse.SUPPRESS: + defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + helpstr += ' (default: %(default)s)' + return helpstr + +# The attributes here are: +# short: a string that will be displayed by "certbot -h commands" +# opts: a string that heads the section of flags with which this command is documented, +# both for "certbot -h SUBCOMMAND" and "certbot -h all" +# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND" +VERB_HELP = [ + ("run (default)", { + "short": "Obtain/renew a certificate, and install it", + "opts": "Options for obtaining & installing certificates", + "usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""), + "realname": "run" + }), + ("certonly", { + "short": "Obtain or renew a certificate, but do not install it", + "opts": "Options for modifying how a certificate is obtained", + "usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n" + "This command obtains a TLS/SSL certificate without installing it anywhere.") + }), + ("renew", { + "short": "Renew all certificates (or one specified with --cert-name)", + "opts": ("The 'renew' subcommand will attempt to renew all" + " certificates (or more precisely, certificate lineages) you have" + " previously obtained if they are close to expiry, and print a" + " summary of the results. By default, 'renew' will reuse the options" + " used to create obtain or most recently successfully renew each" + " certificate lineage. You can try it with `--dry-run` first. For" + " more fine-grained control, you can renew individual lineages with" + " the `certonly` subcommand. Hooks are available to run commands" + " before and after renewal; see" + " https://certbot.eff.org/docs/using.html#renewal for more" + " information on these."), + "usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n" + }), + ("certificates", { + "short": "List certificates managed by Certbot", + "opts": "List certificates managed by Certbot", + "usage": ("\n\n certbot certificates [options] ...\n\n" + "Print information about the status of certificates managed by Certbot.") + }), + ("delete", { + "short": "Clean up all files related to a certificate", + "opts": "Options for deleting a certificate", + "usage": "\n\n certbot delete --cert-name CERTNAME\n\n" + }), + ("revoke", { + "short": "Revoke a certificate specified with --cert-path or --cert-name", + "opts": "Options for revocation of certificates", + "usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | " + "--cert-name example.com] [options]\n\n" + }), + ("register", { + "short": "Register for account with Let's Encrypt / other ACME server", + "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.", + "usage": "\n\n certbot unregister [options]\n\n" + }), + ("install", { + "short": "Install an arbitrary certificate in a server", + "opts": "Options for modifying how a certificate is deployed", + "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " + " --key-path /path/to/private-key [options]\n\n" + }), + ("rollback", { + "short": "Roll back server conf changes made during certificate installation", + "opts": "Options for rolling back server configuration changes", + "usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n" + }), + ("plugins", { + "short": "List plugins that are installed and available on your system", + "opts": 'Options for the "plugins" subcommand', + "usage": "\n\n certbot plugins [options]\n\n" + }), + ("update_symlinks", { + "short": "Recreate symlinks in your /etc/letsencrypt/live/ directory", + "opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand " + "or edited a renewal configuration file".format( + os.path.join(flag_default("config_dir"), "live"))), + "usage": "\n\n certbot update_symlinks [options]\n\n" + }), + ("enhance", { + "short": "Add security enhancements to your existing configuration", + "opts": ("Helps to harden the TLS configuration by adding security enhancements " + "to already existing configuration."), + "usage": "\n\n certbot enhance [options]\n\n" + }), + +] +# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful +VERB_HELP_MAP = dict(VERB_HELP) + + +class HelpfulArgumentParser(object): + """Argparse Wrapper. + + This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'certbot --help security' for security options. + + """ + + + def __init__(self, args, plugins, detect_defaults=False): + from certbot._internal import main + self.VERBS = { + "auth": main.certonly, + "certonly": main.certonly, + "run": main.run, + "install": main.install, + "plugins": main.plugins_cmd, + "register": main.register, + "update_account": main.update_account, + "unregister": main.unregister, + "renew": main.renew, + "revoke": main.revoke, + "rollback": main.rollback, + "everything": main.run, + "update_symlinks": main.update_symlinks, + "certificates": main.certificates, + "delete": main.delete, + "enhance": main.enhance, + } + + # Get notification function for printing + try: + self.notify = zope.component.getUtility( + interfaces.IDisplay).notification + except zope_interfaces.ComponentLookupError: + self.notify = display_util.NoninteractiveDisplay( + sys.stdout).notification + + + # List of topics for which additional help can be provided + HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"] + + plugin_names = list(plugins) + self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore + + self.detect_defaults = detect_defaults + self.args = args + + if self.args and self.args[0] == 'help': + self.args[0] = '--help' + + self.determine_verb() + help1 = self.prescan_for_flag("-h", self.help_topics) + help2 = self.prescan_for_flag("--help", self.help_topics) + if isinstance(help1, bool) and isinstance(help2, bool): + self.help_arg = help1 or help2 + else: + self.help_arg = help1 if isinstance(help1, six.string_types) else help2 + + short_usage = self._usage_string(plugins, self.help_arg) + + self.visible_topics = self.determine_help_topics(self.help_arg) + + # elements are added by .add_group() + self.groups = {} # type: Dict[str, argparse._ArgumentGroup] + # elements are added by .parse_args() + self.defaults = {} # type: Dict[str, Any] + + self.parser = configargparse.ArgParser( + prog="certbot", + usage=short_usage, + formatter_class=CustomHelpFormatter, + args_for_setting_config_path=["-c", "--config"], + default_config_files=flag_default("config_files"), + config_arg_help_message="path to config file (default: {0})".format( + " and ".join(flag_default("config_files")))) + + # This is the only way to turn off overly verbose config flag documentation + self.parser._add_config_file_help = False # pylint: disable=protected-access + + # Help that are synonyms for --help subcommands + COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"] + def _list_subcommands(self): + longest = max(len(v) for v in VERB_HELP_MAP) + + text = "The full list of available SUBCOMMANDS is:\n\n" + for verb, props in sorted(VERB_HELP): + doc = props.get("short", "") + text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest) + + text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n" + return text + + def _usage_string(self, plugins, help_arg): + """Make usage strings late so that plugins can be initialised late + + :param plugins: all discovered plugins + :param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC + :rtype: str + :returns: a short usage string for the top of --help TOPIC) + """ + if "nginx" in plugins: + nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" + else: + nginx_doc = "(the certbot nginx plugin is not installed)" + if "apache" in plugins: + apache_doc = "--apache Use the Apache plugin for authentication & installation" + else: + apache_doc = "(the certbot apache plugin is not installed)" + + usage = SHORT_USAGE + if help_arg is True: + self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE) + sys.exit(0) + elif help_arg in self.COMMANDS_TOPICS: + self.notify(usage + self._list_subcommands()) + sys.exit(0) + elif help_arg == "all": + # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at + # the top; if we're doing --help someothertopic, it's OT so it's not + usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc) + else: + custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None) + usage = custom if custom else usage + + return usage + + def remove_config_file_domains_for_renewal(self, parsed_args): + """Make "certbot renew" safe if domains are set in cli.ini.""" + # Works around https://github.com/certbot/certbot/issues/4096 + if self.verb == "renew": + for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access + if source.startswith("config_file") and "domains" in flags: + parsed_args.domains = _Default() if self.detect_defaults else [] + + def parse_args(self): + """Parses command line arguments and returns the result. + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + parsed_args = self.parser.parse_args(self.args) + parsed_args.func = self.VERBS[self.verb] + parsed_args.verb = self.verb + + self.remove_config_file_domains_for_renewal(parsed_args) + + if self.detect_defaults: + return parsed_args + + self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key))) + for key in vars(parsed_args)) + + # Do any post-parsing homework here + + if self.verb == "renew": + if parsed_args.force_interactive: + raise errors.Error( + "{0} cannot be used with renew".format( + constants.FORCE_INTERACTIVE_FLAG)) + parsed_args.noninteractive_mode = True + + if parsed_args.force_interactive and parsed_args.noninteractive_mode: + raise errors.Error( + "Flag for non-interactive mode and {0} conflict".format( + constants.FORCE_INTERACTIVE_FLAG)) + + if parsed_args.staging or parsed_args.dry_run: + self.set_test_server(parsed_args) + + if parsed_args.csr: + self.handle_csr(parsed_args) + + if parsed_args.must_staple: + parsed_args.staple = True + + if parsed_args.validate_hooks: + hooks.validate_hooks(parsed_args) + + if parsed_args.allow_subset_of_names: + if any(util.is_wildcard_domain(d) for d in parsed_args.domains): + raise errors.Error("Using --allow-subset-of-names with a" + " wildcard domain is not supported.") + + if parsed_args.hsts and parsed_args.auto_hsts: + raise errors.Error( + "Parameters --hsts and --auto-hsts cannot be used simultaneously.") + + return parsed_args + + def set_test_server(self, parsed_args): + """We have --staging/--dry-run; perform sanity check and set config.server""" + + # Flag combinations should produce these results: + # | --staging | --dry-run | + # ------------------------------------------------------------ + # | --server acme-v02 | Use staging | Use staging | + # | --server acme-staging-v02 | Use staging | Use staging | + # | --server | Conflict error | Use | + + default_servers = (flag_default("server"), constants.STAGING_URI) + + if parsed_args.staging and parsed_args.server not in default_servers: + raise errors.Error("--server value conflicts with --staging") + + if parsed_args.server in default_servers: + parsed_args.server = constants.STAGING_URI + + if parsed_args.dry_run: + if self.verb not in ["certonly", "renew"]: + raise errors.Error("--dry-run currently only works with the " + "'certonly' or 'renew' subcommands (%r)" % self.verb) + parsed_args.break_my_certs = parsed_args.staging = True + if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")): + # The user has a prod account, but might not have a staging + # one; we don't want to start trying to perform interactive registration + parsed_args.tos = True + parsed_args.register_unsafely_without_email = True + + def handle_csr(self, parsed_args): + """Process a --csr flag.""" + if parsed_args.verb != "certonly": + raise errors.Error("Currently, a CSR file may only be specified " + "when obtaining a new or replacement " + "via the certonly command. Please try the " + "certonly command instead.") + if parsed_args.allow_subset_of_names: + raise errors.Error("--allow-subset-of-names cannot be used with --csr") + + csrfile, contents = parsed_args.csr[0:2] + typ, csr, domains = crypto_util.import_csr_file(csrfile, contents) + + # This is not necessary for webroot to work, however, + # obtain_certificate_from_csr requires parsed_args.domains to be set + for domain in domains: + add_domains(parsed_args, domain) + + if not domains: + # TODO: add CN to domains instead: + raise errors.Error( + "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" + % parsed_args.csr[0]) + + parsed_args.actual_csr = (csr, typ) + + csr_domains = set([d.lower() for d in domains]) + config_domains = set(parsed_args.domains) + if csr_domains != config_domains: + raise errors.ConfigurationError( + "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" + .format(", ".join(csr_domains), ", ".join(config_domains))) + + + def determine_verb(self): + """Determines the verb/subcommand provided by the user. + + This function works around some of the limitations of argparse. + + """ + if "-h" in self.args or "--help" in self.args: + # all verbs double as help arguments; don't get them confused + self.verb = "help" + return + + for i, token in enumerate(self.args): + if token in self.VERBS: + verb = token + if verb == "auth": + verb = "certonly" + if verb == "everything": + verb = "run" + self.verb = verb + self.args.pop(i) + return + + self.verb = "run" + + def prescan_for_flag(self, flag, possible_arguments): + """Checks cli input for flags. + + Check for a flag, which accepts a fixed set of possible arguments, in + the command line; we will use this information to configure argparse's + help correctly. Return the flag's argument, if it has one that matches + the sequence @possible_arguments; otherwise return whether the flag is + present. + + """ + if flag not in self.args: + return False + pos = self.args.index(flag) + try: + nxt = self.args[pos + 1] + if nxt in possible_arguments: + return nxt + except IndexError: + pass + return True + + def add(self, topics, *args, **kwargs): + """Add a new command line argument. + + :param topics: str or [str] help topic(s) this should be listed under, + or None for options that don't fit under a specific + topic which will only be shown in "--help all" output. + The first entry determines where the flag lives in the + "--help all" output (None -> "optional arguments"). + :param list *args: the names of this argument flag + :param dict **kwargs: various argparse settings for this argument + + """ + + if isinstance(topics, list): + # if this flag can be listed in multiple sections, try to pick the one + # that the user has asked for help about + topic = self.help_arg if self.help_arg in topics else topics[0] + else: + topic = topics # there's only one + + if self.detect_defaults: + kwargs = self.modify_kwargs_for_default_detection(**kwargs) + + if self.visible_topics[topic]: + if topic in self.groups: + group = self.groups[topic] + group.add_argument(*args, **kwargs) + else: + self.parser.add_argument(*args, **kwargs) + else: + kwargs["help"] = argparse.SUPPRESS + self.parser.add_argument(*args, **kwargs) + + def modify_kwargs_for_default_detection(self, **kwargs): + """Modify an arg so we can check if it was set by the user. + + Changes the parameters given to argparse when adding an argument + so we can properly detect if the value was set by the user. + + :param dict kwargs: various argparse settings for this argument + + :returns: a modified versions of kwargs + :rtype: dict + + """ + action = kwargs.get("action", None) + if action not in EXIT_ACTIONS: + kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else + "store") + kwargs["default"] = _Default() + for param in ARGPARSE_PARAMS_TO_REMOVE: + kwargs.pop(param, None) + + return kwargs + + def add_deprecated_argument(self, argument_name, num_args): + """Adds a deprecated argument with the name argument_name. + + Deprecated arguments are not shown in the help. If they are used + on the command line, a warning is shown stating that the + argument is deprecated and no other action is taken. + + :param str argument_name: Name of deprecated argument. + :param int nargs: Number of arguments the option takes. + + """ + util.add_deprecated_argument( + self.parser.add_argument, argument_name, num_args) + + def add_group(self, topic, verbs=(), **kwargs): + """Create a new argument group. + + This method must be called once for every topic, however, calls + to this function are left next to the argument definitions for + clarity. + + :param str topic: Name of the new argument group. + :param str verbs: List of subcommands that should be documented as part of + this help group / topic + + :returns: The new argument group. + :rtype: `HelpfulArgumentGroup` + + """ + if self.visible_topics[topic]: + self.groups[topic] = self.parser.add_argument_group(topic, **kwargs) + if self.help_arg: + for v in verbs: + self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"]) + return HelpfulArgumentGroup(self, topic) + + def add_plugin_args(self, plugins): + """ + + Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics. + + """ + for name, plugin_ep in six.iteritems(plugins): + parser_or_group = self.add_group(name, + description=plugin_ep.long_description) + plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) + + def determine_help_topics(self, chosen_topic): + """ + + The user may have requested help on a topic, return a dict of which + topics to display. @chosen_topic has prescan_for_flag's return type + + :returns: dict + + """ + # topics maps each topic to whether it should be documented by + # argparse on the command line + if chosen_topic == "auth": + chosen_topic = "certonly" + if chosen_topic == "everything": + chosen_topic = "run" + if chosen_topic == "all": + # 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]) + return dict([(t, t == chosen_topic) for t in self.help_topics]) + +def _add_all_groups(helpful): + helpful.add_group("automation", description="Flags for automating execution & other tweaks") + helpful.add_group("security", description="Security parameters & server settings") + helpful.add_group("testing", + description="The following flags are meant for testing and integration purposes only.") + helpful.add_group("paths", description="Flags for changing execution paths & servers") + helpful.add_group("manage", + description="Various subcommands and flags are available for managing your certificates:", + verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"]) + + # VERBS + for verb, docs in VERB_HELP: + name = docs.get("realname", verb) + helpful.add_group(name, description=docs["opts"]) + + +def prepare_and_parse_args(plugins, args, detect_defaults=False): + """Returns parsed command line arguments. + + :param .PluginsRegistry plugins: available plugins + :param list args: command line arguments with the program name removed + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ + + + helpful = HelpfulArgumentParser(args, plugins, detect_defaults) + _add_all_groups(helpful) + + # --help is automatically provided by argparse + helpful.add( + None, "-v", "--verbose", dest="verbose_count", action="count", + default=flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") + helpful.add( + None, "-t", "--text", dest="text_mode", action="store_true", + default=flag_default("text_mode"), help=argparse.SUPPRESS) + helpful.add( + None, "--max-log-backups", type=nonnegative_int, + default=flag_default("max_log_backups"), + help="Specifies the maximum number of backup logs that should " + "be kept by Certbot's built in log rotation. Setting this " + "flag to 0 disables log rotation entirely, causing " + "Certbot to always append to the same log file.") + helpful.add( + [None, "automation", "run", "certonly", "enhance"], + "-n", "--non-interactive", "--noninteractive", + dest="noninteractive_mode", action="store_true", + default=flag_default("noninteractive_mode"), + help="Run without ever asking for user input. This may require " + "additional command line flags; the client will try to explain " + "which ones are required if it finds one missing") + helpful.add( + [None, "register", "run", "certonly", "enhance"], + constants.FORCE_INTERACTIVE_FLAG, action="store_true", + default=flag_default("force_interactive"), + help="Force Certbot to be interactive even if it detects it's not " + "being run in a terminal. This flag cannot be used with the " + "renew subcommand.") + helpful.add( + [None, "run", "certonly", "certificates", "enhance"], + "-d", "--domains", "--domain", dest="domains", + metavar="DOMAIN", action=_DomainsAction, + default=flag_default("domains"), + help="Domain names to apply. For multiple domains you can use " + "multiple -d flags or enter a comma separated list of domains " + "as a parameter. The first domain provided will be the " + "subject CN of the certificate, and all domains will be " + "Subject Alternative Names on the certificate. " + "The first domain will also be used in " + "some software user interfaces and as the file paths for the " + "certificate and related material unless otherwise " + "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", + metavar="CERTNAME", default=flag_default("certname"), + help="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 certificate names, run 'certbot certificates'. " + "When creating a new certificate, specifies the new certificate's name. " + "(default: the first provided domain or the name of an existing " + "certificate on your system for the same domains)") + helpful.add( + [None, "testing", "renew", "certonly"], + "--dry-run", action="store_true", dest="dry_run", + default=flag_default("dry_run"), + help="Perform a test run of the client, obtaining test (invalid) certificates" + " but not saving them to disk. This can currently only be used" + " with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run" + " tries to avoid making any persistent changes on a system, it " + " is not completely side-effect free: if used with webserver authenticator plugins" + " like apache and nginx, it makes and then reverts temporary config changes" + " in order to obtain test certificates, and reloads webservers to deploy and then" + " roll back those changes. It also calls --pre-hook and --post-hook commands" + " if they are defined because they may be necessary to accurately simulate" + " renewal. --deploy-hook commands are not called.") + helpful.add( + ["register", "automation"], "--register-unsafely-without-email", action="store_true", + default=flag_default("register_unsafely_without_email"), + help="Specifying this flag enables registering an account with no " + "email address. This is strongly discouraged, because in the " + "event of key loss or account compromise you will irrevocably " + "lose access to your account. You will also be unable to receive " + "notice about impending expiration or revocation of your " + "certificates. Updates to the Subscriber Agreement will still " + "affect you, and will be effective 14 days after posting an " + "update to the web site.") + helpful.add( + ["register", "update_account", "unregister", "automation"], "-m", "--email", + default=flag_default("email"), + help=config_help("email")) + 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", "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"], + "--keep-until-expiring", "--keep", "--reinstall", + dest="reinstall", action="store_true", default=flag_default("reinstall"), + help="If the requested certificate matches an existing certificate, always keep the " + "existing one until it is due for renewal (for the " + "'run' subcommand this means reinstall the existing certificate). (default: Ask)") + helpful.add( + "automation", "--expand", action="store_true", default=flag_default("expand"), + help="If an existing certificate is a strict subset of the requested names, " + "always expand and replace it with the additional names. (default: Ask)") + helpful.add( + "automation", "--version", action="version", + version="%(prog)s {0}".format(certbot.__version__), + help="show program's version number and exit") + helpful.add( + ["automation", "renew"], + "--force-renewal", "--renew-by-default", dest="renew_by_default", + action="store_true", default=flag_default("renew_by_default"), + help="If a certificate " + "already exists for the requested domains, renew it now, " + "regardless of whether it is near expiry. (Often " + "--keep-until-expiring is more appropriate). Also implies " + "--expand.") + helpful.add( + "automation", "--renew-with-new-domains", dest="renew_with_new_domains", + action="store_true", default=flag_default("renew_with_new_domains"), + help="If a " + "certificate already exists for the requested certificate name " + "but does not match the requested domains, renew it now, " + "regardless of whether it is near expiry.") + helpful.add( + "automation", "--reuse-key", dest="reuse_key", + action="store_true", default=flag_default("reuse_key"), + help="When renewing, use the same private key as the existing " + "certificate.") + + helpful.add( + ["automation", "renew", "certonly"], + "--allow-subset-of-names", action="store_true", + default=flag_default("allow_subset_of_names"), + help="When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This option cannot be used with --csr.") + helpful.add( + "automation", "--agree-tos", dest="tos", action="store_true", + default=flag_default("tos"), + help="Agree to the ACME Subscriber Agreement (default: Ask)") + helpful.add( + ["unregister", "automation"], "--account", metavar="ACCOUNT_ID", + default=flag_default("account"), + help="Account ID to use") + helpful.add( + "automation", "--duplicate", dest="duplicate", action="store_true", + default=flag_default("duplicate"), + help="Allow making a certificate lineage that duplicates an existing one " + "(both can be renewed in parallel)") + helpful.add( + "automation", "--os-packages-only", action="store_true", + default=flag_default("os_packages_only"), + help="(certbot-auto only) install OS package dependencies and then stop") + helpful.add( + "automation", "--no-self-upgrade", action="store_true", + default=flag_default("no_self_upgrade"), + help="(certbot-auto only) prevent the certbot-auto script from" + " upgrading itself to newer released versions (default: Upgrade" + " automatically)") + helpful.add( + "automation", "--no-bootstrap", action="store_true", + default=flag_default("no_bootstrap"), + help="(certbot-auto only) prevent the certbot-auto script from" + " installing OS-level dependencies (default: Prompt to install " + " OS-wide dependencies, but exit if the user says 'No')") + helpful.add( + "automation", "--no-permissions-check", action="store_true", + default=flag_default("no_permissions_check"), + help="(certbot-auto only) skip the check on the file system" + " permissions of the certbot-auto script") + helpful.add( + ["automation", "renew", "certonly", "run"], + "-q", "--quiet", dest="quiet", action="store_true", + default=flag_default("quiet"), + help="Silence all output except errors. Useful for automation via cron." + " Implies --non-interactive.") + # overwrites server, handled in HelpfulArgumentParser.parse_args() + helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging", + dest="staging", action="store_true", default=flag_default("staging"), + help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent" + " to --server " + constants.STAGING_URI) + helpful.add( + "testing", "--debug", action="store_true", default=flag_default("debug"), + help="Show tracebacks in case of errors, and allow certbot-auto " + "execution on experimental platforms") + helpful.add( + [None, "certonly", "run"], "--debug-challenges", action="store_true", + default=flag_default("debug_challenges"), + help="After setting up challenges, wait for user input before " + "submitting to CA") + helpful.add( + "testing", "--no-verify-ssl", action="store_true", + help=config_help("no_verify_ssl"), + default=flag_default("no_verify_ssl")) + helpful.add( + ["testing", "standalone", "manual"], "--http-01-port", type=int, + dest="http01_port", + default=flag_default("http01_port"), help=config_help("http01_port")) + helpful.add( + ["testing", "standalone"], "--http-01-address", + dest="http01_address", + default=flag_default("http01_address"), help=config_help("http01_address")) + helpful.add( + ["testing", "nginx"], "--https-port", type=int, + default=flag_default("https_port"), + help=config_help("https_port")) + helpful.add( + "testing", "--break-my-certs", action="store_true", + default=flag_default("break_my_certs"), + help="Be willing to replace or renew valid certificates with invalid " + "(testing/staging) certificates") + helpful.add( + "security", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) + helpful.add( + "security", "--must-staple", action="store_true", + dest="must_staple", default=flag_default("must_staple"), + help=config_help("must_staple")) + helpful.add( + ["security", "enhance"], + "--redirect", action="store_true", dest="redirect", + default=flag_default("redirect"), + help="Automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + "security", "--no-redirect", action="store_false", dest="redirect", + default=flag_default("redirect"), + help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost. (default: Ask)") + helpful.add( + ["security", "enhance"], + "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"), + help="Add the Strict-Transport-Security header to every HTTP response." + " Forcing browser to always use SSL for the domain." + " Defends against SSL Stripping.") + helpful.add( + "security", "--no-hsts", action="store_false", dest="hsts", + default=flag_default("hsts"), help=argparse.SUPPRESS) + helpful.add( + ["security", "enhance"], + "--uir", action="store_true", dest="uir", default=flag_default("uir"), + help='Add the "Content-Security-Policy: upgrade-insecure-requests"' + ' header to every HTTP response. Forcing the browser to use' + ' https:// for every http:// resource.') + helpful.add( + "security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"), + help=argparse.SUPPRESS) + helpful.add( + "security", "--staple-ocsp", action="store_true", dest="staple", + default=flag_default("staple"), + help="Enables OCSP Stapling. A valid OCSP response is stapled to" + " the certificate that the server offers during TLS.") + helpful.add( + "security", "--no-staple-ocsp", action="store_false", dest="staple", + default=flag_default("staple"), help=argparse.SUPPRESS) + helpful.add( + "security", "--strict-permissions", action="store_true", + default=flag_default("strict_permissions"), + help="Require that all configuration files are owned by the current " + "user; only needed if your config is somewhere unsafe like /tmp/") + helpful.add( + ["manual", "standalone", "certonly", "renew"], + "--preferred-challenges", dest="pref_challs", + action=_PrefChallAction, default=flag_default("pref_challs"), + help='A sorted, comma delimited list of the preferred challenge to ' + 'use during authorization with the most preferred challenge ' + 'listed first (Eg, "dns" or "http,dns"). ' + 'Not all plugins support all challenges. See ' + 'https://certbot.eff.org/docs/using.html#plugins for details. ' + 'ACME Challenges are versioned, but if you pick "http" rather ' + 'than "http-01", Certbot will select the latest version ' + 'automatically.') + helpful.add( + "renew", "--pre-hook", + help="Command to be run in a shell before obtaining any certificates." + " Intended primarily for renewal, where it can be used to temporarily" + " shut down a webserver that might conflict with the standalone" + " plugin. This will only be called if a certificate is actually to be" + " obtained/renewed. When renewing several certificates that have" + " identical pre-hooks, only the first will be executed.") + helpful.add( + "renew", "--post-hook", + help="Command to be run in a shell after attempting to obtain/renew" + " certificates. Can be used to deploy renewed certificates, or to" + " restart any servers that were stopped by --pre-hook. This is only" + " run if an attempt was made to obtain/renew a certificate. If" + " multiple renewed certificates have identical post-hooks, only" + " 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' + ' issued certificate. For this command, the shell variable' + ' $RENEWED_LINEAGE will point to the config live subdirectory' + ' (for example, "/etc/letsencrypt/live/example.com") containing' + ' the new certificates and keys; the shell variable' + ' $RENEWED_DOMAINS will contain a space-delimited list of' + ' renewed certificate domains (for example, "example.com' + ' www.example.com"') + helpful.add( + "renew", "--disable-hook-validation", + action="store_false", dest="validate_hooks", + default=flag_default("validate_hooks"), + help="Ordinarily the commands specified for" + " --pre-hook/--post-hook/--deploy-hook will be checked for" + " validity, to see if the programs being run are in the $PATH," + " so that mistakes can be caught early, even when the hooks" + " aren't being run just yet. The validation is rather" + " simplistic and fails if you use more advanced shell" + " constructs, so you can use this switch to disable it." + " (default: False)") + helpful.add( + "renew", "--no-directory-hooks", action="store_false", + default=flag_default("directory_hooks"), dest="directory_hooks", + help="Disable running executables found in Certbot's hook directories" + " during renewal. (default: False)") + helpful.add( + "renew", "--disable-renew-updates", action="store_true", + default=flag_default("disable_renew_updates"), dest="disable_renew_updates", + help="Disable automatic updates to your server configuration that" + " would otherwise be done by the selected installer plugin, and triggered" + " when the user executes \"certbot renew\", regardless of if the certificate" + " is renewed. This setting does not apply to important TLS configuration" + " updates.") + helpful.add( + "renew", "--no-autorenew", action="store_false", + default=flag_default("autorenew"), dest="autorenew", + help="Disable auto renewal of certificates.") + + # Populate the command line parameters for new style enhancements + enhancements.populate_cli(helpful.add) + + _create_subparsers(helpful) + _paths_parser(helpful) + # _plugins_parsing should be the last thing to act upon the main + # parser (--help should display plugin-specific options last) + _plugins_parsing(helpful, plugins) + + if not detect_defaults: + global helpful_parser # pylint: disable=global-statement + helpful_parser = helpful + return helpful.parse_args() + + +def _create_subparsers(helpful): + from certbot._internal.client import sample_user_agent # avoid import loops + helpful.add( + None, "--user-agent", default=flag_default("user_agent"), + help='Set a custom user agent string for the client. User agent strings allow ' + 'the CA to collect high level statistics about success rates by OS, ' + 'plugin and use 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: {0}). The flags encoded in the user agent are: ' + '--duplicate, --force-renew, --allow-subset-of-names, -n, and ' + 'whether any hooks are set.'.format(sample_user_agent())) + helpful.add( + None, "--user-agent-comment", default=flag_default("user_agent_comment"), + type=_user_agent_comment_type, + help="Add a comment to the default user agent string. May be used when repackaging Certbot " + "or calling it from another tool to allow additional statistical data to be collected." + " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") + helpful.add("certonly", + "--csr", default=flag_default("csr"), type=read_file, + help="Path to a Certificate Signing Request (CSR) in DER or PEM format." + " Currently --csr only works with the 'certonly' subcommand.") + helpful.add("revoke", + "--reason", dest="reason", + choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS, + key=constants.REVOCATION_REASONS.get)), + action=_EncodeReasonAction, default=flag_default("reason"), + help="Specify reason for revoking certificate. (default: unspecified)") + helpful.add("revoke", + "--delete-after-revoke", action="store_true", + default=flag_default("delete_after_revoke"), + help="Delete certificates after revoking them, along with all previous and later " + "versions of those certificates.") + helpful.add("revoke", + "--no-delete-after-revoke", action="store_false", + dest="delete_after_revoke", + default=flag_default("delete_after_revoke"), + help="Do not delete certificates after revoking them. This " + "option should be used with caution because the 'renew' " + "subcommand will attempt to renew undeleted revoked " + "certificates.") + helpful.add("rollback", + "--checkpoints", type=int, metavar="N", + default=flag_default("rollback_checkpoints"), + help="Revert configuration N number of checkpoints.") + helpful.add("plugins", + "--init", action="store_true", default=flag_default("init"), + help="Initialize plugins.") + helpful.add("plugins", + "--prepare", action="store_true", default=flag_default("prepare"), + help="Initialize and prepare plugins.") + helpful.add("plugins", + "--authenticators", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.") + helpful.add("plugins", + "--installers", action="append_const", dest="ifaces", + default=flag_default("ifaces"), + const=interfaces.IInstaller, help="Limit to installer plugins only.") + + +class CaseInsensitiveList(list): + """A list that will ignore case when searching. + + This class is passed to the `choices` argument of `argparse.add_arguments` + through the `helpful` wrapper. It is necessary due to special handling of + command line arguments by `set_by_cli` in which the `type_func` is not applied.""" + def __contains__(self, element): + return super(CaseInsensitiveList, self).__contains__(element.lower()) + + +def _paths_parser(helpful): + add = helpful.add + verb = helpful.verb + if verb == "help": + verb = helpful.help_arg + + cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked." + sections = ["paths", "install", "revoke", "certonly", "manage"] + if verb == "certonly": + add(sections, "--cert-path", type=os.path.abspath, + default=flag_default("auth_cert_path"), help=cph) + elif verb == "revoke": + add(sections, "--cert-path", type=read_file, required=False, help=cph) + else: + add(sections, "--cert-path", type=os.path.abspath, help=cph) + + section = "paths" + if verb in ("install", "revoke"): + section = verb + # revoke --key-path reads a file, install --key-path takes a string + add(section, "--key-path", + type=((verb == "revoke" and read_file) or os.path.abspath), + help="Path to private key for certificate installation " + "or revocation (if account key is missing)") + + default_cp = None + if verb == "certonly": + default_cp = flag_default("auth_chain_path") + add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a full certificate chain (certificate plus chain).") + add("paths", "--chain-path", default=default_cp, type=os.path.abspath, + help="Accompanying path to a certificate chain.") + add("paths", "--config-dir", default=flag_default("config_dir"), + help=config_help("config_dir")) + add("paths", "--work-dir", default=flag_default("work_dir"), + help=config_help("work_dir")) + add("paths", "--logs-dir", default=flag_default("logs_dir"), + help="Logs directory.") + add("paths", "--server", default=flag_default("server"), + help=config_help("server")) + + +def _plugins_parsing(helpful, plugins): + # It's nuts, but there are two "plugins" topics. Somehow this works + helpful.add_group( + "plugins", description="Plugin Selection: Certbot client supports an " + "extensible plugins architecture. See '%(prog)s plugins' for a " + "list of all installed plugins and their names. You can force " + "a particular plugin by setting options provided below. Running " + "--help will list flags specific to that plugin.") + + helpful.add("plugins", "--configurator", default=flag_default("configurator"), + help="Name of the plugin that is both an authenticator and an installer." + " Should not be used together with --authenticator or --installer. " + "(default: Ask)") + helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"), + help="Authenticator plugin name.") + helpful.add("plugins", "-i", "--installer", default=flag_default("installer"), + help="Installer plugin name (also used to find domains).") + helpful.add(["plugins", "certonly", "run", "install"], + "--apache", action="store_true", default=flag_default("apache"), + help="Obtain and install certificates using Apache") + helpful.add(["plugins", "certonly", "run", "install"], + "--nginx", action="store_true", default=flag_default("nginx"), + help="Obtain and install certificates using Nginx") + helpful.add(["plugins", "certonly"], "--standalone", action="store_true", + default=flag_default("standalone"), + help='Obtain certificates using a "standalone" webserver.') + helpful.add(["plugins", "certonly"], "--manual", action="store_true", + default=flag_default("manual"), + help="Provide laborious manual instructions for obtaining a certificate") + helpful.add(["plugins", "certonly"], "--webroot", action="store_true", + default=flag_default("webroot"), + help="Obtain certificates by placing files in a webroot directory.") + helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true", + default=flag_default("dns_cloudflare"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Cloudflare for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true", + default=flag_default("dns_cloudxns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using CloudXNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true", + default=flag_default("dns_digitalocean"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DigitalOcean for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true", + default=flag_default("dns_dnsimple"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNSimple for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true", + default=flag_default("dns_dnsmadeeasy"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using DNS Made Easy for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", + default=flag_default("dns_gehirn"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Gehirn Infrastracture Service for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", + default=flag_default("dns_google"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Google Cloud DNS).")) + helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true", + default=flag_default("dns_linode"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using Linode for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true", + default=flag_default("dns_luadns"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using LuaDNS for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", + default=flag_default("dns_nsone"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using NS1 for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true", + default=flag_default("dns_ovh"), + help=("Obtain certificates using a DNS TXT record (if you are " + "using OVH for DNS).")) + helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true", + default=flag_default("dns_rfc2136"), + help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).") + helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true", + default=flag_default("dns_route53"), + help=("Obtain certificates using a DNS TXT record (if you are using Route53 for " + "DNS).")) + helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true", + default=flag_default("dns_sakuracloud"), + help=("Obtain certificates using a DNS TXT record " + "(if you are using Sakura Cloud for DNS).")) + + # things should not be reorder past/pre this comment: + # plugins_group should be displayed in --help before plugin + # specific groups (so that plugins_group.description makes sense) + + helpful.add_plugin_args(plugins) + + +class _EncodeReasonAction(argparse.Action): + """Action class for parsing revocation reason.""" + + def __call__(self, parser, namespace, reason, option_string=None): + """Encodes the reason for certificate revocation.""" + code = constants.REVOCATION_REASONS[reason.lower()] + setattr(namespace, self.dest, code) + + +class _DomainsAction(argparse.Action): + """Action class for parsing domains.""" + + def __call__(self, parser, namespace, domain, option_string=None): + """Just wrap add_domains in argparseese.""" + add_domains(namespace, domain) + +def add_domains(args_or_config, domains): + """Registers new domains to be used during the current client run. + + Domains are not added to the list of requested domains if they have + already been registered. + + :param args_or_config: parsed command line arguments + :type args_or_config: argparse.Namespace or + configuration.NamespaceConfig + :param str domain: one or more comma separated domains + + :returns: domains after they have been normalized and validated + :rtype: `list` of `str` + + """ + validated_domains = [] + for domain in domains.split(","): + domain = util.enforce_domain_sanity(domain.strip()) + validated_domains.append(domain) + if domain not in args_or_config.domains: + args_or_config.domains.append(domain) + + return validated_domains + +class _PrefChallAction(argparse.Action): + """Action class for parsing preferred challenges.""" + + def __call__(self, parser, namespace, pref_challs, option_string=None): + try: + challs = parse_preferred_challenges(pref_challs.split(",")) + except errors.Error as error: + raise argparse.ArgumentError(self, str(error)) + namespace.pref_challs.extend(challs) + + +def parse_preferred_challenges(pref_challs): + """Translate and validate preferred challenges. + + :param pref_challs: list of preferred challenge types + :type pref_challs: `list` of `str` + + :returns: validated list of preferred challenge types + :rtype: `list` of `str` + + :raises errors.Error: if pref_challs is invalid + + """ + aliases = {"dns": "dns-01", "http": "http-01"} + challs = [c.strip() for c in pref_challs] + challs = [aliases.get(c, c) for c in challs] + + unrecognized = ", ".join(name for name in challs + if name not in challenges.Challenge.TYPES) + if unrecognized: + raise errors.Error( + "Unrecognized challenges: {0}".format(unrecognized)) + return challs + + +def _user_agent_comment_type(value): + if "(" in value or ")" in value: + raise argparse.ArgumentTypeError("may not contain parentheses") + return value + + +class _DeployHookAction(argparse.Action): + """Action class for parsing deploy hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + renew_hook_set = namespace.deploy_hook != namespace.renew_hook + if renew_hook_set and namespace.renew_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --renew-hook value") + namespace.deploy_hook = namespace.renew_hook = values + + +class _RenewHookAction(argparse.Action): + """Action class for parsing renew hooks.""" + + def __call__(self, parser, namespace, values, option_string=None): + deploy_hook_set = namespace.deploy_hook is not None + if deploy_hook_set and namespace.deploy_hook != values: + raise argparse.ArgumentError( + self, "conflicts with --deploy-hook value") + namespace.renew_hook = values + + +def nonnegative_int(value): + """Converts value to an int and checks that it is not negative. + + This function should used as the type parameter for argparse + arguments. + + :param str value: value provided on the command line + + :returns: integer representation of value + :rtype: int + + :raises argparse.ArgumentTypeError: if value isn't a non-negative integer + + """ + try: + int_value = int(value) + except ValueError: + raise argparse.ArgumentTypeError("value must be an integer") + + if int_value < 0: + raise argparse.ArgumentTypeError("value must be non-negative") + return int_value diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py new file mode 100644 index 000000000..2a9a52e73 --- /dev/null +++ b/certbot/certbot/_internal/client.py @@ -0,0 +1,742 @@ +"""Certbot client API.""" +import datetime +import logging +import platform + +import OpenSSL +import josepy as jose +import zope.component +from cryptography.hazmat.backends import default_backend +# https://github.com/python/typeshed/blob/master/third_party/ +# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi +from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore + +from acme import client as acme_client +from acme import crypto_util as acme_crypto_util +from acme import errors as acme_errors +from acme import messages +from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module + +import certbot +from certbot._internal import account +from certbot._internal import auth_handler +from certbot._internal import cli +from certbot._internal import constants +from certbot import crypto_util +from certbot._internal import eff +from certbot._internal import error_handler +from certbot import errors +from certbot import interfaces +from certbot._internal import storage +from certbot import util +from certbot.compat import os +from certbot._internal.display import enhancements +from certbot.display import ops as display_ops +from certbot._internal.plugins import selection as plugin_selection + +logger = logging.getLogger(__name__) + + +def acme_from_config_key(config, key, regr=None): + "Wrangle ACME client construction" + # TODO: Allow for other alg types besides RS256 + net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl), + user_agent=determine_user_agent(config)) + return acme_client.BackwardsCompatibleClientV2(net, key, config.server) + + +def determine_user_agent(config): + """ + Set a user_agent string in the config based on the choice of plugins. + (this wasn't knowable at construction time) + + :returns: the client's User-Agent string + :rtype: `str` + """ + + # WARNING: To ensure changes are in line with Certbot's privacy + # policy, talk to a core Certbot team member before making any + # changes here. + if config.user_agent is None: + ua = ("CertbotACMEClient/{0} ({1}; {2}{8}) Authenticator/{3} Installer/{4} " + "({5}; flags: {6}) Py/{7}") + if os.environ.get("CERTBOT_DOCS") == "1": + cli_command = "certbot(-auto)" + os_info = "OS_NAME OS_VERSION" + python_version = "major.minor.patchlevel" + else: + cli_command = cli.cli_command + os_info = util.get_os_info_ua() + python_version = platform.python_version() + ua = ua.format(certbot.__version__, cli_command, os_info, + config.authenticator, config.installer, config.verb, + ua_flags(config), python_version, + "; " + config.user_agent_comment if config.user_agent_comment else "") + else: + ua = config.user_agent + return ua + +def ua_flags(config): + "Turn some very important CLI flags into clues in the user agent." + if isinstance(config, DummyConfig): + return "FLAGS" + flags = [] + if config.duplicate: + flags.append("dup") + if config.renew_by_default: + flags.append("frn") + if config.allow_subset_of_names: + flags.append("asn") + if config.noninteractive_mode: + flags.append("n") + hook_names = ("pre", "post", "renew", "manual_auth", "manual_cleanup") + hooks = [getattr(config, h + "_hook") for h in hook_names] + if any(hooks): + flags.append("hook") + return " ".join(flags) + +class DummyConfig(object): + "Shim for computing a sample user agent." + def __init__(self): + self.authenticator = "XXX" + self.installer = "YYY" + self.user_agent = None + self.verb = "SUBCOMMAND" + + def __getattr__(self, name): + "Any config properties we might have are None." + return None + +def sample_user_agent(): + "Document what this Certbot's user agent string will be like." + + return determine_user_agent(DummyConfig()) + + +def register(config, account_storage, tos_cb=None): + """Register new account with an ACME CA. + + This function takes care of generating fresh private key, + registering the account, optionally accepting CA Terms of Service + and finally saving the account. It should be called prior to + initialization of `Client`, unless account has already been created. + + :param .IConfig config: Client configuration. + + :param .AccountStorage account_storage: Account storage where newly + registered account will be saved to. Save happens only after TOS + acceptance step, so any account private keys or + `.RegistrationResource` will not be persisted if `tos_cb` + returns ``False``. + + :param tos_cb: If ACME CA requires the user to accept a Terms of + Service before registering account, client action is + necessary. For example, a CLI tool would prompt the user + acceptance. `tos_cb` must be a callable that should accept + `.RegistrationResource` and return a `bool`: ``True`` iff the + Terms of Service present in the contained + `.Registration.terms_of_service` is accepted by the client, and + ``False`` otherwise. ``tos_cb`` will be called only if the + client action is necessary, i.e. when ``terms_of_service is not + None``. This argument is optional, if not supplied it will + default to automatic acceptance! + + :raises certbot.errors.Error: In case of any client problems, in + particular registration failure, or unaccepted Terms of Service. + :raises acme.errors.Error: In case of any protocol problems. + + :returns: Newly registered and saved account, as well as protocol + API handle (should be used in `Client` initialization). + :rtype: `tuple` of `.Account` and `acme.client.Client` + + """ + # Log non-standard actions, potentially wrong API calls + if account_storage.find_all(): + logger.info("There are already existing accounts for %s", config.server) + if config.email is None: + if not config.register_unsafely_without_email: + msg = ("No email was provided and " + "--register-unsafely-without-email was not present.") + logger.warning(msg) + raise errors.Error(msg) + if not config.dry_run: + logger.info("Registering without email!") + + # If --dry-run is used, and there is no staging account, create one with no email. + if config.dry_run: + config.email = None + + # Each new registration shall use a fresh new key + rsa_key = generate_private_key( + public_exponent=65537, + key_size=config.rsa_key_size, + backend=default_backend()) + key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key)) + acme = acme_from_config_key(config, key) + # TODO: add phone? + regr = perform_registration(acme, config, tos_cb) + + acc = account.Account(regr, key) + account.report_new_account(config) + account_storage.save(acc, acme) + + eff.handle_subscription(config) + + return acc, acme + + +def perform_registration(acme, config, tos_cb): + """ + Actually register new account, trying repeatedly if there are email + problems + + :param acme.client.Client client: ACME client object. + :param .IConfig config: Client configuration. + :param Callable tos_cb: a callback to handle Term of Service agreement. + + :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: + 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: + msg = ("The ACME server believes %s is an invalid email address. " + "Please ensure it is a valid email and attempt " + "registration again." % config.email) + raise errors.Error(msg) + else: + config.email = display_ops.get_email(invalid=True) + return perform_registration(acme, config, tos_cb) + else: + raise + + +class Client(object): + """Certbot's client. + + :ivar .IConfig config: Client configuration. + :ivar .Account account: Account registered with `register`. + :ivar .AuthHandler auth_handler: Authorizations handler that will + dispatch DV challenges to appropriate authenticators + (providing `.IAuthenticator` interface). + :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`) + authenticator that can solve ACME challenges. + :ivar .IInstaller installer: Installer. + :ivar acme.client.BackwardsCompatibleClientV2 acme: Optional ACME + client API handle. You might already have one from `register`. + + """ + + def __init__(self, config, account_, auth, installer, acme=None): + """Initialize a client.""" + self.config = config + self.account = account_ + self.auth = auth + self.installer = installer + + # Initialize ACME if account is provided + if acme is None and self.account is not None: + acme = acme_from_config_key(config, self.account.key, self.account.regr) + self.acme = acme + + if auth is not None: + self.auth_handler = auth_handler.AuthHandler( + auth, self.acme, self.account, self.config.pref_challs) + else: + self.auth_handler = None + + def obtain_certificate_from_csr(self, csr, orderr=None): + """Obtain certificate. + + :param .util.CSR csr: PEM-encoded Certificate Signing + Request. The key used to generate this CSR can be different + than `authkey`. + :param acme.messages.OrderResource orderr: contains authzrs + + :returns: certificate and chain as PEM byte strings + :rtype: tuple + + """ + if self.auth_handler is None: + msg = ("Unable to obtain certificate because authenticator is " + "not set.") + logger.warning(msg) + raise errors.Error(msg) + if self.account.regr is None: + raise errors.Error("Please register with the ACME server first.") + + logger.debug("CSR: %s", csr) + + if orderr is None: + orderr = self._get_order_and_authorizations(csr.data, best_effort=False) + + deadline = datetime.datetime.now() + datetime.timedelta(seconds=90) + orderr = self.acme.finalize_order(orderr, deadline) + cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem) + return cert.encode(), chain.encode() + + def obtain_certificate(self, domains, old_keypath=None): + """Obtains a certificate from the ACME server. + + `.register` must be called before `.obtain_certificate` + + :param list domains: domains to get a certificate + + :returns: certificate as PEM string, chain as PEM string, + newly generated private key (`.util.Key`), and DER-encoded + Certificate Signing Request (`.util.CSR`). + :rtype: tuple + + """ + + # We need to determine the key path, key PEM data, CSR path, + # and CSR PEM data. For a dry run, the paths are None because + # they aren't permanently saved to disk. For a lineage with + # --reuse-key, the key path and PEM data are derived from an + # existing file. + + if old_keypath is not None: + # We've been asked to reuse a specific existing private key. + # Therefore, we'll read it now and not generate a new one in + # either case below. + # + # We read in bytes here because the type of `key.pem` + # created below is also bytes. + with open(old_keypath, "rb") as f: + keypath = old_keypath + keypem = f.read() + key = util.Key(file=keypath, pem=keypem) # type: Optional[util.Key] + logger.info("Reusing existing private key from %s.", old_keypath) + else: + # The key is set to None here but will be created below. + key = None + + # Create CSR from names + if self.config.dry_run: + key = key or util.Key(file=None, + pem=crypto_util.make_key(self.config.rsa_key_size)) + csr = util.CSR(file=None, form="pem", + data=acme_crypto_util.make_csr( + key.pem, domains, self.config.must_staple)) + else: + key = key or crypto_util.init_save_key(self.config.rsa_key_size, + self.config.key_dir) + csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir) + + orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names) + authzr = orderr.authorizations + auth_domains = set(a.body.identifier.value for a in authzr) # pylint: disable=not-an-iterable + successful_domains = [d for d in domains if d in auth_domains] + + # allow_subset_of_names is currently disabled for wildcard + # certificates. The reason for this and checking allow_subset_of_names + # below is because successful_domains == domains is never true if + # domains contains a wildcard because the ACME spec forbids identifiers + # in authzs from containing a wildcard character. + if self.config.allow_subset_of_names and successful_domains != domains: + if not self.config.dry_run: + os.remove(key.file) + os.remove(csr.file) + return self.obtain_certificate(successful_domains) + else: + cert, chain = self.obtain_certificate_from_csr(csr, orderr) + + return cert, chain, key, csr + + def _get_order_and_authorizations(self, csr_pem, best_effort): + # type: (str, bool) -> List[messages.OrderResource] + """Request a new order and complete its authorizations. + + :param str csr_pem: A CSR in PEM format. + :param bool best_effort: True if failing to complete all + authorizations should not raise an exception + + :returns: order resource containing its completed authorizations + :rtype: acme.messages.OrderResource + + """ + try: + orderr = self.acme.new_order(csr_pem) + except acme_errors.WildcardUnsupportedError: + raise errors.Error("The currently selected ACME CA endpoint does" + " not support issuing wildcard certificates.") + + # For a dry run, ensure we have an order with fresh authorizations + if orderr and self.config.dry_run: + deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) + if deactivated: + logger.debug("Recreating order after authz deactivations") + orderr = self.acme.new_order(csr_pem) + if failed: + logger.warning("Certbot was unable to obtain fresh authorizations for every domain" + ". The dry run will continue, but results may not be accurate.") + + authzr = self.auth_handler.handle_authorizations(orderr, best_effort) + return orderr.update(authorizations=authzr) + + # pylint: disable=no-member + def obtain_and_enroll_certificate(self, domains, certname): + """Obtain and enroll certificate. + + Get a new certificate for the specified domains using the specified + authenticator and installer, and then create a new renewable lineage + containing it. + + :param domains: domains to request a certificate for + :type domains: `list` of `str` + :param certname: requested name of lineage + :type certname: `str` or `None` + + :returns: A new :class:`certbot._internal.storage.RenewableCert` instance + referred to the enrolled cert lineage, False if the cert could not + be obtained, or None if doing a successful dry run. + + """ + cert, chain, key, _ = self.obtain_certificate(domains) + + if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or + self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): + logger.info( + "Non-standard path(s), might not work with crontab installed " + "by your operating system package manager") + + new_name = self._choose_lineagename(domains, certname) + + if self.config.dry_run: + logger.debug("Dry run: Skipping creating new lineage for %s", + new_name) + return None + return storage.RenewableCert.new_lineage( + new_name, cert, + key.pem, chain, + self.config) + + def _choose_lineagename(self, domains, certname): + """Chooses a name for the new lineage. + + :param domains: domains in certificate request + :type domains: `list` of `str` + :param certname: requested name of lineage + :type certname: `str` or `None` + + :returns: lineage name that should be used + :rtype: str + + """ + if certname: + return certname + elif util.is_wildcard_domain(domains[0]): + # Don't make files and directories starting with *. + return domains[0][2:] + return domains[0] + + def save_certificate(self, cert_pem, chain_pem, + cert_path, chain_path, fullchain_path): + """Saves the certificate received from the ACME server. + + :param str cert_pem: + :param str chain_pem: + :param str cert_path: Candidate path to a certificate. + :param str chain_path: Candidate path to a certificate chain. + :param str fullchain_path: Candidate path to a full cert chain. + + :returns: cert_path, chain_path, and fullchain_path as absolute + paths to the actual files + :rtype: `tuple` of `str` + + :raises IOError: If unable to find room to write the cert files + + """ + for path in cert_path, chain_path, fullchain_path: + util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) + + + cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) + + try: + cert_file.write(cert_pem) + finally: + cert_file.close() + logger.info("Server issued certificate; certificate written to %s", + abs_cert_path) + + chain_file, abs_chain_path =\ + _open_pem_file('chain_path', chain_path) + fullchain_file, abs_fullchain_path =\ + _open_pem_file('fullchain_path', fullchain_path) + + _save_chain(chain_pem, chain_file) + _save_chain(cert_pem + chain_pem, fullchain_file) + + return abs_cert_path, abs_chain_path, abs_fullchain_path + + def deploy_certificate(self, domains, privkey_path, + cert_path, chain_path, fullchain_path): + """Install certificate + + :param list domains: list of domains to install the certificate + :param str privkey_path: path to certificate private key + :param str cert_path: certificate file path (optional) + :param str chain_path: chain file path + + """ + if self.installer is None: + logger.warning("No installer specified, client is unable to deploy" + "the certificate") + raise errors.Error("No installer available") + + chain_path = None if chain_path is None else os.path.abspath(chain_path) + + msg = ("Unable to install the certificate") + with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): + for dom in domains: + self.installer.deploy_cert( + domain=dom, cert_path=os.path.abspath(cert_path), + key_path=os.path.abspath(privkey_path), + chain_path=chain_path, + fullchain_path=fullchain_path) + self.installer.save() # needed by the Apache plugin + + self.installer.save("Deployed ACME Certificate") + + msg = ("We were unable to install your certificate, " + "however, we successfully restored your " + "server to its prior configuration.") + with error_handler.ErrorHandler(self._rollback_and_restart, msg): + # sites may have been enabled / final cleanup + self.installer.restart() + + def enhance_config(self, domains, chain_path, ask_redirect=True): + """Enhance the configuration. + + :param list domains: list of domains to configure + :param chain_path: chain file path + :type chain_path: `str` or `None` + + :raises .errors.Error: if no installer is specified in the + client. + + """ + if self.installer is None: + logger.warning("No installer is specified, there isn't any " + "configuration to enhance.") + raise errors.Error("No installer available") + + enhanced = False + enhancement_info = ( + ("hsts", "ensure-http-header", "Strict-Transport-Security"), + ("redirect", "redirect", None), + ("staple", "staple-ocsp", chain_path), + ("uir", "ensure-http-header", "Upgrade-Insecure-Requests"),) + supported = self.installer.supported_enhancements() + + for config_name, enhancement_name, option in enhancement_info: + config_value = getattr(self.config, config_name) + if enhancement_name in supported: + if ask_redirect: + if config_name == "redirect" and config_value is None: + config_value = enhancements.ask(enhancement_name) + if not config_value: + logger.warning("Future versions of Certbot will automatically " + "configure the webserver so that all requests redirect to secure " + "HTTPS access. You can control this behavior and disable this " + "warning with the --redirect and --no-redirect flags.") + if config_value: + self.apply_enhancement(domains, enhancement_name, option) + enhanced = True + elif config_value: + logger.warning( + "Option %s is not supported by the selected installer. " + "Skipping enhancement.", config_name) + + msg = ("We were unable to restart web server") + if enhanced: + with error_handler.ErrorHandler(self._rollback_and_restart, msg): + self.installer.restart() + + def apply_enhancement(self, domains, enhancement, options=None): + """Applies an enhancement on all domains. + + :param list domains: list of ssl_vhosts (as strings) + :param str enhancement: name of enhancement, e.g. ensure-http-header + :param str options: options to enhancement, e.g. Strict-Transport-Security + + .. note:: When more `options` are needed, make options a list. + + :raises .errors.PluginError: If Enhancement is not supported, or if + there is any other problem with the enhancement. + + + """ + msg = ("We were unable to set up enhancement %s for your server, " + "however, we successfully installed your certificate." + % (enhancement)) + with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg): + for dom in domains: + try: + self.installer.enhance(dom, enhancement, options) + except errors.PluginEnhancementAlreadyPresent: + if enhancement == "ensure-http-header": + logger.warning("Enhancement %s was already set.", + options) + else: + logger.warning("Enhancement %s was already set.", + enhancement) + except errors.PluginError: + logger.warning("Unable to set enhancement %s for %s", + enhancement, dom) + raise + + self.installer.save("Add enhancement %s" % (enhancement)) + + def _recovery_routine_with_msg(self, success_msg): + """Calls the installer's recovery routine and prints success_msg + + :param str success_msg: message to show on successful recovery + + """ + self.installer.recovery_routine() + reporter = zope.component.getUtility(interfaces.IReporter) + reporter.add_message(success_msg, reporter.HIGH_PRIORITY) + + def _rollback_and_restart(self, success_msg): + """Rollback the most recent checkpoint and restart the webserver + + :param str success_msg: message to show on successful rollback + + """ + logger.critical("Rolling back to previous server configuration...") + reporter = zope.component.getUtility(interfaces.IReporter) + try: + self.installer.rollback_checkpoints() + self.installer.restart() + except: + reporter.add_message( + "An error occurred and we failed to restore your config and " + "restart your server. Please post to " + "https://community.letsencrypt.org/c/help " + "with details about your configuration and this error you received.", + reporter.HIGH_PRIORITY) + raise + reporter.add_message(success_msg, reporter.HIGH_PRIORITY) + + +def validate_key_csr(privkey, csr=None): + """Validate Key and CSR files. + + Verifies that the client key and csr arguments are valid and correspond to + one another. This does not currently check the names in the CSR due to + the inability to read SANs from CSRs in python crypto libraries. + + If csr is left as None, only the key will be validated. + + :param privkey: Key associated with CSR + :type privkey: :class:`certbot.util.Key` + + :param .util.CSR csr: CSR + + :raises .errors.Error: when validation fails + + """ + # TODO: Handle all of these problems appropriately + # The client can eventually do things like prompt the user + # and allow the user to take more appropriate actions + + # Key must be readable and valid. + if privkey.pem and not crypto_util.valid_privkey(privkey.pem): + raise errors.Error("The provided key is not a valid key") + + if csr: + if csr.form == "der": + csr_obj = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_ASN1, csr.data) + cert_buffer = OpenSSL.crypto.dump_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_obj + ) + csr = util.CSR(csr.file, cert_buffer, "pem") + + # If CSR is provided, it must be readable and valid. + if csr.data and not crypto_util.valid_csr(csr.data): + raise errors.Error("The provided CSR is not a valid CSR") + + # If both CSR and key are provided, the key must be the same key used + # in the CSR. + if csr.data and privkey.pem: + if not crypto_util.csr_matches_pubkey( + csr.data, privkey.pem): + raise errors.Error("The key and CSR do not match") + + +def rollback(default_installer, checkpoints, config, plugins): + """Revert configuration the specified number of checkpoints. + + :param int checkpoints: Number of checkpoints to revert. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + + """ + # Misconfigurations are only a slight problems... allow the user to rollback + installer = plugin_selection.pick_installer( + config, default_installer, plugins, question="Which installer " + "should be used for rollback?") + + # No Errors occurred during init... proceed normally + # If installer is None... couldn't find an installer... there shouldn't be + # anything to rollback + if installer is not None: + installer.rollback_checkpoints(checkpoints) + installer.restart() + +def _open_pem_file(cli_arg_path, pem_path): + """Open a pem file. + + If cli_arg_path was set by the client, open that. + Otherwise, uniquify the file path. + + :param str cli_arg_path: the cli arg name, e.g. cert_path + :param str pem_path: the pem file path to open + + :returns: a tuple of file object and its absolute file path + + """ + if cli.set_by_cli(cli_arg_path): + return util.safe_open(pem_path, chmod=0o644, mode="wb"),\ + os.path.abspath(pem_path) + uniq = util.unique_file(pem_path, 0o644, "wb") + return uniq[0], os.path.abspath(uniq[1]) + +def _save_chain(chain_pem, chain_file): + """Saves chain_pem at a unique path based on chain_path. + + :param str chain_pem: certificate chain in PEM format + :param str chain_file: chain file object + + """ + try: + chain_file.write(chain_pem) + finally: + chain_file.close() + + logger.info("Cert chain written to %s", chain_file.name) diff --git a/certbot/certbot/_internal/configuration.py b/certbot/certbot/_internal/configuration.py new file mode 100644 index 000000000..48579eb1c --- /dev/null +++ b/certbot/certbot/_internal/configuration.py @@ -0,0 +1,160 @@ +"""Certbot user-supplied configuration.""" +import copy + +import zope.interface +from six.moves.urllib import parse # pylint: disable=relative-import + +from certbot._internal import constants +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import misc +from certbot.compat import os + + +@zope.interface.implementer(interfaces.IConfig) +class NamespaceConfig(object): + """Configuration wrapper around :class:`argparse.Namespace`. + + For more documentation, including available attributes, please see + :class:`certbot.interfaces.IConfig`. However, note that + the following attributes are dynamically resolved using + :attr:`~certbot.interfaces.IConfig.work_dir` and relative + paths defined in :py:mod:`certbot._internal.constants`: + + - `accounts_dir` + - `csr_dir` + - `in_progress_dir` + - `key_dir` + - `temp_checkpoint_dir` + + And the following paths are dynamically resolved using + :attr:`~certbot.interfaces.IConfig.config_dir` and relative + paths defined in :py:mod:`certbot._internal.constants`: + + - `default_archive_dir` + - `live_dir` + - `renewal_configs_dir` + + :ivar namespace: Namespace typically produced by + :meth:`argparse.ArgumentParser.parse_args`. + :type namespace: :class:`argparse.Namespace` + + """ + + def __init__(self, namespace): + object.__setattr__(self, 'namespace', namespace) + + self.namespace.config_dir = os.path.abspath(self.namespace.config_dir) + self.namespace.work_dir = os.path.abspath(self.namespace.work_dir) + self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir) + + # Check command line parameters sanity, and error out in case of problem. + check_config_sanity(self) + + def __getattr__(self, name): + return getattr(self.namespace, name) + + def __setattr__(self, name, value): + setattr(self.namespace, name, value) + + @property + def server_path(self): + """File path based on ``server``.""" + parsed = parse.urlparse(self.namespace.server) + return (parsed.netloc + parsed.path).replace('/', os.path.sep) + + @property + def accounts_dir(self): # pylint: disable=missing-docstring + return self.accounts_dir_for_server_path(self.server_path) + + def accounts_dir_for_server_path(self, server_path): + """Path to accounts directory based on server_path""" + server_path = misc.underscores_for_unsupported_characters_in_path(server_path) + return os.path.join( + self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) + + @property + def backup_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) + + @property + def csr_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.CSR_DIR) + + @property + def in_progress_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) + + @property + def key_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.KEY_DIR) + + @property + def temp_checkpoint_dir(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) + + def __deepcopy__(self, _memo): + # Work around https://bugs.python.org/issue1515 for py26 tests :( :( + # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276 + new_ns = copy.deepcopy(self.namespace) + return type(self)(new_ns) + + @property + def default_archive_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) + + @property + def live_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) + + @property + def renewal_configs_dir(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) + + @property + def renewal_hooks_dir(self): + """Path to directory with hooks to run with the renew subcommand.""" + return os.path.join(self.namespace.config_dir, + constants.RENEWAL_HOOKS_DIR) + + @property + def renewal_pre_hooks_dir(self): + """Path to the pre-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_PRE_HOOKS_DIR) + + @property + def renewal_deploy_hooks_dir(self): + """Path to the deploy-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_DEPLOY_HOOKS_DIR) + + @property + def renewal_post_hooks_dir(self): + """Path to the post-hook directory for the renew subcommand.""" + return os.path.join(self.renewal_hooks_dir, + constants.RENEWAL_POST_HOOKS_DIR) + + +def check_config_sanity(config): + """Validate command line options and display error message if + requirements are not met. + + :param config: IConfig instance holding user configuration + :type args: :class:`certbot.interfaces.IConfig` + + """ + # Port check + if config.http01_port == config.https_port: + raise errors.ConfigurationError( + "Trying to run http-01 and https-port " + "on the same port ({0})".format(config.https_port)) + + # Domain checks + if config.namespace.domains is not None: + for domain in config.namespace.domains: + # This may be redundant, but let's be paranoid + util.enforce_domain_sanity(domain) diff --git a/certbot/certbot/_internal/constants.py b/certbot/certbot/_internal/constants.py new file mode 100644 index 000000000..5ac7ee72d --- /dev/null +++ b/certbot/certbot/_internal/constants.py @@ -0,0 +1,219 @@ +"""Certbot constants.""" +import logging + +import pkg_resources + +from acme import challenges + +from certbot.compat import misc +from certbot.compat import os + +SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" +"""Setuptools entry point group name for plugins.""" + +OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" +"""Plugins Setuptools entry point before rename.""" + +CLI_DEFAULTS = dict( + config_files=[ + os.path.join(misc.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"), + ], + + # Main parser + verbose_count=-int(logging.INFO / 10), + text_mode=False, + max_log_backups=1000, + noninteractive_mode=False, + force_interactive=False, + domains=[], + certname=None, + dry_run=False, + register_unsafely_without_email=False, + email=None, + eff_email=None, + reinstall=False, + expand=False, + renew_by_default=False, + renew_with_new_domains=False, + autorenew=True, + allow_subset_of_names=False, + tos=False, + account=None, + duplicate=False, + os_packages_only=False, + no_self_upgrade=False, + no_permissions_check=False, + no_bootstrap=False, + quiet=False, + staging=False, + debug=False, + debug_challenges=False, + no_verify_ssl=False, + http01_port=challenges.HTTP01Response.PORT, + http01_address="", + https_port=443, + break_my_certs=False, + rsa_key_size=2048, + must_staple=False, + redirect=None, + auto_hsts=False, + hsts=None, + uir=None, + staple=None, + strict_permissions=False, + pref_challs=[], + validate_hooks=True, + 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, + user_agent=None, + user_agent_comment=None, + csr=None, + reason=0, + delete_after_revoke=None, + rollback_checkpoints=1, + init=False, + prepare=False, + ifaces=None, + + # Path parsers + auth_cert_path="./cert.pem", + auth_chain_path="./chain.pem", + key_path=None, + config_dir=misc.get_default_folder('config'), + work_dir=misc.get_default_folder('work'), + logs_dir=misc.get_default_folder('logs'), + server="https://acme-v02.api.letsencrypt.org/directory", + + # Plugins parsers + configurator=None, + authenticator=None, + installer=None, + apache=False, + nginx=False, + standalone=False, + manual=False, + webroot=False, + dns_cloudflare=False, + dns_cloudxns=False, + dns_digitalocean=False, + dns_dnsimple=False, + dns_dnsmadeeasy=False, + dns_gehirn=False, + dns_google=False, + dns_linode=False, + dns_luadns=False, + dns_nsone=False, + dns_ovh=False, + dns_rfc2136=False, + dns_route53=False, + dns_sakuracloud=False + +) +STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory" + +# The set of reasons for revoking a certificate is defined in RFC 5280 in +# section 5.3.1. The reasons that users are allowed to submit are restricted to +# those accepted by the ACME server implementation. They are listed in +# `letsencrypt.boulder.revocation.reasons.go`. +REVOCATION_REASONS = { + "unspecified": 0, + "keycompromise": 1, + "affiliationchanged": 3, + "superseded": 4, + "cessationofoperation": 5} + +"""Defaults for CLI flags and `.IConfig` attributes.""" + +QUIET_LOGGING_LEVEL = logging.WARNING +"""Logging level to use in quiet mode.""" + +RENEWER_DEFAULTS = dict( + renewer_enabled="yes", + renew_before_expiry="30 days", + # This value should ensure that there is never a deployment delay by + # default. + deploy_before_expiry="99 years", +) +"""Defaults for renewer script.""" + +ARCHIVE_DIR = "archive" +"""Archive directory, relative to `IConfig.config_dir`.""" + +CONFIG_DIRS_MODE = 0o755 +"""Directory mode for ``.IConfig.config_dir`` et al.""" + +ACCOUNTS_DIR = "accounts" +"""Directory where all accounts are saved.""" + +LE_REUSE_SERVERS = { + os.path.normpath('acme-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-v01.api.letsencrypt.org/directory'), + os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-staging.api.letsencrypt.org/directory') +} +"""Servers that can reuse accounts from other servers.""" + +BACKUP_DIR = "backups" +"""Directory (relative to `IConfig.work_dir`) where backups are kept.""" + +CSR_DIR = "csr" +"""See `.IConfig.csr_dir`.""" + +IN_PROGRESS_DIR = "IN_PROGRESS" +"""Directory used before a permanent checkpoint is finalized (relative to +`IConfig.work_dir`).""" + +KEY_DIR = "keys" +"""Directory (relative to `IConfig.config_dir`) where keys are saved.""" + +LIVE_DIR = "live" +"""Live directory, relative to `IConfig.config_dir`.""" + +TEMP_CHECKPOINT_DIR = "temp_checkpoint" +"""Temporary checkpoint directory (relative to `IConfig.work_dir`).""" + +RENEWAL_CONFIGS_DIR = "renewal" +"""Renewal configs directory, relative to `IConfig.config_dir`.""" + +RENEWAL_HOOKS_DIR = "renewal-hooks" +"""Basename of directory containing hooks to run with the renew command.""" + +RENEWAL_PRE_HOOKS_DIR = "pre" +"""Basename of directory containing pre-hooks to run with the renew command.""" + +RENEWAL_DEPLOY_HOOKS_DIR = "deploy" +"""Basename of directory containing deploy-hooks to run with the renew command.""" + +RENEWAL_POST_HOOKS_DIR = "post" +"""Basename of directory containing post-hooks to run with the renew command.""" + +FORCE_INTERACTIVE_FLAG = "--force-interactive" +"""Flag to disable TTY checking in IDisplay.""" + +EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot" +"""EFF URI used to submit the e-mail address of users who opt-in.""" + +SSL_DHPARAMS_DEST = "ssl-dhparams.pem" +"""Name of the ssl_dhparams file as saved in `IConfig.config_dir`.""" + +SSL_DHPARAMS_SRC = pkg_resources.resource_filename( + "certbot", "ssl-dhparams.pem") +"""Path to the nginx ssl_dhparams file found in the Certbot distribution.""" + +UPDATED_SSL_DHPARAMS_DIGEST = ".updated-ssl-dhparams-pem-digest.txt" +"""Name of the hash of the updated or informed ssl_dhparams as saved in `IConfig.config_dir`.""" + +ALL_SSL_DHPARAMS_HASHES = [ + '9ba6429597aeed2d8617a7705b56e96d044f64b07971659382e426675105654b', +] +"""SHA256 hashes of the contents of all versions of SSL_DHPARAMS_SRC""" diff --git a/certbot/certbot/_internal/display/__init__.py b/certbot/certbot/_internal/display/__init__.py new file mode 100644 index 000000000..9d39dce92 --- /dev/null +++ b/certbot/certbot/_internal/display/__init__.py @@ -0,0 +1 @@ +"""Certbot display utilities.""" diff --git a/certbot/certbot/_internal/display/completer.py b/certbot/certbot/_internal/display/completer.py new file mode 100644 index 000000000..3be06bec1 --- /dev/null +++ b/certbot/certbot/_internal/display/completer.py @@ -0,0 +1,61 @@ +"""Provides Tab completion when prompting users for a path.""" +import glob +# readline module is not available on all systems +try: + import readline +except ImportError: + import certbot._internal.display.dummy_readline as readline # type: ignore + + +class Completer(object): + """Provides Tab completion when prompting users for a path. + + This class is meant to be used with readline to provide Tab + completion for users entering paths. The complete method can be + passed to readline.set_completer directly, however, this function + works best as a context manager. For example: + + with Completer(): + raw_input() + + In this example, Tab completion will be available during the call to + raw_input above, however, readline will be restored to its previous + state when exiting the body of the with statement. + + """ + + def __init__(self): + self._iter = self._original_completer = self._original_delims = None + + def complete(self, text, state): + """Provides path completion for use with readline. + + :param str text: text to offer completions for + :param int state: which completion to return + + :returns: possible completion for text or ``None`` if all + completions have been returned + :rtype: str + + """ + if state == 0: + self._iter = glob.iglob(text + '*') + return next(self._iter, None) + + def __enter__(self): + self._original_completer = readline.get_completer() + self._original_delims = readline.get_completer_delims() + + readline.set_completer(self.complete) + readline.set_completer_delims(' \t\n;') + + # readline can be implemented using GNU readline, pyreadline or libedit + # which have different configuration syntax + if readline.__doc__ is not None and 'libedit' in readline.__doc__: + readline.parse_and_bind('bind ^I rl_complete') + else: + readline.parse_and_bind('tab: complete') + + def __exit__(self, unused_type, unused_value, unused_traceback): + readline.set_completer_delims(self._original_delims) + readline.set_completer(self._original_completer) diff --git a/certbot/certbot/_internal/display/dummy_readline.py b/certbot/certbot/_internal/display/dummy_readline.py new file mode 100644 index 000000000..fb3d807bb --- /dev/null +++ b/certbot/certbot/_internal/display/dummy_readline.py @@ -0,0 +1,21 @@ +"""A dummy module with no effect for use on systems without readline.""" + + +def get_completer(): + """An empty implementation of readline.get_completer.""" + + +def get_completer_delims(): + """An empty implementation of readline.get_completer_delims.""" + + +def parse_and_bind(unused_command): + """An empty implementation of readline.parse_and_bind.""" + + +def set_completer(unused_function=None): + """An empty implementation of readline.set_completer.""" + + +def set_completer_delims(unused_delims): + """An empty implementation of readline.set_completer_delims.""" diff --git a/certbot/certbot/_internal/display/enhancements.py b/certbot/certbot/_internal/display/enhancements.py new file mode 100644 index 000000000..5498b9547 --- /dev/null +++ b/certbot/certbot/_internal/display/enhancements.py @@ -0,0 +1,64 @@ +"""Certbot Enhancement Display""" +import logging + +import zope.component + +from certbot import errors +from certbot import interfaces +from certbot.display import util as display_util + + +logger = logging.getLogger(__name__) + +# Define a helper function to avoid verbose code +util = zope.component.getUtility + + +def ask(enhancement): + """Display the enhancement to the user. + + :param str enhancement: One of the + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` enhancements + + :returns: True if feature is desired, False otherwise + :rtype: bool + + :raises .errors.Error: if the enhancement provided is not supported + + """ + try: + # Call the appropriate function based on the enhancement + return DISPATCH[enhancement]() + except KeyError: + logger.error("Unsupported enhancement given to ask(): %s", enhancement) + raise errors.Error("Unsupported Enhancement") + + +def redirect_by_default(): + """Determines whether the user would like to redirect to HTTPS. + + :returns: True if redirect is desired, False otherwise + :rtype: bool + + """ + choices = [ + ("No redirect", "Make no further changes to the webserver configuration."), + ("Redirect", "Make all requests redirect to secure HTTPS access. " + "Choose this for new sites, or if you're confident your site works on HTTPS. " + "You can undo this change by editing your web server's configuration."), + ] + + code, selection = util(interfaces.IDisplay).menu( + "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", + choices, default=0, + cli_flag="--redirect / --no-redirect", force_interactive=True) + + if code != display_util.OK: + return False + + return selection == 1 + + +DISPATCH = { + "redirect": redirect_by_default +} diff --git a/certbot/certbot/_internal/eff.py b/certbot/certbot/_internal/eff.py new file mode 100644 index 000000000..a0692009f --- /dev/null +++ b/certbot/certbot/_internal/eff.py @@ -0,0 +1,98 @@ +"""Subscribes users to the EFF newsletter.""" +import logging + +import requests +import zope.component + +from certbot._internal import constants +from certbot import interfaces + + +logger = logging.getLogger(__name__) + + +def handle_subscription(config): + """High level function to take care of EFF newsletter subscriptions. + + The user may be asked if they want to sign up for the newsletter if + they have not already specified. + + :param .IConfig config: Client configuration. + + """ + if config.email is None: + if config.eff_email: + _report_failure("you didn't provide an e-mail address") + return + if config.eff_email is None: + config.eff_email = _want_subscription() + if config.eff_email: + subscribe(config.email) + + +def _want_subscription(): + """Does the user want to be subscribed to the EFF newsletter? + + :returns: True if we should subscribe the user, otherwise, False + :rtype: bool + + """ + prompt = ( + 'Would you be willing to share your email address with the ' + "Electronic Frontier Foundation, a founding partner of the Let's " + 'Encrypt project and the non-profit organization that develops ' + "Certbot? We'd like to send you email about our work encrypting " + "the web, EFF news, campaigns, and ways to support digital freedom. ") + display = zope.component.getUtility(interfaces.IDisplay) + return display.yesno(prompt, default=False) + + +def subscribe(email): + """Subscribe the user to the EFF mailing list. + + :param str email: the e-mail address to subscribe + + """ + url = constants.EFF_SUBSCRIBE_URI + data = {'data_type': 'json', + 'email': email, + 'form_id': 'eff_supporters_library_subscribe_form'} + logger.debug('Sending POST request to %s:\n%s', url, data) + _check_response(requests.post(url, data=data)) + + +def _check_response(response): + """Check for errors in the server's response. + + If an error occurred, it will be reported to the user. + + :param requests.Response response: the server's response to the + subscription request + + """ + logger.debug('Received response:\n%s', response.content) + try: + response.raise_for_status() + if not response.json()['status']: + _report_failure('your e-mail address appears to be invalid') + except requests.exceptions.HTTPError: + _report_failure() + except (ValueError, KeyError): + _report_failure('there was a problem with the server response') + + +def _report_failure(reason=None): + """Notify the user of failing to sign them up for the newsletter. + + :param reason: a phrase describing what the problem was + beginning with a lowercase letter and no closing punctuation + :type reason: `str` or `None` + + """ + msg = ['We were unable to subscribe you the EFF mailing list'] + if reason is not None: + msg.append(' because ') + msg.append(reason) + msg.append('. You can try again later by visiting https://act.eff.org.') + reporter = zope.component.getUtility(interfaces.IReporter) + reporter.add_message(''.join(msg), reporter.LOW_PRIORITY) diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py new file mode 100644 index 000000000..1a570e48e --- /dev/null +++ b/certbot/certbot/_internal/error_handler.py @@ -0,0 +1,172 @@ +"""Registers functions to be called if an exception or signal occurs.""" +import functools +import logging +import signal +import traceback + +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Any, Callable, Dict, List, Union +# pylint: enable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +# _SIGNALS stores the signals that will be handled by the ErrorHandler. These +# signals were chosen as their default handler terminates the process and could +# potentially occur from inside Python. Signals such as SIGILL were not +# included as they could be a sign of something devious and we should terminate +# immediately. +if os.name != "nt": + _SIGNALS = [signal.SIGTERM] + for signal_code in [signal.SIGHUP, signal.SIGQUIT, + signal.SIGXCPU, signal.SIGXFSZ]: + # Adding only those signals that their default action is not Ignore. + # This is platform-dependent, so we check it dynamically. + if signal.getsignal(signal_code) != signal.SIG_IGN: + _SIGNALS.append(signal_code) +else: + # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. + # As consumed by CPython, most of handlers on theses signals are useless, in particular + # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops + # immediately the process without calling the attached handler. Besides, non-POSIX signals + # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the + # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable + # behavior in fact, and maps to the handler to SIGINT. However in this case, a + # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager + # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. + # + # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, + # https://docs.python.org/2/library/os.html#os.kill, + # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question + _SIGNALS = [] + + +class ErrorHandler(object): + """Context manager for running code that must be cleaned up on failure. + + The context manager allows you to register functions that will be called + when an exception (excluding SystemExit) or signal is encountered. + Usage:: + + handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs) + handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs) + + with handler: + do_something() + + Or for one cleanup function:: + + with ErrorHandler(func, args, kwargs): + do_something() + + If an exception is raised out of do_something, the cleanup functions will + be called in last in first out order. Then the exception is raised. + Similarly, if a signal is encountered, the cleanup functions are called + followed by the previously received signal handler. + + Each registered cleanup function is called exactly once. If a registered + function raises an exception, it is logged and the next function is called. + Signals received while the registered functions are executing are + deferred until they finish. + + """ + def __init__(self, func, *args, **kwargs): + self.call_on_regular_exit = False + self.body_executed = False + self.funcs = [] # type: List[Callable[[], Any]] + self.prev_handlers = {} # type: Dict[int, Union[int, None, Callable]] + self.received_signals = [] # type: List[int] + if func is not None: + self.register(func, *args, **kwargs) + + def __enter__(self): + self.body_executed = False + self._set_signal_handlers() + + def __exit__(self, exec_type, exec_value, trace): + self.body_executed = True + retval = False + # SystemExit is ignored to properly handle forks that don't exec + if exec_type is SystemExit: + return retval + elif exec_type is None: + if not self.call_on_regular_exit: + return retval + elif exec_type is errors.SignalExit: + logger.debug("Encountered signals: %s", self.received_signals) + retval = True + else: + logger.debug("Encountered exception:\n%s", "".join( + traceback.format_exception(exec_type, exec_value, trace))) + + self._call_registered() + self._reset_signal_handlers() + self._call_signals() + return retval + + def register(self, func, *args, **kwargs): + # type: (Callable, *Any, **Any) -> None + """Sets func to be run with the given arguments during cleanup. + + :param function func: function to be called in case of an error + + """ + self.funcs.append(functools.partial(func, *args, **kwargs)) + + def _call_registered(self): + """Calls all registered functions""" + logger.debug("Calling registered functions") + while self.funcs: + try: + self.funcs[-1]() + except Exception: # pylint: disable=broad-except + logger.error("Encountered exception during recovery: ", exc_info=True) + self.funcs.pop() + + def _set_signal_handlers(self): + """Sets signal handlers for signals in _SIGNALS.""" + for signum in _SIGNALS: + prev_handler = signal.getsignal(signum) + # If prev_handler is None, the handler was set outside of Python + if prev_handler is not None: + self.prev_handlers[signum] = prev_handler + signal.signal(signum, self._signal_handler) + + def _reset_signal_handlers(self): + """Resets signal handlers for signals in _SIGNALS.""" + for signum in self.prev_handlers: + signal.signal(signum, self.prev_handlers[signum]) + self.prev_handlers.clear() + + def _signal_handler(self, signum, unused_frame): + """Replacement function for handling received signals. + + Store the received signal. If we are executing the code block in + the body of the context manager, stop by raising signal exit. + + :param int signum: number of current signal + + """ + self.received_signals.append(signum) + if not self.body_executed: + raise errors.SignalExit + + def _call_signals(self): + """Finally call the deferred signals.""" + for signum in self.received_signals: + logger.debug("Calling signal %s", signum) + os.kill(os.getpid(), signum) + +class ExitHandler(ErrorHandler): + """Context manager for running code that must be cleaned up. + + Subclass of ErrorHandler, with the same usage and parameters. + In addition to cleaning up on all signals, also cleans up on + regular exit. + """ + def __init__(self, func, *args, **kwargs): + ErrorHandler.__init__(self, func, *args, **kwargs) + self.call_on_regular_exit = True diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py new file mode 100644 index 000000000..1bb3a2eab --- /dev/null +++ b/certbot/certbot/_internal/hooks.py @@ -0,0 +1,272 @@ +"""Facilities for implementing hooks that call shell commands.""" +from __future__ import print_function + +import logging +from subprocess import Popen, PIPE + +from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot import util +from certbot.compat import filesystem +from certbot.compat import os +from certbot.plugins import util as plug_util + +logger = logging.getLogger(__name__) + + +def validate_hooks(config): + """Check hook commands are executable.""" + validate_hook(config.pre_hook, "pre") + validate_hook(config.post_hook, "post") + validate_hook(config.deploy_hook, "deploy") + validate_hook(config.renew_hook, "renew") + + +def _prog(shell_cmd): + """Extract the program run by a shell command. + + :param str shell_cmd: command to be executed + + :returns: basename of command or None if the command isn't found + :rtype: str or None + + """ + if not util.exe_exists(shell_cmd): + plug_util.path_surgery(shell_cmd) + if not util.exe_exists(shell_cmd): + return None + return os.path.basename(shell_cmd) + + +def validate_hook(shell_cmd, hook_name): + """Check that a command provided as a hook is plausibly executable. + + :raises .errors.HookCommandNotFound: if the command is not found + """ + if shell_cmd: + cmd = shell_cmd.split(None, 1)[0] + if not _prog(cmd): + path = os.environ["PATH"] + if os.path.exists(cmd): + msg = "{1}-hook command {0} exists, but is not executable.".format(cmd, hook_name) + else: + msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format( + cmd, path, hook_name) + + raise errors.HookCommandNotFound(msg) + + +def pre_hook(config): + """Run pre-hooks if they exist and haven't already been run. + + When Certbot is running with the renew subcommand, this function + runs any hooks found in the config.renewal_pre_hooks_dir (if they + have not already been run) followed by any pre-hook in the config. + If hooks in config.renewal_pre_hooks_dir are run and the pre-hook in + the config is a path to one of these scripts, it is not run twice. + + :param configuration.NamespaceConfig config: Certbot settings + + """ + if config.verb == "renew" and config.directory_hooks: + for hook in list_hooks(config.renewal_pre_hooks_dir): + _run_pre_hook_if_necessary(hook) + + cmd = config.pre_hook + if cmd: + _run_pre_hook_if_necessary(cmd) + + +executed_pre_hooks = set() # type: Set[str] + + +def _run_pre_hook_if_necessary(command): + """Run the specified pre-hook if we haven't already. + + If we've already run this exact command before, a message is logged + saying the pre-hook was skipped. + + :param str command: pre-hook to be run + + """ + if command in executed_pre_hooks: + logger.info("Pre-hook command already run, skipping: %s", command) + else: + _run_hook("pre-hook", command) + executed_pre_hooks.add(command) + + +def post_hook(config): + """Run post-hooks if defined. + + This function also registers any executables found in + config.renewal_post_hooks_dir to be run when Certbot is used with + the renew subcommand. + + If the verb is renew, we delay executing any post-hooks until + :func:`run_saved_post_hooks` is called. In this case, this function + registers all hooks found in config.renewal_post_hooks_dir to be + called followed by any post-hook in the config. If the post-hook in + the config is a path to an executable in the post-hook directory, it + is not scheduled to be run twice. + + :param configuration.NamespaceConfig config: Certbot settings + + """ + + cmd = config.post_hook + # In the "renew" case, we save these up to run at the end + if config.verb == "renew": + if config.directory_hooks: + for hook in list_hooks(config.renewal_post_hooks_dir): + _run_eventually(hook) + if cmd: + _run_eventually(cmd) + # certonly / run + elif cmd: + _run_hook("post-hook", cmd) + + +post_hooks = [] # type: List[str] + + +def _run_eventually(command): + """Registers a post-hook to be run eventually. + + All commands given to this function will be run exactly once in the + order they were given when :func:`run_saved_post_hooks` is called. + + :param str command: post-hook to register to be run + + """ + if command not in post_hooks: + post_hooks.append(command) + + +def run_saved_post_hooks(): + """Run any post hooks that were saved up in the course of the 'renew' verb""" + for cmd in post_hooks: + _run_hook("post-hook", cmd) + + +def deploy_hook(config, domains, lineage_path): + """Run post-issuance hook if defined. + + :param configuration.NamespaceConfig config: Certbot settings + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + + """ + if config.deploy_hook: + _run_deploy_hook(config.deploy_hook, domains, + lineage_path, config.dry_run) + + +def renew_hook(config, domains, lineage_path): + """Run post-renewal hooks. + + This function runs any hooks found in + config.renewal_deploy_hooks_dir followed by any renew-hook in the + config. If the renew-hook in the config is a path to a script in + config.renewal_deploy_hooks_dir, it is not run twice. + + If Certbot is doing a dry run, no hooks are run and messages are + logged saying that they were skipped. + + :param configuration.NamespaceConfig config: Certbot settings + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + + """ + executed_dir_hooks = set() + if config.directory_hooks: + for hook in list_hooks(config.renewal_deploy_hooks_dir): + _run_deploy_hook(hook, domains, lineage_path, config.dry_run) + executed_dir_hooks.add(hook) + + if config.renew_hook: + if config.renew_hook in executed_dir_hooks: + logger.info("Skipping deploy-hook '%s' as it was already run.", + config.renew_hook) + else: + _run_deploy_hook(config.renew_hook, domains, + lineage_path, config.dry_run) + + +def _run_deploy_hook(command, domains, lineage_path, dry_run): + """Run the specified deploy-hook (if not doing a dry run). + + If dry_run is True, command is not run and a message is logged + saying that it was skipped. If dry_run is False, the hook is run + after setting the appropriate environment variables. + + :param str command: command to run as a deploy-hook + :param domains: domains in the obtained certificate + :type domains: `list` of `str` + :param str lineage_path: live directory path for the new cert + :param bool dry_run: True iff Certbot is doing a dry run + + """ + if dry_run: + logger.warning("Dry run: skipping deploy hook command: %s", + command) + return + + os.environ["RENEWED_DOMAINS"] = " ".join(domains) + os.environ["RENEWED_LINEAGE"] = lineage_path + _run_hook("deploy-hook", command) + + +def _run_hook(cmd_name, shell_cmd): + """Run a hook command. + + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` + + :returns: stderr if there was any""" + err, _ = execute(cmd_name, shell_cmd) + return err + + +def execute(cmd_name, shell_cmd): + """Run a command. + + :param str cmd_name: the user facing name of the hook being run + :param shell_cmd: shell command to execute + :type shell_cmd: `list` of `str` or `str` + + :returns: `tuple` (`str` stderr, `str` stdout)""" + logger.info("Running %s command: %s", cmd_name, shell_cmd) + + # universal_newlines causes Popen.communicate() + # to return str objects instead of bytes in Python 3 + cmd = Popen(shell_cmd, shell=True, stdout=PIPE, + stderr=PIPE, universal_newlines=True) + out, err = cmd.communicate() + base_cmd = os.path.basename(shell_cmd.split(None, 1)[0]) + if out: + logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out) + if cmd.returncode != 0: + logger.error('%s command "%s" returned error code %d', + cmd_name, shell_cmd, cmd.returncode) + if err: + logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) + return err, out + + +def list_hooks(dir_path): + """List paths to all hooks found in dir_path in sorted order. + + :param str dir_path: directory to search + + :returns: `list` of `str` + :rtype: sorted list of absolute paths to executables in dir_path + + """ + allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) + hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] + return sorted(hooks) diff --git a/certbot/certbot/_internal/lock.py b/certbot/certbot/_internal/lock.py new file mode 100644 index 000000000..eda2a72a1 --- /dev/null +++ b/certbot/certbot/_internal/lock.py @@ -0,0 +1,261 @@ +"""Implements file locks compatible with Linux and Windows for locking files and directories.""" +import errno +import logging +try: + import fcntl # pylint: disable=import-error +except ImportError: + import msvcrt # pylint: disable=import-error + POSIX_MODE = False +else: + POSIX_MODE = True + +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem + +logger = logging.getLogger(__name__) + + +def lock_dir(dir_path): + # type: (str) -> LockFile + """Place a lock file on the directory at dir_path. + + The lock file is placed in the root of dir_path with the name + .certbot.lock. + + :param str dir_path: path to directory + + :returns: the locked LockFile object + :rtype: LockFile + + :raises errors.LockError: if unable to acquire the lock + + """ + return LockFile(os.path.join(dir_path, '.certbot.lock')) + + +class LockFile(object): + """ + Platform independent file lock system. + LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, + instance is created, the associated file is 'locked from the point of view of the OS, + meaning that if another instance of Certbot try at the same time to acquire the same lock, + it will raise an Exception. Calling release method will release the lock, and make it + available to every other instance. + Upon exit, Certbot will also release all the locks. + This allows us to protect a file or directory from being concurrently accessed + or modified by two Certbot instances. + LockFile is platform independent: it will proceed to the appropriate OS lock mechanism + depending on Linux or Windows. + """ + def __init__(self, path): + # type: (str) -> None + """ + Create a LockFile instance on the given file path, and acquire lock. + :param str path: the path to the file that will hold a lock + """ + self._path = path + mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism + self._lock_mechanism = mechanism(path) + + self.acquire() + + def __repr__(self): + # type: () -> str + repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) + if self.is_locked(): + repr_str += 'acquired>' + else: + repr_str += 'released>' + return repr_str + + def acquire(self): + # type: () -> None + """ + Acquire the lock on the file, forbidding any other Certbot instance to acquire it. + :raises errors.LockError: if unable to acquire the lock + """ + self._lock_mechanism.acquire() + + def release(self): + # type: () -> None + """ + Release the lock on the file, allowing any other Certbot instance to acquire it. + """ + self._lock_mechanism.release() + + def is_locked(self): + # type: () -> bool + """ + Check if the file is currently locked. + :return: True if the file is locked, False otherwise + """ + return self._lock_mechanism.is_locked() + + +class _BaseLockMechanism(object): + def __init__(self, path): + # type: (str) -> None + """ + Create a lock file mechanism for Unix. + :param str path: the path to the lock file + """ + self._path = path + self._fd = None # type: Optional[int] + + def is_locked(self): + # type: () -> bool + """Check if lock file is currently locked. + :return: True if the lock file is locked + :rtype: bool + """ + return self._fd is not None + + def acquire(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + def release(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + +class _UnixLockMechanism(_BaseLockMechanism): + """ + A UNIX lock file mechanism. + This lock file is released when the locked file is closed or the + process exits. It cannot be used to provide synchronization between + threads. It is based on the lock_file package by Martin Horcicka. + """ + def acquire(self): + # type: () -> None + """Acquire the lock.""" + while self._fd is None: + # Open the file + fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) + try: + self._try_lock(fd) + if self._lock_success(fd): + self._fd = fd + finally: + # Close the file if it is not the required one + if self._fd is None: + os.close(fd) + + def _try_lock(self, fd): + # type: (int) -> None + """ + Try to acquire the lock file without blocking. + :param int fd: file descriptor of the opened file to lock + """ + try: + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError as err: + if err.errno in (errno.EACCES, errno.EAGAIN): + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + raise + + def _lock_success(self, fd): + # type: (int) -> bool + """ + Did we successfully grab the lock? + Because this class deletes the locked file when the lock is + released, it is possible another process removed and recreated + the file between us opening the file and acquiring the lock. + :param int fd: file descriptor of the opened file to lock + :returns: True if the lock was successfully acquired + :rtype: bool + """ + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since _lock_success is private and called only on Linux. + from os import stat, fstat # pylint: disable=os-module-forbidden + try: + stat1 = stat(self._path) + except OSError as err: + if err.errno == errno.ENOENT: + return False + raise + + stat2 = fstat(fd) + # If our locked file descriptor and the file on disk refer to + # the same device and inode, they're the same file. + return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino + + def release(self): + # type: () -> None + """Remove, close, and release the lock file.""" + # It is important the lock file is removed before it's released, + # otherwise: + # + # process A: open lock file + # process B: release lock file + # process A: lock file + # process A: check device and inode + # process B: delete file + # process C: open and lock a different file at the same path + try: + os.remove(self._path) + finally: + # Following check is done to make mypy happy: it ensure that self._fd, marked + # as Optional[int] is effectively int to make it compatible with os.close signature. + if self._fd is None: # pragma: no cover + raise TypeError('Error, self._fd is None.') + try: + os.close(self._fd) + finally: + self._fd = None + + +class _WindowsLockMechanism(_BaseLockMechanism): + """ + A Windows lock file mechanism. + By default on Windows, acquiring a file handler gives exclusive access to the process + and results in an effective lock. However, it is possible to explicitly acquire the + file handler in shared access in terms of read and write, and this is done by os.open + and io.open in Python. So an explicit lock needs to be done through the call of + msvcrt.locking, that will lock the first byte of the file. In theory, it is also + possible to access a file in shared delete access, allowing other processes to delete an + opened file. But this needs also to be done explicitly by all processes using the Windows + low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers + state that deleting a file opened by a process from another process is not possible with + os.open and io.open. + Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race + condition encountered on Linux is not possible on Windows, leading to a simpler workflow. + """ + def acquire(self): + """Acquire the lock""" + open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC + + fd = None + try: + # Under Windows, filesystem.open will raise directly an EACCES error + # if the lock file is already locked. + fd = filesystem.open(self._path, open_mode, 0o600) + msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) + except (IOError, OSError) as err: + if fd: + os.close(fd) + # Anything except EACCES is unexpected. Raise directly the error in that case. + if err.errno != errno.EACCES: + raise + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + + self._fd = fd + + def release(self): + """Release the lock.""" + try: + msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) + os.close(self._fd) + + try: + os.remove(self._path) + except OSError as e: + # If the lock file cannot be removed, it is not a big deal. + # Likely another instance is acquiring the lock we just released. + logger.debug(str(e)) + finally: + self._fd = None diff --git a/certbot/certbot/_internal/log.py b/certbot/certbot/_internal/log.py new file mode 100644 index 000000000..2109e0427 --- /dev/null +++ b/certbot/certbot/_internal/log.py @@ -0,0 +1,353 @@ +"""Logging utilities for Certbot. + +The best way to use this module is through `pre_arg_parse_setup` and +`post_arg_parse_setup`. `pre_arg_parse_setup` configures a minimal +terminal logger and ensures a detailed log is written to a secure +temporary file if Certbot exits before `post_arg_parse_setup` is called. +`post_arg_parse_setup` relies on the parsed command line arguments and +does the full logging setup with terminal and rotating file handling as +configured by the user. Any logged messages before +`post_arg_parse_setup` is called are sent to the rotating file handler. +Special care is taken by both methods to ensure all errors are logged +and properly flushed before program exit. + +""" +from __future__ import print_function + +import functools +import logging +import logging.handlers +import shutil +import sys +import tempfile +import traceback + +from acme import messages + +from certbot._internal import constants +from certbot import errors +from certbot import util +from certbot.compat import os + +# Logging format +CLI_FMT = "%(message)s" +FILE_FMT = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" + + +logger = logging.getLogger(__name__) + + +def pre_arg_parse_setup(): + """Setup logging before command line arguments are parsed. + + Terminal logging is setup using + `certbot._internal.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as + possible. File logging is setup so that logging messages are + buffered in memory. If Certbot exits before `post_arg_parse_setup` + is called, these buffered messages are written to a temporary file. + If Certbot doesn't exit, `post_arg_parse_setup` writes the messages + to the normal log files. + + This function also sets `logging.shutdown` to be called on program + exit which automatically flushes logging handlers and + `sys.excepthook` to properly log/display fatal exceptions. + + """ + temp_handler = TempHandler() + temp_handler.setFormatter(logging.Formatter(FILE_FMT)) + temp_handler.setLevel(logging.DEBUG) + memory_handler = MemoryHandler(temp_handler) + + stream_handler = ColoredStreamHandler() + stream_handler.setFormatter(logging.Formatter(CLI_FMT)) + stream_handler.setLevel(constants.QUIET_LOGGING_LEVEL) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) # send all records to handlers + root_logger.addHandler(memory_handler) + root_logger.addHandler(stream_handler) + + # logging.shutdown will flush the memory handler because flush() and + # close() are explicitly called + util.atexit_register(logging.shutdown) + sys.excepthook = functools.partial( + pre_arg_parse_except_hook, memory_handler, + debug='--debug' in sys.argv, log_path=temp_handler.path) + + +def post_arg_parse_setup(config): + """Setup logging after command line arguments are parsed. + + This function assumes `pre_arg_parse_setup` was called earlier and + the root logging configuration has not been modified. A rotating + file logging handler is created and the buffered log messages are + sent to that handler. Terminal logging output is set to the level + requested by the user. + + :param certbot.interface.IConfig config: Configuration object + + """ + file_handler, file_path = setup_log_file_handler( + config, 'letsencrypt.log', FILE_FMT) + logs_dir = os.path.dirname(file_path) + + root_logger = logging.getLogger() + memory_handler = stderr_handler = None + for handler in root_logger.handlers: + if isinstance(handler, ColoredStreamHandler): + stderr_handler = handler + elif isinstance(handler, MemoryHandler): + memory_handler = handler + msg = 'Previously configured logging handlers have been removed!' + assert memory_handler is not None and stderr_handler is not None, msg + + root_logger.addHandler(file_handler) + root_logger.removeHandler(memory_handler) + temp_handler = memory_handler.target + memory_handler.setTarget(file_handler) + memory_handler.flush(force=True) + memory_handler.close() + temp_handler.close() + + if config.quiet: + level = constants.QUIET_LOGGING_LEVEL + else: + level = -config.verbose_count * 10 + stderr_handler.setLevel(level) + logger.debug('Root logging level set at %d', level) + logger.info('Saving debug log to %s', file_path) + + sys.excepthook = functools.partial( + post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir) + + +def setup_log_file_handler(config, logfile, fmt): + """Setup file debug logging. + + :param certbot.interface.IConfig config: Configuration object + :param str logfile: basename for the log file + :param str fmt: logging format string + + :returns: file handler and absolute path to the log file + :rtype: tuple + + """ + # TODO: logs might contain sensitive data such as contents of the + # private key! #525 + util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) + log_file_path = os.path.join(config.logs_dir, logfile) + try: + handler = logging.handlers.RotatingFileHandler( + log_file_path, maxBytes=2 ** 20, + backupCount=config.max_log_backups) + except IOError as error: + raise errors.Error(util.PERM_ERR_FMT.format(error)) + # rotate on each invocation, rollover only possible when maxBytes + # is nonzero and backupCount is nonzero, so we set maxBytes as big + # as possible not to overrun in single CLI invocation (1MB). + handler.doRollover() # TODO: creates empty letsencrypt.log.1 file + handler.setLevel(logging.DEBUG) + handler_formatter = logging.Formatter(fmt=fmt) + handler.setFormatter(handler_formatter) + return handler, log_file_path + + +class ColoredStreamHandler(logging.StreamHandler): + """Sends colored logging output to a stream. + + If the specified stream is not a tty, the class works like the + standard `logging.StreamHandler`. Default red_level is + `logging.WARNING`. + + :ivar bool colored: True if output should be colored + :ivar bool red_level: The level at which to output + + """ + def __init__(self, stream=None): + super(ColoredStreamHandler, self).__init__(stream) + self.colored = (sys.stderr.isatty() if stream is None else + stream.isatty()) + self.red_level = logging.WARNING + + def format(self, record): + """Formats the string representation of record. + + :param logging.LogRecord record: Record to be formatted + + :returns: Formatted, string representation of record + :rtype: str + + """ + out = super(ColoredStreamHandler, self).format(record) + if self.colored and record.levelno >= self.red_level: + return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET)) + return out + + +class MemoryHandler(logging.handlers.MemoryHandler): + """Buffers logging messages in memory until the buffer is flushed. + + This differs from `logging.handlers.MemoryHandler` in that flushing + only happens when flush(force=True) is called. + + """ + def __init__(self, target=None, capacity=10000): + # capacity doesn't matter because should_flush() is overridden + super(MemoryHandler, self).__init__(capacity, target=target) + + def close(self): + """Close the memory handler, but don't set the target to None.""" + # This allows the logging module which may only have a weak + # reference to the target handler to properly flush and close it. + target = self.target + super(MemoryHandler, self).close() + self.target = target + + def flush(self, force=False): # pylint: disable=arguments-differ + """Flush the buffer if force=True. + + If force=False, this call is a noop. + + :param bool force: True if the buffer should be flushed. + + """ + # This method allows flush() calls in logging.shutdown to be a + # noop so we can control when this handler is flushed. + if force: + super(MemoryHandler, self).flush() + + def shouldFlush(self, record): + """Should the buffer be automatically flushed? + + :param logging.LogRecord record: log record to be considered + + :returns: False because the buffer should never be auto-flushed + :rtype: bool + + """ + return False + + +class TempHandler(logging.StreamHandler): + """Safely logs messages to a temporary file. + + The file is created with permissions 600. If no log records are sent + to this handler, the temporary file is deleted when the handler is + closed. + + :ivar str path: file system path to the temporary log file + + """ + def __init__(self): + self._workdir = tempfile.mkdtemp() + self.path = os.path.join(self._workdir, 'log') + stream = util.safe_open(self.path, mode='w', chmod=0o600) + super(TempHandler, self).__init__(stream) + self._delete = True + + def emit(self, record): + """Log the specified logging record. + + :param logging.LogRecord record: Record to be formatted + + """ + self._delete = False + super(TempHandler, self).emit(record) + + def close(self): + """Close the handler and the temporary log file. + + The temporary log file is deleted if it wasn't used. + + """ + self.acquire() + try: + # StreamHandler.close() doesn't close the stream to allow a + # stream like stderr to be used + self.stream.close() + if self._delete: + shutil.rmtree(self._workdir) + self._delete = False + super(TempHandler, self).close() + finally: + self.release() + + +def pre_arg_parse_except_hook(memory_handler, *args, **kwargs): + """A simple wrapper around post_arg_parse_except_hook. + + The additional functionality provided by this wrapper is the memory + handler will be flushed before Certbot exits. This allows us to + write logging messages to a temporary file if we crashed before + logging was fully configured. + + Since sys.excepthook isn't called on SystemExit exceptions, the + memory handler will not be flushed in this case which prevents us + from creating temporary log files when argparse exits because a + command line argument was invalid or -h, --help, or --version was + provided on the command line. + + :param MemoryHandler memory_handler: memory handler to flush + :param tuple args: args for post_arg_parse_except_hook + :param dict kwargs: kwargs for post_arg_parse_except_hook + + """ + try: + post_arg_parse_except_hook(*args, **kwargs) + finally: + # flush() is called here so messages logged during + # post_arg_parse_except_hook are also flushed. + memory_handler.flush(force=True) + + +def post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path): + """Logs fatal exceptions and reports them to the user. + + If debug is True, the full exception and traceback is shown to the + user, otherwise, it is suppressed. sys.exit is always called with a + nonzero status. + + :param type exc_type: type of the raised exception + :param BaseException exc_value: raised exception + :param traceback trace: traceback of where the exception was raised + :param bool debug: True if the traceback should be shown to the user + :param str log_path: path to file or directory containing the log + + """ + exc_info = (exc_type, exc_value, trace) + # constants.QUIET_LOGGING_LEVEL or higher should be used to + # display message the user, otherwise, a lower level like + # logger.DEBUG should be used + if debug or not issubclass(exc_type, Exception): + assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR + logger.error('Exiting abnormally:', exc_info=exc_info) + else: + logger.debug('Exiting abnormally:', exc_info=exc_info) + if issubclass(exc_type, errors.Error): + sys.exit(exc_value) + logger.error('An unexpected error occurred:') + if messages.is_acme_error(exc_value): + # Remove the ACME error prefix from the exception + _, _, exc_str = str(exc_value).partition(':: ') + logger.error(exc_str) + else: + traceback.print_exception(exc_type, exc_value, None) + exit_with_log_path(log_path) + + +def exit_with_log_path(log_path): + """Print a message about the log location and exit. + + The message is printed to stderr and the program will exit with a + nonzero status. + + :param str log_path: path to file or directory containing the log + + """ + msg = 'Please see the ' + if os.path.isdir(log_path): + msg += 'logfiles in {0} '.format(log_path) + else: + msg += "logfile '{0}' ".format(log_path) + msg += 'for more details.' + sys.exit(msg) diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py new file mode 100644 index 000000000..c674efd79 --- /dev/null +++ b/certbot/certbot/_internal/main.py @@ -0,0 +1,1350 @@ +"""Certbot main entry point.""" +# pylint: disable=too-many-lines +from __future__ import print_function + +import functools +import logging.handlers +import sys + +import configobj +import josepy as jose +import zope.component + +from acme import errors as acme_errors +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module + +import certbot +from certbot._internal import account +from certbot._internal import cert_manager +from certbot._internal import cli +from certbot._internal import client +from certbot._internal import configuration +from certbot._internal import constants +from certbot import crypto_util +from certbot._internal import eff +from certbot import errors +from certbot._internal import hooks +from certbot import interfaces +from certbot._internal import log +from certbot._internal import renewal +from certbot._internal import reporter +from certbot._internal import storage +from certbot._internal import updater +from certbot import util +from certbot.compat import filesystem +from certbot.compat import misc +from certbot.compat import os +from certbot.display import util as display_util, ops as display_ops +from certbot._internal.plugins import disco as plugins_disco +from certbot.plugins import enhancements +from certbot._internal.plugins import selection as plug_sel + +USER_CANCELLED = ("User chose to cancel the operation and may " + "reinvoke the client.") + + +logger = logging.getLogger(__name__) + + +def _suggest_donation_if_appropriate(config): + """Potentially suggest a donation to support Certbot. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + assert config.verb != "renew" + if config.staging: + # --dry-run implies --staging + return + reporter_util = zope.component.getUtility(interfaces.IReporter) + msg = ("If you like Certbot, please consider supporting our work by:\n\n" + "Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n" + "Donating to EFF: https://eff.org/donate-le\n\n") + reporter_util.add_message(msg, reporter_util.LOW_PRIORITY) + +def _report_successful_dry_run(config): + """Reports on successful dry run + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + reporter_util = zope.component.getUtility(interfaces.IReporter) + assert config.verb != "renew" + reporter_util.add_message("The dry run was successful.", + reporter_util.HIGH_PRIORITY, on_crash=False) + + +def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None): + """Authenticate and enroll certificate. + + This method finds the relevant lineage, figures out what to do with it, + then performs that action. Includes calls to hooks, various reports, + checks, and requests for user input. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names to get a certificate. Defaults to `None` + :type domains: `list` of `str` + + :param certname: Name of new certificate. Defaults to `None` + :type certname: str + + :param lineage: Certificate lineage object. Defaults to `None` + :type lineage: storage.RenewableCert + + :returns: the issued certificate or `None` if doing a dry run + :rtype: storage.RenewableCert or None + + :raises errors.Error: if certificate could not be obtained + + """ + hooks.pre_hook(config) + try: + if lineage is not None: + # Renewal, where we already know the specific lineage we're + # interested in + logger.info("Renewing an existing certificate") + renewal.renew_cert(config, domains, le_client, lineage) + else: + # TREAT AS NEW REQUEST + assert domains is not None + logger.info("Obtaining a new certificate") + lineage = le_client.obtain_and_enroll_certificate(domains, certname) + if lineage is False: + raise errors.Error("Certificate could not be obtained") + elif lineage is not None: + hooks.deploy_hook(config, lineage.names(), lineage.live_dir) + finally: + hooks.post_hook(config) + + return lineage + + +def _handle_subset_cert_request(config, domains, cert): + """Figure out what to do if a previous cert had a subset of the names now requested + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param cert: Certificate object + :type cert: storage.RenewableCert + + :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname + action can be: "newcert" | "renew" | "reinstall" + :rtype: `tuple` of `str` + + """ + existing = ", ".join(cert.names()) + question = ( + "You have an existing certificate that contains a portion of " + "the domains you requested (ref: {0}){br}{br}It contains these " + "names: {1}{br}{br}You requested these names for the new " + "certificate: {2}.{br}{br}Do you want to expand and replace this existing " + "certificate with the new certificate?" + ).format(cert.configfile.filename, + existing, + ", ".join(domains), + br=os.linesep) + if config.expand or config.renew_by_default or zope.component.getUtility( + interfaces.IDisplay).yesno(question, "Expand", "Cancel", + cli_flag="--expand", + force_interactive=True): + return "renew", cert + else: + reporter_util = zope.component.getUtility(interfaces.IReporter) + reporter_util.add_message( + "To obtain a new certificate that contains these names without " + "replacing your existing certificate for {0}, you must use the " + "--duplicate option.{br}{br}" + "For example:{br}{br}{1} --duplicate {2}".format( + existing, + sys.argv[0], " ".join(sys.argv[1:]), + br=os.linesep + ), + reporter_util.HIGH_PRIORITY) + raise errors.Error(USER_CANCELLED) + + +def _handle_identical_cert_request(config, lineage): + """Figure out what to do if a lineage has the same names as a previously obtained one + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname + action can be: "newcert" | "renew" | "reinstall" + :rtype: `tuple` of `str` + + """ + if not lineage.ensure_deployed(): + return "reinstall", lineage + if renewal.should_renew(config, lineage): + return "renew", lineage + if config.reinstall: + # Set with --reinstall, force an identical certificate to be + # reinstalled without further prompting. + return "reinstall", lineage + question = ( + "You have an existing certificate that has exactly the same " + "domains or certificate name you requested and isn't close to expiry." + "{br}(ref: {0}){br}{br}What would you like to do?" + ).format(lineage.configfile.filename, br=os.linesep) + + if config.verb == "run": + keep_opt = "Attempt to reinstall this existing certificate" + elif config.verb == "certonly": + keep_opt = "Keep the existing certificate for now" + choices = [keep_opt, + "Renew & replace the cert (limit ~5 per 7 days)"] + + display = zope.component.getUtility(interfaces.IDisplay) + response = display.menu(question, choices, + default=0, force_interactive=True) + if response[0] == display_util.CANCEL: + # TODO: Add notification related to command-line options for + # skipping the menu for this case. + raise errors.Error( + "Operation canceled. You may re-run the client.") + elif response[1] == 0: + return "reinstall", lineage + elif response[1] == 1: + return "renew", lineage + raise AssertionError('This is impossible') + + +def _find_lineage_for_domains(config, domains): + """Determine whether there are duplicated names and how to handle + them (renew, reinstall, newcert, or raising an error to stop + the client run if the user chooses to cancel the operation when + prompted). + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :returns: Two-element tuple containing desired new-certificate behavior as + a string token ("reinstall", "renew", or "newcert"), plus either + a RenewableCert instance or `None` if renewal shouldn't occur. + :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` + + :raises errors.Error: If the user would like to rerun the client again. + + """ + # Considering the possibility that the requested certificate is + # related to an existing certificate. (config.duplicate, which + # is set with --duplicate, skips all of this logic and forces any + # kind of certificate to be obtained with renewal = False.) + if config.duplicate: + return "newcert", None + # TODO: Also address superset case + ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, domains) + # XXX ^ schoen is not sure whether that correctly reads the systemwide + # configuration file. + if ident_names_cert is None and subset_names_cert is None: + return "newcert", None + + if ident_names_cert is not None: + return _handle_identical_cert_request(config, ident_names_cert) + elif subset_names_cert is not None: + return _handle_subset_cert_request(config, domains, subset_names_cert) + return None, None + +def _find_cert(config, domains, certname): + """Finds an existing certificate object given domains and/or a certificate name. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :returns: Two-element tuple of a boolean that indicates if this function should be + followed by a call to fetch a certificate from the server, and either a + RenewableCert instance or None. + :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None` + + """ + action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname) + if action == "reinstall": + logger.info("Keeping the existing certificate") + return (action != "reinstall"), lineage + +def _find_lineage_for_domains_and_certname(config, domains, certname): + """Find appropriate lineage based on given domains and/or certname. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param domains: List of domain names + :type domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :returns: Two-element tuple containing desired new-certificate behavior as + a string token ("reinstall", "renew", or "newcert"), plus either + a RenewableCert instance or None if renewal should not occur. + + :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None` + + :raises errors.Error: If the user would like to rerun the client again. + + """ + if not certname: + return _find_lineage_for_domains(config, domains) + else: + lineage = cert_manager.lineage_for_certname(config, certname) + if lineage: + if domains: + if set(cert_manager.domains_for_certname(config, certname)) != set(domains): + _ask_user_to_confirm_new_names(config, domains, certname, + lineage.names()) # raises if no + return "renew", lineage + # unnecessarily specified domains or no domains specified + return _handle_identical_cert_request(config, lineage) + else: + if domains: + return "newcert", None + else: + raise errors.ConfigurationError("No certificate with name {0} found. " + "Use -d to specify domains, or run certbot certificates to see " + "possible certificate names.".format(certname)) + +def _get_added_removed(after, before): + """Get lists of items removed from `before` + and a lists of items added to `after` + """ + added = list(set(after) - set(before)) + removed = list(set(before) - set(after)) + added.sort() + removed.sort() + return added, removed + +def _format_list(character, strings): + """Format list with given character + """ + if not strings: + formatted = "{br}(None)" + else: + formatted = "{br}{ch} " + "{br}{ch} ".join(strings) + return formatted.format( + ch=character, + br=os.linesep + ) + +def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains): + """Ask user to confirm update cert certname to contain new_domains. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param new_domains: List of new domain names + :type new_domains: `list` of `str` + + :param certname: Name of certificate + :type certname: str + + :param old_domains: List of old domain names + :type old_domains: `list` of `str` + + :returns: None + :rtype: None + + :raises errors.ConfigurationError: if cert name and domains mismatch + + """ + if config.renew_with_new_domains: + return + + added, removed = _get_added_removed(new_domains, old_domains) + + msg = ("You are updating certificate {0} to include new domain(s): {1}{br}{br}" + "You are also removing previously included domain(s): {2}{br}{br}" + "Did you intend to make this change?".format( + certname, + _format_list("+", added), + _format_list("-", removed), + br=os.linesep)) + obj = zope.component.getUtility(interfaces.IDisplay) + if not obj.yesno(msg, "Update cert", "Cancel", default=True): + raise errors.ConfigurationError("Specified mismatched cert name and domains.") + +def _find_domains_or_certname(config, installer, question=None): + """Retrieve domains and certname from config or user input. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param `str` question: Overriding dialog question to ask the user if asked + to choose from domain names. + + :returns: Two-part tuple of domains and certname + :rtype: `tuple` of list of `str` and `str` + + :raises errors.Error: Usage message, if parameters are not used correctly + + """ + domains = None + certname = config.certname + # first, try to get domains from the config + if config.domains: + domains = config.domains + # if we can't do that but we have a certname, get the domains + # with that certname + elif certname: + domains = cert_manager.domains_for_certname(config, certname) + + # that certname might not have existed, or there was a problem. + # try to get domains from the user. + if not domains: + domains = display_ops.choose_names(installer, question) + + if not domains and not certname: + raise errors.Error("Please specify --domains, or --installer that " + "will help in domain names autodiscovery, or " + "--cert-name for an existing certificate name.") + + return domains, certname + + +def _report_new_cert(config, cert_path, fullchain_path, key_path=None): + """Reports the creation of a new certificate to the user. + + :param cert_path: path to certificate + :type cert_path: str + + :param fullchain_path: path to full chain + :type fullchain_path: str + + :param key_path: path to private key, if available + :type key_path: str + + :returns: `None` + :rtype: None + + """ + if config.dry_run: + _report_successful_dry_run(config) + return + + assert cert_path and fullchain_path, "No certificates saved to report." + + expiry = crypto_util.notAfter(cert_path).date() + reporter_util = zope.component.getUtility(interfaces.IReporter) + # Print the path to fullchain.pem because that's what modern webservers + # (Nginx and Apache2.4) will want. + + verbswitch = ' with the "certonly" option' if config.verb == "run" else "" + privkey_statement = 'Your key file has been saved at:{br}{0}{br}'.format( + key_path, br=os.linesep) if key_path else "" + # XXX Perhaps one day we could detect the presence of known old webservers + # and say something more informative here. + msg = ('Congratulations! Your certificate and chain have been saved at:{br}' + '{0}{br}{1}' + 'Your cert will expire on {2}. To obtain a new or tweaked version of this ' + 'certificate in the future, simply run {3} again{4}. ' + 'To non-interactively renew *all* of your certificates, run "{3} renew"' + .format(fullchain_path, privkey_statement, expiry, cli.cli_command, verbswitch, + br=os.linesep)) + reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY) + + +def _determine_account(config): + """Determine which account to use. + + If ``config.account`` is ``None``, it will be updated based on the + user input. Same for ``config.email``. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: Account and optionally ACME client API (biproduct of new + registration). + :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client` + + :raises errors.Error: If unable to register an account with ACME server + + """ + def _tos_cb(terms_of_service): + if config.tos: + return True + msg = ("Please read the Terms of Service at {0}. You " + "must agree in order to register with the ACME " + "server at {1}".format( + terms_of_service, config.server)) + obj = zope.component.getUtility(interfaces.IDisplay) + result = obj.yesno(msg, "Agree", "Cancel", + cli_flag="--agree-tos", force_interactive=True) + if not result: + raise errors.Error( + "Registration cannot proceed without accepting " + "Terms of Service.") + return None + + account_storage = account.AccountFileStorage(config) + acme = None + + if config.account is not None: + acc = account_storage.load(config.account) + else: + accounts = account_storage.find_all() + if len(accounts) > 1: + acc = display_ops.choose_account(accounts) + elif len(accounts) == 1: + acc = accounts[0] + else: # no account registered yet + if config.email is None and not config.register_unsafely_without_email: + config.email = display_ops.get_email() + try: + acc, acme = client.register( + config, account_storage, tos_cb=_tos_cb) + except errors.MissingCommandlineFlag: + raise + except errors.Error: + logger.debug("", exc_info=True) + raise errors.Error( + "Unable to register an account with ACME server") + + config.account = acc.id + return acc, acme + + +def _delete_if_appropriate(config): + """Does the user want to delete their now-revoked certs? If run in non-interactive mode, + deleting happens automatically. + + :param config: parsed command line arguments + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping + archive dir is found for the specified lineage, etc ... + """ + display = zope.component.getUtility(interfaces.IDisplay) + reporter_util = zope.component.getUtility(interfaces.IReporter) + + attempt_deletion = config.delete_after_revoke + if attempt_deletion is None: + msg = ("Would you like to delete the cert(s) you just revoked, along with all earlier and " + "later versions of the cert?") + attempt_deletion = display.yesno(msg, yes_label="Yes (recommended)", no_label="No", + force_interactive=True, default=True) + + if not attempt_deletion: + reporter_util.add_message("Not deleting revoked certs.", reporter_util.LOW_PRIORITY) + return + + # config.cert_path must have been set + # config.certname may have been set + assert config.cert_path + + if not config.certname: + config.certname = cert_manager.cert_path_to_lineage(config) + + # don't delete if the archive_dir is used by some other lineage + archive_dir = storage.full_archive_path( + configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)), + config, config.certname) + try: + cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir], + lambda x: x.archive_dir, lambda x: x) + except errors.OverlappingMatchFound: + msg = ('Not deleting revoked certs due to overlapping archive dirs. More than ' + 'one lineage is using {0}'.format(archive_dir)) + reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY) + return + except Exception as e: + msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},' + 'original exception: {3}') + msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e) + raise errors.Error(msg) + + cert_manager.delete(config) + + +def _init_le_client(config, authenticator, installer): + """Initialize Let's Encrypt Client + + :param config: Configuration object + :type config: interfaces.IConfig + + :param authenticator: Acme authentication handler + :type authenticator: interfaces.IAuthenticator + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: client: Client object + :rtype: client.Client + + """ + if authenticator is not None: + # if authenticator was given, then we will need account... + acc, acme = _determine_account(config) + logger.debug("Picked account: %r", acc) + # XXX + #crypto_util.validate_key_csr(acc.key) + else: + acc, acme = None, None + + return client.Client(config, acc, authenticator, installer, acme=acme) + + +def unregister(config, unused_plugins): + """Deactivate account on server + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + account_storage = account.AccountFileStorage(config) + accounts = account_storage.find_all() + reporter_util = zope.component.getUtility(interfaces.IReporter) + + if not accounts: + return "Could not find existing account to deactivate." + yesno = zope.component.getUtility(interfaces.IDisplay).yesno + prompt = ("Are you sure you would like to irrevocably deactivate " + "your account?") + wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort', + default=True) + + if not wants_deactivate: + return "Deactivation aborted." + + acc, acme = _determine_account(config) + cb_client = client.Client(config, acc, None, None, acme=acme) + + # delete on boulder + cb_client.acme.deactivate_registration(acc.regr) + account_files = account.AccountFileStorage(config) + # delete local account files + account_files.delete(config.account) + + reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY) + return None + + +def register(config, unused_plugins): + """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 + + """ + # Portion of _determine_account logic to see whether accounts already + # exist or not. + account_storage = account.AccountFileStorage(config) + accounts = account_storage.find_all() + + if accounts: + # 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 None + + +def update_account(config, unused_plugins): + """Modify 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 + + """ + # Portion of _determine_account logic to see whether accounts already + # exist or not. + account_storage = account.AccountFileStorage(config) + accounts = account_storage.find_all() + reporter_util = zope.component.getUtility(interfaces.IReporter) + add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY) + + if not accounts: + return "Could not find an existing account to update." + if config.email is None: + if config.register_unsafely_without_email: + return ("--register-unsafely-without-email provided, however, a " + "new e-mail address must\ncurrently be provided when " + "updating a registration.") + config.email = display_ops.get_email(optional=False) + + acc, acme = _determine_account(config) + cb_client = client.Client(config, acc, None, None, acme=acme) + # We rely on an exception to interrupt this process if it didn't work. + acc_contacts = ['mailto:' + email for email in config.email.split(',')] + prev_regr_uri = acc.regr.uri + acc.regr = cb_client.acme.update_registration(acc.regr.update( + body=acc.regr.body.update(contact=acc_contacts))) + # A v1 account being used as a v2 account will result in changing the uri to + # the v2 uri. Since it's the same object on disk, put it back to the v1 uri + # so that we can also continue to use the account object with acmev1. + acc.regr = acc.regr.update(uri=prev_regr_uri) + account_storage.save_regr(acc, cb_client.acme) + eff.handle_subscription(config) + add_msg("Your e-mail address was updated to {0}.".format(config.email)) + return None + +def _install_cert(config, le_client, domains, lineage=None): + """Install a cert + + :param config: Configuration object + :type config: interfaces.IConfig + + :param le_client: Client object + :type le_client: client.Client + + :param domains: List of domains + :type domains: `list` of `str` + + :param lineage: Certificate lineage object. Defaults to `None` + :type lineage: storage.RenewableCert + + :returns: `None` + :rtype: None + + """ + path_provider = lineage if lineage else config + assert path_provider.cert_path is not None + + le_client.deploy_certificate(domains, path_provider.key_path, + path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path) + le_client.enhance_config(domains, path_provider.chain_path) + + +def install(config, plugins): + """Install a previously obtained cert in a server. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + # XXX: Update for renewer/RenewableCert + # FIXME: be consistent about whether errors are raised or returned from + # this function ... + + try: + installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "install") + except errors.PluginSelectionError as e: + return str(e) + + custom_cert = (config.key_path and config.cert_path) + if not config.certname and not custom_cert: + certname_question = "Which certificate would you like to install?" + config.certname = cert_manager.get_certnames( + config, "install", allow_multiple=False, + custom_prompt=certname_question)[0] + + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + # If cert-path is defined, populate missing (ie. not overridden) values. + # Unfortunately this can't be done in argument parser, as certificate + # manager needs the access to renewal directory paths + if config.certname: + config = _populate_from_certname(config) + elif enhancements.are_requested(config): + # Preflight config check + raise errors.ConfigurationError("One or more of the requested enhancements " + "require --cert-name to be provided") + + if config.key_path and config.cert_path: + _check_certificate_and_key(config) + domains, _ = _find_domains_or_certname(config, installer) + le_client = _init_le_client(config, authenticator=None, installer=installer) + _install_cert(config, le_client, domains) + else: + raise errors.ConfigurationError("Path to certificate or key was not defined. " + "If your certificate is managed by Certbot, please use --cert-name " + "to define which certificate you would like to install.") + + if enhancements.are_requested(config): + # In the case where we don't have certname, we have errored out already + lineage = cert_manager.lineage_for_certname(config, config.certname) + enhancements.enable(lineage, domains, installer, config) + + return None + +def _populate_from_certname(config): + """Helper function for install to populate missing config values from lineage + defined by --cert-name.""" + + lineage = cert_manager.lineage_for_certname(config, config.certname) + if not lineage: + return config + if not config.key_path: + config.namespace.key_path = lineage.key_path + if not config.cert_path: + config.namespace.cert_path = lineage.cert_path + if not config.chain_path: + config.namespace.chain_path = lineage.chain_path + if not config.fullchain_path: + config.namespace.fullchain_path = lineage.fullchain_path + return config + +def _check_certificate_and_key(config): + if not os.path.isfile(filesystem.realpath(config.cert_path)): + raise errors.ConfigurationError("Error while reading certificate from path " + "{0}".format(config.cert_path)) + if not os.path.isfile(filesystem.realpath(config.key_path)): + raise errors.ConfigurationError("Error while reading private key from path " + "{0}".format(config.key_path)) +def plugins_cmd(config, plugins): + """List server software plugins. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + logger.debug("Expected interfaces: %s", config.ifaces) + + ifaces = [] if config.ifaces is None else config.ifaces + filtered = plugins.visible().ifaces(ifaces) + logger.debug("Filtered plugins: %r", filtered) + + notify = functools.partial(zope.component.getUtility( + interfaces.IDisplay).notification, pause=False) + if not config.init and not config.prepare: + notify(str(filtered)) + return + + filtered.init(config) + verified = filtered.verify(ifaces) + logger.debug("Verified plugins: %r", verified) + + if not config.prepare: + notify(str(verified)) + return + + verified.prepare() + available = verified.available() + logger.debug("Prepared plugins: %s", available) + notify(str(available)) + + +def enhance(config, plugins): + """Add security enhancements to existing configuration + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + supported_enhancements = ["hsts", "redirect", "uir", "staple"] + # Check that at least one enhancement was requested on command line + oldstyle_enh = any([getattr(config, enh) for enh in supported_enhancements]) + if not enhancements.are_requested(config) and not oldstyle_enh: + msg = ("Please specify one or more enhancement types to configure. To list " + "the available enhancement types, run:\n\n%s --help enhance\n") + logger.warning(msg, sys.argv[0]) + raise errors.MisconfigurationError("No enhancements requested, exiting.") + + try: + installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance") + except errors.PluginSelectionError as e: + return str(e) + + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + + certname_question = ("Which certificate would you like to use to enhance " + "your configuration?") + config.certname = cert_manager.get_certnames( + config, "enhance", allow_multiple=False, + custom_prompt=certname_question)[0] + cert_domains = cert_manager.domains_for_certname(config, config.certname) + if config.noninteractive_mode: + domains = cert_domains + else: + domain_question = ("Which domain names would you like to enable the " + "selected enhancements for?") + domains = display_ops.choose_values(cert_domains, domain_question) + if not domains: + raise errors.Error("User cancelled the domain selection. No domains " + "defined, exiting.") + + lineage = cert_manager.lineage_for_certname(config, config.certname) + if not config.chain_path: + config.chain_path = lineage.chain_path + if oldstyle_enh: + le_client = _init_le_client(config, authenticator=None, installer=installer) + le_client.enhance_config(domains, config.chain_path, ask_redirect=False) + if enhancements.are_requested(config): + enhancements.enable(lineage, domains, installer, config) + + return None + + +def rollback(config, plugins): + """Rollback server configuration changes made during install. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + client.rollback(config.installer, config.checkpoints, config, plugins) + +def update_symlinks(config, unused_plugins): + """Update the certificate file family symlinks + + Use the information in the config file to make symlinks point to + the correct archive directory. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.update_live_symlinks(config) + +def rename(config, unused_plugins): + """Rename a certificate + + Use the information in the config file to rename an existing + lineage. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.rename_lineage(config) + +def delete(config, unused_plugins): + """Delete a certificate + + Use the information in the config file to delete an existing + lineage. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.delete(config) + +def certificates(config, unused_plugins): + """Display information about certs configured with Certbot + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + cert_manager.certificates(config) + +# TODO: coop with renewal config +def revoke(config, unused_plugins): + """Revoke a previously obtained certificate. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` or string indicating error in case of error + :rtype: None or str + + """ + # For user-agent construction + config.installer = config.authenticator = None + + if config.cert_path is None and config.certname: + config.cert_path = storage.cert_path_for_cert_name(config, config.certname) + elif not config.cert_path or (config.cert_path and config.certname): + # intentionally not supporting --cert-path & --cert-name together, + # to avoid dealing with mismatched values + raise errors.Error("Error! Exactly one of --cert-path or --cert-name must be specified!") + + if config.key_path is not None: # revocation by cert key + logger.debug("Revoking %s using cert key %s", + config.cert_path[0], config.key_path[0]) + crypto_util.verify_cert_matches_priv_key(config.cert_path[0], config.key_path[0]) + key = jose.JWK.load(config.key_path[1]) + acme = client.acme_from_config_key(config, key) + else: # revocation by account key + logger.debug("Revoking %s using Account Key", config.cert_path[0]) + acc, _ = _determine_account(config) + acme = client.acme_from_config_key(config, acc.key, acc.regr) + cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0] + logger.debug("Reason code for revocation: %s", config.reason) + try: + acme.revoke(jose.ComparableX509(cert), config.reason) + _delete_if_appropriate(config) + except acme_errors.ClientError as e: + return str(e) + + display_ops.success_revocation(config.cert_path[0]) + return None + + +def run(config, plugins): + """Obtain a certificate and install. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + # TODO: Make run as close to auth + install as possible + # Possible difficulties: config.csr was hacked into auth + try: + installer, authenticator = plug_sel.choose_configurator_plugins(config, plugins, "run") + except errors.PluginSelectionError as e: + return str(e) + + # Preflight check for enhancement support by the selected installer + if not enhancements.are_supported(config, installer): + raise errors.NotSupportedError("One ore more of the requested enhancements " + "are not supported by the selected installer") + + # TODO: Handle errors from _init_le_client? + le_client = _init_le_client(config, authenticator, installer) + + domains, certname = _find_domains_or_certname(config, installer) + should_get_cert, lineage = _find_cert(config, domains, certname) + + new_lineage = lineage + if should_get_cert: + new_lineage = _get_and_save_cert(le_client, config, domains, + certname, lineage) + + cert_path = new_lineage.cert_path if new_lineage else None + fullchain_path = new_lineage.fullchain_path if new_lineage else None + key_path = new_lineage.key_path if new_lineage else None + _report_new_cert(config, cert_path, fullchain_path, key_path) + + _install_cert(config, le_client, domains, new_lineage) + + if enhancements.are_requested(config) and new_lineage: + enhancements.enable(new_lineage, domains, installer, config) + + if lineage is None or not should_get_cert: + display_ops.success_installation(domains) + else: + display_ops.success_renewal(domains) + + _suggest_donation_if_appropriate(config) + return None + + +def _csr_get_and_save_cert(config, le_client): + """Obtain a cert using a user-supplied CSR + + This works differently in the CSR case (for now) because we don't + have the privkey, and therefore can't construct the files for a lineage. + So we just save the cert & chain to disk :/ + + :param config: Configuration object + :type config: interfaces.IConfig + + :param client: Client object + :type client: client.Client + + :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files + :rtype: `tuple` of `str` + + """ + csr, _ = config.actual_csr + cert, chain = le_client.obtain_certificate_from_csr(csr) + if config.dry_run: + logger.debug( + "Dry run: skipping saving certificate to %s", config.cert_path) + return None, None + cert_path, _, fullchain_path = le_client.save_certificate( + cert, chain, os.path.normpath(config.cert_path), + os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path)) + return cert_path, fullchain_path + +def renew_cert(config, plugins, lineage): + """Renew & save an existing cert. Do not install it. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :returns: `None` + :rtype: None + + :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass + + """ + try: + # installers are used in auth mode to determine domain names + installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") + except errors.PluginSelectionError as e: + logger.info("Could not choose appropriate plugin: %s", e) + raise + le_client = _init_le_client(config, auth, installer) + + renewed_lineage = _get_and_save_cert(le_client, config, lineage=lineage) + + notify = zope.component.getUtility(interfaces.IDisplay).notification + if installer is None: + notify("new certificate deployed without reload, fullchain is {0}".format( + lineage.fullchain), pause=False) + else: + # In case of a renewal, reload server to pick up new certificate. + # In principle we could have a configuration option to inhibit this + # from happening. + # Run deployer + updater.run_renewal_deployer(config, renewed_lineage, installer) + installer.restart() + notify("new certificate deployed with reload of {0} server; fullchain is {1}".format( + config.installer, lineage.fullchain), pause=False) + +def certonly(config, plugins): + """Authenticate & obtain cert, but do not install it. + + This implements the 'certonly' subcommand. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + + :raises errors.Error: If specified plugin could not be used + + """ + # SETUP: Select plugins and construct a client instance + try: + # installers are used in auth mode to determine domain names + installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly") + except errors.PluginSelectionError as e: + logger.info("Could not choose appropriate plugin: %s", e) + raise + + le_client = _init_le_client(config, auth, installer) + + if config.csr: + cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client) + _report_new_cert(config, cert_path, fullchain_path) + _suggest_donation_if_appropriate(config) + return + + domains, certname = _find_domains_or_certname(config, installer) + should_get_cert, lineage = _find_cert(config, domains, certname) + + if not should_get_cert: + notify = zope.component.getUtility(interfaces.IDisplay).notification + notify("Certificate not yet due for renewal; no action taken.", pause=False) + return + + lineage = _get_and_save_cert(le_client, config, domains, certname, lineage) + + cert_path = lineage.cert_path if lineage else None + fullchain_path = lineage.fullchain_path if lineage else None + key_path = lineage.key_path if lineage else None + _report_new_cert(config, cert_path, fullchain_path, key_path) + _suggest_donation_if_appropriate(config) + +def renew(config, unused_plugins): + """Renew previously-obtained certificates. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param unused_plugins: List of plugins (deprecated) + :type unused_plugins: `list` of `str` + + :returns: `None` + :rtype: None + + """ + try: + renewal.handle_renewal_request(config) + finally: + hooks.run_saved_post_hooks() + + +def make_or_verify_needed_dirs(config): + """Create or verify existence of config, work, and hook directories. + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + + hook_dirs = (config.renewal_pre_hooks_dir, + config.renewal_deploy_hooks_dir, + config.renewal_post_hooks_dir,) + for hook_dir in hook_dirs: + util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) + + +def set_displayer(config): + """Set the displayer + + :param config: Configuration object + :type config: interfaces.IConfig + + :returns: `None` + :rtype: None + + """ + if config.quiet: + config.noninteractive_mode = True + displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) \ + # type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay] + elif config.noninteractive_mode: + displayer = display_util.NoninteractiveDisplay(sys.stdout) + else: + displayer = display_util.FileDisplay(sys.stdout, + config.force_interactive) + zope.component.provideUtility(displayer) + + +def main(cli_args=None): + """Command line argument parsing and main script execution. + + :returns: result of requested command + + :raises errors.Error: OS errors triggered by wrong permissions + :raises errors.Error: error if plugin command is not supported + + """ + if not cli_args: + cli_args = sys.argv[1:] + + log.pre_arg_parse_setup() + + plugins = plugins_disco.PluginsRegistry.find_all() + logger.debug("certbot version: %s", certbot.__version__) + # do not log `config`, as it contains sensitive data (e.g. revoke --key)! + logger.debug("Arguments: %r", cli_args) + logger.debug("Discovered plugins: %r", plugins) + + # note: arg parser internally handles --help (and exits afterwards) + args = cli.prepare_and_parse_args(plugins, cli_args) + config = configuration.NamespaceConfig(args) + zope.component.provideUtility(config) + + # On windows, shell without administrative right cannot create symlinks required by certbot. + # So we check the rights before continuing. + misc.raise_for_non_administrative_windows_rights() + + try: + log.post_arg_parse_setup(config) + make_or_verify_needed_dirs(config) + except errors.Error: + # Let plugins_cmd be run as un-privileged user. + if config.func != plugins_cmd: + raise + + set_displayer(config) + + # Reporter + report = reporter.Reporter(config) + zope.component.provideUtility(report) + util.atexit_register(report.print_messages) + + return config.func(config, plugins) diff --git a/certbot/certbot/_internal/notify.py b/certbot/certbot/_internal/notify.py new file mode 100644 index 000000000..dda0a85af --- /dev/null +++ b/certbot/certbot/_internal/notify.py @@ -0,0 +1,34 @@ +"""Send e-mail notification to system administrators.""" + +import email +import smtplib +import socket +import subprocess + + +def notify(subject, whom, what): + """Send email notification. + + Try to notify the addressee (``whom``) by e-mail, with Subject: + defined by ``subject`` and message body by ``what``. + + """ + msg = email.message_from_string(what) + msg.add_header("From", "Certbot renewal agent ") + msg.add_header("To", whom) + msg.add_header("Subject", subject) + msg = msg.as_string() + try: + lmtp = smtplib.LMTP() + lmtp.connect() + lmtp.sendmail("root", [whom], msg) + except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused, + smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error): + # We should try using /usr/sbin/sendmail in this case + try: + proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"], + stdin=subprocess.PIPE) + proc.communicate(msg) + except OSError: + return False + return True diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/_internal/ocsp.py new file mode 100644 index 000000000..2a63412a0 --- /dev/null +++ b/certbot/certbot/_internal/ocsp.py @@ -0,0 +1,292 @@ +"""Tools for checking certificate revocation.""" +import logging +import re +from datetime import datetime, timedelta +from subprocess import Popen, PIPE + +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp # pylint: disable=import-error + getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp = None # type: ignore +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +import pytz +import requests + +from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot import crypto_util +from certbot import errors +from certbot._internal.storage import RenewableCert # pylint: disable=unused-import +from certbot import util + +logger = logging.getLogger(__name__) + + +class RevocationChecker(object): + """This class figures out OCSP checking on this system, and performs it.""" + + def __init__(self, enforce_openssl_binary_usage=False): + self.broken = False + self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp + + if self.use_openssl_binary: + if not util.exe_exists("openssl"): + logger.info("openssl not installed, can't check revocation") + self.broken = True + return + + # New versions of openssl want -header var=val, old ones want -header var val + test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], + stdout=PIPE, stderr=PIPE, universal_newlines=True) + _out, err = test_host_format.communicate() + if "Missing =" in err: + self.host_args = lambda host: ["Host=" + host] + else: + self.host_args = lambda host: ["Host", host] + + def ocsp_revoked(self, cert): + # type: (RenewableCert) -> bool + """Get revoked status for a particular cert version. + + .. todo:: Make this a non-blocking call + + :param `.storage.RenewableCert` cert: Certificate object + :returns: True if revoked; False if valid or the check failed or cert is expired. + :rtype: bool + + """ + cert_path, chain_path = cert.cert, cert.chain + + if self.broken: + return False + + # Let's Encrypt doesn't update OCSP for expired certificates, + # so don't check OCSP if the cert is expired. + # https://github.com/certbot/certbot/issues/7152 + now = pytz.UTC.fromutc(datetime.utcnow()) + if cert.target_expiry <= now: + return False + + url, host = _determine_ocsp_server(cert_path) + if not host or not url: + return False + + if self.use_openssl_binary: + return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url) + return _check_ocsp_cryptography(cert_path, chain_path, url) + + def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url): + # type: (str, str, str, str) -> bool + # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! + cmd = ["openssl", "ocsp", + "-no_nonce", + "-issuer", chain_path, + "-cert", cert_path, + "-url", url, + "-CAfile", chain_path, + "-verify_other", chain_path, + "-trust_other", + "-header"] + self.host_args(host) + logger.debug("Querying OCSP for %s", cert_path) + logger.debug(" ".join(cmd)) + try: + output, err = util.run_script(cmd, log=logger.debug) + except errors.SubprocessError: + logger.info("OCSP check failed for %s (are we offline?)", cert_path) + return False + return _translate_ocsp_query(cert_path, output, err) + + +def _determine_ocsp_server(cert_path): + # type: (str) -> Tuple[Optional[str], Optional[str]] + """Extract the OCSP server host from a certificate. + + :param str cert_path: Path to the cert we're checking OCSP for + :rtype tuple: + :returns: (OCSP server URL or None, OCSP server host or None) + + """ + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + try: + extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + ocsp_oid = x509.AuthorityInformationAccessOID.OCSP + descriptions = [description for description in extension.value + if description.access_method == ocsp_oid] + + url = descriptions[0].access_location.value + except (x509.ExtensionNotFound, IndexError): + logger.info("Cannot extract OCSP URI from %s", cert_path) + return None, None + + url = url.rstrip() + host = url.partition("://")[2].rstrip("/") + + if host: + return url, host + logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) + return None, None + + +def _check_ocsp_cryptography(cert_path, chain_path, url): + # type: (str, str, str) -> bool + # Retrieve OCSP response + with open(chain_path, 'rb') as file_handler: + issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + with open(cert_path, 'rb') as file_handler: + cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend()) + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + request = builder.build() + request_binary = request.public_bytes(serialization.Encoding.DER) + try: + response = requests.post(url, data=request_binary, + headers={'Content-Type': 'application/ocsp-request'}) + except requests.exceptions.RequestException: + logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True) + return False + if response.status_code != 200: + logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code) + return False + + response_ocsp = ocsp.load_der_ocsp_response(response.content) + + # Check OCSP response validity + if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL: + logger.error("Invalid OCSP response status for %s: %s", + cert_path, response_ocsp.response_status) + return False + + # Check OCSP signature + try: + _check_ocsp_response(response_ocsp, request, issuer, cert_path) + except UnsupportedAlgorithm as e: + logger.error(str(e)) + except errors.Error as e: + logger.error(str(e)) + except InvalidSignature: + logger.error('Invalid signature on OCSP response for %s', cert_path) + except AssertionError as error: + logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error)) + else: + # Check OCSP certificate status + logger.debug("OCSP certificate status for %s is: %s", + cert_path, response_ocsp.certificate_status) + return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED + + return False + + +def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): + """Verify that the OCSP is valid for serveral criterias""" + # Assert OCSP response corresponds to the certificate we are talking about + if response_ocsp.serial_number != request_ocsp.serial_number: + raise AssertionError('the certificate in response does not correspond ' + 'to the certificate in request') + + # Assert signature is valid + _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path) + + # Assert issuer in response is the expected one + if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm)) + or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash + or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash): + raise AssertionError('the issuer does not correspond to issuer of the certificate.') + + # In following checks, two situations can occur: + # * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate + # * nextUpdate is not set, and requirement is thisUpdate < now + # NB1: We add a validity period tolerance to handle clock time inconsistencies, + # value is 5 min like for OpenSSL. + # NB2: Another check is to verify that thisUpdate is not too old, it is optional + # for OpenSSL, so we do not do it here. + # See OpenSSL implementation as a reference: + # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391 + now = datetime.utcnow() # thisUpdate/nextUpdate are expressed in UTC/GMT time zone + if not response_ocsp.this_update: + raise AssertionError('param thisUpdate is not set.') + if response_ocsp.this_update > now + timedelta(minutes=5): + raise AssertionError('param thisUpdate is in the future.') + if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5): + raise AssertionError('param nextUpdate is in the past.') + + +def _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path): + """Verify an OCSP response signature against certificate issuer or responder""" + if response_ocsp.responder_name == issuer_cert.subject: + # Case where the OCSP responder is also the certificate issuer + logger.debug('OCSP response for certificate %s is signed by the certificate\'s issuer.', + cert_path) + responder_cert = issuer_cert + else: + # Case where the OCSP responder is not the certificate issuer + logger.debug('OCSP response for certificate %s is delegated to an external responder.', + cert_path) + + responder_certs = [cert for cert in response_ocsp.certificates + if cert.subject == response_ocsp.responder_name] + if not responder_certs: + raise AssertionError('no matching responder certificate could be found') + + # We suppose here that the ACME server support only one certificate in the OCSP status + # request. This is currently the case for LetsEncrypt servers. + # See https://github.com/letsencrypt/boulder/issues/2331 + responder_cert = responder_certs[0] + + if responder_cert.issuer != issuer_cert.subject: + raise AssertionError('responder certificate is not signed ' + 'by the certificate\'s issuer') + + try: + extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value + except (x509.ExtensionNotFound, IndexError): + delegate_authorized = False + if not delegate_authorized: + raise AssertionError('responder is not authorized by issuer to sign OCSP responses') + + # Following line may raise UnsupportedAlgorithm + chosen_hash = responder_cert.signature_hash_algorithm + # For a delegate OCSP responder, we need first check that its certificate is effectively + # signed by the certificate issuer. + crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature, + responder_cert.tbs_certificate_bytes, chosen_hash) + + # Following line may raise UnsupportedAlgorithm + chosen_hash = response_ocsp.signature_hash_algorithm + # We check that the OSCP response is effectively signed by the responder + # (an authorized delegate one or the certificate issuer itself). + crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature, + response_ocsp.tbs_response_bytes, chosen_hash) + + +def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): + """Parse openssl's weird output to work out what it means.""" + + states = ("good", "revoked", "unknown") + patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states] + good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns) + + warning = good.group(1) if good else None + + if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown: + logger.info("Revocation status for %s is unknown", cert_path) + logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) + return False + elif good and not warning: + return False + elif revoked: + warning = revoked.group(1) + if warning: + logger.info("OCSP revocation warning: %s", warning) + return True + else: + logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", + ocsp_output, ocsp_errors) + return False diff --git a/certbot/certbot/_internal/plugins/__init__.py b/certbot/certbot/_internal/plugins/__init__.py new file mode 100644 index 000000000..7831eab61 --- /dev/null +++ b/certbot/certbot/_internal/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot plugins.""" diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py new file mode 100644 index 000000000..0bee88ae1 --- /dev/null +++ b/certbot/certbot/_internal/plugins/disco.py @@ -0,0 +1,289 @@ +"""Utilities for plugins discovery and selection.""" +import collections +import itertools +import logging + +import pkg_resources +import six + +import zope.interface +import zope.interface.verify + +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from certbot._internal import constants +from certbot import errors +from certbot import interfaces + + +logger = logging.getLogger(__name__) + + +class PluginEntryPoint(object): + """Plugin entry point.""" + + PREFIX_FREE_DISTRIBUTIONS = [ + "certbot", + "certbot-apache", + "certbot-dns-cloudflare", + "certbot-dns-cloudxns", + "certbot-dns-digitalocean", + "certbot-dns-dnsimple", + "certbot-dns-dnsmadeeasy", + "certbot-dns-gehirn", + "certbot-dns-google", + "certbot-dns-linode", + "certbot-dns-luadns", + "certbot-dns-nsone", + "certbot-dns-ovh", + "certbot-dns-rfc2136", + "certbot-dns-route53", + "certbot-dns-sakuracloud", + "certbot-nginx", + ] + """Distributions for which prefix will be omitted.""" + + # this object is mutable, don't allow it to be hashed! + __hash__ = None # type: ignore + + def __init__(self, entry_point): + self.name = self.entry_point_to_plugin_name(entry_point) + self.plugin_cls = entry_point.load() + self.entry_point = entry_point + self._initialized = None + self._prepared = None + + @classmethod + def entry_point_to_plugin_name(cls, entry_point): + """Unique plugin name for an ``entry_point``""" + if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS: + return entry_point.name + return entry_point.dist.key + ":" + entry_point.name + + @property + def description(self): + """Description of the plugin.""" + return self.plugin_cls.description + + @property + def description_with_name(self): + """Description with name. Handy for UI.""" + return "{0} ({1})".format(self.description, self.name) + + @property + def long_description(self): + """Long description of the plugin.""" + try: + return self.plugin_cls.long_description + except AttributeError: + return self.description + + @property + def hidden(self): + """Should this plugin be hidden from UI?""" + return getattr(self.plugin_cls, "hidden", False) + + def ifaces(self, *ifaces_groups): + """Does plugin implements specified interface groups?""" + return not ifaces_groups or any( + all(iface.implementedBy(self.plugin_cls) + for iface in ifaces) + for ifaces in ifaces_groups) + + @property + def initialized(self): + """Has the plugin been initialized already?""" + return self._initialized is not None + + def init(self, config=None): + """Memoized plugin initialization.""" + if not self.initialized: + self.entry_point.require() # fetch extras! + self._initialized = self.plugin_cls(config, self.name) + return self._initialized + + def verify(self, ifaces): + """Verify that the plugin conforms to the specified interfaces.""" + assert self.initialized + for iface in ifaces: # zope.interface.providedBy(plugin) + try: + zope.interface.verify.verifyObject(iface, self.init()) + except zope.interface.exceptions.BrokenImplementation as error: + if iface.implementedBy(self.plugin_cls): + logger.debug( + "%s implements %s but object does not verify: %s", + self.plugin_cls, iface.__name__, error, exc_info=True) + return False + return True + + @property + def prepared(self): + """Has the plugin been prepared already?""" + if not self.initialized: + logger.debug(".prepared called on uninitialized %r", self) + return self._prepared is not None + + def prepare(self): + """Memoized plugin preparation.""" + assert self.initialized + if self._prepared is None: + try: + self._initialized.prepare() + except errors.MisconfigurationError as error: + logger.debug("Misconfigured %r: %s", self, error, exc_info=True) + self._prepared = error + except errors.NoInstallationError as error: + logger.debug( + "No installation (%r): %s", self, error, exc_info=True) + self._prepared = error + except errors.PluginError as error: + logger.debug("Other error:(%r): %s", self, error, exc_info=True) + self._prepared = error + else: + self._prepared = True + return self._prepared + + @property + def misconfigured(self): + """Is plugin misconfigured?""" + return isinstance(self._prepared, errors.MisconfigurationError) + + @property + def problem(self): + """Return the Exception raised during plugin setup, or None if all is well""" + if isinstance(self._prepared, Exception): + return self._prepared + return None + + @property + def available(self): + """Is plugin available, i.e. prepared or misconfigured?""" + return self._prepared is True or self.misconfigured + + def __repr__(self): + return "PluginEntryPoint#{0}".format(self.name) + + def __str__(self): + lines = [ + "* {0}".format(self.name), + "Description: {0}".format(self.plugin_cls.description), + "Interfaces: {0}".format(", ".join( + iface.__name__ for iface in zope.interface.implementedBy( + self.plugin_cls))), + "Entry point: {0}".format(self.entry_point), + ] + + if self.initialized: + lines.append("Initialized: {0}".format(self.init())) + if self.prepared: + lines.append("Prep: {0}".format(self.prepare())) + + return "\n".join(lines) + + +class PluginsRegistry(collections.Mapping): + """Plugins registry.""" + + def __init__(self, plugins): + # plugins are sorted so the same order is used between runs. + # This prevents deadlock caused by plugins acquiring a lock + # and ensures at least one concurrent Certbot instance will run + # successfully. + + # Pylint checks for super init, but also claims the super + # has no __init__member + # pylint: disable=super-init-not-called + self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins))) + + @classmethod + def find_all(cls): + """Find plugins using setuptools entry points.""" + plugins = {} # type: Dict[str, PluginEntryPoint] + # pylint: disable=not-callable + entry_points = itertools.chain( + pkg_resources.iter_entry_points( + constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), + pkg_resources.iter_entry_points( + constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),) + for entry_point in entry_points: + plugin_ep = PluginEntryPoint(entry_point) + assert plugin_ep.name not in plugins, ( + "PREFIX_FREE_DISTRIBUTIONS messed up") + # providedBy | pylint: disable=no-member + if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls): + plugins[plugin_ep.name] = plugin_ep + else: # pragma: no cover + logger.warning( + "%r does not provide IPluginFactory, skipping", plugin_ep) + return cls(plugins) + + def __getitem__(self, name): + return self._plugins[name] + + def __iter__(self): + return iter(self._plugins) + + def __len__(self): + return len(self._plugins) + + def init(self, config): + """Initialize all plugins in the registry.""" + return [plugin_ep.init(config) for plugin_ep + in six.itervalues(self._plugins)] + + def filter(self, pred): + """Filter plugins based on predicate.""" + return type(self)(dict((name, plugin_ep) for name, plugin_ep + in six.iteritems(self._plugins) if pred(plugin_ep))) + + def visible(self): + """Filter plugins based on visibility.""" + return self.filter(lambda plugin_ep: not plugin_ep.hidden) + + def ifaces(self, *ifaces_groups): + """Filter plugins based on interfaces.""" + return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups)) + + def verify(self, ifaces): + """Filter plugins based on verification.""" + return self.filter(lambda p_ep: p_ep.verify(ifaces)) + + def prepare(self): + """Prepare all plugins in the registry.""" + return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)] + + def available(self): + """Filter plugins based on availability.""" + return self.filter(lambda p_ep: p_ep.available) + # successfully prepared + misconfigured + + def find_init(self, plugin): + """Find an initialized plugin. + + This is particularly useful for finding a name for the plugin + (although `.IPluginFactory.__call__` takes ``name`` as one of + the arguments, ``IPlugin.name`` is not part of the interface):: + + # plugin is an instance providing IPlugin, initialized + # somewhere else in the code + plugin_registry.find_init(plugin).name + + Returns ``None`` if ``plugin`` is not found in the registry. + + """ + # use list instead of set because PluginEntryPoint is not hashable + candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins) + if plugin_ep.initialized and plugin_ep.init() is plugin] + assert len(candidates) <= 1 + if candidates: + return candidates[0] + return None + + def __repr__(self): + return "{0}({1})".format( + self.__class__.__name__, ','.join( + repr(p_ep) for p_ep in six.itervalues(self._plugins))) + + def __str__(self): + if not self._plugins: + return "No plugins" + return "\n\n".join(str(p_ep) for p_ep in six.itervalues(self._plugins)) diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py new file mode 100644 index 000000000..43f70d650 --- /dev/null +++ b/certbot/certbot/_internal/plugins/manual.py @@ -0,0 +1,185 @@ +"""Manual authenticator plugin""" +import zope.component +import zope.interface + +from acme import challenges +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +from certbot import achallenges # pylint: disable=unused-import +from certbot import errors +from certbot._internal import hooks +from certbot import interfaces +from certbot import reverter +from certbot.compat import os +from certbot.plugins import common + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Manual authenticator + + This plugin allows the user to perform the domain validation + challenge(s) themselves. This either be done manually by the user or + through shell scripts provided to Certbot. + + """ + + description = 'Manual configuration or run your own shell scripts' + hidden = True + long_description = ( + 'Authenticate through manual configuration or custom shell scripts. ' + 'When using shell scripts, an authenticator script must be provided. ' + 'The environment variables available to this script depend on the ' + 'type of challenge. $CERTBOT_DOMAIN will always contain the domain ' + 'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION ' + 'is the validation string, and $CERTBOT_TOKEN is the filename of the ' + 'resource requested when performing an HTTP-01 challenge. An additional ' + 'cleanup script can also be provided and can use the additional variable ' + '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.') + _DNS_INSTRUCTIONS = """\ +Please deploy a DNS TXT record under the name +{domain} with the following value: + +{validation} + +Before continuing, verify the record is deployed.""" + _HTTP_INSTRUCTIONS = """\ +Create a file containing just this data: + +{validation} + +And make it available on your web server at this URL: + +{uri} +""" + _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = """ +(This must be set up in addition to the previous challenges; do not remove, +replace, or undo the previous challenge tasks yet.) +""" + _SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS = """ +(This must be set up in addition to the previous challenges; do not remove, +replace, or undo the previous challenge tasks yet. Note that you might be +asked to create multiple distinct TXT records with the same name. This is +permitted by DNS standards.) +""" + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.reverter = reverter.Reverter(self.config) + self.reverter.recovery_routine() + self.env = dict() \ + # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]] + self.subsequent_dns_challenge = False + self.subsequent_any_challenge = False + + @classmethod + def add_parser_arguments(cls, add): + add('auth-hook', + help='Path or command to execute for the authentication script') + add('cleanup-hook', + help='Path or command to execute for the cleanup script') + add('public-ip-logging-ok', action='store_true', + help='Automatically allows public IP logging (default: Ask)') + + def prepare(self): # pylint: disable=missing-docstring + if self.config.noninteractive_mode and not self.conf('auth-hook'): + raise errors.PluginError( + 'An authentication script must be provided with --{0} when ' + 'using the manual plugin non-interactively.'.format( + self.option_name('auth-hook'))) + self._validate_hooks() + + def _validate_hooks(self): + if self.config.validate_hooks: + for name in ('auth-hook', 'cleanup-hook'): + hook = self.conf(name) + if hook is not None: + hook_prefix = self.option_name(name)[:-len('-hook')] + hooks.validate_hook(hook, hook_prefix) + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return ( + 'This plugin allows the user to customize setup for domain ' + 'validation challenges either through shell scripts provided by ' + 'the user or by performing the setup manually.') + + def get_chall_pref(self, domain): + # pylint: disable=missing-docstring,no-self-use,unused-argument + return [challenges.HTTP01, challenges.DNS01] + + def perform(self, achalls): # pylint: disable=missing-docstring + self._verify_ip_logging_ok() + if self.conf('auth-hook'): + perform_achall = self._perform_achall_with_script + else: + perform_achall = self._perform_achall_manually + + responses = [] + for achall in achalls: + perform_achall(achall) + responses.append(achall.response(achall.account_key)) + return responses + + def _verify_ip_logging_ok(self): + if not self.conf('public-ip-logging-ok'): + cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok')) + msg = ('NOTE: The IP of this machine will be publicly logged as ' + "having requested this certificate. If you're running " + 'certbot in manual mode on a machine that is not your ' + "server, please ensure you're okay with that.\n\n" + 'Are you OK with your IP being logged?') + display = zope.component.getUtility(interfaces.IDisplay) + if display.yesno(msg, cli_flag=cli_flag, force_interactive=True): + setattr(self.config, self.dest('public-ip-logging-ok'), True) + else: + raise errors.PluginError('Must agree to IP logging to proceed') + + def _perform_achall_with_script(self, achall): + env = dict(CERTBOT_DOMAIN=achall.domain, + CERTBOT_VALIDATION=achall.validation(achall.account_key)) + if isinstance(achall.chall, challenges.HTTP01): + env['CERTBOT_TOKEN'] = achall.chall.encode('token') + else: + os.environ.pop('CERTBOT_TOKEN', None) + os.environ.update(env) + _, out = self._execute_hook('auth-hook') + env['CERTBOT_AUTH_OUTPUT'] = out.strip() + self.env[achall] = env + + def _perform_achall_manually(self, achall): + validation = achall.validation(achall.account_key) + if isinstance(achall.chall, challenges.HTTP01): + msg = self._HTTP_INSTRUCTIONS.format( + achall=achall, encoded_token=achall.chall.encode('token'), + port=self.config.http01_port, + uri=achall.chall.uri(achall.domain), validation=validation) + else: + assert isinstance(achall.chall, challenges.DNS01) + msg = self._DNS_INSTRUCTIONS.format( + domain=achall.validation_domain_name(achall.domain), + validation=validation) + if isinstance(achall.chall, challenges.DNS01): + if self.subsequent_dns_challenge: + # 2nd or later dns-01 challenge + msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS + self.subsequent_dns_challenge = True + elif self.subsequent_any_challenge: + # 2nd or later challenge of another type + msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS + display = zope.component.getUtility(interfaces.IDisplay) + display.notification(msg, wrap=False, force_interactive=True) + self.subsequent_any_challenge = True + + def cleanup(self, achalls): # pylint: disable=missing-docstring + if self.conf('cleanup-hook'): + for achall in achalls: + env = self.env.pop(achall) + if 'CERTBOT_TOKEN' not in env: + os.environ.pop('CERTBOT_TOKEN', None) + os.environ.update(env) + self._execute_hook('cleanup-hook') + self.reverter.recovery_routine() + + def _execute_hook(self, hook_name): + return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) diff --git a/certbot/certbot/_internal/plugins/null.py b/certbot/certbot/_internal/plugins/null.py new file mode 100644 index 000000000..6deb358f1 --- /dev/null +++ b/certbot/certbot/_internal/plugins/null.py @@ -0,0 +1,56 @@ +"""Null plugin.""" +import logging + +import zope.component +import zope.interface + +from certbot import interfaces +from certbot.plugins import common + + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class Installer(common.Plugin): + """Null installer.""" + + description = "Null Installer" + hidden = True + + # pylint: disable=missing-docstring,no-self-use + + def prepare(self): + pass # pragma: no cover + + def more_info(self): + return "Installer that doesn't do anything (for testing)." + + def get_all_names(self): + return [] + + def deploy_cert(self, domain, cert_path, key_path, + chain_path=None, fullchain_path=None): + pass # pragma: no cover + + def enhance(self, domain, enhancement, options=None): + pass # pragma: no cover + + def supported_enhancements(self): + return [] + + def save(self, title=None, temporary=False): + pass # pragma: no cover + + def rollback_checkpoints(self, rollback=1): + pass # pragma: no cover + + def recovery_routine(self): + pass # pragma: no cover + + def config_test(self): + pass # pragma: no cover + + def restart(self): + pass # pragma: no cover diff --git a/certbot/certbot/_internal/plugins/selection.py b/certbot/certbot/_internal/plugins/selection.py new file mode 100644 index 000000000..de1d27227 --- /dev/null +++ b/certbot/certbot/_internal/plugins/selection.py @@ -0,0 +1,338 @@ +"""Decide which plugins to use for authentication & installation""" +from __future__ import print_function + +import logging + +import six +import zope.component + +from certbot import errors +from certbot import interfaces +from certbot.compat import os +from certbot.display import util as display_util + +logger = logging.getLogger(__name__) +z_util = zope.component.getUtility + +def pick_configurator( + config, default, plugins, + question="How would you like to authenticate and install " + "certificates?"): + """Pick configurator plugin.""" + return pick_plugin( + config, default, plugins, question, + (interfaces.IAuthenticator, interfaces.IInstaller)) + + +def pick_installer(config, default, plugins, + question="How would you like to install certificates?"): + """Pick installer plugin.""" + return pick_plugin( + config, default, plugins, question, (interfaces.IInstaller,)) + + +def pick_authenticator( + config, default, plugins, question="How would you " + "like to authenticate with the ACME CA?"): + """Pick authentication plugin.""" + return pick_plugin( + config, default, plugins, question, (interfaces.IAuthenticator,)) + +def get_unprepared_installer(config, plugins): + """ + Get an unprepared interfaces.IInstaller object. + + :param certbot.interfaces.IConfig config: Configuration + :param certbot._internal.plugins.disco.PluginsRegistry plugins: + All plugins registered as entry points. + + :returns: Unprepared installer plugin or None + :rtype: IPlugin or None + """ + + _, req_inst = cli_plugin_requests(config) + if not req_inst: + return None + installers = plugins.filter(lambda p_ep: p_ep.name == req_inst) + installers.init(config) + installers = installers.verify((interfaces.IInstaller,)) + if len(installers) > 1: + raise errors.PluginSelectionError( + "Found multiple installers with the name %s, Certbot is unable to " + "determine which one to use. Skipping." % req_inst) + if installers: + inst = list(installers.values())[0] + logger.debug("Selecting plugin: %s", inst) + return inst.init(config) + else: + raise errors.PluginSelectionError( + "Could not select or initialize the requested installer %s." % req_inst) + +def pick_plugin(config, default, plugins, question, ifaces): + """Pick plugin. + + :param certbot.interfaces.IConfig: Configuration + :param str default: Plugin name supplied by user or ``None``. + :param certbot._internal.plugins.disco.PluginsRegistry plugins: + All plugins registered as entry points. + :param str question: Question to be presented to the user in case + multiple candidates are found. + :param list ifaces: Interfaces that plugins must provide. + + :returns: Initialized plugin. + :rtype: IPlugin + + """ + if default is not None: + # throw more UX-friendly error if default not in plugins + filtered = plugins.filter(lambda p_ep: p_ep.name == default) + else: + if config.noninteractive_mode: + # it's really bad to auto-select the single available plugin in + # non-interactive mode, because an update could later add a second + # available plugin + raise errors.MissingCommandlineFlag( + "Missing command line flags. For non-interactive execution, " + "you will need to specify a plugin on the command line. Run " + "with '--help plugins' to see a list of options, and see " + "https://eff.org/letsencrypt-plugins for more detail on what " + "the plugins do and how to use them.") + + filtered = plugins.visible().ifaces(ifaces) + + filtered.init(config) + verified = filtered.verify(ifaces) + verified.prepare() + prepared = verified.available() + + if len(prepared) > 1: + logger.debug("Multiple candidate plugins: %s", prepared) + plugin_ep = choose_plugin(list(six.itervalues(prepared)), question) + if plugin_ep is None: + return None + return plugin_ep.init() + elif len(prepared) == 1: + plugin_ep = list(prepared.values())[0] + logger.debug("Single candidate plugin: %s", plugin_ep) + if plugin_ep.misconfigured: + return None + return plugin_ep.init() + else: + logger.debug("No candidate plugin") + return None + + +def choose_plugin(prepared, question): + """Allow the user to choose their plugin. + + :param list prepared: List of `~.PluginEntryPoint`. + :param str question: Question to be presented to the user. + + :returns: Plugin entry point chosen by the user. + :rtype: `~.PluginEntryPoint` + + """ + opts = [plugin_ep.description_with_name + + (" [Misconfigured]" if plugin_ep.misconfigured else "") + for plugin_ep in prepared] + names = set(plugin_ep.name for plugin_ep in prepared) + + while True: + disp = z_util(interfaces.IDisplay) + if "CERTBOT_AUTO" in os.environ and names == set(("apache", "nginx")): + # The possibility of being offered exactly apache and nginx here + # is new interactivity brought by https://github.com/certbot/certbot/issues/4079, + # so set apache as a default for those kinds of non-interactive use + # (the user will get a warning to set --non-interactive or --force-interactive) + apache_idx = [n for n, p in enumerate(prepared) if p.name == "apache"][0] + code, index = disp.menu(question, opts, default=apache_idx) + else: + code, index = disp.menu(question, opts, force_interactive=True) + + if code == display_util.OK: + plugin_ep = prepared[index] + if plugin_ep.misconfigured: + z_util(interfaces.IDisplay).notification( + "The selected plugin encountered an error while parsing " + "your server configuration and cannot be used. The error " + "was:\n\n{0}".format(plugin_ep.prepare()), pause=False) + else: + return plugin_ep + else: + return None + +noninstaller_plugins = ["webroot", "manual", "standalone", "dns-cloudflare", "dns-cloudxns", + "dns-digitalocean", "dns-dnsimple", "dns-dnsmadeeasy", "dns-gehirn", + "dns-google", "dns-linode", "dns-luadns", "dns-nsone", "dns-ovh", + "dns-rfc2136", "dns-route53", "dns-sakuracloud"] + +def record_chosen_plugins(config, plugins, auth, inst): + "Update the config entries to reflect the plugins we actually selected." + config.authenticator = plugins.find_init(auth).name if auth else None + config.installer = plugins.find_init(inst).name if inst else None + logger.info("Plugins selected: Authenticator %s, Installer %s", + config.authenticator, config.installer) + + +def choose_configurator_plugins(config, plugins, verb): + """ + Figure out which configurator we're going to use, modifies + config.authenticator and config.installer strings to reflect that choice if + necessary. + + :raises errors.PluginSelectionError if there was a problem + + :returns: (an `IAuthenticator` or None, an `IInstaller` or None) + :rtype: tuple + """ + + req_auth, req_inst = cli_plugin_requests(config) + installer_question = None + + if verb == "enhance": + installer_question = ("Which installer would you like to use to " + "configure the selected enhancements?") + + # Which plugins do we need? + if verb == "run": + need_inst = need_auth = True + from certbot._internal.cli import cli_command + if req_auth in noninstaller_plugins and not req_inst: + msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' + '{1} {2} certonly --{0}{1}{1}' + '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins' + '{1} and "--help plugins" for more information.)'.format( + req_auth, os.linesep, cli_command)) + + raise errors.MissingCommandlineFlag(msg) + else: + need_inst = need_auth = False + if verb == "certonly": + need_auth = True + if verb == "install" or verb == "enhance": + need_inst = True + if config.authenticator: + logger.warning("Specifying an authenticator doesn't make sense when " + "running Certbot with verb \"%s\"", verb) + # Try to meet the user's request and/or ask them to pick plugins + authenticator = installer = None + if verb == "run" and req_auth == req_inst: + # Unless the user has explicitly asked for different auth/install, + # only consider offering a single choice + authenticator = installer = pick_configurator(config, req_inst, plugins) + else: + if need_inst or req_inst: + installer = pick_installer(config, req_inst, plugins, installer_question) + if need_auth: + authenticator = pick_authenticator(config, req_auth, plugins) + logger.debug("Selected authenticator %s and installer %s", authenticator, installer) + + # Report on any failures + if need_inst and not installer: + diagnose_configurator_problem("installer", req_inst, plugins) + if need_auth and not authenticator: + diagnose_configurator_problem("authenticator", req_auth, plugins) + + record_chosen_plugins(config, plugins, authenticator, installer) + return installer, authenticator + + +def set_configurator(previously, now): + """ + Setting configurators multiple ways is okay, as long as they all agree + :param str previously: previously identified request for the installer/authenticator + :param str requested: the request currently being processed + """ + if not now: + # we're not actually setting anything + return previously + if previously: + if previously != now: + msg = "Too many flags setting configurators/installers/authenticators {0} -> {1}" + raise errors.PluginSelectionError(msg.format(repr(previously), repr(now))) + return now + + +def cli_plugin_requests(config): + """ + Figure out which plugins the user requested with CLI and config options + + :returns: (requested authenticator string or None, requested installer string or None) + :rtype: tuple + """ + req_inst = req_auth = config.configurator + req_inst = set_configurator(req_inst, config.installer) + req_auth = set_configurator(req_auth, config.authenticator) + + if config.nginx: + req_inst = set_configurator(req_inst, "nginx") + req_auth = set_configurator(req_auth, "nginx") + if config.apache: + req_inst = set_configurator(req_inst, "apache") + req_auth = set_configurator(req_auth, "apache") + if config.standalone: + req_auth = set_configurator(req_auth, "standalone") + if config.webroot: + req_auth = set_configurator(req_auth, "webroot") + if config.manual: + req_auth = set_configurator(req_auth, "manual") + if config.dns_cloudflare: + req_auth = set_configurator(req_auth, "dns-cloudflare") + if config.dns_cloudxns: + req_auth = set_configurator(req_auth, "dns-cloudxns") + if config.dns_digitalocean: + req_auth = set_configurator(req_auth, "dns-digitalocean") + if config.dns_dnsimple: + req_auth = set_configurator(req_auth, "dns-dnsimple") + if config.dns_dnsmadeeasy: + req_auth = set_configurator(req_auth, "dns-dnsmadeeasy") + if config.dns_gehirn: + req_auth = set_configurator(req_auth, "dns-gehirn") + if config.dns_google: + req_auth = set_configurator(req_auth, "dns-google") + if config.dns_linode: + req_auth = set_configurator(req_auth, "dns-linode") + if config.dns_luadns: + req_auth = set_configurator(req_auth, "dns-luadns") + if config.dns_nsone: + req_auth = set_configurator(req_auth, "dns-nsone") + if config.dns_ovh: + req_auth = set_configurator(req_auth, "dns-ovh") + if config.dns_rfc2136: + req_auth = set_configurator(req_auth, "dns-rfc2136") + if config.dns_route53: + req_auth = set_configurator(req_auth, "dns-route53") + if config.dns_sakuracloud: + req_auth = set_configurator(req_auth, "dns-sakuracloud") + logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst) + return req_auth, req_inst + + +def diagnose_configurator_problem(cfg_type, requested, plugins): + """ + Raise the most helpful error message about a plugin being unavailable + + :param str cfg_type: either "installer" or "authenticator" + :param str requested: the plugin that was requested + :param .PluginsRegistry plugins: available plugins + + :raises error.PluginSelectionError: if there was a problem + """ + + if requested: + if requested not in plugins: + msg = "The requested {0} plugin does not appear to be installed".format(requested) + else: + msg = ("The {0} plugin is not working; there may be problems with " + "your existing configuration.\nThe error was: {1!r}" + .format(requested, plugins[requested].problem)) + elif cfg_type == "installer": + from certbot._internal.cli import cli_command + msg = ('Certbot doesn\'t know how to automatically configure the web ' + 'server on this system. However, it can still get a certificate for ' + 'you. Please run "{0} certonly" to do so. You\'ll need to ' + 'manually configure your web server to use the resulting ' + 'certificate.').format(cli_command) + else: + msg = "{0} could not be determined or is not installed".format(cfg_type) + raise errors.PluginSelectionError(msg) diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py new file mode 100644 index 000000000..9723116c1 --- /dev/null +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -0,0 +1,210 @@ +"""Standalone Authenticator.""" +import collections +import logging +import socket +# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +from socket import errno as socket_errors # type: ignore + +import OpenSSL # pylint: disable=unused-import +import six +import zope.interface + +from acme import challenges +from acme import standalone as acme_standalone +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING + +from certbot import achallenges # pylint: disable=unused-import +from certbot import errors +from certbot import interfaces + +from certbot.plugins import common + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + ServedType = DefaultDict[ + acme_standalone.BaseDualNetworkedServers, + Set[achallenges.KeyAuthorizationAnnotatedChallenge] + ] + +class ServerManager(object): + """Standalone servers manager. + + Manager for `ACMEServer` and `ACMETLSServer` instances. + + `certs` and `http_01_resources` correspond to + `acme.crypto_util.SSLSocket.certs` and + `acme.crypto_util.SSLSocket.http_01_resources` respectively. All + created servers share the same certificates and resources, so if + you're running both TLS and non-TLS instances, HTTP01 handlers + will serve the same URLs! + + """ + def __init__(self, certs, http_01_resources): + self._instances = {} # type: Dict[int, acme_standalone.BaseDualNetworkedServers] + self.certs = certs + self.http_01_resources = http_01_resources + + def run(self, port, challenge_type, listenaddr=""): + """Run ACME server on specified ``port``. + + This method is idempotent, i.e. all calls with the same pair of + ``(port, challenge_type)`` will reuse the same server. + + :param int port: Port to run the server on. + :param challenge_type: Subclass of `acme.challenges.Challenge`, + currently only `acme.challenge.HTTP01`. + :param str listenaddr: (optional) The address to listen on. Defaults to all addrs. + + :returns: DualNetworkedServers instance. + :rtype: ACMEServerMixin + + """ + assert challenge_type == challenges.HTTP01 + if port in self._instances: + return self._instances[port] + + address = (listenaddr, port) + try: + servers = acme_standalone.HTTP01DualNetworkedServers( + address, self.http_01_resources) + except socket.error as error: + raise errors.StandaloneBindError(error, port) + + servers.serve_forever() + + # if port == 0, then random free port on OS is taken + # pylint: disable=no-member + # both servers, if they exist, have the same port + real_port = servers.getsocknames()[0][1] + self._instances[real_port] = servers + return servers + + def stop(self, port): + """Stop ACME server running on the specified ``port``. + + :param int port: + + """ + instance = self._instances[port] + for sockname in instance.getsocknames(): + logger.debug("Stopping server at %s:%d...", + *sockname[:2]) + instance.shutdown_and_server_close() + del self._instances[port] + + def running(self): + """Return all running instances. + + Once the server is stopped using `stop`, it will not be + returned. + + :returns: Mapping from ``port`` to ``servers``. + :rtype: tuple + + """ + return self._instances.copy() + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Standalone Authenticator. + + This authenticator creates its own ephemeral TCP listener on the + necessary port in order to respond to incoming http-01 + challenges from the certificate authority. Therefore, it does not + rely on any existing server program. + """ + + description = "Spin up a temporary webserver" + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + + self.served = collections.defaultdict(set) # type: ServedType + + # Stuff below is shared across threads (i.e. servers read + # values, main thread writes). Due to the nature of CPython's + # GIL, the operations are safe, c.f. + # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe + self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] + self.http_01_resources = set() \ + # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] + + self.servers = ServerManager(self.certs, self.http_01_resources) + + @classmethod + def add_parser_arguments(cls, add): + pass # No additional argument for the standalone plugin parser + + def more_info(self): # pylint: disable=missing-docstring + return("This authenticator creates its own ephemeral TCP listener " + "on the necessary port in order to respond to incoming " + "http-01 challenges from the certificate authority. Therefore, " + "it does not rely on any existing server program.") + + def prepare(self): # pylint: disable=missing-docstring + pass + + def get_chall_pref(self, domain): + # pylint: disable=unused-argument,missing-docstring + return [challenges.HTTP01] + + def perform(self, achalls): # pylint: disable=missing-docstring + return [self._try_perform_single(achall) for achall in achalls] + + def _try_perform_single(self, achall): + while True: + try: + return self._perform_single(achall) + except errors.StandaloneBindError as error: + _handle_perform_error(error) + + def _perform_single(self, achall): + servers, response = self._perform_http_01(achall) + self.served[servers].add(achall) + return response + + def _perform_http_01(self, achall): + port = self.config.http01_port + addr = self.config.http01_address + servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr) + response, validation = achall.response_and_validation() + resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource( + chall=achall.chall, response=response, validation=validation) + self.http_01_resources.add(resource) + return servers, response + + def cleanup(self, achalls): # pylint: disable=missing-docstring + # reduce self.served and close servers if no challenges are served + for unused_servers, server_achalls in self.served.items(): + for achall in achalls: + if achall in server_achalls: + server_achalls.remove(achall) + for port, servers in six.iteritems(self.servers.running()): + if not self.served[servers]: + self.servers.stop(port) + + +def _handle_perform_error(error): + if error.socket_error.errno == socket_errors.EACCES: + raise errors.PluginError( + "Could not bind TCP port {0} because you don't have " + "the appropriate permissions (for example, you " + "aren't running this program as " + "root).".format(error.port)) + elif error.socket_error.errno == socket_errors.EADDRINUSE: + display = zope.component.getUtility(interfaces.IDisplay) + msg = ( + "Could not bind TCP port {0} because it is already in " + "use by another process on this system (such as a web " + "server). Please stop the program in question and " + "then try again.".format(error.port)) + should_retry = display.yesno(msg, "Retry", + "Cancel", default=False) + if not should_retry: + raise errors.PluginError(msg) + else: + raise error diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py new file mode 100644 index 000000000..b87b3092a --- /dev/null +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -0,0 +1,288 @@ +"""Webroot plugin.""" +import argparse +import collections +import errno +import json +import logging + +import six +import zope.component +import zope.interface + +from acme import challenges # pylint: disable=unused-import +# pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict, Set, DefaultDict, List +# pylint: enable=unused-import, no-name-in-module + +from certbot import achallenges # pylint: disable=unused-import +from certbot._internal import cli +from certbot import errors +from certbot import interfaces +from certbot.compat import os +from certbot.compat import filesystem +from certbot.display import ops +from certbot.display import util as display_util +from certbot.plugins import common +from certbot.plugins import util +from certbot.util import safe_open + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Webroot Authenticator.""" + + description = "Place files in webroot directory" + + MORE_INFO = """\ +Authenticator plugin that performs http-01 challenge by saving +necessary validation resources to appropriate paths on the file +system. It expects that there is some other HTTP server configured +to serve all files under specified web root ({0}).""" + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return self.MORE_INFO.format(self.conf("path")) + + @classmethod + def add_parser_arguments(cls, add): + add("path", "-w", default=[], action=_WebrootPathAction, + help="public_html / webroot path. This can be specified multiple " + "times to handle different domains; each domain will have " + "the webroot path that preceded it. For instance: `-w " + "/var/www/example -d example.com -d www.example.com -w " + "/var/www/thing -d thing.net -d m.thing.net` (default: Ask)") + add("map", default={}, action=_WebrootMapAction, + help="JSON dictionary mapping domains to webroot paths; this " + "implies -d for each entry. You may need to escape this from " + "your shell. E.g.: --webroot-map " + '\'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}\' ' + "This option is merged with, but takes precedence over, -w / " + "-d entries. At present, if you put webroot-map in a config " + "file, it needs to be on a single line, like: webroot-map = " + '{"example.com":"/var/www"}.') + + def get_chall_pref(self, domain): # pragma: no cover + # pylint: disable=missing-docstring,no-self-use,unused-argument + return [challenges.HTTP01] + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.full_roots = {} # type: Dict[str, str] + self.performed = collections.defaultdict(set) \ + # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]] + # stack of dirs successfully created by this authenticator + self._created_dirs = [] # type: List[str] + + def prepare(self): # pylint: disable=missing-docstring + pass + + def perform(self, achalls): # pylint: disable=missing-docstring + self._set_webroots(achalls) + + self._create_challenge_dirs() + + return [self._perform_single(achall) for achall in achalls] + + def _set_webroots(self, achalls): + if self.conf("path"): + webroot_path = self.conf("path")[-1] + logger.info("Using the webroot path %s for all unmatched domains.", + webroot_path) + for achall in achalls: + self.conf("map").setdefault(achall.domain, webroot_path) + else: + known_webroots = list(set(six.itervalues(self.conf("map")))) + for achall in achalls: + if achall.domain not in self.conf("map"): + new_webroot = self._prompt_for_webroot(achall.domain, + known_webroots) + # Put the most recently input + # webroot first for easy selection + try: + known_webroots.remove(new_webroot) + except ValueError: + pass + known_webroots.insert(0, new_webroot) + self.conf("map")[achall.domain] = new_webroot + + def _prompt_for_webroot(self, domain, known_webroots): + webroot = None + + while webroot is None: + if known_webroots: + # Only show the menu if we have options for it + webroot = self._prompt_with_webroot_list(domain, known_webroots) + if webroot is None: + webroot = self._prompt_for_new_webroot(domain) + else: + # Allow prompt to raise PluginError instead of looping forever + webroot = self._prompt_for_new_webroot(domain, True) + + return webroot + + def _prompt_with_webroot_list(self, domain, known_webroots): + display = zope.component.getUtility(interfaces.IDisplay) + path_flag = "--" + self.option_name("path") + + while True: + code, index = display.menu( + "Select the webroot for {0}:".format(domain), + ["Enter a new webroot"] + known_webroots, + cli_flag=path_flag, force_interactive=True) + if code == display_util.CANCEL: + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + else: # code == display_util.OK + return None if index == 0 else known_webroots[index - 1] + + def _prompt_for_new_webroot(self, domain, allowraise=False): + code, webroot = ops.validated_directory( + _validate_webroot, + "Input the webroot for {0}:".format(domain), + force_interactive=True) + if code == display_util.CANCEL: + if not allowraise: + return None + else: + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + else: # code == display_util.OK + return _validate_webroot(webroot) + + def _create_challenge_dirs(self): + path_map = self.conf("map") + if not path_map: + raise errors.PluginError( + "Missing parts of webroot configuration; please set either " + "--webroot-path and --domains, or --webroot-map. Run with " + " --help webroot for examples.") + for name, path in path_map.items(): + self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) + logger.debug("Creating root challenges validation dir at %s", + self.full_roots[name]) + + # Change the permissions to be writable (GH #1389) + # Umask is used instead of chmod to ensure the client can also + # run as non-root (GH #1795) + old_umask = os.umask(0o022) + try: + # We ignore the last prefix in the next iteration, + # as it does not correspond to a folder path ('/' or 'C:') + for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len): + try: + # Set owner as parent directory if possible, apply mode for Linux/Windows. + # For Linux, this is coupled with the "umask" call above because + # os.mkdir's "mode" parameter may not always work: + # https://docs.python.org/3/library/os.html#os.mkdir + filesystem.mkdir(prefix, 0o755) + self._created_dirs.append(prefix) + try: + filesystem.copy_ownership_and_apply_mode( + path, prefix, 0o755, copy_user=True, copy_group=True) + except (OSError, AttributeError) as exception: + logger.info("Unable to change owner and uid of webroot directory") + logger.debug("Error was: %s", exception) + except OSError as exception: + if exception.errno not in (errno.EEXIST, errno.EISDIR): + raise errors.PluginError( + "Couldn't create root for {0} http-01 " + "challenge responses: {1}".format(name, exception)) + finally: + os.umask(old_umask) + + def _get_validation_path(self, root_path, achall): + return os.path.join(root_path, achall.chall.encode("token")) + + def _perform_single(self, achall): + response, validation = achall.response_and_validation() + + root_path = self.full_roots[achall.domain] + validation_path = self._get_validation_path(root_path, achall) + logger.debug("Attempting to save validation to %s", validation_path) + + # Change permissions to be world-readable, owner-writable (GH #1795) + old_umask = os.umask(0o022) + + try: + with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: + validation_file.write(validation.encode()) + finally: + os.umask(old_umask) + + self.performed[root_path].add(achall) + return response + + def cleanup(self, achalls): # pylint: disable=missing-docstring + for achall in achalls: + root_path = self.full_roots.get(achall.domain, None) + if root_path is not None: + validation_path = self._get_validation_path(root_path, achall) + logger.debug("Removing %s", validation_path) + os.remove(validation_path) + self.performed[root_path].remove(achall) + + not_removed = [] # type: List[str] + while self._created_dirs: + path = self._created_dirs.pop() + try: + os.rmdir(path) + except OSError as exc: + not_removed.insert(0, path) + logger.info("Challenge directory %s was not empty, didn't remove", path) + logger.debug("Error was: %s", exc) + self._created_dirs = not_removed + logger.debug("All challenges cleaned up") + + +class _WebrootMapAction(argparse.Action): + """Action class for parsing webroot_map.""" + + def __call__(self, parser, namespace, webroot_map, option_string=None): + for domains, webroot_path in six.iteritems(json.loads(webroot_map)): + webroot_path = _validate_webroot(webroot_path) + namespace.webroot_map.update( + (d, webroot_path) for d in cli.add_domains(namespace, domains)) + + +class _WebrootPathAction(argparse.Action): + """Action class for parsing webroot_path.""" + + def __init__(self, *args, **kwargs): + super(_WebrootPathAction, self).__init__(*args, **kwargs) + self._domain_before_webroot = False + + def __call__(self, parser, namespace, webroot_path, option_string=None): + if self._domain_before_webroot: + raise errors.PluginError( + "If you specify multiple webroot paths, " + "one of them must precede all domain flags") + + if namespace.webroot_path: + # Apply previous webroot to all matched + # domains before setting the new webroot path + prev_webroot = namespace.webroot_path[-1] + for domain in namespace.domains: + namespace.webroot_map.setdefault(domain, prev_webroot) + elif namespace.domains: + self._domain_before_webroot = True + + namespace.webroot_path.append(_validate_webroot(webroot_path)) + + +def _validate_webroot(webroot_path): + """Validates and returns the absolute path of webroot_path. + + :param str webroot_path: path to the webroot directory + + :returns: absolute path of webroot_path + :rtype: str + + """ + if not os.path.isdir(webroot_path): + raise errors.PluginError(webroot_path + " does not exist or is not a directory") + + return os.path.abspath(webroot_path) diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py new file mode 100644 index 000000000..f96cd004f --- /dev/null +++ b/certbot/certbot/_internal/renewal.py @@ -0,0 +1,476 @@ +"""Functionality for autorenewal and associated juggling of configurations""" +from __future__ import print_function + +import copy +import itertools +import logging +import random +import sys +import time +import traceback + +import OpenSSL +import six +import zope.component + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot._internal import cli +from certbot import crypto_util +from certbot import errors +from certbot._internal import hooks +from certbot import interfaces +from certbot._internal import storage +from certbot._internal import updater +from certbot import util +from certbot.compat import os +from certbot._internal.plugins import disco as plugins_disco + +logger = logging.getLogger(__name__) + +# These are the items which get pulled out of a renewal configuration +# file's renewalparams and actually used in the client configuration +# during the renewal process. We have to record their types here because +# the renewal configuration process loses this information. +STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent", + "server", "account", "authenticator", "installer", + "renew_hook", "pre_hook", "post_hook", "http01_address"] +INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"] +BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key", + "autorenew"] + +CONFIG_ITEMS = set(itertools.chain( + BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',))) + + +def _reconstitute(config, full_path): + """Try to instantiate a RenewableCert, updating config with relevant items. + + This is specifically for use in renewal and enforces several checks + and policies to ensure that we can try to proceed with the renewal + request. The config argument is modified by including relevant options + read from the renewal configuration file. + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param str full_path: Absolute path to the configuration file that + defines this lineage + + :returns: the RenewableCert object or None if a fatal error occurred + :rtype: `storage.RenewableCert` or NoneType + + """ + try: + renewal_candidate = storage.RenewableCert(full_path, config) + except (errors.CertStorageError, IOError): + logger.warning("", exc_info=True) + logger.warning("Renewal configuration file %s is broken. Skipping.", full_path) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + if "renewalparams" not in renewal_candidate.configuration: + logger.warning("Renewal configuration file %s lacks " + "renewalparams. Skipping.", full_path) + return None + renewalparams = renewal_candidate.configuration["renewalparams"] + if "authenticator" not in renewalparams: + logger.warning("Renewal configuration file %s does not specify " + "an authenticator. Skipping.", full_path) + return None + # Now restore specific values along with their data types, if + # those elements are present. + try: + restore_required_config_elements(config, renewalparams) + _restore_plugin_configs(config, renewalparams) + except (ValueError, errors.Error) as error: + logger.warning( + "An error occurred while parsing %s. The error was %s. " + "Skipping the file.", full_path, str(error)) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + return None + + try: + config.domains = [util.enforce_domain_sanity(d) + for d in renewal_candidate.names()] + except errors.ConfigurationError as error: + logger.warning("Renewal configuration file %s references a cert " + "that contains an invalid domain name. The problem " + "was: %s. Skipping.", full_path, error) + return None + + return renewal_candidate + + +def _restore_webroot_config(config, renewalparams): + """ + webroot_map is, uniquely, a dict, and the general-purpose configuration + restoring logic is not able to correctly parse it from the serialized + form. + """ + if "webroot_map" in renewalparams and not cli.set_by_cli("webroot_map"): + config.webroot_map = renewalparams["webroot_map"] + # To understand why webroot_path and webroot_map processing are not mutually exclusive, + # see https://github.com/certbot/certbot/pull/7095 + if "webroot_path" in renewalparams and not cli.set_by_cli("webroot_path"): + wp = renewalparams["webroot_path"] + if isinstance(wp, six.string_types): # prior to 0.1.0, webroot_path was a string + wp = [wp] + config.webroot_path = wp + + +def _restore_plugin_configs(config, renewalparams): + """Sets plugin specific values in config from renewalparams + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param configobj.Section renewalparams: Parameters from the renewal + configuration file that defines this lineage + + """ + # Now use parser to get plugin-prefixed items with correct types + # XXX: the current approach of extracting only prefixed items + # related to the actually-used installer and authenticator + # works as long as plugins don't need to read plugin-specific + # variables set by someone else (e.g., assuming Apache + # configurator doesn't need to read webroot_ variables). + # Note: if a parameter that used to be defined in the parser is no + # longer defined, stored copies of that parameter will be + # deserialized as strings by this logic even if they were + # originally meant to be some other type. + plugin_prefixes = [] # type: List[str] + if renewalparams["authenticator"] == "webroot": + _restore_webroot_config(config, renewalparams) + else: + plugin_prefixes.append(renewalparams["authenticator"]) + + if renewalparams.get("installer") is not None: + plugin_prefixes.append(renewalparams["installer"]) + + for plugin_prefix in set(plugin_prefixes): + plugin_prefix = plugin_prefix.replace('-', '_') + for config_item, config_value in six.iteritems(renewalparams): + if config_item.startswith(plugin_prefix + "_") and not cli.set_by_cli(config_item): + # Values None, True, and False need to be treated specially, + # As their types aren't handled correctly by configobj + if config_value in ("None", "True", "False"): + # bool("False") == True + # pylint: disable=eval-used + setattr(config, config_item, eval(config_value)) + else: + cast = cli.argparse_type(config_item) + setattr(config, config_item, cast(config_value)) + + +def restore_required_config_elements(config, renewalparams): + """Sets non-plugin specific values in config from renewalparams + + :param configuration.NamespaceConfig config: configuration for the + current lineage + :param configobj.Section renewalparams: parameters from the renewal + configuration file that defines this lineage + + """ + + required_items = itertools.chain( + (("pref_challs", _restore_pref_challs),), + six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)), + six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)), + six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str))) + for item_name, restore_func in required_items: + if item_name in renewalparams and not cli.set_by_cli(item_name): + value = restore_func(item_name, renewalparams[item_name]) + setattr(config, item_name, value) + + +def _restore_pref_challs(unused_name, value): + """Restores preferred challenges from a renewal config file. + + If value is a `str`, it should be a single challenge type. + + :param str unused_name: option name + :param value: option value + :type value: `list` of `str` or `str` + + :returns: converted option value to be stored in the runtime config + :rtype: `list` of `str` + + :raises errors.Error: if value can't be converted to an bool + + """ + # If pref_challs has only one element, configobj saves the value + # with a trailing comma so it's parsed as a list. If this comma is + # removed by the user, the value is parsed as a str. + value = [value] if isinstance(value, six.string_types) else value + return cli.parse_preferred_challenges(value) + + +def _restore_bool(name, value): + """Restores an boolean key-value pair from a renewal config file. + + :param str name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: bool + + :raises errors.Error: if value can't be converted to an bool + + """ + lowercase_value = value.lower() + if lowercase_value not in ("true", "false"): + raise errors.Error( + "Expected True or False for {0} but found {1}".format(name, value)) + return lowercase_value == "true" + + +def _restore_int(name, value): + """Restores an integer key-value pair from a renewal config file. + + :param str name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: int + + :raises errors.Error: if value can't be converted to an int + + """ + if name == "http01_port" and value == "None": + logger.info("updating legacy http01_port value") + return cli.flag_default("http01_port") + + try: + return int(value) + except ValueError: + raise errors.Error("Expected a numeric value for {0}".format(name)) + + +def _restore_str(unused_name, value): + """Restores an string key-value pair from a renewal config file. + + :param str unused_name: option name + :param str value: option value + + :returns: converted option value to be stored in the runtime config + :rtype: str or None + + """ + return None if value == "None" else value + + +def should_renew(config, lineage): + "Return true if any of the circumstances for automatic renewal apply." + if config.renew_by_default: + logger.debug("Auto-renewal forced with --force-renewal...") + return True + if lineage.should_autorenew(): + logger.info("Cert is due for renewal, auto-renewing...") + return True + if config.dry_run: + logger.info("Cert not due for renewal, but simulating renewal for dry run") + return True + logger.info("Cert not yet due for renewal") + return False + + +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, 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() + + if util.is_staging(config.server): + if not util.is_staging(original_server) or now_valid: + if not config.break_my_certs: + names = ", ".join(lineage.names()) + raise errors.Error( + "You've asked to renew/replace a seemingly valid certificate with " + "a test certificate (domains: {0}). We will not do that " + "unless you use the --break-my-certs flag!".format(names)) + + +def renew_cert(config, domains, le_client, lineage): + "Renew a certificate lineage." + renewal_params = lineage.configuration["renewalparams"] + original_server = renewal_params.get("server", cli.flag_default("server")) + _avoid_invalidating_lineage(config, lineage, original_server) + if not domains: + domains = lineage.names() + # The private key is the existing lineage private key if reuse_key is set. + # Otherwise, generate a fresh private key by passing None. + new_key = os.path.normpath(lineage.privkey) if config.reuse_key else None + new_cert, new_chain, new_key, _ = le_client.obtain_certificate(domains, new_key) + if config.dry_run: + logger.debug("Dry run: skipping updating lineage at %s", + os.path.dirname(lineage.cert)) + else: + prior_version = lineage.latest_common_version() + # TODO: Check return value of save_successor + lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config) + lineage.update_all_links_to(lineage.latest_common_version()) + + hooks.renew_hook(config, domains, lineage.live_dir) + + +def report(msgs, category): + "Format a results report for a category of renewal outcomes" + lines = ("%s (%s)" % (m, category) for m in msgs) + return " " + "\n ".join(lines) + +def _renew_describe_results(config, renew_successes, renew_failures, + renew_skipped, parse_failures): + + out = [] # type: List[str] + notify = out.append + disp = zope.component.getUtility(interfaces.IDisplay) + + def notify_error(err): + """Notify and log errors.""" + notify(str(err)) + logger.error(err) + + if config.dry_run: + notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") + notify("** (The test certificates below have not been saved.)") + notify("") + if renew_skipped: + notify("The following certs are not due for renewal yet:") + notify(report(renew_skipped, "skipped")) + if not renew_successes and not renew_failures: + notify("No renewals were attempted.") + if (config.pre_hook is not None or + config.renew_hook is not None or config.post_hook is not None): + notify("No hooks were run.") + elif renew_successes and not renew_failures: + notify("Congratulations, all renewals succeeded. The following certs " + "have been renewed:") + notify(report(renew_successes, "success")) + elif renew_failures and not renew_successes: + notify_error("All renewal attempts failed. The following certs could " + "not be renewed:") + notify_error(report(renew_failures, "failure")) + elif renew_failures and renew_successes: + notify("The following certs were successfully renewed:") + notify(report(renew_successes, "success") + "\n") + notify_error("The following certs could not be renewed:") + notify_error(report(renew_failures, "failure")) + + if parse_failures: + notify("\nAdditionally, the following renewal configurations " + "were invalid: ") + notify(report(parse_failures, "parsefail")) + + if config.dry_run: + notify("** DRY RUN: simulating 'certbot renew' close to cert expiry") + notify("** (The test certificates above have not been saved.)") + + disp.notification("\n".join(out), wrap=False) + + +def handle_renewal_request(config): + """Examine each lineage; renew if due and report results""" + + # This is trivially False if config.domains is empty + if any(domain not in config.webroot_map for domain in config.domains): + # If more plugins start using cli.add_domains, + # we may want to only log a warning here + raise errors.Error("Currently, the renew verb is capable of either " + "renewing all installed certificates that are due " + "to be renewed or renewing a single certificate specified " + "by its name. If you would like to renew specific " + "certificates by their domains, use the certonly command " + "instead. The renew verb may provide other options " + "for selecting certificates to renew in the future.") + + if config.certname: + conf_files = [storage.renewal_file_for_certname(config, config.certname)] + else: + conf_files = storage.renewal_conf_files(config) + + renew_successes = [] + 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) + lineage_config = copy.deepcopy(config) + lineagename = storage.lineagename_for_filename(renewal_file) + + # Note that this modifies config (to add back the configuration + # elements from within the renewal configuration file). + try: + renewal_candidate = _reconstitute(lineage_config, renewal_file) + except Exception as e: # pylint: disable=broad-except + logger.warning("Renewal configuration file %s (cert: %s) " + "produced an unexpected error: %s. Skipping.", + renewal_file, lineagename, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + parse_failures.append(renewal_file) + continue + + try: + if renewal_candidate is None: + parse_failures.append(renewal_file) + else: + # XXX: ensure that each call here replaces the previous one + zope.component.provideUtility(lineage_config) + renewal_candidate.ensure_deployed() + from certbot._internal 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.uniform(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 + # we already know it's time to renew based on should_renew + # and we have a lineage in renewal_candidate + main.renew_cert(lineage_config, plugins, renewal_candidate) + renew_successes.append(renewal_candidate.fullchain) + else: + expiry = crypto_util.notAfter(renewal_candidate.version( + "cert", renewal_candidate.latest_common_version())) + renew_skipped.append("%s expires on %s" % (renewal_candidate.fullchain, + expiry.strftime("%Y-%m-%d"))) + # Run updater interface methods + updater.run_generic_updaters(lineage_config, renewal_candidate, + plugins) + + except Exception as e: # pylint: disable=broad-except + # obtain_cert (presumably) encountered an unanticipated problem. + logger.warning("Attempting to renew cert (%s) from %s produced an " + "unexpected error: %s. Skipping.", lineagename, + renewal_file, e) + logger.debug("Traceback was:\n%s", traceback.format_exc()) + renew_failures.append(renewal_candidate.fullchain) + + # Describe all the results + _renew_describe_results(config, renew_successes, renew_failures, + renew_skipped, parse_failures) + + if renew_failures or parse_failures: + raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( + len(renew_failures), len(parse_failures))) + else: + logger.debug("no renewal failures") diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py new file mode 100644 index 000000000..e0063d8e5 --- /dev/null +++ b/certbot/certbot/_internal/reporter.py @@ -0,0 +1,100 @@ +"""Collects and displays information to the user.""" +from __future__ import print_function + +import collections +import logging +import sys +import textwrap + +from six.moves import queue # type: ignore # pylint: disable=import-error +import zope.interface + +from certbot import interfaces +from certbot import util + + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IReporter) +class Reporter(object): + """Collects and displays information to the user. + + :ivar `queue.PriorityQueue` messages: Messages to be displayed to + the user. + + """ + + HIGH_PRIORITY = 0 + """High priority constant. See `add_message`.""" + MEDIUM_PRIORITY = 1 + """Medium priority constant. See `add_message`.""" + LOW_PRIORITY = 2 + """Low priority constant. See `add_message`.""" + + _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') + + def __init__(self, config): + self.messages = queue.PriorityQueue() + self.config = config + + def add_message(self, msg, priority, on_crash=True): + """Adds msg to the list of messages to be printed. + + :param str msg: Message to be displayed to the user. + + :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`, + or `LOW_PRIORITY`. + + :param bool on_crash: Whether or not the message should be + printed if the program exits abnormally. + + """ + assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY + self.messages.put(self._msg_type(priority, msg, on_crash)) + logger.debug("Reporting to user: %s", msg) + + def print_messages(self): + """Prints messages to the user and clears the message queue. + + If there is an unhandled exception, only messages for which + ``on_crash`` is ``True`` are printed. + + """ + bold_on = False + if not self.messages.empty(): + no_exception = sys.exc_info()[0] is None + bold_on = sys.stdout.isatty() + if not self.config.quiet: + if bold_on: + print(util.ANSI_SGR_BOLD) + print('IMPORTANT NOTES:') + first_wrapper = textwrap.TextWrapper( + initial_indent=' - ', + subsequent_indent=(' ' * 3), + break_long_words=False, + break_on_hyphens=False) + next_wrapper = textwrap.TextWrapper( + initial_indent=first_wrapper.subsequent_indent, + subsequent_indent=first_wrapper.subsequent_indent, + break_long_words=False, + break_on_hyphens=False) + while not self.messages.empty(): + msg = self.messages.get() + if self.config.quiet: + # In --quiet mode, we only print high priority messages that + # are flagged for crash cases + if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash): + continue + if no_exception or msg.on_crash: + if bold_on and msg.priority > self.HIGH_PRIORITY: + if not self.config.quiet: + sys.stdout.write(util.ANSI_SGR_RESET) + bold_on = False + lines = msg.text.splitlines() + print(first_wrapper.fill(lines[0])) + if len(lines) > 1: + print("\n".join( + next_wrapper.fill(line) for line in lines[1:])) + if bold_on and not self.config.quiet: + sys.stdout.write(util.ANSI_SGR_RESET) diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py new file mode 100644 index 000000000..bb36f462a --- /dev/null +++ b/certbot/certbot/_internal/storage.py @@ -0,0 +1,1128 @@ +"""Renewable certificates storage.""" +import datetime +import glob +import logging +import re +import shutil +import stat + +import configobj +import parsedatetime +import pytz +import six + +import certbot +from certbot._internal import cli +from certbot._internal import constants +from certbot import crypto_util +from certbot._internal import error_handler +from certbot import errors +from certbot import util +from certbot.compat import os +from certbot.compat import filesystem +from certbot.plugins import common as plugins_common +from certbot._internal.plugins import disco as plugins_disco + +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): + """Build a list of all renewal configuration files. + + :param certbot.interfaces.IConfig config: Configuration object + + :returns: list of renewal configuration files + :rtype: `list` of `str` + + """ + 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""" + path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname)) + if not os.path.exists(path): + raise errors.CertStorageError("No certificate found with name {0} (expected " + "{1}).".format(certname, path)) + return path + + +def cert_path_for_cert_name(config, cert_name): + """ If `--cert-name` was specified, but you need a value for `--cert-path`. + + :param `configuration.NamespaceConfig` config: parsed command line arguments + :param str cert_name: cert name. + + """ + cert_name_implied_conf = renewal_file_for_certname(config, cert_name) + fullchain_path = configobj.ConfigObj(cert_name_implied_conf)["fullchain"] + with open(fullchain_path) as f: + cert_path = (fullchain_path, f.read()) + return cert_path + + +def config_with_defaults(config=None): + """Merge supplied config, if provided, on top of builtin defaults.""" + defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS) + defaults_copy.merge(config if config is not None else configobj.ConfigObj()) + return defaults_copy + + +def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()): + """Parse the time specified time interval, and add it to the base_time + + The interval can be in the English-language format understood by + parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or + a sequence of such intervals like '6 months 1 week' or '3 days 12 + hours'. If an integer is found with no associated unit, it is + interpreted by default as a number of days. + + :param datetime.datetime base_time: The time to be added with the interval. + :param str interval: The time interval to parse. + + :returns: The base_time plus the interpretation of the time interval. + :rtype: :class:`datetime.datetime`""" + + if interval.strip().isdigit(): + interval += " days" + + # try to use the same timezone, but fallback to UTC + tzinfo = base_time.tzinfo or pytz.UTC + + return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0] + + +def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data): + """Writes a renewal config file with the specified name and values. + + :param str o_filename: Absolute path to the previous version of config file + :param str n_filename: Absolute path to the new destination of config file + :param str archive_dir: Absolute path to the archive directory + :param dict target: Maps ALL_FOUR to their symlink paths + :param dict relevant_data: Renewal configuration options to save + + :returns: Configuration object for the new config file + :rtype: configobj.ConfigObj + + """ + config = configobj.ConfigObj(o_filename) + config["version"] = certbot.__version__ + config["archive_dir"] = archive_dir + for kind in ALL_FOUR: + config[kind] = target[kind] + + if "renewalparams" not in config: + config["renewalparams"] = {} + config.comments["renewalparams"] = ["", + "Options used in " + "the renewal process"] + + config["renewalparams"].update(relevant_data) + + for k in config["renewalparams"].keys(): + if k not in relevant_data: + del config["renewalparams"][k] + + if "renew_before_expiry" not in config: + default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] + config.initial_comment = ["renew_before_expiry = " + default_interval] + + # TODO: add human-readable comments explaining other available + # parameters + logger.debug("Writing new config %s.", n_filename) + + # Ensure that the file exists + open(n_filename, 'a').close() + + # Copy permissions from the old version of the file, if it exists. + if os.path.exists(o_filename): + current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode) + filesystem.chmod(n_filename, current_permissions) + + with open(n_filename, "wb") as f: + config.write(outfile=f) + return config + + +def rename_renewal_config(prev_name, new_name, cli_config): + """Renames cli_config.certname's config to cli_config.new_certname. + + :param .NamespaceConfig cli_config: parsed command line + arguments + """ + prev_filename = renewal_filename_for_lineagename(cli_config, prev_name) + new_filename = renewal_filename_for_lineagename(cli_config, new_name) + if os.path.exists(new_filename): + raise errors.ConfigurationError("The new certificate name " + "is already in use.") + try: + filesystem.replace(prev_filename, new_filename) + except OSError: + raise errors.ConfigurationError("Please specify a valid filename " + "for the new certificate name.") + + +def update_configuration(lineagename, archive_dir, target, cli_config): + """Modifies lineagename's config to contain the specified values. + + :param str lineagename: Name of the lineage being modified + :param str archive_dir: Absolute path to the archive directory + :param dict target: Maps ALL_FOUR to their symlink paths + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: Configuration object for the updated config file + :rtype: configobj.ConfigObj + + """ + config_filename = renewal_filename_for_lineagename(cli_config, lineagename) + temp_filename = config_filename + ".new" + + # If an existing tempfile exists, delete it + if os.path.exists(temp_filename): + os.unlink(temp_filename) + + # Save only the config items that are relevant to renewal + values = relevant_values(vars(cli_config.namespace)) + write_renewal_config(config_filename, temp_filename, archive_dir, target, values) + filesystem.replace(temp_filename, config_filename) + + return configobj.ConfigObj(config_filename) + + +def get_link_target(link): + """Get an absolute path to the target of link. + + :param str link: Path to a symbolic link + + :returns: Absolute path to the target of link + :rtype: str + + :raises .CertStorageError: If link does not exists. + + """ + try: + target = os.readlink(link) + except OSError: + raise errors.CertStorageError( + "Expected {0} to be a symlink".format(link)) + + if not os.path.isabs(target): + target = os.path.join(os.path.dirname(link), target) + return os.path.abspath(target) + +def _write_live_readme_to(readme_path, is_base_dir=False): + prefix = "" + if is_base_dir: + prefix = "[cert name]/" + with open(readme_path, "w") as f: + logger.debug("Writing README to %s.", readme_path) + f.write("This directory contains your keys and certificates.\n\n" + "`{prefix}privkey.pem` : the private key for your certificate.\n" + "`{prefix}fullchain.pem`: the certificate file used in most server software.\n" + "`{prefix}chain.pem` : used for OCSP stapling in Nginx >=1.3.7.\n" + "`{prefix}cert.pem` : will break many server configurations, and " + "should not be used\n" + " without reading further documentation (see link below).\n\n" + "WARNING: DO NOT MOVE OR RENAME THESE FILES!\n" + " Certbot expects these files to remain in this location in order\n" + " to function properly!\n\n" + "We recommend not moving these files. For more information, see the Certbot\n" + "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-" + "certificates.\n".format(prefix=prefix)) + + +def _relevant(namespaces, option): + """ + Is this option one that could be restored for future renewal purposes? + + :param namespaces: plugin namespaces for configuration options + :type namespaces: `list` of `str` + :param str option: the name of the option + + :rtype: bool + """ + from certbot._internal import renewal + + return (option in renewal.CONFIG_ITEMS or + any(option.startswith(namespace) for namespace in namespaces)) + + +def relevant_values(all_values): + """Return a new dict containing only items relevant for renewal. + + :param dict all_values: The original values. + + :returns: A new dictionary containing items that can be used in renewal. + :rtype dict: + + """ + plugins = plugins_disco.PluginsRegistry.find_all() + namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins] + + rv = dict( + (option, value) + for option, value in six.iteritems(all_values) + if _relevant(namespaces, option) and cli.option_was_set(option, value)) + # We always save the server value to help with forward compatibility + # and behavioral consistency when versions of Certbot with different + # server defaults are used. + rv["server"] = all_values["server"] + return rv + +def lineagename_for_filename(config_filename): + """Returns the lineagename for a configuration filename. + """ + if not config_filename.endswith(".conf"): + raise errors.CertStorageError( + "renewal config file name must end in .conf") + return os.path.basename(config_filename[:-len(".conf")]) + +def renewal_filename_for_lineagename(config, lineagename): + """Returns the lineagename for a configuration filename. + """ + return os.path.join(config.renewal_configs_dir, lineagename) + ".conf" + +def _relpath_from_file(archive_dir, from_file): + """Path to a directory from a file""" + return os.path.relpath(archive_dir, os.path.dirname(from_file)) + +def full_archive_path(config_obj, cli_config, lineagename): + """Returns the full archive path for a lineagename + + Uses cli_config to determine archive path if not available from config_obj. + + :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None) + :param configuration.NamespaceConfig cli_config: Main config file + :param str lineagename: Certificate name + """ + if config_obj and "archive_dir" in config_obj: + return config_obj["archive_dir"] + return os.path.join(cli_config.default_archive_dir, lineagename) + +def _full_live_path(cli_config, lineagename): + """Returns the full default live path for a lineagename""" + return os.path.join(cli_config.live_dir, lineagename) + +def delete_files(config, certname): + """Delete all files related to the certificate. + + If some files are not found, ignore them and continue. + """ + renewal_filename = renewal_file_for_certname(config, certname) + # file exists + full_default_archive_dir = full_archive_path(None, config, certname) + full_default_live_dir = _full_live_path(config, certname) + try: + renewal_config = configobj.ConfigObj(renewal_filename) + except configobj.ConfigObjError: + # config is corrupted + logger.warning("Could not parse %s. You may wish to manually " + "delete the contents of %s and %s.", renewal_filename, + full_default_live_dir, full_default_archive_dir) + raise errors.CertStorageError( + "error parsing {0}".format(renewal_filename)) + finally: + # we couldn't read it, but let's at least delete it + # if this was going to fail, it already would have. + os.remove(renewal_filename) + logger.debug("Removed %s", renewal_filename) + + # cert files and (hopefully) live directory + # it's not guaranteed that the files are in our default storage + # structure. so, first delete the cert files. + directory_names = set() + for kind in ALL_FOUR: + link = renewal_config.get(kind) + try: + os.remove(link) + logger.debug("Removed %s", link) + except OSError: + logger.debug("Unable to delete %s", link) + directory = os.path.dirname(link) + directory_names.add(directory) + + # if all four were in the same directory, and the only thing left + # is the README file (or nothing), delete that directory. + # this will be wrong in very few but some cases. + if len(directory_names) == 1: + # delete the README file + directory = directory_names.pop() + readme_path = os.path.join(directory, README) + try: + os.remove(readme_path) + logger.debug("Removed %s", readme_path) + except OSError: + logger.debug("Unable to delete %s", readme_path) + # if it's now empty, delete the directory + try: + os.rmdir(directory) # only removes empty directories + logger.debug("Removed %s", directory) + except OSError: + logger.debug("Unable to remove %s; may not be empty.", directory) + + # archive directory + try: + archive_path = full_archive_path(renewal_config, config, certname) + shutil.rmtree(archive_path) + logger.debug("Removed %s", archive_path) + except OSError: + logger.debug("Unable to remove %s", archive_path) + + +class RenewableCert(object): + """Renewable certificate. + + Represents a lineage of certificates that is under the management of + Certbot, indicated by the existence of an associated renewal + configuration file. + + Note that the notion of "current version" for a lineage is + maintained on disk in the structure of symbolic links, and is not + explicitly stored in any instance variable in this object. The + RenewableCert object is able to determine information about the + current (or other) version by accessing data on disk, but does not + inherently know any of this information except by examining the + symbolic links as needed. The instance variables mentioned below + point to symlinks that reflect the notion of "current version" of + each managed object, and it is these paths that should be used when + configuring servers to use the certificate managed in a lineage. + These paths are normally within the "live" directory, and their + symlink targets -- the actual cert files -- are normally found + within the "archive" directory. + + :ivar str cert: The path to the symlink representing the current + version of the certificate managed by this lineage. + :ivar str privkey: The path to the symlink representing the current + version of the private key managed by this lineage. + :ivar str chain: The path to the symlink representing the current version + of the chain managed by this lineage. + :ivar str fullchain: The path to the symlink representing the + current version of the fullchain (combined chain and cert) + managed by this lineage. + :ivar configobj.ConfigObj configuration: The renewal configuration + options associated with this lineage, obtained from parsing the + renewal configuration file and/or systemwide defaults. + + """ + def __init__(self, config_filename, cli_config, update_symlinks=False): + """Instantiate a RenewableCert object from an existing lineage. + + :param str config_filename: the path to the renewal config file + that defines this lineage. + :param .NamespaceConfig: parsed command line arguments + + :raises .CertStorageError: if the configuration file's name didn't end + in ".conf", or the file is missing or broken. + + """ + self.cli_config = cli_config + self.lineagename = lineagename_for_filename(config_filename) + + # self.configuration should be used to read parameters that + # may have been chosen based on default values from the + # systemwide renewal configuration; self.configfile should be + # used to make and save changes. + try: + self.configfile = configobj.ConfigObj(config_filename) + except configobj.ConfigObjError: + raise errors.CertStorageError( + "error parsing {0}".format(config_filename)) + # TODO: Do we actually use anything from defaults and do we want to + # read further defaults from the systemwide renewal configuration + # file at this stage? + self.configuration = config_with_defaults(self.configfile) + + if not all(x in self.configuration for x in ALL_FOUR): + raise errors.CertStorageError( + "renewal config file {0} is missing a required " + "file reference".format(self.configfile)) + + conf_version = self.configuration.get("version") + if (conf_version is not None and + util.get_strict_version(conf_version) > CURRENT_VERSION): + logger.info( + "Attempting to parse the version %s renewal configuration " + "file found at %s with version %s of Certbot. This might not " + "work.", conf_version, config_filename, certbot.__version__) + + self.cert = self.configuration["cert"] + self.privkey = self.configuration["privkey"] + self.chain = self.configuration["chain"] + self.fullchain = self.configuration["fullchain"] + self.live_dir = os.path.dirname(self.cert) + + self._fix_symlinks() + if update_symlinks: + self._update_symlinks() + self._check_symlinks() + + @property + def key_path(self): + """Duck type for self.privkey""" + return self.privkey + + @property + def cert_path(self): + """Duck type for self.cert""" + return self.cert + + @property + def chain_path(self): + """Duck type for self.chain""" + return self.chain + + @property + def fullchain_path(self): + """Duck type for self.fullchain""" + return self.fullchain + + @property + def target_expiry(self): + """The current target certificate's expiration datetime + + :returns: Expiration datetime of the current target certificate + :rtype: :class:`datetime.datetime` + """ + return crypto_util.notAfter(self.current_target("cert")) + + @property + def archive_dir(self): + """Returns the default or specified archive directory""" + return full_archive_path(self.configuration, + self.cli_config, self.lineagename) + + def relative_archive_dir(self, from_file): + """Returns the default or specified archive directory as a relative path + + Used for creating symbolic links. + """ + return _relpath_from_file(self.archive_dir, from_file) + + @property + def is_test_cert(self): + """Returns true if this is a test cert from a staging server.""" + server = self.configuration["renewalparams"].get("server", None) + if server: + return util.is_staging(server) + return False + + def _check_symlinks(self): + """Raises an exception if a symlink doesn't exist""" + for kind in ALL_FOUR: + link = getattr(self, kind) + if not os.path.islink(link): + raise errors.CertStorageError( + "expected {0} to be a symlink".format(link)) + target = get_link_target(link) + if not os.path.exists(target): + raise errors.CertStorageError("target {0} of symlink {1} does " + "not exist".format(target, link)) + + def _update_symlinks(self): + """Updates symlinks to use archive_dir""" + for kind in ALL_FOUR: + link = getattr(self, kind) + previous_link = get_link_target(link) + new_link = os.path.join(self.relative_archive_dir(link), + os.path.basename(previous_link)) + + os.unlink(link) + os.symlink(new_link, link) + + def _consistent(self): + """Are the files associated with this lineage self-consistent? + + :returns: Whether the files stored in connection with this + lineage appear to be correct and consistent with one + another. + :rtype: bool + + """ + # Each element must be referenced with an absolute path + for x in (self.cert, self.privkey, self.chain, self.fullchain): + if not os.path.isabs(x): + logger.debug("Element %s is not referenced with an " + "absolute path.", x) + return False + + # Each element must exist and be a symbolic link + for x in (self.cert, self.privkey, self.chain, self.fullchain): + if not os.path.islink(x): + logger.debug("Element %s is not a symbolic link.", x) + return False + for kind in ALL_FOUR: + link = getattr(self, kind) + target = get_link_target(link) + + # Each element's link must point within the cert lineage's + # directory within the official archive directory + if not os.path.samefile(os.path.dirname(target), self.archive_dir): + logger.debug("Element's link does not point within the " + "cert lineage's directory within the " + "official archive directory. Link: %s, " + "target directory: %s, " + "archive directory: %s. If you've specified " + "the archive directory in the renewal configuration " + "file, you may need to update links by running " + "certbot update_symlinks.", + link, os.path.dirname(target), self.archive_dir) + return False + + # The link must point to a file that exists + if not os.path.exists(target): + logger.debug("Link %s points to file %s that does not exist.", + link, target) + return False + + # The link must point to a file that follows the archive + # naming convention + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + if not pattern.match(os.path.basename(target)): + logger.debug("%s does not follow the archive naming " + "convention.", target) + return False + + # It is NOT required that the link's target be a regular + # file (it may itself be a symlink). But we should probably + # do a recursive check that ultimately the target does + # exist? + # XXX: Additional possible consistency checks (e.g. + # cryptographic validation of the chain being a chain, + # the chain matching the cert, and the cert matching + # the subject key) + # XXX: All four of the targets are in the same directory + # (This check is redundant with the check that they + # are all in the desired directory!) + # len(set(os.path.basename(self.current_target(x) + # for x in ALL_FOUR))) == 1 + return True + + def _fix(self): + """Attempt to fix defects or inconsistencies in this lineage. + + .. todo:: Currently unimplemented. + + """ + # TODO: Figure out what kinds of fixes are possible. For + # example, checking if there is a valid version that + # we can update the symlinks to. (Maybe involve + # parsing keys and certs to see if they exist and + # if a key corresponds to the subject key of a cert?) + + # TODO: In general, the symlink-reading functions below are not + # cautious enough about the possibility that links or their + # targets may not exist. (This shouldn't happen, but might + # happen as a result of random tampering by a sysadmin, or + # filesystem errors, or crashes.) + + def _previous_symlinks(self): + """Returns the kind and path of all symlinks used in recovery. + + :returns: list of (kind, symlink) tuples + :rtype: list + + """ + previous_symlinks = [] + for kind in ALL_FOUR: + link_dir = os.path.dirname(getattr(self, kind)) + link_base = "previous_{0}.pem".format(kind) + previous_symlinks.append((kind, os.path.join(link_dir, link_base))) + + return previous_symlinks + + def _fix_symlinks(self): + """Fixes symlinks in the event of an incomplete version update. + + If there is no problem with the current symlinks, this function + has no effect. + + """ + previous_symlinks = self._previous_symlinks() + if all(os.path.exists(link[1]) for link in previous_symlinks): + for kind, previous_link in previous_symlinks: + current_link = getattr(self, kind) + if os.path.lexists(current_link): + os.unlink(current_link) + os.symlink(os.readlink(previous_link), current_link) + + for _, link in previous_symlinks: + if os.path.exists(link): + os.unlink(link) + + def current_target(self, kind): + """Returns full path to which the specified item currently points. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + + :returns: The path to the current version of the specified + member. + :rtype: str or None + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + link = getattr(self, kind) + if not os.path.exists(link): + logger.debug("Expected symlink %s for %s does not exist.", + link, kind) + return None + return get_link_target(link) + + def current_version(self, kind): + """Returns numerical version of the specified item. + + For example, if kind is "chain" and the current chain link + points to a file named "chain7.pem", returns the integer 7. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + + :returns: the current version of the specified member. + :rtype: int + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + target = self.current_target(kind) + if target is None or not os.path.exists(target): + logger.debug("Current-version target for %s " + "does not exist at %s.", kind, target) + target = "" + matches = pattern.match(os.path.basename(target)) + if matches: + return int(matches.groups()[0]) + logger.debug("No matches for target %s.", kind) + return None + + def version(self, kind, version): + """The filename that corresponds to the specified version and kind. + + .. warning:: The specified version may not exist in this + lineage. There is no guarantee that the file path returned + by this method actually exists. + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + :param int version: the desired version + + :returns: The path to the specified version of the specified member. + :rtype: str + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + where = os.path.dirname(self.current_target(kind)) + return os.path.join(where, "{0}{1}.pem".format(kind, version)) + + def available_versions(self, kind): + """Which alternative versions of the specified kind of item exist? + + The archive directory where the current version is stored is + consulted to obtain the list of alternatives. + + :param str kind: the lineage member item ( + ``cert``, ``privkey``, ``chain``, or ``fullchain``) + + :returns: all of the version numbers that currently exist + :rtype: `list` of `int` + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + where = os.path.dirname(self.current_target(kind)) + files = os.listdir(where) + pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind)) + matches = [pattern.match(f) for f in files] + return sorted([int(m.groups()[0]) for m in matches if m]) + + def newest_available_version(self, kind): + """Newest available version of the specified kind of item? + + :param str kind: the lineage member item (``cert``, + ``privkey``, ``chain``, or ``fullchain``) + + :returns: the newest available version of this member + :rtype: int + + """ + return max(self.available_versions(kind)) + + def latest_common_version(self): + """Newest version for which all items are available? + + :returns: the newest available version for which all members + (``cert, ``privkey``, ``chain``, and ``fullchain``) exist + :rtype: int + + """ + # TODO: this can raise CertStorageError if there is no version overlap + # (it should probably return None instead) + # TODO: this can raise a spurious AttributeError if the current + # link for any kind is missing (it should probably return None) + versions = [self.available_versions(x) for x in ALL_FOUR] + return max(n for n in versions[0] if all(n in v for v in versions[1:])) + + def next_free_version(self): + """Smallest version newer than all full or partial versions? + + :returns: the smallest version number that is larger than any + version of any item currently stored in this lineage + :rtype: int + + """ + # TODO: consider locking/mutual exclusion between updating processes + # This isn't self.latest_common_version() + 1 because we don't want + # collide with a version that might exist for one file type but not + # for the others. + return max(self.newest_available_version(x) for x in ALL_FOUR) + 1 + + def ensure_deployed(self): + """Make sure we've deployed the latest version. + + :returns: False if a change was needed, True otherwise + :rtype: bool + + May need to recover from rare interrupted / crashed states.""" + + if self.has_pending_deployment(): + 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 + return True + + + def has_pending_deployment(self): + """Is there a later version of all of the managed items? + + :returns: ``True`` if there is a complete version of this + lineage with a larger version number than the current + version, and ``False`` otherwise + :rtype: bool + + """ + # TODO: consider whether to assume consistency or treat + # inconsistent/consistent versions differently + smallest_current = min(self.current_version(x) for x in ALL_FOUR) + return smallest_current < self.latest_common_version() + + def _update_link_to(self, kind, version): + """Make the specified item point at the specified version. + + (Note that this method doesn't verify that the specified version + exists.) + + :param str kind: the lineage member item ("cert", "privkey", + "chain", or "fullchain") + :param int version: the desired version + + """ + if kind not in ALL_FOUR: + raise errors.CertStorageError("unknown kind of item") + link = getattr(self, kind) + filename = "{0}{1}.pem".format(kind, version) + # Relative rather than absolute target directory + target_directory = os.path.dirname(os.readlink(link)) + # TODO: it could be safer to make the link first under a temporary + # filename, then unlink the old link, then rename the new link + # to the old link; this ensures that this process is able to + # create symlinks. + # TODO: we might also want to check consistency of related links + # for the other corresponding items + os.unlink(link) + os.symlink(os.path.join(target_directory, filename), link) + + def update_all_links_to(self, version): + """Change all member objects to point to the specified version. + + :param int version: the desired version + + """ + with error_handler.ErrorHandler(self._fix_symlinks): + previous_links = self._previous_symlinks() + for kind, link in previous_links: + os.symlink(self.current_target(kind), link) + + for kind in ALL_FOUR: + self._update_link_to(kind, version) + + for _, link in previous_links: + os.unlink(link) + + def names(self, version=None): + """What are the subject names of this certificate? + + (If no version is specified, use the current version.) + + :param int version: the desired version number + :returns: the subject names + :rtype: `list` of `str` + :raises .CertStorageError: if could not find cert file. + + """ + if version is None: + target = self.current_target("cert") + else: + target = self.version("cert", version) + if target is None: + raise errors.CertStorageError("could not find cert file") + with open(target) as f: + return crypto_util.get_names_from_cert(f.read()) + + def ocsp_revoked(self, version=None): + # pylint: disable=no-self-use,unused-argument + """Is the specified cert version revoked according to OCSP? + + Also returns True if the cert version is declared as intended + to be revoked according to Let's Encrypt OCSP extensions. + (If no version is specified, uses the current version.) + + This method is not yet implemented and currently always returns + False. + + :param int version: the desired version number + + :returns: whether the certificate is or will be revoked + :rtype: bool + + """ + # XXX: This query and its associated network service aren't + # implemented yet, so we currently return False (indicating that the + # certificate is not revoked). + return False + + def autorenewal_is_enabled(self): + """Is automatic renewal enabled for this cert? + + If autorenew is not specified, defaults to True. + + :returns: True if automatic renewal is enabled + :rtype: bool + + """ + return ("autorenew" not in self.configuration["renewalparams"] or + self.configuration["renewalparams"].as_bool("autorenew")) + + def should_autorenew(self): + """Should we now try to autorenew the most recent cert version? + + This is a policy question and does not only depend on whether + the cert is expired. (This considers whether autorenewal is + enabled, whether the cert is revoked, and whether the time + interval for autorenewal has been reached.) + + Note that this examines the numerically most recent cert version, + not the currently deployed version. + + :returns: whether an attempt should now be made to autorenew the + most current cert version in this lineage + :rtype: bool + + """ + if self.autorenewal_is_enabled(): + # Consider whether to attempt to autorenew this cert now + + # Renewals on the basis of revocation + if self.ocsp_revoked(self.latest_common_version()): + logger.debug("Should renew, certificate is revoked.") + return True + + # Renews some period before expiry time + default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] + interval = self.configuration.get("renew_before_expiry", default_interval) + expiry = crypto_util.notAfter(self.version( + "cert", self.latest_common_version())) + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + if expiry < add_time_interval(now, interval): + logger.debug("Should renew, less than %s before certificate " + "expiry %s.", interval, + expiry.strftime("%Y-%m-%d %H:%M:%S %Z")) + return True + return False + + @classmethod + def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): + """Create a new certificate lineage. + + Attempts to create a certificate lineage -- enrolled for + potential future renewal -- with the (suggested) lineage name + lineagename, and the associated cert, privkey, and chain (the + associated fullchain will be created automatically). Optional + configurator and renewalparams record the configuration that was + originally used to obtain this cert, so that it can be reused + later during automated renewal. + + Returns a new RenewableCert object referring to the created + lineage. (The actual lineage name, as well as all the relevant + file paths, will be available within this object.) + + :param str lineagename: the suggested name for this lineage + (normally the current cert's first subject DNS name) + :param str cert: the initial certificate version in PEM format + :param str privkey: the private key in PEM format + :param str chain: the certificate chain in PEM format + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: the newly-created RenewalCert object + :rtype: :class:`storage.renewableCert` + + """ + + # Examine the configuration and find the new lineage's name + for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, + cli_config.live_dir): + if not os.path.exists(i): + filesystem.makedirs(i, 0o700) + logger.debug("Creating directory %s.", i) + config_file, config_filename = util.unique_lineage_name( + cli_config.renewal_configs_dir, lineagename) + base_readme_path = os.path.join(cli_config.live_dir, README) + if not os.path.exists(base_readme_path): + _write_live_readme_to(base_readme_path, is_base_dir=True) + + # Determine where on disk everything will go + # lineagename will now potentially be modified based on which + # renewal configuration file could actually be created + lineagename = lineagename_for_filename(config_filename) + 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) + filesystem.mkdir(archive) + filesystem.mkdir(live_dir) + logger.debug("Archive directory %s and live " + "directory %s created.", archive, live_dir) + + # 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(_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 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 + with open(target["chain"], "wb") as f: + logger.debug("Writing chain to %s.", target["chain"]) + f.write(chain) + with open(target["fullchain"], "wb") as f: + # assumes that OpenSSL.crypto.dump_certificate includes + # ending newline character + logger.debug("Writing full chain to %s.", target["fullchain"]) + f.write(cert + chain) + + # Write a README file to the live directory + readme_path = os.path.join(live_dir, README) + _write_live_readme_to(readme_path) + + # Document what we've done in a new renewal config file + config_file.close() + + # Save only the config items that are relevant to renewal + values = relevant_values(vars(cli_config.namespace)) + + new_config = write_renewal_config(config_filename, config_filename, archive, + target, values) + return cls(new_config.filename, cli_config) + + def save_successor(self, prior_version, new_cert, + new_privkey, new_chain, cli_config): + """Save new cert and chain as a successor of a prior version. + + Returns the new version number that was created. + + .. note:: this function does NOT update links to deploy this + version + + :param int prior_version: the old version to which this version + is regarded as a successor (used to choose a privkey, if the + key has not changed, but otherwise this information is not + permanently recorded anywhere) + :param bytes new_cert: the new certificate, in PEM format + :param bytes new_privkey: the new private key, in PEM format, + or ``None``, if the private key has not changed + :param bytes new_chain: the new chain, in PEM format + :param .NamespaceConfig cli_config: parsed command line + arguments + + :returns: the new version number that was created + :rtype: int + + """ + # XXX: assumes official archive location rather than examining links + # XXX: consider using os.open for availability of os.O_EXCL + # XXX: ensure file permissions are correct; also create directories + # if needed (ensuring their permissions are correct) + # Figure out what the new version is and hence where to save things + + self.cli_config = cli_config + target_version = self.next_free_version() + target = dict( + [(kind, + 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. + if os.path.islink(old_privkey): + old_privkey = os.readlink(old_privkey) + else: + old_privkey = "privkey{0}.pem".format(prior_version) + logger.debug("Writing symlink to old private key, %s.", old_privkey) + os.symlink(old_privkey, target["privkey"]) + else: + 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 & MASK_FOR_PRIVATE_KEY_PERMISSIONS) + # from previous privkey in this lineage. + mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) + filesystem.copy_ownership_and_apply_mode( + old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) + + # Save everything else + with open(target["cert"], "wb") as f: + logger.debug("Writing certificate to %s.", target["cert"]) + f.write(new_cert) + with open(target["chain"], "wb") as f: + logger.debug("Writing chain to %s.", target["chain"]) + f.write(new_chain) + with open(target["fullchain"], "wb") as f: + logger.debug("Writing full chain to %s.", target["fullchain"]) + f.write(new_cert + new_chain) + + symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR) + # Update renewal config file + self.configfile = update_configuration( + self.lineagename, self.archive_dir, symlinks, cli_config) + self.configuration = config_with_defaults(self.configfile) + + return target_version diff --git a/certbot/certbot/_internal/updater.py b/certbot/certbot/_internal/updater.py new file mode 100644 index 000000000..50db0e21c --- /dev/null +++ b/certbot/certbot/_internal/updater.py @@ -0,0 +1,122 @@ +"""Updaters run at renewal""" +import logging + +from certbot import errors +from certbot import interfaces + +from certbot._internal.plugins import selection as plug_sel +import certbot.plugins.enhancements as enhancements + +logger = logging.getLogger(__name__) + +def run_generic_updaters(config, lineage, plugins): + """Run updaters that the plugin supports + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param plugins: List of plugins + :type plugins: `list` of `str` + + :returns: `None` + :rtype: None + """ + if config.dry_run: + logger.debug("Skipping updaters in dry-run mode.") + return + try: + installer = plug_sel.get_unprepared_installer(config, plugins) + except errors.Error as e: + logger.warning("Could not choose appropriate plugin for updaters: %s", e) + return + if installer: + _run_updaters(lineage, installer, config) + _run_enhancement_updaters(lineage, installer, config) + +def run_renewal_deployer(config, lineage, installer): + """Helper function to run deployer interface method if supported by the used + installer plugin. + + :param config: Configuration object + :type config: interfaces.IConfig + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: `None` + :rtype: None + """ + if config.dry_run: + logger.debug("Skipping renewal deployer in dry-run mode.") + return + + if not config.disable_renew_updates and isinstance(installer, + interfaces.RenewDeployer): + installer.renew_deploy(lineage) + _run_enhancement_deployers(lineage, installer, config) + +def _run_updaters(lineage, installer, config): + """Helper function to run the updater interface methods if supported by the + used installer plugin. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: `None` + :rtype: None + """ + if not config.disable_renew_updates: + if isinstance(installer, interfaces.GenericUpdater): + installer.generic_updates(lineage) + +def _run_enhancement_updaters(lineage, installer, config): + """Iterates through known enhancement interfaces. If the installer implements + an enhancement interface and the enhance interface has an updater method, the + updater method gets run. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param config: Configuration object + :type config: interfaces.IConfig + """ + + if config.disable_renew_updates: + return + for enh in enhancements._INDEX: # pylint: disable=protected-access + if isinstance(installer, enh["class"]) and enh["updater_function"]: + getattr(installer, enh["updater_function"])(lineage) + + +def _run_enhancement_deployers(lineage, installer, config): + """Iterates through known enhancement interfaces. If the installer implements + an enhancement interface and the enhance interface has an deployer method, the + deployer method gets run. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param config: Configuration object + :type config: interfaces.IConfig + """ + + if config.disable_renew_updates: + return + for enh in enhancements._INDEX: # pylint: disable=protected-access + if isinstance(installer, enh["class"]) and enh["deployer_function"]: + getattr(installer, enh["deployer_function"])(lineage) diff --git a/certbot/certbot/achallenges.py b/certbot/certbot/achallenges.py new file mode 100644 index 000000000..2f2e1f3bd --- /dev/null +++ b/certbot/certbot/achallenges.py @@ -0,0 +1,60 @@ +"""Client annotated ACME challenges. + +Please use names such as ``achall`` to distinguish from variables "of type" +:class:`acme.challenges.Challenge` (denoted by ``chall``) +and :class:`.ChallengeBody` (denoted by ``challb``):: + + from acme import challenges + from acme import messages + from certbot import achallenges + + chall = challenges.DNS(token='foo') + challb = messages.ChallengeBody(chall=chall) + achall = achallenges.DNS(chall=challb, domain='example.com') + +Note, that all annotated challenges act as a proxy objects:: + + achall.token == challb.token + +""" +import logging + +import josepy as jose + +from acme import challenges + + +logger = logging.getLogger(__name__) + + + +class AnnotatedChallenge(jose.ImmutableMap): + """Client annotated challenge. + + Wraps around server provided challenge and annotates with data + useful for the client. + + :ivar challb: Wrapped `~.ChallengeBody`. + + """ + __slots__ = ('challb',) + acme_type = NotImplemented + + def __getattr__(self, name): + return getattr(self.challb, name) + + +class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge): + """Client annotated `KeyAuthorizationChallenge` challenge.""" + __slots__ = ('challb', 'domain', 'account_key') + + def response_and_validation(self, *args, **kwargs): + """Generate response and validation.""" + return self.challb.chall.response_and_validation( + self.account_key, *args, **kwargs) + + +class DNS(AnnotatedChallenge): + """Client annotated "dns" ACME challenge.""" + __slots__ = ('challb', 'domain') + acme_type = challenges.DNS diff --git a/certbot/certbot/compat/__init__.py b/certbot/certbot/compat/__init__.py new file mode 100644 index 000000000..74451131a --- /dev/null +++ b/certbot/certbot/compat/__init__.py @@ -0,0 +1,6 @@ +""" +Compatibility layer to run certbot both on Linux and Windows. + +This package contains all logic that needs to be implemented specifically for Linux and for Windows. +Then the rest of certbot code relies on this module to be platform agnostic. +""" diff --git a/certbot/certbot/compat/_path.py b/certbot/certbot/compat/_path.py new file mode 100644 index 000000000..fe2d2d1d2 --- /dev/null +++ b/certbot/certbot/compat/_path.py @@ -0,0 +1,31 @@ +"""This compat module wraps os.path to forbid some functions.""" +# pylint: disable=function-redefined +from __future__ import absolute_import + +# First round of wrapping: we import statically all public attributes exposed by the os.path +# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path +# members are available in certbot.compat.path. +from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden + +# Second round of wrapping: we import dynamically all attributes from the os.path module that have +# not yet been imported by the first round (static star import). +import os.path as std_os_path # pylint: disable=os-module-forbidden +import sys as std_sys + +ourselves = std_sys.modules[__name__] +for attribute in dir(std_os_path): + # Check if the attribute does not already exist in our module. It could be internal attributes + # of the module (__name__, __doc__), or attributes from standard os.path already imported with + # `from os.path import *`. + if not hasattr(ourselves, attribute): + setattr(ourselves, attribute, getattr(std_os_path, attribute)) + +# Clean all remaining importables that are not from the core os.path module. +del ourselves, std_os_path, std_sys + + +# Function os.path.realpath is broken on some versions of Python for Windows. +def realpath(*unused_args, **unused_kwargs): + """Method os.path.realpath() is forbidden""" + raise RuntimeError('Usage of os.path.realpath() is forbidden. ' + 'Use certbot.compat.filesystem.realpath() instead.') diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py new file mode 100644 index 000000000..5fba440cc --- /dev/null +++ b/certbot/certbot/compat/filesystem.py @@ -0,0 +1,602 @@ +"""Compat module to handle files security on Windows and Linux""" +from __future__ import absolute_import + +import errno +import os # pylint: disable=os-module-forbidden +import stat + +try: + # pylint: disable=import-error + import ntsecuritycon + import win32security + import win32con + import win32api + import win32file + import pywintypes + import winerror + # pylint: enable=import-error +except ImportError: + POSIX_MODE = True +else: + POSIX_MODE = False + +from acme.magic_typing import List, Union, Tuple # pylint: disable=unused-import, no-name-in-module + + +def chmod(file_path, mode): + # type: (str, int) -> None + """ + Apply a POSIX mode on given file_path: + * for Linux, the POSIX mode will be directly applied using chmod, + * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for + Certbot context, and applied to the file using kernel calls. + + The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, + is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the + method _generate_windows_flags(). + + :param str file_path: Path of the file + :param int mode: POSIX mode to apply + """ + if POSIX_MODE: + os.chmod(file_path, mode) + else: + _apply_win_mode(file_path, mode) + + +# One could ask why there is no copy_ownership() function, or even a reimplementation +# of os.chown() that would modify the ownership of file without touching the mode itself. +# This is because on Windows, it would require recalculating the existing DACL against +# the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically +# the current owner of a file. This action would be necessary to keep consistency between +# the POSIX mode applied to the file and the current owner of this file. +# Since copying and editing arbitrary DACL is very difficult, and since we actually know +# the mode to apply at the time the owner of a file should change, it is easier to just +# change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does. +def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): + # type: (str, str, int, bool, bool) -> None + """ + Copy ownership (user and optionally group on Linux) from the source to the + destination, then apply given mode in compatible way for Linux and Windows. + This replaces the os.chown command. + :param str src: Path of the source file + :param str dst: Path of the destination file + :param int mode: Permission mode to apply on the destination file + :param bool copy_user: Copy user if `True` + :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) + """ + if POSIX_MODE: + stats = os.stat(src) + user_id = stats.st_uid if copy_user else -1 + group_id = stats.st_gid if copy_group else -1 + os.chown(dst, user_id, group_id) + elif copy_user: + # There is no group handling in Windows + _copy_win_ownership(src, dst) + + chmod(dst, mode) + + +def check_mode(file_path, mode): + # type: (str, int) -> bool + """ + Check if the given mode matches the permissions of the given file. + On Linux, will make a direct comparison, on Windows, mode will be compared against + the security model. + :param str file_path: Path of the file + :param int mode: POSIX mode to test + :rtype: bool + :return: True if the POSIX mode matches the file permissions + """ + if POSIX_MODE: + return stat.S_IMODE(os.stat(file_path).st_mode) == mode + + return _check_win_mode(file_path, mode) + + +def check_owner(file_path): + # type: (str) -> bool + """ + Check if given file is owned by current user. + :param str file_path: File path to check + :rtype: bool + :return: True if given file is owned by current user, False otherwise. + """ + if POSIX_MODE: + return os.stat(file_path).st_uid == os.getuid() + + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # Compare sids + return _get_current_user() == user + + +def check_permissions(file_path, mode): + # type: (str, int) -> bool + """ + Check if given file has the given mode and is owned by current user. + :param str file_path: File path to check + :param int mode: POSIX mode to check + :rtype: bool + :return: True if file has correct mode and owner, False otherwise. + """ + return check_owner(file_path) and check_mode(file_path, mode) + + +def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin + # type: (str, int, int) -> int + """ + Wrapper of original os.open function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int flags: Flags to apply on file while opened + :param int mode: POSIX mode to apply on file when opened, + Python defaults will be applied if ``None`` + :returns: the file descriptor to the opened file + :rtype: int + :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set, + OSError(errno.EACCES) on Windows if the file already exists and is a directory, and + os.O_CREAT is set. + """ + if POSIX_MODE: + # On Linux, invoke os.open directly. + return os.open(file_path, flags, mode) + + # Windows: handle creation of the file atomically with proper permissions. + if flags & os.O_CREAT: + # If os.O_EXCL is set, we will use the "CREATE_NEW", that will raise an exception if + # file exists, matching the API contract of this bit flag. Otherwise, we use + # "CREATE_ALWAYS" that will always create the file whether it exists or not. + disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + # We set second parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long + security.SetSecurityDescriptorOwner(user, 0) + # We set first parameter to 1 (`True`) to say that this security descriptor contains + # a DACL. Otherwise second and third parameters are ignored. + # We set third parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long + security.SetSecurityDescriptorDacl(1, dacl, 0) + + handle = None + try: + handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, + win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, + attributes, disposition, 0, None) + except pywintypes.error as err: + # Handle native windows errors into python errors to be consistent with the API + # of os.open in the situation of a file already existing or locked. + if err.winerror == winerror.ERROR_FILE_EXISTS: + raise OSError(errno.EEXIST, err.strerror) + if err.winerror == winerror.ERROR_SHARING_VIOLATION: + raise OSError(errno.EACCES, err.strerror) + raise err + finally: + if handle: + handle.Close() + + # At this point, the file that did not exist has been created with proper permissions, + # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to + # avoid a FileExists exception before calling os.open. + return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL) + + # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine. + handle = os.open(file_path, flags) + chmod(file_path, mode) + return handle + + +def makedirs(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.makedirs function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on leaf directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.makedirs(file_path, mode) + + orig_mkdir_fn = os.mkdir + try: + # As we know that os.mkdir is called internally by os.makedirs, we will swap the function in + # os module for the time of makedirs execution on Windows. + os.mkdir = mkdir # type: ignore + return os.makedirs(file_path, mode) + finally: + os.mkdir = orig_mkdir_fn + + +def mkdir(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.mkdir function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.mkdir(file_path, mode) + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + security.SetSecurityDescriptorOwner(user, False) + security.SetSecurityDescriptorDacl(1, dacl, 0) + + try: + win32file.CreateDirectory(file_path, attributes) + except pywintypes.error as err: + # Handle native windows error into python error to be consistent with the API + # of os.mkdir in the situation of a directory already existing. + if err.winerror == winerror.ERROR_ALREADY_EXISTS: + raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror) + raise err + + return None + + +def replace(src, dst): + # type: (str, str) -> None + """ + Rename a file to a destination path and handles situations where the destination exists. + :param str src: The current file path. + :param str dst: The new file path. + """ + if hasattr(os, 'replace'): + # Use replace if possible. On Windows, only Python >= 3.4 is supported + # so we can assume that os.replace() is always available for this platform. + getattr(os, 'replace')(src, dst) + else: + # Otherwise, use os.rename() that behaves like os.replace() on Linux. + os.rename(src, dst) + + +def realpath(file_path): + # type: (str) -> str + """ + Find the real path for the given path. This method resolves symlinks, including + recursive symlinks, and is protected against symlinks that creates an infinite loop. + """ + original_path = file_path + + if POSIX_MODE: + path = os.path.realpath(file_path) + if os.path.islink(path): + # If path returned by realpath is still a link, it means that it failed to + # resolve the symlink because of a loop. + # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + return path + + inspected_paths = [] # type: List[str] + while os.path.islink(file_path): + link_path = file_path + file_path = os.readlink(file_path) + if not os.path.isabs(file_path): + file_path = os.path.join(os.path.dirname(link_path), file_path) + if file_path in inspected_paths: + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + inspected_paths.append(file_path) + + return os.path.abspath(file_path) + + +# On Windows is_executable run from an unprivileged shell may claim that a path is +# executable when it is excutable only if run from a privileged shell. This result +# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights +# without taking into consideration if the target user has currently required the +# elevated privileges or not. However this is not a problem since certbot always +# requires to be run under a privileged shell, so the user will always benefit +# from the highest (privileged one) set of permissions on a given file. +def is_executable(path): + # type: (str) -> bool + """ + Is path an executable file? + :param str path: path to test + :return: True if path is an executable file + :rtype: bool + """ + if POSIX_MODE: + return os.path.isfile(path) and os.access(path, os.X_OK) + + return _win_is_executable(path) + + +def has_world_permissions(path): + # type: (str) -> bool + """ + Check if everybody/world has any right (read/write/execute) on a file given its path + :param str path: path to test + :return: True if everybody/world has any right to the file + :rtype: bool + """ + if POSIX_MODE: + return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO) + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + return bool(dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'), + })) + + +def compute_private_key_mode(old_key, base_mode): + # type: (str, int) -> int + """ + Calculate the POSIX mode to apply to a private key given the previous private key + :param str old_key: path to the previous private key + :param int base_mode: the minimum modes to apply to a private key + :return: the POSIX mode to apply + :rtype: int + """ + if POSIX_MODE: + # On Linux, we keep read/write/execute permissions + # for group and read permissions for everybody. + old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) & + (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH)) + return base_mode | old_mode + + # On Windows, the mode returned by os.stat is not reliable, + # so we do not keep any permission from the previous private key. + return base_mode + + +def has_same_ownership(path1, path2): + # type: (str, str) -> bool + """ + Return True if the ownership of two files given their respective path is the same. + On Windows, ownership is checked against owner only, since files do not have a group owner. + :param str path1: path to the first file + :param str path2: path to the second file + :return: True if both files have the same ownership, False otherwise + :rtype: bool + + """ + if POSIX_MODE: + stats1 = os.stat(path1) + stats2 = os.stat(path2) + return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid) + + security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION) + user1 = security1.GetSecurityDescriptorOwner() + + security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION) + user2 = security2.GetSecurityDescriptorOwner() + + return user1 == user2 + + +def has_min_permissions(path, min_mode): + # type: (str, int) -> bool + """ + Check if a file given its path has at least the permissions defined by the given minimal mode. + On Windows, group permissions are ignored since files do not have a group owner. + :param str path: path to the file to check + :param int min_mode: the minimal permissions expected + :return: True if the file matches the minimal permissions expectations, False otherwise + :rtype: bool + """ + if POSIX_MODE: + st_mode = os.stat(path).st_mode + return st_mode == st_mode | min_mode + + # Resolve symlinks, to get a consistent result with os.stat on Linux, + # that follows symlinks by default. + path = realpath(path) + + # Get owner sid of the file + security = win32security.GetFileSecurity( + path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + dacl = security.GetSecurityDescriptorDacl() + min_dacl = _generate_dacl(user, min_mode) + + for index in range(min_dacl.GetAceCount()): + min_ace = min_dacl.GetAce(index) + + # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID. + # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html + mask = min_ace[1] + user = min_ace[2] + + effective_mask = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': user, + }) + + if effective_mask != effective_mask | mask: + return False + + return True + + +def _win_is_executable(path): + if not os.path.isfile(path): + return False + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': _get_current_user(), + }) + + return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE + + +def _apply_win_mode(file_path, mode): + """ + This function converts the given POSIX mode into a Windows ACL list, and applies it to the + file given its path. If the given path is a symbolic link, it will resolved to apply the + mode on the targeted file. + """ + file_path = realpath(file_path) + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # New DACL, that will overwrite existing one (including inherited permissions) + dacl = _generate_dacl(user, mode) + + # Apply the new DACL + security.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) + + +def _generate_dacl(user_sid, mode): + analysis = _analyze_mode(mode) + + # Get standard accounts from "well-known" sid + # See the list here: + # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + system = win32security.ConvertStringSidToSid('S-1-5-18') + admins = win32security.ConvertStringSidToSid('S-1-5-32-544') + everyone = win32security.ConvertStringSidToSid('S-1-1-0') + + # New dacl, without inherited permissions + dacl = win32security.ACL() + + # If user is already system or admins, any ACE defined here would be superseded by + # the full control ACE that will be added after. + if user_sid not in [system, admins]: + # Handle user rights + user_flags = _generate_windows_flags(analysis['user']) + if user_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) + + # Handle everybody rights + everybody_flags = _generate_windows_flags(analysis['all']) + if everybody_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) + + # Handle administrator rights + full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) + + return dacl + + +def _analyze_mode(mode): + return { + 'user': { + 'read': mode & stat.S_IRUSR, + 'write': mode & stat.S_IWUSR, + 'execute': mode & stat.S_IXUSR, + }, + 'all': { + 'read': mode & stat.S_IROTH, + 'write': mode & stat.S_IWOTH, + 'execute': mode & stat.S_IXOTH, + }, + } + + +def _copy_win_ownership(src, dst): + security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION) + user_src = security_src.GetSecurityDescriptorOwner() + + security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION) + # Second parameter indicates, if `False`, that the owner of the file is not provided by some + # default mechanism, but is explicitly set instead. This is obviously what we are doing here. + security_dst.SetSecurityDescriptorOwner(user_src, False) + + win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst) + + +def _generate_windows_flags(rights_desc): + # Some notes about how each POSIX right is interpreted. + # + # For the rights read and execute, we have a pretty bijective relation between + # POSIX flags and their generic counterparts on Windows, so we use them directly + # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). + # + # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a + # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or + # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. + # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS + # substracted of the rights corresponding to POSIX read and POSIX execute. + # + # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, + # so a "Full Control" on the file. + # + # A complete list of the rights defined on NTFS can be found here: + # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders + flag = 0 + if rights_desc['read']: + flag = flag | ntsecuritycon.FILE_GENERIC_READ + if rights_desc['write']: + flag = flag | (ntsecuritycon.FILE_ALL_ACCESS + ^ ntsecuritycon.FILE_GENERIC_READ + ^ ntsecuritycon.FILE_GENERIC_EXECUTE) + if rights_desc['execute']: + flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE + + return flag + + +def _check_win_mode(file_path, mode): + # Resolve symbolic links + file_path = realpath(file_path) + # Get current dacl file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION + | win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + # Get current file owner sid + user = security.GetSecurityDescriptorOwner() + + if not dacl: + # No DACL means full control to everyone + # This is not a deterministic permissions set. + return False + + # Calculate the target dacl + ref_dacl = _generate_dacl(user, mode) + + return _compare_dacls(dacl, ref_dacl) + + +def _compare_dacls(dacl1, dacl2): + """ + This method compare the two given DACLs to check if they are identical. + Identical means here that they contains the same set of ACEs in the same order. + """ + return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] == + [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())]) + + +def _get_current_user(): + """ + Return the pySID corresponding to the current user. + """ + # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx, + # because this function returns nonsense values when Certbot is run under NT AUTHORITY\SYSTEM. + # To run Certbot under NT AUTHORITY\SYSTEM, you can open a shell using the instructions here: + # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/ + account_name = r"{0}\{1}".format(win32api.GetDomainName(), win32api.GetUserName()) + # LookupAccountName() expects the system name as first parameter. By passing None to it, + # we instruct Windows to first search the matching account in the machine local accounts, + # then into the primary domain accounts, if the machine has joined a domain, then finally + # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows + # if there is no reason to use a specific lookup mechanism. + # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea + return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot/certbot/compat/misc.py b/certbot/certbot/compat/misc.py new file mode 100644 index 000000000..a8fbf2c96 --- /dev/null +++ b/certbot/certbot/compat/misc.py @@ -0,0 +1,110 @@ +""" +This compat module handles various platform specific calls that do not fall into one +particular category. +""" +from __future__ import absolute_import + +import select +import sys + +try: + from win32com.shell import shell as shellwin32 # pylint: disable=import-error + POSIX_MODE = False +except ImportError: # pragma: no cover + POSIX_MODE = True + +from certbot import errors +from certbot.compat import os + + +# For Linux: define OS specific standard binary directories +STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] + + +def raise_for_non_administrative_windows_rights(): + # type: () -> None + """ + On Windows, raise if current shell does not have the administrative rights. + Do nothing on Linux. + + :raises .errors.Error: If the current shell does not have administrative rights on Windows. + """ + if not POSIX_MODE and shellwin32.IsUserAnAdmin() == 0: # pragma: no cover + raise errors.Error('Error, certbot must be run on a shell with administrative rights.') + + +def readline_with_timeout(timeout, prompt): + # type: (float, str) -> str + """ + Read user input to return the first line entered, or raise after specified timeout. + + :param float timeout: The timeout in seconds given to the user. + :param str prompt: The prompt message to display to the user. + + :returns: The first line entered by the user. + :rtype: str + + """ + try: + # Linux specific + # + # Call to select can only be done like this on UNIX + rlist, _, _ = select.select([sys.stdin], [], [], timeout) + if not rlist: + raise errors.Error( + "Timed out waiting for answer to prompt '{0}'".format(prompt)) + return rlist[0].readline() + except OSError: + # Windows specific + # + # No way with select to make a timeout to the user input on Windows, + # as select only supports socket in this case. + # So no timeout on Windows for now. + return sys.stdin.readline() + + +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): + # type: (str) -> str + """ + 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 os.name != 'nt': + # Linux specific + return LINUX_DEFAULT_FOLDERS[folder_type] + # Windows specific + return WINDOWS_DEFAULT_FOLDERS[folder_type] + + +def underscores_for_unsupported_characters_in_path(path): + # type: (str) -> str + """ + Replace unsupported characters in path for current OS by underscores. + :param str path: the path to normalize + :return: the normalized path + :rtype: str + """ + if os.name != 'nt': + # Linux specific + return path + + # Windows specific + drive, tail = os.path.splitdrive(path) + return drive + tail.replace(':', '_') diff --git a/certbot/certbot/compat/os.py b/certbot/certbot/compat/os.py new file mode 100644 index 000000000..e5438f365 --- /dev/null +++ b/certbot/certbot/compat/os.py @@ -0,0 +1,136 @@ +""" +This compat modules is a wrapper of the core os module that forbids usage of specific operations +(e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. +This module is intended to replace standard os module throughout certbot projects (except acme). +""" +# pylint: disable=function-redefined +from __future__ import absolute_import + +# First round of wrapping: we import statically all public attributes exposed by the os module +# This allows in particular to have pylint, mypy, IDEs be aware that most of os members are +# available in certbot.compat.os. +from os import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden + +# Second round of wrapping: we import dynamically all attributes from the os module that have not +# yet been imported by the first round (static import). This covers in particular the case of +# specific python 3.x versions where not all public attributes are in the special __all__ of os, +# and so not in `from os import *`. +import os as std_os # pylint: disable=os-module-forbidden +import sys as std_sys + +ourselves = std_sys.modules[__name__] +for attribute in dir(std_os): + # Check if the attribute does not already exist in our module. It could be internal attributes + # of the module (__name__, __doc__), or attributes from standard os already imported with + # `from os import *`. + if not hasattr(ourselves, attribute): + setattr(ourselves, attribute, getattr(std_os, attribute)) + +# Import our internal path module, then allow certbot.compat.os.path +# to behave as a module (similarly to os.path). +from certbot.compat import _path as path # type: ignore # pylint: disable=wrong-import-position +std_sys.modules[__name__ + '.path'] = path + +# Clean all remaining importables that are not from the core os module. +del ourselves, std_os, std_sys + + +# Chmod is the root of all evil for our security model on Windows. With the default implementation +# of os.chmod on Windows, almost all bits on mode will be ignored, and only a general RO or RW will +# be applied. The DACL, the inner mechanism to control file access on Windows, will stay on its +# default definition, giving effectively at least read permissions to any one, as the default +# permissions on root path will be inherit by the file (as NTFS state), and root path can be read +# by anyone. So the given mode needs to be translated into a secured and not inherited DACL that +# will be applied to this file using filesystem.chmod, calling internally the win32security +# module to construct and apply the DACL. Complete security model to translate a POSIX mode into +# a suitable DACL on Windows for Certbot can be found here: +# https://github.com/certbot/certbot/issues/6356 +# Basically, it states that appropriate permissions will be set for the owner, nothing for the +# group, appropriate permissions for the "Everyone" group, and all permissions to the +# "Administrators" group + "System" user, as they can do everything anyway. +def chmod(*unused_args, **unused_kwargs): + """Method os.chmod() is forbidden""" + raise RuntimeError('Usage of os.chmod() is forbidden. ' + 'Use certbot.compat.filesystem.chmod() instead.') + + +# Because uid is not a concept on Windows, chown is useless. In fact, it is not even available +# on Python for Windows. So to be consistent on both platforms for Certbot, this method is +# always forbidden. +def chown(*unused_args, **unused_kwargs): + """Method os.chown() is forbidden""" + raise RuntimeError('Usage of os.chown() is forbidden.' + 'Use certbot.compat.filesystem.copy_ownership_and_apply_mode() instead.') + + +# The os.open function on Windows has the same effect as a call to os.chown concerning the file +# modes: these modes lack the correct control over the permissions given to the file. Instead, +# filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are +# atomically set in case of file creation, or invokes filesystem.chmod to properly set the +# permissions for the other cases. +def open(*unused_args, **unused_kwargs): + """Method os.open() is forbidden""" + raise RuntimeError('Usage of os.open() is forbidden. ' + 'Use certbot.compat.filesystem.open() instead.') + + +# Very similarly to os.open, os.mkdir has the same effects on Windows and creates an unsecured +# folder. So a similar mitigation to security.chmod is provided on this platform. +def mkdir(*unused_args, **unused_kwargs): + """Method os.mkdir() is forbidden""" + raise RuntimeError('Usage of os.mkdir() is forbidden. ' + 'Use certbot.compat.filesystem.mkdir() instead.') + + +# As said above, os.makedirs would call the original os.mkdir function recursively on Windows, +# creating the same flaws for every actual folder created. This method is modified to ensure +# that our modified os.mkdir is called on Windows, by monkey patching temporarily the mkdir method +# on the original os module, executing the modified logic to correctly protect newly created +# folders, then restoring original mkdir method in the os module. +def makedirs(*unused_args, **unused_kwargs): + """Method os.makedirs() is forbidden""" + raise RuntimeError('Usage of os.makedirs() is forbidden. ' + 'Use certbot.compat.filesystem.makedirs() instead.') + + +# Because of the blocking strategy on file handlers on Windows, rename does not behave as expected +# with POSIX systems: an exception will be raised if dst already exists. +def rename(*unused_args, **unused_kwargs): + """Method os.rename() is forbidden""" + raise RuntimeError('Usage of os.rename() is forbidden. ' + 'Use certbot.compat.filesystem.replace() instead.') + + +# Behavior of os.replace is consistent between Windows and Linux. However, it is not supported on +# Python 2.x. So, as for os.rename, we forbid it in favor of filesystem.replace. +def replace(*unused_args, **unused_kwargs): + """Method os.replace() is forbidden""" + raise RuntimeError('Usage of os.replace() is forbidden. ' + 'Use certbot.compat.filesystem.replace() instead.') + + +# Results given by os.access are inconsistent or partial on Windows, because this platform is not +# following the POSIX approach. +def access(*unused_args, **unused_kwargs): + """Method os.access() is forbidden""" + raise RuntimeError('Usage of os.access() is forbidden. ' + 'Use certbot.compat.filesystem.check_mode() or ' + 'certbot.compat.filesystem.is_executable() instead.') + + +# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or +# meaningless. We need to use specialized functions from the certbot.compat.filesystem module. +def stat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.stat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') + + +# Method os.fstat has the same problem than os.stat, since it is the same function, +# but accepting a file descriptor instead of a path. +def fstat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.fstat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py new file mode 100644 index 000000000..12291af38 --- /dev/null +++ b/certbot/certbot/crypto_util.py @@ -0,0 +1,491 @@ +"""Certbot client crypto utility functions. + +.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server + is capable of handling the signatures. + +""" +import hashlib +import logging +import warnings + +import pyrfc3339 +import six +import zope.component +from OpenSSL import SSL # type: ignore +from OpenSSL import crypto +# https://github.com/python/typeshed/tree/master/third_party/2/cryptography +from cryptography import x509 # type: ignore +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric.ec import ECDSA +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey + +from acme import crypto_util as acme_crypto_util +from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os + +logger = logging.getLogger(__name__) + + +# High level functions +def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): + """Initializes and saves a privkey. + + Inits key and saves it in PEM format on the filesystem. + + .. note:: keyname is the attempted filename, it may be different if a file + already exists at the path. + + :param int key_size: RSA key size in bits + :param str key_dir: Key save directory. + :param str keyname: Filename of key + + :returns: Key + :rtype: :class:`certbot.util.Key` + + :raises ValueError: If unable to generate the key given key_size. + + """ + try: + key_pem = make_key(key_size) + except ValueError as err: + logger.error("", exc_info=True) + raise err + + config = zope.component.getUtility(interfaces.IConfig) + # Save file + util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions) + key_f, key_path = util.unique_file( + os.path.join(key_dir, keyname), 0o600, "wb") + with key_f: + key_f.write(key_pem) + logger.debug("Generating key (%d bits): %s", key_size, key_path) + + return util.Key(key_path, key_pem) + + +def init_save_csr(privkey, names, path): + """Initialize a CSR with the given private key. + + :param privkey: Key to include in the CSR + :type privkey: :class:`certbot.util.Key` + + :param set names: `str` names to include in the CSR + + :param str path: Certificate save directory. + + :returns: CSR + :rtype: :class:`certbot.util.CSR` + + """ + config = zope.component.getUtility(interfaces.IConfig) + + csr_pem = acme_crypto_util.make_csr( + privkey.pem, names, must_staple=config.must_staple) + + # Save CSR + util.make_or_verify_dir(path, 0o755, config.strict_permissions) + csr_f, csr_filename = util.unique_file( + os.path.join(path, "csr-certbot.pem"), 0o644, "wb") + with csr_f: + csr_f.write(csr_pem) + logger.debug("Creating CSR: %s", csr_filename) + + return util.CSR(csr_filename, csr_pem, "pem") + + +# WARNING: the csr and private key file are possible attack vectors for TOCTOU +# We should either... +# A. Do more checks to verify that the CSR is trusted/valid +# B. Audit the parsing code for vulnerabilities + +def valid_csr(csr): + """Validate CSR. + + Check if `csr` is a valid CSR for the given domains. + + :param str csr: CSR in PEM. + + :returns: Validity of CSR. + :rtype: bool + + """ + try: + req = crypto.load_certificate_request( + crypto.FILETYPE_PEM, csr) + return req.verify(req.get_pubkey()) + except crypto.Error: + logger.debug("", exc_info=True) + return False + + +def csr_matches_pubkey(csr, privkey): + """Does private key correspond to the subject public key in the CSR? + + :param str csr: CSR in PEM. + :param str privkey: Private key file contents (PEM) + + :returns: Correspondence of private key to CSR subject public key. + :rtype: bool + + """ + req = crypto.load_certificate_request( + crypto.FILETYPE_PEM, csr) + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey) + try: + return req.verify(pkey) + except crypto.Error: + logger.debug("", exc_info=True) + return False + + +def import_csr_file(csrfile, data): + """Import a CSR file, which can be either PEM or DER. + + :param str csrfile: CSR filename + :param str data: contents of the CSR file + + :returns: (`crypto.FILETYPE_PEM`, + util.CSR object representing the CSR, + list of domains requested in the CSR) + :rtype: tuple + + """ + PEM = crypto.FILETYPE_PEM + load = crypto.load_certificate_request + try: + # Try to parse as DER first, then fall back to PEM. + csr = load(crypto.FILETYPE_ASN1, data) + except crypto.Error: + try: + csr = load(PEM, data) + except crypto.Error: + raise errors.Error("Failed to parse CSR file: {0}".format(csrfile)) + + domains = _get_names_from_loaded_cert_or_req(csr) + # Internally we always use PEM, so re-encode as PEM before returning. + data_pem = crypto.dump_certificate_request(PEM, csr) + return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains + + +def make_key(bits): + """Generate PEM encoded RSA key. + + :param int bits: Number of bits, at least 1024. + + :returns: new RSA key in PEM form with specified number of bits + :rtype: str + + """ + assert bits >= 1024 # XXX + key = crypto.PKey() + key.generate_key(crypto.TYPE_RSA, bits) + return crypto.dump_privatekey(crypto.FILETYPE_PEM, key) + + +def valid_privkey(privkey): + """Is valid RSA private key? + + :param str privkey: Private key file contents in PEM + + :returns: Validity of private key. + :rtype: bool + + """ + try: + return crypto.load_privatekey( + crypto.FILETYPE_PEM, privkey).check() + except (TypeError, crypto.Error): + return False + + +def verify_renewable_cert(renewable_cert): + """For checking that your certs were not corrupted on disk. + + Several things are checked: + 1. Signature verification for the cert. + 2. That fullchain matches cert and chain when concatenated. + 3. Check that the private key matches the certificate. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If verification fails. + """ + verify_renewable_cert_sig(renewable_cert) + verify_fullchain(renewable_cert) + verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey) + + +def verify_renewable_cert_sig(renewable_cert): + """Verifies the signature of a `.storage.RenewableCert` object. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If signature verification fails. + """ + try: + with open(renewable_cert.chain, 'rb') as chain_file: # type: IO[bytes] + chain = x509.load_pem_x509_certificate(chain_file.read(), default_backend()) + with open(renewable_cert.cert, 'rb') as cert_file: # type: IO[bytes] + cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) + pk = chain.public_key() + with warnings.catch_warnings(): + verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, + cert.signature_hash_algorithm) + except (IOError, ValueError, InvalidSignature) as e: + error_str = "verifying the signature of the cert located at {0} has failed. \ + Details: {1}".format(renewable_cert.cert, e) + logger.exception(error_str) + raise errors.Error(error_str) + + +def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm): + """Check the signature of a payload. + + :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature + :param bytes signature: the signature bytes + :param bytes payload: the payload bytes + :param cryptography.hazmat.primitives.hashes.HashAlgorithm + signature_hash_algorithm: algorithm used to hash the payload + + :raises InvalidSignature: If signature verification fails. + :raises errors.Error: If public key type is not supported + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + if isinstance(public_key, RSAPublicKey): + # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi + verifier = public_key.verifier( # type: ignore + signature, PKCS1v15(), signature_hash_algorithm + ) + verifier.update(payload) + verifier.verify() + elif isinstance(public_key, EllipticCurvePublicKey): + verifier = public_key.verifier( + signature, ECDSA(signature_hash_algorithm) + ) + verifier.update(payload) + verifier.verify() + else: + raise errors.Error("Unsupported public key type") + + +def verify_cert_matches_priv_key(cert_path, key_path): + """ Verifies that the private key and cert match. + + :param str cert_path: path to a cert in PEM format + :param str key_path: path to a private key file + + :raises errors.Error: If they don't match. + """ + try: + context = SSL.Context(SSL.SSLv23_METHOD) + context.use_certificate_file(cert_path) + context.use_privatekey_file(key_path) + context.check_privatekey() + except (IOError, SSL.Error) as e: + error_str = "verifying the cert located at {0} matches the \ + private key located at {1} has failed. \ + Details: {2}".format(cert_path, + key_path, e) + logger.exception(error_str) + raise errors.Error(error_str) + + +def verify_fullchain(renewable_cert): + """ Verifies that fullchain is indeed cert concatenated with chain. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If cert and chain do not combine to fullchain. + """ + try: + with open(renewable_cert.chain) as chain_file: # type: IO[str] + chain = chain_file.read() + with open(renewable_cert.cert) as cert_file: # type: IO[str] + cert = cert_file.read() + with open(renewable_cert.fullchain) as fullchain_file: # type: IO[str] + fullchain = fullchain_file.read() + if (cert + chain) != fullchain: + error_str = "fullchain does not match cert + chain for {0}!" + error_str = error_str.format(renewable_cert.lineagename) + raise errors.Error(error_str) + except IOError as e: + error_str = "reading one of cert, chain, or fullchain has failed: {0}".format(e) + logger.exception(error_str) + raise errors.Error(error_str) + except errors.Error as e: + raise e + + +def pyopenssl_load_certificate(data): + """Load PEM/DER certificate. + + :raises errors.Error: + + """ + + openssl_errors = [] + + for file_type in (crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1): + try: + return crypto.load_certificate(file_type, data), file_type + except crypto.Error as error: # TODO: other errors? + openssl_errors.append(error) + raise errors.Error("Unable to load: {0}".format(",".join( + str(error) for error in openssl_errors))) + + +def _load_cert_or_req(cert_or_req_str, load_func, + typ=crypto.FILETYPE_PEM): + try: + return load_func(typ, cert_or_req_str) + except crypto.Error: + logger.error("", exc_info=True) + raise + + +def _get_sans_from_cert_or_req(cert_or_req_str, load_func, + typ=crypto.FILETYPE_PEM): + # pylint: disable=protected-access + return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req( + cert_or_req_str, load_func, typ)) + + +def get_sans_from_cert(cert, typ=crypto.FILETYPE_PEM): + """Get a list of Subject Alternative Names from a certificate. + + :param str cert: Certificate (encoded). + :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1` + + :returns: A list of Subject Alternative Names. + :rtype: list + + """ + return _get_sans_from_cert_or_req( + cert, crypto.load_certificate, typ) + + +def _get_names_from_cert_or_req(cert_or_req, load_func, typ): + loaded_cert_or_req = _load_cert_or_req(cert_or_req, load_func, typ) + return _get_names_from_loaded_cert_or_req(loaded_cert_or_req) + + +def _get_names_from_loaded_cert_or_req(loaded_cert_or_req): + # pylint: disable=protected-access + return acme_crypto_util._pyopenssl_cert_or_req_all_names(loaded_cert_or_req) + + +def get_names_from_cert(csr, typ=crypto.FILETYPE_PEM): + """Get a list of domains from a cert, including the CN if it is set. + + :param str cert: Certificate (encoded). + :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1` + + :returns: A list of domain names. + :rtype: list + + """ + return _get_names_from_cert_or_req( + csr, crypto.load_certificate, typ) + + +def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM): + """Dump certificate chain into a bundle. + + :param list chain: List of `crypto.X509` (or wrapped in + :class:`josepy.util.ComparableX509`). + + """ + # XXX: returns empty string when no chain is available, which + # shuts up RenewableCert, but might not be the best solution... + return acme_crypto_util.dump_pyopenssl_chain(chain, filetype) + + +def notBefore(cert_path): + """When does the cert at cert_path start being valid? + + :param str cert_path: path to a cert in PEM format + + :returns: the notBefore value from the cert at cert_path + :rtype: :class:`datetime.datetime` + + """ + return _notAfterBefore(cert_path, crypto.X509.get_notBefore) + + +def notAfter(cert_path): + """When does the cert at cert_path stop being valid? + + :param str cert_path: path to a cert in PEM format + + :returns: the notAfter value from the cert at cert_path + :rtype: :class:`datetime.datetime` + + """ + return _notAfterBefore(cert_path, crypto.X509.get_notAfter) + + +def _notAfterBefore(cert_path, method): + """Internal helper function for finding notbefore/notafter. + + :param str cert_path: path to a cert in PEM format + :param function method: one of ``crypto.X509.get_notBefore`` + or ``crypto.X509.get_notAfter`` + + :returns: the notBefore or notAfter value from the cert at cert_path + :rtype: :class:`datetime.datetime` + + """ + # pylint: disable=redefined-outer-name + with open(cert_path) as f: + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, + f.read()) + # pyopenssl always returns bytes + timestamp = method(x509) + reformatted_timestamp = [timestamp[0:4], b"-", timestamp[4:6], b"-", + timestamp[6:8], b"T", timestamp[8:10], b":", + timestamp[10:12], b":", timestamp[12:]] + timestamp_str = b"".join(reformatted_timestamp) + # pyrfc3339 uses "native" strings. That is, bytes on Python 2 and unicode + # on Python 3 + if six.PY3: + timestamp_str = timestamp_str.decode('ascii') + return pyrfc3339.parse(timestamp_str) + + +def sha256sum(filename): + """Compute a sha256sum of a file. + + NB: In given file, platform specific newlines characters will be converted + into their equivalent unicode counterparts before calculating the hash. + + :param str filename: path to the file whose hash will be computed + + :returns: sha256 digest of the file in hexadecimal + :rtype: str + """ + sha256 = hashlib.sha256() + with open(filename, 'r') as file_d: + sha256.update(file_d.read().encode('UTF-8')) + return sha256.hexdigest() + +def cert_and_chain_from_fullchain(fullchain_pem): + """Split fullchain_pem into cert_pem and chain_pem + + :param str fullchain_pem: concatenated cert + chain + + :returns: tuple of string cert_pem and chain_pem + :rtype: tuple + + """ + cert = crypto.dump_certificate(crypto.FILETYPE_PEM, + crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode() + chain = fullchain_pem[len(cert):].lstrip() + return (cert, chain) diff --git a/certbot/certbot/display/__init__.py b/certbot/certbot/display/__init__.py new file mode 100644 index 000000000..9d39dce92 --- /dev/null +++ b/certbot/certbot/display/__init__.py @@ -0,0 +1 @@ +"""Certbot display utilities.""" diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py new file mode 100644 index 000000000..1a36744a5 --- /dev/null +++ b/certbot/certbot/display/ops.py @@ -0,0 +1,368 @@ +"""Contains UI methods for LE user operations.""" +import logging + +import zope.component + +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import misc +from certbot.compat import os +from certbot.display import util as display_util + +logger = logging.getLogger(__name__) + +# Define a helper function to avoid verbose code +z_util = zope.component.getUtility + + +def get_email(invalid=False, optional=True): + """Prompt for valid email address. + + :param bool invalid: True if an invalid address was provided by the user + :param bool optional: True if the user can use + --register-unsafely-without-email to avoid providing an e-mail + + :returns: e-mail address + :rtype: str + + :raises errors.Error: if the user cancels + + """ + invalid_prefix = "There seem to be problems with that address. " + msg = "Enter email address (used for urgent renewal and security notices)" + 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 " + "{0}\n\n".format(os.path.join( + misc.get_default_folder('config'), 'accounts'))) + if optional: + if invalid: + msg += unsafe_suggestion + suggest_unsafe = False + else: + suggest_unsafe = True + else: + suggest_unsafe = False + + while True: + try: + code, email = z_util(interfaces.IDisplay).input( + invalid_prefix + msg if invalid else msg, + force_interactive=True) + except errors.MissingCommandlineFlag: + msg = ("You should register before running non-interactively, " + "or provide --agree-tos and --email flags.") + raise errors.MissingCommandlineFlag(msg) + + if code != display_util.OK: + if optional: + raise errors.Error( + "An e-mail address or " + "--register-unsafely-without-email must be provided.") + else: + raise errors.Error("An e-mail address must be provided.") + elif util.safe_email(email): + return email + elif suggest_unsafe: + msg += unsafe_suggestion + suggest_unsafe = False # add this message at most once + + invalid = bool(email) + + +def choose_account(accounts): + """Choose an account. + + :param list accounts: Containing at least one + :class:`~certbot._internal.account.Account` + + """ + # Note this will get more complicated once we start recording authorizations + labels = [acc.slug for acc in accounts] + + code, index = z_util(interfaces.IDisplay).menu( + "Please choose an account", labels, force_interactive=True) + if code == display_util.OK: + return accounts[index] + return None + +def choose_values(values, question=None): + """Display screen to let user pick one or multiple values from the provided + list. + + :param list values: Values to select from + + :returns: List of selected values + :rtype: list + """ + code, items = z_util(interfaces.IDisplay).checklist( + question, tags=values, force_interactive=True) + if code == display_util.OK and items: + return items + return [] + +def choose_names(installer, question=None): + """Display screen to select domains to validate. + + :param installer: An installer object + :type installer: :class:`certbot.interfaces.IInstaller` + + :param `str` question: Overriding dialog question to ask the user if asked + to choose from domain names. + + :returns: List of selected names + :rtype: `list` of `str` + + """ + if installer is None: + logger.debug("No installer, picking names manually") + return _choose_names_manually() + + domains = list(installer.get_all_names()) + names = get_valid_domains(domains) + + if not names: + return _choose_names_manually( + "No names were found in your configuration files. ") + + code, names = _filter_names(names, question) + if code == display_util.OK and names: + return names + return [] + + +def get_valid_domains(domains): + """Helper method for choose_names that implements basic checks + on domain names + + :param list domains: Domain names to validate + :return: List of valid domains + :rtype: list + """ + valid_domains = [] + for domain in domains: + try: + valid_domains.append(util.enforce_domain_sanity(domain)) + except errors.ConfigurationError: + continue + return valid_domains + +def _sort_names(FQDNs): + """Sort FQDNs by SLD (and if many, by their subdomains) + + :param list FQDNs: list of domain names + + :returns: Sorted list of domain names + :rtype: list + """ + return sorted(FQDNs, key=lambda fqdn: fqdn.split('.')[::-1][1:]) + + +def _filter_names(names, override_question=None): + """Determine which names the user would like to select from a list. + + :param list names: domain names + + :returns: tuple of the form (`code`, `names`) where + `code` - str display exit code + `names` - list of names selected + :rtype: tuple + + """ + #Sort by domain first, and then by subdomain + sorted_names = _sort_names(names) + if override_question: + question = override_question + else: + question = "Which names would you like to activate HTTPS for?" + code, names = z_util(interfaces.IDisplay).checklist( + question, tags=sorted_names, cli_flag="--domains", force_interactive=True) + return code, [str(s) for s in names] + + +def _choose_names_manually(prompt_prefix=""): + """Manually input names for those without an installer. + + :param str prompt_prefix: string to prepend to prompt for domains + + :returns: list of provided names + :rtype: `list` of `str` + + """ + code, input_ = z_util(interfaces.IDisplay).input( + prompt_prefix + + "Please enter in your domain name(s) (comma and/or space separated) ", + cli_flag="--domains", force_interactive=True) + + if code == display_util.OK: + invalid_domains = dict() + retry_message = "" + try: + domain_list = display_util.separate_list_input(input_) + except UnicodeEncodeError: + domain_list = [] + retry_message = ( + "Internationalized domain names are not presently " + "supported.{0}{0}Would you like to re-enter the " + "names?{0}").format(os.linesep) + + for i, domain in enumerate(domain_list): + try: + domain_list[i] = util.enforce_domain_sanity(domain) + except errors.ConfigurationError as e: + invalid_domains[domain] = str(e) + + if invalid_domains: + retry_message = ( + "One or more of the entered domain names was not valid:" + "{0}{0}").format(os.linesep) + for domain in invalid_domains: + retry_message = retry_message + "{1}: {2}{0}".format( + os.linesep, domain, invalid_domains[domain]) + retry_message = retry_message + ( + "{0}Would you like to re-enter the names?{0}").format( + os.linesep) + + if retry_message: + # We had error in input + retry = z_util(interfaces.IDisplay).yesno(retry_message, + force_interactive=True) + if retry: + return _choose_names_manually() + else: + return domain_list + return [] + + +def success_installation(domains): + """Display a box confirming the installation of HTTPS. + + :param list domains: domain names which were enabled + + """ + z_util(interfaces.IDisplay).notification( + "Congratulations! You have successfully enabled {0}{1}{1}" + "You should test your configuration at:{1}{2}".format( + _gen_https_names(domains), + os.linesep, + os.linesep.join(_gen_ssl_lab_urls(domains))), + pause=False) + + +def success_renewal(domains): + """Display a box confirming the renewal of an existing certificate. + + :param list domains: domain names which were renewed + + """ + z_util(interfaces.IDisplay).notification( + "Your existing certificate has been successfully renewed, and the " + "new certificate has been installed.{1}{1}" + "The new certificate covers the following domains: {0}{1}{1}" + "You should test your configuration at:{1}{2}".format( + _gen_https_names(domains), + os.linesep, + os.linesep.join(_gen_ssl_lab_urls(domains))), + pause=False) + +def success_revocation(cert_path): + """Display a box confirming a certificate has been revoked. + + :param list cert_path: path to certificate which was revoked. + + """ + z_util(interfaces.IDisplay).notification( + "Congratulations! You have successfully revoked the certificate " + "that was located at {0}{1}{1}".format( + cert_path, + os.linesep), + pause=False) + + +def _gen_ssl_lab_urls(domains): + """Returns a list of urls. + + :param list domains: Each domain is a 'str' + + """ + return ["https://www.ssllabs.com/ssltest/analyze.html?d=%s" % dom for dom in domains] + + +def _gen_https_names(domains): + """Returns a string of the https domains. + + Domains are formatted nicely with https:// prepended to each. + + :param list domains: Each domain is a 'str' + + """ + if len(domains) == 1: + return "https://{0}".format(domains[0]) + elif len(domains) == 2: + return "https://{dom[0]} and https://{dom[1]}".format(dom=domains) + elif len(domains) > 2: + return "{0}{1}{2}".format( + ", ".join("https://%s" % dom for dom in domains[:-1]), + ", and https://", + domains[-1]) + + return "" + + +def _get_validated(method, validator, message, default=None, **kwargs): + if default is not None: + try: + validator(default) + except errors.Error as error: + logger.debug('Encountered invalid default value "%s" when prompting for "%s"', + default, + message, + exc_info=True) + raise AssertionError('Invalid default "{0}"'.format(default)) + + while True: + code, raw = method(message, default=default, **kwargs) + if code == display_util.OK: + try: + validator(raw) + return code, raw + except errors.Error as error: + logger.debug('Validator rejected "%s" when prompting for "%s"', + raw, + message, + exc_info=True) + zope.component.getUtility(interfaces.IDisplay).notification(str(error), pause=False) + else: + return code, raw + + +def validated_input(validator, *args, **kwargs): + """Like `~certbot.interfaces.IDisplay.input`, but with validation. + + :param callable validator: A method which will be called on the + supplied input. If the method raises a `errors.Error`, its + text will be displayed and the user will be re-prompted. + :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. + :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. + :return: as `~certbot.interfaces.IDisplay.input` + :rtype: tuple + """ + return _get_validated(zope.component.getUtility(interfaces.IDisplay).input, + validator, *args, **kwargs) + + +def validated_directory(validator, *args, **kwargs): + """Like `~certbot.interfaces.IDisplay.directory_select`, but with validation. + + :param callable validator: A method which will be called on the + supplied input. If the method raises a `errors.Error`, its + text will be displayed and the user will be re-prompted. + :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`. + :param dict `**kwargs`: Arguments to be passed to + `~certbot.interfaces.IDisplay.directory_select`. + :return: as `~certbot.interfaces.IDisplay.directory_select` + :rtype: tuple + """ + return _get_validated(zope.component.getUtility(interfaces.IDisplay).directory_select, + validator, *args, **kwargs) diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py new file mode 100644 index 000000000..b79ba338f --- /dev/null +++ b/certbot/certbot/display/util.py @@ -0,0 +1,598 @@ +"""Certbot display.""" +import logging +import sys +import textwrap + +import zope.interface + +from certbot._internal import constants +from certbot import errors +from certbot import interfaces +from certbot.compat import misc +from certbot.compat import os +from certbot._internal.display import completer + +logger = logging.getLogger(__name__) + +WIDTH = 72 + +# Display exit codes +OK = "ok" +"""Display exit code indicating user acceptance.""" + +CANCEL = "cancel" +"""Display exit code for a user canceling the display.""" + +HELP = "help" +"""Display exit code when for when the user requests more help. (UNUSED)""" + +ESC = "esc" +"""Display exit code when the user hits Escape (UNUSED)""" + +# Display constants +SIDE_FRAME = ("- " * 39) + "-" +"""Display boundary (alternates spaces, so when copy-pasted, markdown doesn't interpret +it as a heading)""" + +def _wrap_lines(msg): + """Format lines nicely to 80 chars. + + :param str msg: Original message + + :returns: Formatted message respecting newlines in message + :rtype: str + + """ + lines = msg.splitlines() + fixed_l = [] + + for line in lines: + fixed_l.append(textwrap.fill( + line, + 80, + break_long_words=False, + break_on_hyphens=False)) + + return '\n'.join(fixed_l) + + +def input_with_timeout(prompt=None, timeout=36000.0): + """Get user input with a timeout. + + Behaves the same as six.moves.input, however, an error is raised if + a user doesn't answer after timeout seconds. The default timeout + value was chosen to place it just under 12 hours for users following + our advice and running Certbot twice a day. + + :param str prompt: prompt to provide for input + :param float timeout: maximum number of seconds to wait for input + + :returns: user response + :rtype: str + + :raises errors.Error if no answer is given before the timeout + + """ + # use of sys.stdin and sys.stdout to mimic six.moves.input based on + # https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129 + if prompt: + sys.stdout.write(prompt) + sys.stdout.flush() + + line = misc.readline_with_timeout(timeout, prompt) + + if not line: + raise EOFError + return line.rstrip('\n') + + +@zope.interface.implementer(interfaces.IDisplay) +class FileDisplay(object): + """File-based display.""" + # see https://github.com/certbot/certbot/issues/3915 + + def __init__(self, outfile, force_interactive): + super(FileDisplay, self).__init__() + self.outfile = outfile + self.force_interactive = force_interactive + self.skipped_interaction = False + + def notification(self, message, pause=True, + wrap=True, force_interactive=False): + """Displays a notification and waits for user acceptance. + + :param str message: Message to display + :param bool pause: Whether or not the program should pause for the + user's confirmation + :param bool wrap: Whether or not the application should wrap text + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + """ + if wrap: + message = _wrap_lines(message) + self.outfile.write( + "{line}{frame}{line}{msg}{line}{frame}{line}".format( + line='\n', frame=SIDE_FRAME, msg=message)) + self.outfile.flush() + if pause: + if self._can_interact(force_interactive): + input_with_timeout("Press Enter to Continue") + else: + logger.debug("Not pausing for user confirmation") + + def menu(self, message, choices, ok_label=None, cancel_label=None, # pylint: disable=unused-argument + help_label=None, default=None, # pylint: disable=unused-argument + cli_flag=None, force_interactive=False, **unused_kwargs): + """Display a menu. + + .. todo:: This doesn't enable the help label/button (I wasn't sold on + any interface I came up with for this). It would be a nice feature + + :param str message: title of menu + :param choices: Menu lines, len must be > 0 + :type choices: list of tuples (tag, item) or + list of descriptions (tags will be enumerated) + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of (`code`, `index`) where + `code` - str display exit code + `index` - int index of the user's selection + + :rtype: tuple + + """ + if self._return_default(message, default, cli_flag, force_interactive): + return OK, default + + self._print_menu(message, choices) + + code, selection = self._get_valid_int_ans(len(choices)) + + return code, selection - 1 + + def input(self, message, default=None, + cli_flag=None, force_interactive=False, **unused_kwargs): + """Accept input from the user. + + :param str message: message to display to the user + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of (`code`, `input`) where + `code` - str display exit code + `input` - str of the user's input + :rtype: tuple + + """ + if self._return_default(message, default, cli_flag, force_interactive): + return OK, default + + # Trailing space must be added outside of _wrap_lines to be preserved + message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " " + ans = input_with_timeout(message) + + if ans == "c" or ans == "C": + return CANCEL, "-1" + return OK, ans + + def yesno(self, message, yes_label="Yes", no_label="No", default=None, + cli_flag=None, force_interactive=False, **unused_kwargs): + """Query the user with a yes/no question. + + Yes and No label must begin with different letters, and must contain at + least one letter each. + + :param str message: question for the user + :param str yes_label: Label of the "Yes" parameter + :param str no_label: Label of the "No" parameter + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: True for "Yes", False for "No" + :rtype: bool + + """ + if self._return_default(message, default, cli_flag, force_interactive): + return default + + message = _wrap_lines(message) + + self.outfile.write("{0}{frame}{msg}{0}{frame}".format( + os.linesep, frame=SIDE_FRAME + os.linesep, msg=message)) + self.outfile.flush() + + while True: + ans = input_with_timeout("{yes}/{no}: ".format( + yes=_parens_around_char(yes_label), + no=_parens_around_char(no_label))) + + # Couldn't get pylint indentation right with elif + # elif doesn't matter in this situation + if (ans.startswith(yes_label[0].lower()) or + ans.startswith(yes_label[0].upper())): + return True + if (ans.startswith(no_label[0].lower()) or + ans.startswith(no_label[0].upper())): + return False + + def checklist(self, message, tags, default=None, + cli_flag=None, force_interactive=False, **unused_kwargs): + """Display a checklist. + + :param str message: Message to display to user + :param list tags: `str` tags to select, len(tags) > 0 + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of (`code`, `tags`) where + `code` - str display exit code + `tags` - list of selected tags + :rtype: tuple + + """ + if self._return_default(message, default, cli_flag, force_interactive): + return OK, default + + while True: + self._print_menu(message, tags) + + code, ans = self.input("Select the appropriate numbers separated " + "by commas and/or spaces, or leave input " + "blank to select all options shown", + force_interactive=True) + + if code == OK: + if not ans.strip(): + ans = " ".join(str(x) for x in range(1, len(tags)+1)) + indices = separate_list_input(ans) + selected_tags = self._scrub_checklist_input(indices, tags) + if selected_tags: + return code, selected_tags + else: + self.outfile.write( + "** Error - Invalid selection **%s" % os.linesep) + self.outfile.flush() + else: + return code, [] + + def _return_default(self, prompt, default, cli_flag, force_interactive): + """Should we return the default instead of prompting the user? + + :param str prompt: prompt for the user + :param default: default answer to prompt + :param str cli_flag: command line option for setting an answer + to this question + :param bool force_interactive: if interactivity is forced by the + IDisplay call + + :returns: True if we should return the default without prompting + :rtype: bool + + """ + # assert_valid_call(prompt, default, cli_flag, force_interactive) + if self._can_interact(force_interactive): + return False + elif default is None: + msg = "Unable to get an answer for the question:\n{0}".format(prompt) + if cli_flag: + msg += ( + "\nYou can provide an answer on the " + "command line with the {0} flag.".format(cli_flag)) + raise errors.Error(msg) + else: + logger.debug( + "Falling back to default %s for the prompt:\n%s", + default, prompt) + return True + + def _can_interact(self, force_interactive): + """Can we safely interact with the user? + + :param bool force_interactive: if interactivity is forced by the + IDisplay call + + :returns: True if the display can interact with the user + :rtype: bool + + """ + if (self.force_interactive or force_interactive or + sys.stdin.isatty() and self.outfile.isatty()): + return True + elif not self.skipped_interaction: + logger.warning( + "Skipped user interaction because Certbot doesn't appear to " + "be running in a terminal. You should probably include " + "--non-interactive or %s on the command line.", + constants.FORCE_INTERACTIVE_FLAG) + self.skipped_interaction = True + return False + + def directory_select(self, message, default=None, cli_flag=None, + force_interactive=False, **unused_kwargs): + """Display a directory selection screen. + + :param str message: prompt to give the user + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of the form (`code`, `string`) where + `code` - display exit code + `string` - input entered by the user + + """ + with completer.Completer(): + return self.input(message, default, cli_flag, force_interactive) + + def _scrub_checklist_input(self, indices, tags): + # pylint: disable=no-self-use + """Validate input and transform indices to appropriate tags. + + :param list indices: input + :param list tags: Original tags of the checklist + + :returns: valid tags the user selected + :rtype: :class:`list` of :class:`str` + + """ + # They should all be of type int + try: + indices = [int(index) for index in indices] + except ValueError: + return [] + + # Remove duplicates + indices = list(set(indices)) + + # Check all input is within range + for index in indices: + if index < 1 or index > len(tags): + return [] + # Transform indices to appropriate tags + return [tags[index - 1] for index in indices] + + def _print_menu(self, message, choices): + """Print a menu on the screen. + + :param str message: title of menu + :param choices: Menu lines + :type choices: list of tuples (tag, item) or + list of descriptions (tags will be enumerated) + + """ + # Can take either tuples or single items in choices list + if choices and isinstance(choices[0], tuple): + choices = ["%s - %s" % (c[0], c[1]) for c in choices] + + # Write out the message to the user + self.outfile.write( + "{new}{msg}{new}".format(new=os.linesep, msg=message)) + self.outfile.write(SIDE_FRAME + os.linesep) + + # Write out the menu choices + for i, desc in enumerate(choices, 1): + msg = "{num}: {desc}".format(num=i, desc=desc) + self.outfile.write(_wrap_lines(msg)) + + # Keep this outside of the textwrap + self.outfile.write(os.linesep) + + self.outfile.write(SIDE_FRAME + os.linesep) + self.outfile.flush() + + def _get_valid_int_ans(self, max_): + """Get a numerical selection. + + :param int max: The maximum entry (len of choices), must be positive + + :returns: tuple of the form (`code`, `selection`) where + `code` - str display exit code ('ok' or cancel') + `selection` - int user's selection + :rtype: tuple + + """ + selection = -1 + if max_ > 1: + input_msg = ("Select the appropriate number " + "[1-{max_}] then [enter] (press 'c' to " + "cancel): ".format(max_=max_)) + else: + input_msg = ("Press 1 [enter] to confirm the selection " + "(press 'c' to cancel): ") + while selection < 1: + ans = input_with_timeout(input_msg) + if ans.startswith("c") or ans.startswith("C"): + return CANCEL, -1 + try: + selection = int(ans) + if selection < 1 or selection > max_: + selection = -1 + raise ValueError + + except ValueError: + self.outfile.write( + "{0}** Invalid input **{0}".format(os.linesep)) + self.outfile.flush() + + return OK, selection + + +def assert_valid_call(prompt, default, cli_flag, force_interactive): + """Verify that provided arguments is a valid IDisplay call. + + :param str prompt: prompt for the user + :param default: default answer to prompt + :param str cli_flag: command line option for setting an answer + to this question + :param bool force_interactive: if interactivity is forced by the + IDisplay call + + """ + msg = "Invalid IDisplay call for this prompt:\n{0}".format(prompt) + if cli_flag: + msg += ("\nYou can set an answer to " + "this prompt with the {0} flag".format(cli_flag)) + assert default is not None or force_interactive, msg + + +@zope.interface.implementer(interfaces.IDisplay) +class NoninteractiveDisplay(object): + """An iDisplay implementation that never asks for interactive user input""" + + def __init__(self, outfile, *unused_args, **unused_kwargs): + super(NoninteractiveDisplay, self).__init__() + self.outfile = outfile + + def _interaction_fail(self, message, cli_flag, extra=""): + "Error out in case of an attempt to interact in noninteractive mode" + msg = "Missing command line flag or config entry for this setting:\n" + msg += message + if extra: + msg += "\n" + extra + if cli_flag: + msg += "\n\n(You can set this with the {0} flag)".format(cli_flag) + raise errors.MissingCommandlineFlag(msg) + + def notification(self, message, pause=False, wrap=True, **unused_kwargs): # pylint: disable=unused-argument + """Displays a notification without waiting for user acceptance. + + :param str message: Message to display to stdout + :param bool pause: The NoninteractiveDisplay waits for no keyboard + :param bool wrap: Whether or not the application should wrap text + + """ + if wrap: + message = _wrap_lines(message) + self.outfile.write( + "{line}{frame}{line}{msg}{line}{frame}{line}".format( + line=os.linesep, frame=SIDE_FRAME, msg=message)) + self.outfile.flush() + + def menu(self, message, choices, ok_label=None, cancel_label=None, + help_label=None, default=None, cli_flag=None, **unused_kwargs): + # pylint: disable=unused-argument + """Avoid displaying a menu. + + :param str message: title of menu + :param choices: Menu lines, len must be > 0 + :type choices: list of tuples (tag, item) or + list of descriptions (tags will be enumerated) + :param int default: the default choice + :param dict kwargs: absorbs various irrelevant labelling arguments + + :returns: tuple of (`code`, `index`) where + `code` - str display exit code + `index` - int index of the user's selection + :rtype: tuple + :raises errors.MissingCommandlineFlag: if there was no default + + """ + if default is None: + self._interaction_fail(message, cli_flag, "Choices: " + repr(choices)) + + return OK, default + + def input(self, message, default=None, cli_flag=None, **unused_kwargs): + """Accept input from the user. + + :param str message: message to display to the user + + :returns: tuple of (`code`, `input`) where + `code` - str display exit code + `input` - str of the user's input + :rtype: tuple + :raises errors.MissingCommandlineFlag: if there was no default + + """ + if default is None: + self._interaction_fail(message, cli_flag) + return OK, default + + def yesno(self, message, yes_label=None, no_label=None, # pylint: disable=unused-argument + default=None, cli_flag=None, **unused_kwargs): + """Decide Yes or No, without asking anybody + + :param str message: question for the user + :param dict kwargs: absorbs yes_label, no_label + + :raises errors.MissingCommandlineFlag: if there was no default + :returns: True for "Yes", False for "No" + :rtype: bool + + """ + if default is None: + self._interaction_fail(message, cli_flag) + return default + + def checklist(self, message, tags, default=None, + cli_flag=None, **unused_kwargs): + """Display a checklist. + + :param str message: Message to display to user + :param list tags: `str` tags to select, len(tags) > 0 + :param dict kwargs: absorbs default_status arg + + :returns: tuple of (`code`, `tags`) where + `code` - str display exit code + `tags` - list of selected tags + :rtype: tuple + + """ + if default is None: + self._interaction_fail(message, cli_flag, "? ".join(tags)) + return OK, default + + def directory_select(self, message, default=None, + cli_flag=None, **unused_kwargs): + """Simulate prompting the user for a directory. + + This function returns default if it is not ``None``, otherwise, + an exception is raised explaining the problem. If cli_flag is + not ``None``, the error message will include the flag that can + be used to set this value with the CLI. + + :param str message: prompt to give the user + :param default: default value to return (if one exists) + :param str cli_flag: option used to set this value with the CLI + + :returns: tuple of the form (`code`, `string`) where + `code` - int display exit code + `string` - input entered by the user + + """ + return self.input(message, default, cli_flag) + + +def separate_list_input(input_): + """Separate a comma or space separated list. + + :param str input_: input from the user + + :returns: strings + :rtype: list + + """ + no_commas = input_.replace(",", " ") + # Each string is naturally unicode, this causes problems with M2Crypto SANs + # TODO: check if above is still true when M2Crypto is gone ^ + return [str(string) for string in no_commas.split()] + + +def _parens_around_char(label): + """Place parens around first character of label. + + :param str label: Must contain at least one character + + """ + return "({first}){rest}".format(first=label[0], rest=label[1:]) diff --git a/certbot/certbot/errors.py b/certbot/certbot/errors.py new file mode 100644 index 000000000..48aebc267 --- /dev/null +++ b/certbot/certbot/errors.py @@ -0,0 +1,110 @@ +"""Certbot client errors.""" + + +class Error(Exception): + """Generic Certbot client error.""" + + +class AccountStorageError(Error): + """Generic `.AccountStorage` error.""" + + +class AccountNotFound(AccountStorageError): + """Account not found error.""" + + +class ReverterError(Error): + """Certbot Reverter error.""" + + +class SubprocessError(Error): + """Subprocess handling error.""" + + +class CertStorageError(Error): + """Generic `.CertStorage` error.""" + + +class HookCommandNotFound(Error): + """Failed to find a hook command in the PATH.""" + + +class SignalExit(Error): + """A Unix signal was received while in the ErrorHandler context manager.""" + +class OverlappingMatchFound(Error): + """Multiple lineages matched what should have been a unique result.""" + +class LockError(Error): + """File locking error.""" + + +# Auth Handler Errors +class AuthorizationError(Error): + """Authorization error.""" + + +class FailedChallenges(AuthorizationError): + """Failed challenges error. + + :ivar set failed_achalls: Failed `.AnnotatedChallenge` instances. + + """ + def __init__(self, failed_achalls): + assert failed_achalls + self.failed_achalls = failed_achalls + super(FailedChallenges, self).__init__() + + def __str__(self): + return "Failed authorization procedure. {0}".format( + ", ".join( + "{0} ({1}): {2}".format(achall.domain, achall.typ, achall.error) + for achall in self.failed_achalls if achall.error is not None)) + + +# Plugin Errors +class PluginError(Error): + """Certbot Plugin error.""" + + +class PluginEnhancementAlreadyPresent(Error): + """ Enhancement was already set """ + + +class PluginSelectionError(Error): + """A problem with plugin/configurator selection or setup""" + + +class NoInstallationError(PluginError): + """Certbot No Installation error.""" + + +class MisconfigurationError(PluginError): + """Certbot Misconfiguration error.""" + + +class NotSupportedError(PluginError): + """Certbot Plugin function not supported error.""" + + +class PluginStorageError(PluginError): + """Certbot Plugin Storage error.""" + + +class StandaloneBindError(Error): + """Standalone plugin bind error.""" + + def __init__(self, socket_error, port): + super(StandaloneBindError, self).__init__( + "Problem binding to port {0}: {1}".format(port, socket_error)) + self.socket_error = socket_error + self.port = port + + +class ConfigurationError(Error): + """Configuration sanity error.""" + +# NoninteractiveDisplay iDisplay plugin error: + +class MissingCommandlineFlag(Error): + """A command line argument was missing in noninteractive usage""" diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py new file mode 100644 index 000000000..edf71e63f --- /dev/null +++ b/certbot/certbot/interfaces.py @@ -0,0 +1,604 @@ +"""Certbot client interfaces.""" +import abc +import six +import zope.interface + +# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class + + +@six.add_metaclass(abc.ABCMeta) +class AccountStorage(object): + """Accounts storage interface.""" + + @abc.abstractmethod + def find_all(self): # pragma: no cover + """Find all accounts. + + :returns: All found accounts. + :rtype: list + + """ + raise NotImplementedError() + + @abc.abstractmethod + def load(self, account_id): # pragma: no cover + """Load an account by its id. + + :raises .AccountNotFound: if account could not be found + :raises .AccountStorageError: if account could not be loaded + + """ + raise NotImplementedError() + + @abc.abstractmethod + def save(self, account, client): # pragma: no cover + """Save account. + + :raises .AccountStorageError: if account could not be saved + + """ + raise NotImplementedError() + + +class IPluginFactory(zope.interface.Interface): + """IPlugin factory. + + Objects providing this interface will be called without satisfying + any entry point "extras" (extra dependencies) you might have defined + for your plugin, e.g (excerpt from ``setup.py`` script):: + + setup( + ... + entry_points={ + 'certbot.plugins': [ + 'name=example_project.plugin[plugin_deps]', + ], + }, + extras_require={ + 'plugin_deps': ['dep1', 'dep2'], + } + ) + + Therefore, make sure such objects are importable and usable without + extras. This is necessary, because CLI does the following operations + (in order): + + - loads an entry point, + - calls `inject_parser_options`, + - requires an entry point, + - creates plugin instance (`__call__`). + + """ + + description = zope.interface.Attribute("Short plugin description") + + def __call__(config, name): # pylint: disable=signature-differs + """Create new `IPlugin`. + + :param IConfig config: Configuration. + :param str name: Unique plugin name. + + """ + + def inject_parser_options(parser, name): + """Inject argument parser options (flags). + + 1. Be nice and prepend all options and destinations with + `~.common.option_namespace` and `~common.dest_namespace`. + + 2. Inject options (flags) only. Positional arguments are not + allowed, as this would break the CLI. + + :param ArgumentParser parser: (Almost) top-level CLI parser. + :param str name: Unique plugin name. + + """ + + +class IPlugin(zope.interface.Interface): + """Certbot plugin.""" + + def prepare(): # type: ignore + """Prepare the plugin. + + Finish up any additional initialization. + + :raises .PluginError: + when full initialization cannot be completed. + :raises .MisconfigurationError: + when full initialization cannot be completed. Plugin will + be displayed on a list of available plugins. + :raises .NoInstallationError: + when the necessary programs/files cannot be located. Plugin + will NOT be displayed on a list of available plugins. + :raises .NotSupportedError: + when the installation is recognized, but the version is not + currently supported. + + """ + + def more_info(): # type: ignore + """Human-readable string to help the user. + + Should describe the steps taken and any relevant info to help the user + decide which plugin to use. + + :rtype str: + + """ + + +class IAuthenticator(IPlugin): + """Generic Certbot Authenticator. + + Class represents all possible tools processes that have the + ability to perform challenges and attain a certificate. + + """ + + def get_chall_pref(domain): + """Return `collections.Iterable` of challenge preferences. + + :param str domain: Domain for which challenge preferences are sought. + + :returns: `collections.Iterable` of challenge types (subclasses of + :class:`acme.challenges.Challenge`) with the most + preferred challenges first. If a type is not specified, it means the + Authenticator cannot perform the challenge. + :rtype: `collections.Iterable` + + """ + + def perform(achalls): + """Perform the given challenge. + + :param list achalls: Non-empty (guaranteed) list of + :class:`~certbot.achallenges.AnnotatedChallenge` + instances, such that it contains types found within + :func:`get_chall_pref` only. + + :returns: `collections.Iterable` of ACME + :class:`~acme.challenges.ChallengeResponse` instances corresponding to each provided + :class:`~acme.challenges.Challenge`. + :rtype: :class:`collections.Iterable` of + :class:`acme.challenges.ChallengeResponse`, + where responses are required to be returned in + the same order as corresponding input challenges + + :raises .PluginError: If some or all challenges cannot be performed + + """ + + def cleanup(achalls): + """Revert changes and shutdown after challenges complete. + + This method should be able to revert all changes made by + perform, even if perform exited abnormally. + + :param list achalls: Non-empty (guaranteed) list of + :class:`~certbot.achallenges.AnnotatedChallenge` + instances, a subset of those previously passed to :func:`perform`. + + :raises PluginError: if original configuration cannot be restored + + """ + + +class IConfig(zope.interface.Interface): + """Certbot user-supplied configuration. + + .. warning:: The values stored in the configuration have not been + filtered, stripped or sanitized. + + """ + server = zope.interface.Attribute("ACME Directory Resource URI.") + email = zope.interface.Attribute( + "Email used for registration and recovery contact. Use comma to " + "register multiple emails, ex: u1@example.com,u2@example.com. " + "(default: Ask).") + rsa_key_size = zope.interface.Attribute("Size of the RSA key.") + must_staple = zope.interface.Attribute( + "Adds the OCSP Must Staple extension to the certificate. " + "Autoconfigures OCSP Stapling for supported setups " + "(Apache version >= 2.3.3 ).") + + config_dir = zope.interface.Attribute("Configuration directory.") + work_dir = zope.interface.Attribute("Working directory.") + + accounts_dir = zope.interface.Attribute( + "Directory where all account information is stored.") + backup_dir = zope.interface.Attribute("Configuration backups directory.") + csr_dir = zope.interface.Attribute( + "Directory where newly generated Certificate Signing Requests " + "(CSRs) are saved.") + in_progress_dir = zope.interface.Attribute( + "Directory used before a permanent checkpoint is finalized.") + key_dir = zope.interface.Attribute("Keys storage.") + temp_checkpoint_dir = zope.interface.Attribute( + "Temporary checkpoint directory.") + + no_verify_ssl = zope.interface.Attribute( + "Disable verification of the ACME server's certificate.") + + http01_port = zope.interface.Attribute( + "Port used in the http-01 challenge. " + "This only affects the port Certbot listens on. " + "A conforming ACME server will still attempt to connect on port 80.") + + http01_address = zope.interface.Attribute( + "The address the server listens to during http-01 challenge.") + + https_port = zope.interface.Attribute( + "Port used to serve HTTPS. " + "This affects which port Nginx will listen on after a LE certificate " + "is installed.") + + pref_challs = zope.interface.Attribute( + "Sorted user specified preferred challenges" + "type strings with the most preferred challenge listed first") + + allow_subset_of_names = zope.interface.Attribute( + "When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This is a boolean") + + strict_permissions = zope.interface.Attribute( + "Require that all configuration files are owned by the current " + "user; only needed if your config is somewhere unsafe like /tmp/." + "This is a boolean") + + disable_renew_updates = zope.interface.Attribute( + "If updates provided by installer enhancements when Certbot is being run" + " with \"renew\" verb should be disabled.") + +class IInstaller(IPlugin): + """Generic Certbot Installer Interface. + + Represents any server that an X509 certificate can be placed. + + It is assumed that :func:`save` is the only method that finalizes a + checkpoint. This is important to ensure that checkpoints are + restored in a consistent manner if requested by the user or in case + of an error. + + Using :class:`certbot.reverter.Reverter` to implement checkpoints, + rollback, and recovery can dramatically simplify plugin development. + + """ + + def get_all_names(): # type: ignore + """Returns all names that may be authenticated. + + :rtype: `collections.Iterable` of `str` + + """ + + def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path): + """Deploy certificate. + + :param str domain: domain to deploy certificate file + :param str cert_path: absolute path to the certificate file + :param str key_path: absolute path to the private key file + :param str chain_path: absolute path to the certificate chain file + :param str fullchain_path: absolute path to the certificate fullchain + file (cert plus chain) + + :raises .PluginError: when cert cannot be deployed + + """ + + def enhance(domain, enhancement, options=None): + """Perform a configuration enhancement. + + :param str domain: domain for which to provide enhancement + :param str enhancement: An enhancement as defined in + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + :param options: Flexible options parameter for enhancement. + Check documentation of + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + for expected options for each enhancement. + + :raises .PluginError: If Enhancement is not supported, or if + an error occurs during the enhancement. + + """ + + def supported_enhancements(): # type: ignore + """Returns a `collections.Iterable` of supported enhancements. + + :returns: supported enhancements which should be a subset of + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + :rtype: :class:`collections.Iterable` of :class:`str` + + """ + + def save(title=None, temporary=False): + """Saves all changes to the configuration files. + + Both title and temporary are needed because a save may be + intended to be permanent, but the save is not ready to be a full + checkpoint. + + It is assumed that at most one checkpoint is finalized by this + method. Additionally, if an exception is raised, it is assumed a + new checkpoint was not finalized. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. `title` has no effect if temporary is true. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (challenges) + + :raises .PluginError: when save is unsuccessful + + """ + + def rollback_checkpoints(rollback=1): + """Revert `rollback` number of configuration checkpoints. + + :raises .PluginError: when configuration cannot be fully reverted + + """ + + def recovery_routine(): # type: ignore + """Revert configuration to most recent finalized checkpoint. + + Remove all changes (temporary and permanent) that have not been + finalized. This is useful to protect against crashes and other + execution interruptions. + + :raises .errors.PluginError: If unable to recover the configuration + + """ + + def config_test(): # type: ignore + """Make sure the configuration is valid. + + :raises .MisconfigurationError: when the config is not in a usable state + + """ + + def restart(): # type: ignore + """Restart or refresh the server content. + + :raises .PluginError: when server cannot be restarted + + """ + + +class IDisplay(zope.interface.Interface): + """Generic display.""" + # see https://github.com/certbot/certbot/issues/3915 + + def notification(message, pause, wrap=True, force_interactive=False): + """Displays a string message + + :param str message: Message to display + :param bool pause: Whether or not the application should pause for + confirmation (if available) + :param bool wrap: Whether or not the application should wrap text + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + """ + + def menu(message, choices, ok_label=None, + cancel_label=None, help_label=None, + default=None, cli_flag=None, force_interactive=False): + """Displays a generic menu. + + When not setting force_interactive=True, you must provide a + default value. + + :param str message: message to display + + :param choices: choices + :type choices: :class:`list` of :func:`tuple` or :class:`str` + + :param str ok_label: label for OK button (UNUSED) + :param str cancel_label: label for Cancel button (UNUSED) + :param str help_label: label for Help button (UNUSED) + :param int default: default (non-interactive) choice from the menu + :param str cli_flag: to automate choice from the menu, eg "--keep" + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of (`code`, `index`) where + `code` - str display exit code + `index` - int index of the user's selection + + :raises errors.MissingCommandlineFlag: if called in non-interactive + mode without a default set + + """ + + def input(message, default=None, cli_args=None, force_interactive=False): + """Accept input from the user. + + When not setting force_interactive=True, you must provide a + default value. + + :param str message: message to display to the user + :param str default: default (non-interactive) response to prompt + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of (`code`, `input`) where + `code` - str display exit code + `input` - str of the user's input + :rtype: tuple + + :raises errors.MissingCommandlineFlag: if called in non-interactive + mode without a default set + + """ + + def yesno(message, yes_label="Yes", no_label="No", default=None, + cli_args=None, force_interactive=False): + """Query the user with a yes/no question. + + Yes and No label must begin with different letters. + + When not setting force_interactive=True, you must provide a + default value. + + :param str message: question for the user + :param str default: default (non-interactive) choice from the menu + :param str cli_flag: to automate choice from the menu, eg "--redirect / --no-redirect" + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: True for "Yes", False for "No" + :rtype: bool + + :raises errors.MissingCommandlineFlag: if called in non-interactive + mode without a default set + + """ + + def checklist(message, tags, default=None, cli_args=None, force_interactive=False): + """Allow for multiple selections from a menu. + + When not setting force_interactive=True, you must provide a + default value. + + :param str message: message to display to the user + :param list tags: where each is of type :class:`str` len(tags) > 0 + :param str default: default (non-interactive) state of the checklist + :param str cli_flag: to automate choice from the menu, eg "--domains" + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of the form (code, list_tags) where + `code` - int display exit code + `list_tags` - list of str tags selected by the user + :rtype: tuple + + :raises errors.MissingCommandlineFlag: if called in non-interactive + mode without a default set + + """ + + def directory_select(self, message, default=None, + cli_flag=None, force_interactive=False): + """Display a directory selection screen. + + When not setting force_interactive=True, you must provide a + default value. + + :param str message: prompt to give the user + :param default: the default value to return, if one exists, when + using the NoninteractiveDisplay + :param str cli_flag: option used to set this value with the CLI, + if one exists, to be included in error messages given by + NoninteractiveDisplay + :param bool force_interactive: True if it's safe to prompt the user + because it won't cause any workflow regressions + + :returns: tuple of the form (`code`, `string`) where + `code` - int display exit code + `string` - input entered by the user + + """ + + +class IReporter(zope.interface.Interface): + """Interface to collect and display information to the user.""" + + HIGH_PRIORITY = zope.interface.Attribute( + "Used to denote high priority messages") + MEDIUM_PRIORITY = zope.interface.Attribute( + "Used to denote medium priority messages") + LOW_PRIORITY = zope.interface.Attribute( + "Used to denote low priority messages") + + def add_message(self, msg, priority, on_crash=True): + """Adds msg to the list of messages to be printed. + + :param str msg: Message to be displayed to the user. + + :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or + LOW_PRIORITY. + + :param bool on_crash: Whether or not the message should be printed if + the program exits abnormally. + + """ + + def print_messages(self): + """Prints messages to the user and clears the message queue.""" + + +# Updater interfaces +# +# When "certbot renew" is run, Certbot will iterate over each lineage and check +# if the selected installer for that lineage is a subclass of each updater +# class. If it is and the update of that type is configured to be run for that +# lineage, the relevant update function will be called for it. These functions +# are never called for other subcommands, so if an installer wants to perform +# an update during the run or install subcommand, it should do so when +# :func:`IInstaller.deploy_cert` is called. + +@six.add_metaclass(abc.ABCMeta) +class GenericUpdater(object): + """Interface for update types not currently specified by Certbot. + + This class allows plugins to perform types of updates that Certbot hasn't + defined (yet). + + To make use of this interface, the installer should implement the interface + methods, and interfaces.GenericUpdater.register(InstallerClass) should + be called from the installer code. + + The plugins implementing this enhancement are responsible of handling + the saving of configuration checkpoints as well as other calls to + interface methods of `interfaces.IInstaller` such as prepare() and restart() + """ + + @abc.abstractmethod + def generic_updates(self, lineage, *args, **kwargs): + """Perform any update types defined by the installer. + + If an installer is a subclass of the class containing this method, this + function will always be called when "certbot renew" is run. If the + update defined by the installer should be run conditionally, the + installer needs to handle checking the conditions itself. + + This method is called once for each lineage. + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + """ + + +@six.add_metaclass(abc.ABCMeta) +class RenewDeployer(object): + """Interface for update types run when a lineage is renewed + + This class allows plugins to perform types of updates that need to run at + lineage renewal that Certbot hasn't defined (yet). + + To make use of this interface, the installer should implement the interface + methods, and interfaces.RenewDeployer.register(InstallerClass) should + be called from the installer code. + """ + + @abc.abstractmethod + def renew_deploy(self, lineage, *args, **kwargs): + """Perform updates defined by installer when a certificate has been renewed + + If an installer is a subclass of the class containing this method, this + function will always be called when a certficate has been renewed by + running "certbot renew". For example if a plugin needs to copy a + certificate over, or change configuration based on the new certificate. + + This method is called once for each lineage renewed + + :param lineage: Certificate lineage object + :type lineage: storage.RenewableCert + + """ diff --git a/certbot/certbot/main.py b/certbot/certbot/main.py new file mode 100644 index 000000000..b329f15c5 --- /dev/null +++ b/certbot/certbot/main.py @@ -0,0 +1,14 @@ +"""Certbot main public entry point.""" +from certbot._internal import main as internal_main + + +def main(cli_args=None): + """Command line argument parsing and main script execution. + + :returns: result of requested command + + :raises errors.Error: OS errors triggered by wrong permissions + :raises errors.Error: error if plugin command is not supported + + """ + return internal_main.main(cli_args) diff --git a/certbot/certbot/plugins/__init__.py b/certbot/certbot/plugins/__init__.py new file mode 100644 index 000000000..7831eab61 --- /dev/null +++ b/certbot/certbot/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot plugins.""" diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py new file mode 100644 index 000000000..843e27a1b --- /dev/null +++ b/certbot/certbot/plugins/common.py @@ -0,0 +1,458 @@ +"""Plugin common functions.""" +import logging +import re +import shutil +import sys +import tempfile +import warnings + +import pkg_resources +import zope.interface + +from josepy import util as jose_util + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import achallenges # pylint: disable=unused-import +from certbot._internal import constants +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import reverter +from certbot.compat import os +from certbot.compat import filesystem +from certbot.plugins.storage import PluginStorage + +logger = logging.getLogger(__name__) + + +def option_namespace(name): + """ArgumentParser options namespace (prefix of all options).""" + return name + "-" + + +def dest_namespace(name): + """ArgumentParser dest namespace (prefix of all destinations).""" + return name.replace("-", "_") + "_" + +private_ips_regex = re.compile( + r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|" + r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)") +hostname_regex = re.compile( + r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE) + + +@zope.interface.implementer(interfaces.IPlugin) +class Plugin(object): + """Generic plugin.""" + # provider is not inherited, subclasses must define it on their own + # @zope.interface.provider(interfaces.IPluginFactory) + + def __init__(self, config, name): + self.config = config + self.name = name + + @jose_util.abstractclassmethod + def add_parser_arguments(cls, add): + """Add plugin arguments to the CLI argument parser. + + NOTE: If some of your flags interact with others, you can + use cli.report_config_interaction to register this to ensure + values are correctly saved/overridable during renewal. + + :param callable add: Function that proxies calls to + `argparse.ArgumentParser.add_argument` prepending options + with unique plugin name prefix. + + """ + + @classmethod + def inject_parser_options(cls, parser, name): + """Inject parser options. + + See `~.IPlugin.inject_parser_options` for docs. + + """ + # dummy function, doesn't check if dest.startswith(self.dest_namespace) + def add(arg_name_no_prefix, *args, **kwargs): + # pylint: disable=missing-docstring + return parser.add_argument( + "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), + *args, **kwargs) + return cls.add_parser_arguments(add) + + @property + def option_namespace(self): + """ArgumentParser options namespace (prefix of all options).""" + return option_namespace(self.name) + + def option_name(self, name): + """Option name (include plugin namespace).""" + return self.option_namespace + name + + @property + def dest_namespace(self): + """ArgumentParser dest namespace (prefix of all destinations).""" + return dest_namespace(self.name) + + def dest(self, var): + """Find a destination for given variable ``var``.""" + # this should do exactly the same what ArgumentParser(arg), + # does to "arg" to compute "dest" + return self.dest_namespace + var.replace("-", "_") + + def conf(self, var): + """Find a configuration value for variable ``var``.""" + return getattr(self.config, self.dest(var)) + + +class Installer(Plugin): + """An installer base class with reverter and ssl_dhparam methods defined. + + Installer plugins do not have to inherit from this class. + + """ + def __init__(self, *args, **kwargs): + super(Installer, self).__init__(*args, **kwargs) + self.storage = PluginStorage(self.config, self.name) + self.reverter = reverter.Reverter(self.config) + + def add_to_checkpoint(self, save_files, save_notes, temporary=False): + """Add files to a checkpoint. + + :param set save_files: set of filepaths to save + :param str save_notes: notes about changes during the save + :param bool temporary: True if the files should be added to a + temporary checkpoint rather than a permanent one. This is + usually used for changes that will soon be reverted. + + :raises .errors.PluginError: when unable to add to checkpoint + + """ + if temporary: + checkpoint_func = self.reverter.add_to_temp_checkpoint + else: + checkpoint_func = self.reverter.add_to_checkpoint + + try: + checkpoint_func(save_files, save_notes) + except errors.ReverterError as err: + raise errors.PluginError(str(err)) + + def finalize_checkpoint(self, title): + """Timestamp and save changes made through the reverter. + + :param str title: Title describing checkpoint + + :raises .errors.PluginError: when an error occurs + + """ + try: + self.reverter.finalize_checkpoint(title) + except errors.ReverterError as err: + raise errors.PluginError(str(err)) + + def recovery_routine(self): + """Revert all previously modified files. + + Reverts all modified files that have not been saved as a checkpoint + + :raises .errors.PluginError: If unable to recover the configuration + + """ + try: + self.reverter.recovery_routine() + except errors.ReverterError as err: + raise errors.PluginError(str(err)) + + def revert_temporary_config(self): + """Rollback temporary checkpoint. + + :raises .errors.PluginError: when unable to revert config + + """ + try: + self.reverter.revert_temporary_config() + except errors.ReverterError as err: + raise errors.PluginError(str(err)) + + def rollback_checkpoints(self, rollback=1): + """Rollback saved checkpoints. + + :param int rollback: Number of checkpoints to revert + + :raises .errors.PluginError: If there is a problem with the input or + the function is unable to correctly revert the configuration + + """ + try: + self.reverter.rollback_checkpoints(rollback) + except errors.ReverterError as err: + raise errors.PluginError(str(err)) + + @property + def ssl_dhparams(self): + """Full absolute path to ssl_dhparams file.""" + return os.path.join(self.config.config_dir, constants.SSL_DHPARAMS_DEST) + + @property + def updated_ssl_dhparams_digest(self): + """Full absolute path to digest of updated ssl_dhparams file.""" + return os.path.join(self.config.config_dir, constants.UPDATED_SSL_DHPARAMS_DIGEST) + + def install_ssl_dhparams(self): + """Copy Certbot's ssl_dhparams file into the system's config dir if required.""" + return install_version_controlled_file( + self.ssl_dhparams, + self.updated_ssl_dhparams_digest, + constants.SSL_DHPARAMS_SRC, + constants.ALL_SSL_DHPARAMS_HASHES) + + +class Addr(object): + r"""Represents an virtual host address. + + :param str addr: addr part of vhost address + :param str port: port number or \*, or "" + + """ + def __init__(self, tup, ipv6=False): + self.tup = tup + self.ipv6 = ipv6 + + @classmethod + def fromstring(cls, str_addr): + """Initialize Addr from string.""" + if str_addr.startswith('['): + # ipv6 addresses starts with [ + endIndex = str_addr.rfind(']') + host = str_addr[:endIndex + 1] + port = '' + if len(str_addr) > endIndex + 2 and str_addr[endIndex + 1] == ':': + port = str_addr[endIndex + 2:] + return cls((host, port), ipv6=True) + else: + tup = str_addr.partition(':') + return cls((tup[0], tup[2])) + + def __str__(self): + if self.tup[1]: + return "%s:%s" % self.tup + return self.tup[0] + + def normalized_tuple(self): + """Normalized representation of addr/port tuple + """ + if self.ipv6: + return (self.get_ipv6_exploded(), self.tup[1]) + return self.tup + + def __eq__(self, other): + if isinstance(other, self.__class__): + # compare normalized to take different + # styles of representation into account + return self.normalized_tuple() == other.normalized_tuple() + + return False + + def __hash__(self): + return hash(self.tup) + + def get_addr(self): + """Return addr part of Addr object.""" + return self.tup[0] + + def get_port(self): + """Return port.""" + return self.tup[1] + + def get_addr_obj(self, port): + """Return new address object with same addr and new port.""" + return self.__class__((self.tup[0], port), self.ipv6) + + def _normalize_ipv6(self, addr): + """Return IPv6 address in normalized form, helper function""" + addr = addr.lstrip("[") + addr = addr.rstrip("]") + return self._explode_ipv6(addr) + + def get_ipv6_exploded(self): + """Return IPv6 in normalized form""" + if self.ipv6: + return ":".join(self._normalize_ipv6(self.tup[0])) + return "" + + def _explode_ipv6(self, addr): + """Explode IPv6 address for comparison""" + result = ['0', '0', '0', '0', '0', '0', '0', '0'] + addr_list = addr.split(":") + if len(addr_list) > len(result): + # too long, truncate + addr_list = addr_list[0:len(result)] + append_to_end = False + for i, block in enumerate(addr_list): + if not block: + # encountered ::, so rest of the blocks should be + # appended to the end + append_to_end = True + continue + elif len(block) > 1: + # remove leading zeros + block = block.lstrip("0") + if not append_to_end: + result[i] = str(block) + else: + # count the location from the end using negative indices + result[i-len(addr_list)] = str(block) + return result + + +class ChallengePerformer(object): + """Abstract base for challenge performers. + + :ivar configurator: Authenticator and installer plugin + :ivar achalls: Annotated challenges + :vartype achalls: `list` of `.KeyAuthorizationAnnotatedChallenge` + :ivar indices: Holds the indices of challenges from a larger array + so the user of the class doesn't have to. + :vartype indices: `list` of `int` + + """ + + def __init__(self, configurator): + self.configurator = configurator + self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge] + self.indices = [] # type: List[int] + + def add_chall(self, achall, idx=None): + """Store challenge to be performed when perform() is called. + + :param .KeyAuthorizationAnnotatedChallenge achall: Annotated + challenge. + :param int idx: index to challenge in a larger array + + """ + self.achalls.append(achall) + if idx is not None: + self.indices.append(idx) + + def perform(self): + """Perform all added challenges. + + :returns: challenge responses + :rtype: `list` of `acme.challenges.KeyAuthorizationChallengeResponse` + + + """ + raise NotImplementedError() + + +def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes): + """Copy a file into an active location (likely the system's config dir) if required. + + :param str dest_path: destination path for version controlled file + :param str digest_path: path to save a digest of the file in + :param str src_path: path to version controlled file found in distribution + :param list all_hashes: hashes of every released version of the file + """ + current_hash = crypto_util.sha256sum(src_path) + + def _write_current_hash(): + with open(digest_path, "w") as f: + f.write(current_hash) + + def _install_current_file(): + shutil.copyfile(src_path, dest_path) + _write_current_hash() + + # Check to make sure options-ssl.conf is installed + if not os.path.isfile(dest_path): + _install_current_file() + return + # there's already a file there. if it's up to date, do nothing. if it's not but + # it matches a known file hash, we can update it. + # otherwise, print a warning once per new version. + active_file_digest = crypto_util.sha256sum(dest_path) + if active_file_digest == current_hash: # already up to date + return + elif active_file_digest in all_hashes: # safe to update + _install_current_file() + else: # has been manually modified, not safe to update + # did they modify the current version or an old version? + if os.path.isfile(digest_path): + with open(digest_path, "r") as f: + saved_digest = f.read() + # they modified it after we either installed or told them about this version, so return + if saved_digest == current_hash: + return + # there's a new version but we couldn't update the file, or they deleted the digest. + # save the current digest so we only print this once, and print a warning + _write_current_hash() + logger.warning("%s has been manually modified; updated file " + "saved to %s. We recommend updating %s for security purposes.", + dest_path, src_path, dest_path) + + +# test utils used by certbot_apache/certbot_nginx (hence +# "pragma: no cover") TODO: this might quickly lead to dead code (also +# c.f. #383) + +def dir_setup(test_dir, pkg): # pragma: no cover + """Setup the directories necessary for the configurator.""" + def expanded_tempdir(prefix): + """Return the real path of a temp directory with the specified prefix + + Some plugins rely on real paths of symlinks for working correctly. For + example, certbot-apache uses real paths of configuration files to tell + a virtual host from another. On systems where TMP itself is a symbolic + link, (ex: OS X) such plugins will be confused. This function prevents + such a case. + """ + return filesystem.realpath(tempfile.mkdtemp(prefix)) + + temp_dir = expanded_tempdir("temp") + config_dir = expanded_tempdir("config") + work_dir = expanded_tempdir("work") + + filesystem.chmod(temp_dir, constants.CONFIG_DIRS_MODE) + filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE) + filesystem.chmod(work_dir, constants.CONFIG_DIRS_MODE) + + test_configs = pkg_resources.resource_filename( + pkg, os.path.join("testdata", test_dir)) + + shutil.copytree( + test_configs, os.path.join(temp_dir, test_dir), symlinks=True) + + return temp_dir, config_dir, work_dir + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if attr == 'TLSSNI01': + warnings.warn('TLSSNI01 is deprecated and will be removed soon.', + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py new file mode 100644 index 000000000..931778b07 --- /dev/null +++ b/certbot/certbot/plugins/dns_common.py @@ -0,0 +1,335 @@ +"""Common code for DNS Authenticator Plugins.""" + +import abc +import logging +from time import sleep + +import configobj +import zope.interface + +from acme import challenges + +from certbot import errors +from certbot import interfaces +from certbot.compat import filesystem +from certbot.compat import os +from certbot.display import ops +from certbot.display import util as display_util +from certbot.plugins import common + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class DNSAuthenticator(common.Plugin): + """Base class for DNS Authenticators""" + + def __init__(self, config, name): + super(DNSAuthenticator, self).__init__(config, name) + + self._attempt_cleanup = False + + @classmethod + def add_parser_arguments(cls, add, default_propagation_seconds=10): # pylint: disable=arguments-differ + add('propagation-seconds', + default=default_propagation_seconds, + type=int, + help='The number of seconds to wait for DNS to propagate before asking the ACME server ' + 'to verify the DNS record.') + + def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use + return [challenges.DNS01] + + def prepare(self): # pylint: disable=missing-docstring + pass + + def perform(self, achalls): # pylint: disable=missing-docstring + self._setup_credentials() + + self._attempt_cleanup = True + + responses = [] + for achall in achalls: + domain = achall.domain + validation_domain_name = achall.validation_domain_name(domain) + validation = achall.validation(achall.account_key) + + self._perform(domain, validation_domain_name, validation) + responses.append(achall.response(achall.account_key)) + + # DNS updates take time to propagate and checking to see if the update has occurred is not + # reliable (the machine this code is running on might be able to see an update before + # the ACME server). So: we sleep for a short amount of time we believe to be long enough. + logger.info("Waiting %d seconds for DNS changes to propagate", + self.conf('propagation-seconds')) + sleep(self.conf('propagation-seconds')) + + return responses + + def cleanup(self, achalls): # pylint: disable=missing-docstring + if self._attempt_cleanup: + for achall in achalls: + domain = achall.domain + validation_domain_name = achall.validation_domain_name(domain) + validation = achall.validation(achall.account_key) + + self._cleanup(domain, validation_domain_name, validation) + + @abc.abstractmethod + def _setup_credentials(self): # pragma: no cover + """ + Establish credentials, prompting if necessary. + """ + raise NotImplementedError() + + @abc.abstractmethod + def _perform(self, domain, validation_name, validation): # pragma: no cover + """ + Performs a dns-01 challenge by creating a DNS TXT record. + + :param str domain: The domain being validated. + :param str validation_domain_name: The validation record domain name. + :param str validation: The validation record content. + :raises errors.PluginError: If the challenge cannot be performed + """ + raise NotImplementedError() + + @abc.abstractmethod + def _cleanup(self, domain, validation_name, validation): # pragma: no cover + """ + Deletes the DNS TXT record which would have been created by `_perform_achall`. + + Fails gracefully if no such record exists. + + :param str domain: The domain being validated. + :param str validation_domain_name: The validation record domain name. + :param str validation: The validation record content. + """ + raise NotImplementedError() + + def _configure(self, key, label): + """ + Ensure that a configuration value is available. + + If necessary, prompts the user and stores the result. + + :param str key: The configuration key. + :param str label: The user-friendly label for this piece of information. + """ + + configured_value = self.conf(key) + if not configured_value: + new_value = self._prompt_for_data(label) + + setattr(self.config, self.dest(key), new_value) + + def _configure_file(self, key, label, validator=None): + """ + Ensure that a configuration value is available for a path. + + If necessary, prompts the user and stores the result. + + :param str key: The configuration key. + :param str label: The user-friendly label for this piece of information. + """ + + configured_value = self.conf(key) + if not configured_value: + new_value = self._prompt_for_file(label, validator) + + setattr(self.config, self.dest(key), os.path.abspath(os.path.expanduser(new_value))) + + def _configure_credentials(self, key, label, required_variables=None, validator=None): + """ + As `_configure_file`, but for a credential configuration file. + + If necessary, prompts the user and stores the result. + + Always stores absolute paths to avoid issues during renewal. + + :param str key: The configuration key. + :param str label: The user-friendly label for this piece of information. + :param dict required_variables: Map of variable which must be present to error to display. + :param callable validator: A method which will be called to validate the + `CredentialsConfiguration` resulting from the supplied input after it has been validated + to contain the `required_variables`. Should throw a `~certbot.errors.PluginError` to + indicate any issue. + """ + + def __validator(filename): + configuration = CredentialsConfiguration(filename, self.dest) + + if required_variables: + configuration.require(required_variables) + + if validator: + validator(configuration) + + self._configure_file(key, label, __validator) + + credentials_configuration = CredentialsConfiguration(self.conf(key), self.dest) + if required_variables: + credentials_configuration.require(required_variables) + + if validator: + validator(credentials_configuration) + + return credentials_configuration + + @staticmethod + def _prompt_for_data(label): + """ + Prompt the user for a piece of information. + + :param str label: The user-friendly label for this piece of information. + :returns: The user's response (guaranteed non-empty). + :rtype: str + """ + + def __validator(i): + if not i: + raise errors.PluginError('Please enter your {0}.'.format(label)) + + code, response = ops.validated_input( + __validator, + 'Input your {0}'.format(label), + force_interactive=True) + + if code == display_util.OK: + return response + else: + raise errors.PluginError('{0} required to proceed.'.format(label)) + + @staticmethod + def _prompt_for_file(label, validator=None): + """ + Prompt the user for a path. + + :param str label: The user-friendly label for the file. + :param callable validator: A method which will be called to validate the supplied input + after it has been validated to be a non-empty path to an existing file. Should throw a + `~certbot.errors.PluginError` to indicate any issue. + :returns: The user's response (guaranteed to exist). + :rtype: str + """ + + def __validator(filename): + if not filename: + raise errors.PluginError('Please enter a valid path to your {0}.'.format(label)) + + filename = os.path.expanduser(filename) + + validate_file(filename) + + if validator: + validator(filename) + + code, response = ops.validated_directory( + __validator, + 'Input the path to your {0}'.format(label), + force_interactive=True) + + if code == display_util.OK: + return response + else: + raise errors.PluginError('{0} required to proceed.'.format(label)) + + +class CredentialsConfiguration(object): + """Represents a user-supplied filed which stores API credentials.""" + + def __init__(self, filename, mapper=lambda x: x): + """ + :param str filename: A path to the configuration file. + :param callable mapper: A transformation to apply to configuration key names + :raises errors.PluginError: If the file does not exist or is not a valid format. + """ + validate_file_permissions(filename) + + try: + self.confobj = configobj.ConfigObj(filename) + except configobj.ConfigObjError as e: + logger.debug("Error parsing credentials configuration: %s", e, exc_info=True) + raise errors.PluginError("Error parsing credentials configuration: {0}".format(e)) + + self.mapper = mapper + + def require(self, required_variables): + """Ensures that the supplied set of variables are all present in the file. + + :param dict required_variables: Map of variable which must be present to error to display. + :raises errors.PluginError: If one or more are missing. + """ + messages = [] + + for var in required_variables: + if not self._has(var): + messages.append('Property "{0}" not found (should be {1}).' + .format(self.mapper(var), required_variables[var])) + elif not self._get(var): + messages.append('Property "{0}" not set (should be {1}).' + .format(self.mapper(var), required_variables[var])) + + if messages: + raise errors.PluginError( + 'Missing {0} in credentials configuration file {1}:\n * {2}'.format( + 'property' if len(messages) == 1 else 'properties', + self.confobj.filename, + '\n * '.join(messages) + ) + ) + + def conf(self, var): + """Find a configuration value for variable `var`, as transformed by `mapper`. + + :param str var: The variable to get. + :returns: The value of the variable. + :rtype: str + """ + + return self._get(var) + + def _has(self, var): + return self.mapper(var) in self.confobj + + def _get(self, var): + return self.confobj.get(self.mapper(var)) + + +def validate_file(filename): + """Ensure that the specified file exists.""" + + if not os.path.exists(filename): + raise errors.PluginError('File not found: {0}'.format(filename)) + + if os.path.isdir(filename): + raise errors.PluginError('Path is a directory: {0}'.format(filename)) + + +def validate_file_permissions(filename): + """Ensure that the specified file exists and warn about unsafe permissions.""" + + validate_file(filename) + + if filesystem.has_world_permissions(filename): + logger.warning('Unsafe permissions on credentials configuration file: %s', filename) + + +def base_domain_name_guesses(domain): + """Return a list of progressively less-specific domain names. + + One of these will probably be the domain name known to the DNS provider. + + :Example: + + >>> base_domain_name_guesses('foo.bar.baz.example.com') + ['foo.bar.baz.example.com', 'bar.baz.example.com', 'baz.example.com', 'example.com', 'com'] + + :param str domain: The domain for which to return guesses. + :returns: The a list of less specific domain names. + :rtype: list + """ + + fragments = domain.split('.') + return ['.'.join(fragments[i:]) for i in range(0, len(fragments))] diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py new file mode 100644 index 000000000..2c82db030 --- /dev/null +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -0,0 +1,138 @@ +"""Common code for DNS Authenticator Plugins built on Lexicon.""" +import logging + +from requests.exceptions import HTTPError, RequestException + +from acme.magic_typing import Union, Dict, Any # pylint: disable=unused-import,no-name-in-module +from certbot import errors +from certbot.plugins import dns_common + +# Lexicon is not declared as a dependency in Certbot itself, +# but in the Certbot plugins backed by Lexicon. +# So we catch import error here to allow this module to be +# always importable, even if it does not make sense to use it +# if Lexicon is not available, obviously. +try: + from lexicon.config import ConfigResolver +except ImportError: + ConfigResolver = None # type: ignore + +logger = logging.getLogger(__name__) + + +class LexiconClient(object): + """ + Encapsulates all communication with a DNS provider via Lexicon. + """ + + def __init__(self): + self.provider = None + + def add_txt_record(self, domain, record_name, record_content): + """ + Add a TXT record using the supplied information. + + :param str domain: The domain to use to look up the managed zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :raises errors.PluginError: if an error occurs communicating with the DNS Provider API + """ + self._find_domain_id(domain) + + try: + self.provider.create_record(type='TXT', name=record_name, content=record_content) + except RequestException as e: + logger.debug('Encountered error adding TXT record: %s', e, exc_info=True) + raise errors.PluginError('Error adding TXT record: {0}'.format(e)) + + def del_txt_record(self, domain, record_name, record_content): + """ + Delete a TXT record using the supplied information. + + :param str domain: The domain to use to look up the managed zone. + :param str record_name: The record name (typically beginning with '_acme-challenge.'). + :param str record_content: The record content (typically the challenge validation). + :raises errors.PluginError: if an error occurs communicating with the DNS Provider API + """ + try: + self._find_domain_id(domain) + except errors.PluginError as e: + logger.debug('Encountered error finding domain_id during deletion: %s', e, + exc_info=True) + return + + try: + self.provider.delete_record(type='TXT', name=record_name, content=record_content) + except RequestException as e: + logger.debug('Encountered error deleting TXT record: %s', e, exc_info=True) + + def _find_domain_id(self, domain): + """ + Find the domain_id for a given domain. + + :param str domain: The domain for which to find the domain_id. + :raises errors.PluginError: if the domain_id cannot be found. + """ + + domain_name_guesses = dns_common.base_domain_name_guesses(domain) + + for domain_name in domain_name_guesses: + try: + if hasattr(self.provider, 'options'): + # For Lexicon 2.x + self.provider.options['domain'] = domain_name + else: + # For Lexicon 3.x + self.provider.domain = domain_name + + self.provider.authenticate() + + return # If `authenticate` doesn't throw an exception, we've found the right name + except HTTPError as e: + result = self._handle_http_error(e, domain_name) + + if result: + raise result + except Exception as e: # pylint: disable=broad-except + result = self._handle_general_error(e, domain_name) + + if result: + raise result + + raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}' + .format(domain, domain_name_guesses)) + + def _handle_http_error(self, e, domain_name): + return errors.PluginError('Error determining zone identifier for {0}: {1}.' + .format(domain_name, e)) + + def _handle_general_error(self, e, domain_name): + if not str(e).startswith('No domain found'): + return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' + .format(domain_name, e)) + return None + + +def build_lexicon_config(lexicon_provider_name, lexicon_options, provider_options): + # type: (str, Dict, Dict) -> Union[ConfigResolver, Dict] + """ + Convenient function to build a Lexicon 2.x/3.x config object. + :param str lexicon_provider_name: the name of the lexicon provider to use + :param dict lexicon_options: options specific to lexicon + :param dict provider_options: options specific to provider + :return: configuration to apply to the provider + :rtype: ConfigurationResolver or dict + """ + config = {'provider_name': lexicon_provider_name} # type: Dict[str, Any] + config.update(lexicon_options) + if not ConfigResolver: + # Lexicon 2.x + config.update(provider_options) + else: + # Lexicon 3.x + provider_config = {} + provider_config.update(provider_options) + config[lexicon_provider_name] = provider_config + config = ConfigResolver().with_dict(config).with_env() + + return config diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py new file mode 100644 index 000000000..0fc0c9a71 --- /dev/null +++ b/certbot/certbot/plugins/dns_test_common.py @@ -0,0 +1,63 @@ +"""Base test class for DNS authenticators.""" + +import configobj +import josepy as jose +import mock +import six + +from acme import challenges + +from certbot import achallenges +from certbot.compat import filesystem +from certbot.tests import acme_util +from certbot.tests import util as test_util + +DOMAIN = 'example.com' +KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + + +class BaseAuthenticatorTest(object): + """ + A base test class to reduce duplication between test code for DNS Authenticator Plugins. + + Assumes: + * That subclasses also subclass unittest.TestCase + * That the authenticator is stored as self.auth + """ + + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.DNS01, domain=DOMAIN, account_key=KEY) + + def test_more_info(self): + # pylint: disable=no-member + self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) + + def test_get_chall_pref(self): + # pylint: disable=no-member + self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) + + def test_parser_arguments(self): + m = mock.MagicMock() + + # pylint: disable=no-member + self.auth.add_parser_arguments(m) + + m.assert_any_call('propagation-seconds', type=int, default=mock.ANY, help=mock.ANY) + + +def write(values, path): + """Write the specified values to a config file. + + :param dict values: A map of values to write. + :param str path: Where to write the values. + """ + + config = configobj.ConfigObj() + + for key in values: + config[key] = values[key] + + with open(path, "wb") as f: + config.write(outfile=f) + + filesystem.chmod(path, 0o600) diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py new file mode 100644 index 000000000..a221cf1bf --- /dev/null +++ b/certbot/certbot/plugins/dns_test_common_lexicon.py @@ -0,0 +1,128 @@ +"""Base test class for DNS authenticators built on Lexicon.""" + +import josepy as jose +import mock +from requests.exceptions import HTTPError, RequestException + +from certbot import errors +from certbot.plugins import dns_test_common +from certbot.tests import util as test_util + +DOMAIN = 'example.com' +KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + +# These classes are intended to be subclassed/mixed in, so not all members are defined. +# pylint: disable=no-member + +class BaseLexiconAuthenticatorTest(dns_test_common.BaseAuthenticatorTest): + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + self.auth._attempt_cleanup = True # _attempt_cleanup | pylint: disable=protected-access + self.auth.cleanup([self.achall]) + + expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + +class BaseLexiconClientTest(object): + DOMAIN_NOT_FOUND = Exception('No domain found') + GENERIC_ERROR = RequestException + LOGIN_ERROR = HTTPError('400 Client Error: ...') + UNKNOWN_LOGIN_ERROR = HTTPError('500 Surprise! Error: ...') + + record_prefix = "_acme-challenge" + record_name = record_prefix + "." + DOMAIN + record_content = "bar" + + def test_add_txt_record(self): + self.client.add_txt_record(DOMAIN, self.record_name, self.record_content) + + self.provider_mock.create_record.assert_called_with(type='TXT', + name=self.record_name, + content=self.record_content) + + def test_add_txt_record_try_twice_to_find_domain(self): + self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, ''] + + self.client.add_txt_record(DOMAIN, self.record_name, self.record_content) + + self.provider_mock.create_record.assert_called_with(type='TXT', + name=self.record_name, + content=self.record_content) + + def test_add_txt_record_fail_to_find_domain(self): + self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, + self.DOMAIN_NOT_FOUND, + self.DOMAIN_NOT_FOUND,] + + self.assertRaises(errors.PluginError, + self.client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_fail_to_authenticate(self): + self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR + + self.assertRaises(errors.PluginError, + self.client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_fail_to_authenticate_with_unknown_error(self): + self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR + + self.assertRaises(errors.PluginError, + self.client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_error_finding_domain(self): + self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR + + self.assertRaises(errors.PluginError, + self.client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_error_adding_record(self): + self.provider_mock.create_record.side_effect = self.GENERIC_ERROR + + self.assertRaises(errors.PluginError, + self.client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record(self): + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + self.provider_mock.delete_record.assert_called_with(type='TXT', + name=self.record_name, + content=self.record_content) + + def test_del_txt_record_fail_to_find_domain(self): + self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, + self.DOMAIN_NOT_FOUND, + self.DOMAIN_NOT_FOUND, ] + + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_fail_to_authenticate(self): + self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR + + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_fail_to_authenticate_with_unknown_error(self): + self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR + + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_error_finding_domain(self): + self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR + + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_error_deleting_record(self): + self.provider_mock.delete_record.side_effect = self.GENERIC_ERROR + + self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py new file mode 100644 index 000000000..d917b0ea4 --- /dev/null +++ b/certbot/certbot/plugins/enhancements.py @@ -0,0 +1,175 @@ +"""New interface style Certbot enhancements""" +import abc +import six + +from certbot._internal import constants + +from acme.magic_typing import Dict, List, Any # pylint: disable=unused-import, no-name-in-module + +ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] +"""List of possible :class:`certbot.interfaces.IInstaller` +enhancements. + +List of expected options parameters: +- redirect: None +- ensure-http-header: name of header (i.e. Strict-Transport-Security) +- ocsp-stapling: certificate chain file path + +""" + +def enabled_enhancements(config): + """ + Generator to yield the enabled new style enhancements. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + """ + for enh in _INDEX: + if getattr(config, enh["cli_dest"]): + yield enh + +def are_requested(config): + """ + Checks if one or more of the requested enhancements are those of the new + enhancement interfaces. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + """ + return any(enabled_enhancements(config)) + +def are_supported(config, installer): + """ + Checks that all of the requested enhancements are supported by the + installer. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :returns: If all the requested enhancements are supported by the installer + :rtype: bool + """ + for enh in enabled_enhancements(config): + if not isinstance(installer, enh["class"]): + return False + return True + +def enable(lineage, domains, installer, config): + """ + Run enable method for each requested enhancement that is supported. + + :param lineage: Certificate lineage object + :type lineage: certbot._internal.storage.RenewableCert + + :param domains: List of domains in certificate to enhance + :type domains: str + + :param installer: Installer object + :type installer: interfaces.IInstaller + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + """ + for enh in enabled_enhancements(config): + getattr(installer, enh["enable_function"])(lineage, domains) + +def populate_cli(add): + """ + Populates the command line flags for certbot._internal.cli.HelpfulParser + + :param add: Add function of certbot._internal.cli.HelpfulParser + :type add: func + """ + for enh in _INDEX: + add(enh["cli_groups"], enh["cli_flag"], action=enh["cli_action"], + dest=enh["cli_dest"], default=enh["cli_flag_default"], + help=enh["cli_help"]) + + +@six.add_metaclass(abc.ABCMeta) +class AutoHSTSEnhancement(object): + """ + Enhancement interface that installer plugins can implement in order to + provide functionality that configures the software to have a + 'Strict-Transport-Security' with initially low max-age value that will + increase over time. + + The plugins implementing new style enhancements are responsible of handling + the saving of configuration checkpoints as well as calling possible restarts + of managed software themselves. For update_autohsts method, the installer may + have to call prepare() to finalize the plugin initialization. + + Methods: + enable_autohsts is called when the header is initially installed using a + low max-age value. + + update_autohsts is called every time when Certbot is run using 'renew' + verb. The max-age value should be increased over time using this method. + + deploy_autohsts is called for every lineage that has had its certificate + renewed. A long HSTS max-age value should be set here, as we should be + confident that the user is able to automatically renew their certificates. + + + """ + + @abc.abstractmethod + def update_autohsts(self, lineage, *args, **kwargs): + """ + Gets called for each lineage every time Certbot is run with 'renew' verb. + Implementation of this method should increase the max-age value. + + :param lineage: Certificate lineage object + :type lineage: certbot._internal.storage.RenewableCert + + .. note:: prepare() method inherited from `interfaces.IPlugin` might need + to be called manually within implementation of this interface method + to finalize the plugin initialization. + """ + + @abc.abstractmethod + def deploy_autohsts(self, lineage, *args, **kwargs): + """ + Gets called for a lineage when its certificate is successfully renewed. + Long max-age value should be set in implementation of this method. + + :param lineage: Certificate lineage object + :type lineage: certbot._internal.storage.RenewableCert + """ + + @abc.abstractmethod + def enable_autohsts(self, lineage, domains, *args, **kwargs): + """ + Enables the AutoHSTS enhancement, installing + Strict-Transport-Security header with a low initial value to be increased + over the subsequent runs of Certbot renew. + + :param lineage: Certificate lineage object + :type lineage: certbot._internal.storage.RenewableCert + + :param domains: List of domains in certificate to enhance + :type domains: str + """ + +# This is used to configure internal new style enhancements in Certbot. These +# enhancement interfaces need to be defined in this file. Please do not modify +# this list from plugin code. +_INDEX = [ + { + "name": "AutoHSTS", + "cli_help": "Gradually increasing max-age value for HTTP Strict Transport "+ + "Security security header", + "cli_flag": "--auto-hsts", + "cli_flag_default": constants.CLI_DEFAULTS["auto_hsts"], + "cli_groups": ["security", "enhance"], + "cli_dest": "auto_hsts", + "cli_action": "store_true", + "class": AutoHSTSEnhancement, + "updater_function": "update_autohsts", + "deployer_function": "deploy_autohsts", + "enable_function": "enable_autohsts" + } +] # type: List[Dict[str, Any]] diff --git a/certbot/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py new file mode 100644 index 000000000..294dfa0e8 --- /dev/null +++ b/certbot/certbot/plugins/storage.py @@ -0,0 +1,123 @@ +"""Plugin storage class.""" +import json +import logging + +from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem + +logger = logging.getLogger(__name__) + + +class PluginStorage(object): + """Class implementing storage functionality for plugins""" + + def __init__(self, config, classkey): + """Initializes PluginStorage object storing required configuration + options. + + :param .configuration.NamespaceConfig config: Configuration object + :param str classkey: class name to use as root key in storage file + + """ + + self._config = config + self._classkey = classkey + self._initialized = False + self._data = None + self._storagepath = None + + def _initialize_storage(self): + """Initializes PluginStorage data and reads current state from the disk + if the storage json exists.""" + + self._storagepath = os.path.join(self._config.config_dir, ".pluginstorage.json") + self._load() + self._initialized = True + + def _load(self): + """Reads PluginStorage content from the disk to a dict structure + + :raises .errors.PluginStorageError: when unable to open or read the file + """ + data = dict() # type: Dict[str, Any] + filedata = "" + try: + with open(self._storagepath, 'r') as fh: + filedata = fh.read() + except IOError as e: + errmsg = "Could not read PluginStorage data file: {0} : {1}".format( + self._storagepath, str(e)) + if os.path.isfile(self._storagepath): + # Only error out if file exists, but cannot be read + logger.error(errmsg) + raise errors.PluginStorageError(errmsg) + try: + data = json.loads(filedata) + except ValueError: + if not filedata: + logger.debug("Plugin storage file %s was empty, no values loaded", + self._storagepath) + else: + errmsg = "PluginStorage file {0} is corrupted.".format( + self._storagepath) + logger.error(errmsg) + raise errors.PluginStorageError(errmsg) + self._data = data + + def save(self): + """Saves PluginStorage content to disk + + :raises .errors.PluginStorageError: when unable to serialize the data + or write it to the filesystem + """ + if not self._initialized: + errmsg = "Unable to save, no values have been added to PluginStorage." + logger.error(errmsg) + raise errors.PluginStorageError(errmsg) + + try: + serialized = json.dumps(self._data) + except TypeError as e: + errmsg = "Could not serialize PluginStorage data: {0}".format( + str(e)) + logger.error(errmsg) + raise errors.PluginStorageError(errmsg) + try: + with os.fdopen(filesystem.open( + self._storagepath, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + 0o600), 'w') as fh: + fh.write(serialized) + except IOError as e: + errmsg = "Could not write PluginStorage data to file {0} : {1}".format( + self._storagepath, str(e)) + logger.error(errmsg) + raise errors.PluginStorageError(errmsg) + + def put(self, key, value): + """Put configuration value to PluginStorage + + :param str key: Key to store the value to + :param value: Data to store + """ + if not self._initialized: + self._initialize_storage() + + if not self._classkey in self._data.keys(): + self._data[self._classkey] = dict() + self._data[self._classkey][key] = value + + def fetch(self, key): + """Get configuration value from PluginStorage + + :param str key: Key to get value from the storage + + :raises KeyError: If the key doesn't exist in the storage + """ + if not self._initialized: + self._initialize_storage() + + return self._data[self._classkey][key] diff --git a/certbot/certbot/plugins/util.py b/certbot/certbot/plugins/util.py new file mode 100644 index 000000000..87eb45fe9 --- /dev/null +++ b/certbot/certbot/plugins/util.py @@ -0,0 +1,58 @@ +"""Plugin utilities.""" +import logging + +from certbot import util +from certbot.compat import os +from certbot.compat.misc import STANDARD_BINARY_DIRS + +logger = logging.getLogger(__name__) + + +def get_prefixes(path): + """Retrieves all possible path prefixes of a path, in descending order + of length. For instance, + (linux) /a/b/c returns ['/a/b/c', '/a/b', '/a', '/'] + (windows) C:\\a\\b\\c returns ['C:\\a\\b\\c', 'C:\\a\\b', 'C:\\a', 'C:'] + :param str path: the path to break into prefixes + + :returns: all possible path prefixes of given path in descending order + :rtype: `list` of `str` + """ + prefix = os.path.normpath(path) + prefixes = [] + while prefix: + prefixes.append(prefix) + prefix, _ = os.path.split(prefix) + # break once we hit the root path + if prefix == prefixes[-1]: + break + return prefixes + + +def path_surgery(cmd): + """Attempt to perform PATH surgery to find cmd + + Mitigates https://github.com/certbot/certbot/issues/1833 + + :param str cmd: the command that is being searched for in the PATH + + :returns: True if the operation succeeded, False otherwise + """ + path = os.environ["PATH"] + added = [] + for d in STANDARD_BINARY_DIRS: + if d not in path: + path += os.pathsep + d + added.append(d) + + if any(added): + logger.debug("Can't find %s, attempting PATH mitigation by adding %s", + cmd, os.pathsep.join(added)) + os.environ["PATH"] = path + + if util.exe_exists(cmd): + return True + expanded = " expanded" if any(added) else "" + logger.debug("Failed to find executable %s in%s PATH: %s", cmd, + expanded, path) + return False diff --git a/certbot/certbot/reverter.py b/certbot/certbot/reverter.py new file mode 100644 index 000000000..9118fef94 --- /dev/null +++ b/certbot/certbot/reverter.py @@ -0,0 +1,535 @@ +"""Reverter class saves configuration checkpoints and allows for recovery.""" +import csv +import glob +import logging +import shutil +import sys +import time +import traceback + +import six + +from certbot._internal import constants +from certbot import errors +from certbot import util +from certbot.compat import os +from certbot.compat import filesystem + +logger = logging.getLogger(__name__) + + +class Reverter(object): + """Reverter Class - save and revert configuration checkpoints. + + This class can be used by the plugins, especially Installers, to + undo changes made to the user's system. Modifications to files and + commands to do undo actions taken by the plugin should be registered + with this class before the action is taken. + + Once a change has been registered with this class, there are three + states the change can be in. First, the change can be a temporary + change. This should be used for changes that will soon be reverted, + such as config changes for the purpose of solving a challenge. + Changes are added to this state through calls to + :func:`~add_to_temp_checkpoint` and reverted when + :func:`~revert_temporary_config` or :func:`~recovery_routine` is + called. + + The second state a change can be in is in progress. These changes + are not temporary, however, they also have not been finalized in a + checkpoint. A change must become in progress before it can be + finalized. Changes are added to this state through calls to + :func:`~add_to_checkpoint` and reverted when + :func:`~recovery_routine` is called. + + The last state a change can be in is finalized in a checkpoint. A + change is put into this state by first becoming an in progress + change and then calling :func:`~finalize_checkpoint`. Changes + in this state can be reverted through calls to + :func:`~rollback_checkpoints`. + + As a final note, creating new files and registering undo commands + are handled specially and use the methods + :func:`~register_file_creation` and :func:`~register_undo_command` + respectively. Both of these methods can be used to create either + temporary or in progress changes. + + .. note:: Consider moving everything over to CSV format. + + :param config: Configuration. + :type config: :class:`certbot.interfaces.IConfig` + + """ + def __init__(self, config): + self.config = config + + util.make_or_verify_dir( + config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) + + def revert_temporary_config(self): + """Reload users original configuration files after a temporary save. + + This function should reinstall the users original configuration files + for all saves with temporary=True + + :raises .ReverterError: when unable to revert config + + """ + if os.path.isdir(self.config.temp_checkpoint_dir): + try: + self._recover_checkpoint(self.config.temp_checkpoint_dir) + except errors.ReverterError: + # We have a partial or incomplete recovery + logger.critical( + "Incomplete or failed recovery for %s", + self.config.temp_checkpoint_dir, + ) + raise errors.ReverterError("Unable to revert temporary config") + + def rollback_checkpoints(self, rollback=1): + """Revert 'rollback' number of configuration checkpoints. + + :param int rollback: Number of checkpoints to reverse. A str num will be + cast to an integer. So "2" is also acceptable. + + :raises .ReverterError: + if there is a problem with the input or if the function is + unable to correctly revert the configuration checkpoints + + """ + try: + rollback = int(rollback) + except ValueError: + logger.error("Rollback argument must be a positive integer") + raise errors.ReverterError("Invalid Input") + # Sanity check input + if rollback < 0: + logger.error("Rollback argument must be a positive integer") + raise errors.ReverterError("Invalid Input") + + backups = os.listdir(self.config.backup_dir) + backups.sort() + + if not backups: + logger.warning( + "Certbot hasn't modified your configuration, so rollback " + "isn't available.") + elif len(backups) < rollback: + logger.warning("Unable to rollback %d checkpoints, only %d exist", + rollback, len(backups)) + + while rollback > 0 and backups: + cp_dir = os.path.join(self.config.backup_dir, backups.pop()) + try: + self._recover_checkpoint(cp_dir) + except errors.ReverterError: + logger.critical("Failed to load checkpoint during rollback") + raise errors.ReverterError( + "Unable to load checkpoint during rollback") + rollback -= 1 + + def add_to_temp_checkpoint(self, save_files, save_notes): + """Add files to temporary checkpoint. + + :param set save_files: set of filepaths to save + :param str save_notes: notes about changes during the save + + """ + self._add_to_checkpoint_dir( + self.config.temp_checkpoint_dir, save_files, save_notes) + + def add_to_checkpoint(self, save_files, save_notes): + """Add files to a permanent checkpoint. + + :param set save_files: set of filepaths to save + :param str save_notes: notes about changes during the save + + """ + # Check to make sure we are not overwriting a temp file + self._check_tempfile_saves(save_files) + self._add_to_checkpoint_dir( + self.config.in_progress_dir, save_files, save_notes) + + def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes): + """Add save files to checkpoint directory. + + :param str cp_dir: Checkpoint directory filepath + :param set save_files: set of files to save + :param str save_notes: notes about changes made during the save + + :raises IOError: if unable to open cp_dir + FILEPATHS file + :raises .ReverterError: if unable to add checkpoint + + """ + util.make_or_verify_dir( + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) + + op_fd, existing_filepaths = self._read_and_append( + os.path.join(cp_dir, "FILEPATHS")) + + idx = len(existing_filepaths) + + for filename in save_files: + # No need to copy/index already existing files + # The oldest copy already exists in the directory... + if filename not in existing_filepaths: + # Tag files with index so multiple files can + # have the same filename + logger.debug("Creating backup of %s", filename) + try: + shutil.copy2(filename, os.path.join( + cp_dir, os.path.basename(filename) + "_" + str(idx))) + op_fd.write('{0}\n'.format(filename)) + # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 + except IOError: + op_fd.close() + logger.error( + "Unable to add file %s to checkpoint %s", + filename, cp_dir) + raise errors.ReverterError( + "Unable to add file {0} to checkpoint " + "{1}".format(filename, cp_dir)) + idx += 1 + op_fd.close() + + with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: + notes_fd.write(save_notes) + + def _read_and_append(self, filepath): # pylint: disable=no-self-use + """Reads the file lines and returns a file obj. + + Read the file returning the lines, and a pointer to the end of the file. + + """ + # Open up filepath differently depending on if it already exists + if os.path.isfile(filepath): + op_fd = open(filepath, "r+") + lines = op_fd.read().splitlines() + else: + lines = [] + op_fd = open(filepath, "w") + + return op_fd, lines + + def _recover_checkpoint(self, cp_dir): + """Recover a specific checkpoint. + + Recover a specific checkpoint provided by cp_dir + Note: this function does not reload augeas. + + :param str cp_dir: checkpoint directory file path + + :raises errors.ReverterError: If unable to recover checkpoint + + """ + # Undo all commands + if os.path.isfile(os.path.join(cp_dir, "COMMANDS")): + self._run_undo_commands(os.path.join(cp_dir, "COMMANDS")) + # Revert all changed files + if os.path.isfile(os.path.join(cp_dir, "FILEPATHS")): + try: + with open(os.path.join(cp_dir, "FILEPATHS")) as paths_fd: + filepaths = paths_fd.read().splitlines() + for idx, path in enumerate(filepaths): + shutil.copy2(os.path.join( + cp_dir, + os.path.basename(path) + "_" + str(idx)), path) + except (IOError, OSError): + # This file is required in all checkpoints. + logger.error("Unable to recover files from %s", cp_dir) + raise errors.ReverterError( + "Unable to recover files from %s" % cp_dir) + + # Remove any newly added files if they exist + self._remove_contained_files(os.path.join(cp_dir, "NEW_FILES")) + + try: + shutil.rmtree(cp_dir) + except OSError: + logger.error("Unable to remove directory: %s", cp_dir) + raise errors.ReverterError( + "Unable to remove directory: %s" % cp_dir) + + def _run_undo_commands(self, filepath): # pylint: disable=no-self-use + """Run all commands in a file.""" + # NOTE: csv module uses native strings. That is, bytes on Python 2 and + # unicode on Python 3 + # It is strongly advised to set newline = '' on Python 3 with CSV, + # and it fixes problems on Windows. + kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} + with open(filepath, 'r', **kwargs) as csvfile: # type: ignore + csvreader = csv.reader(csvfile) + for command in reversed(list(csvreader)): + try: + util.run_script(command) + except errors.SubprocessError: + logger.error( + "Unable to run undo command: %s", " ".join(command)) + + def _check_tempfile_saves(self, save_files): + """Verify save isn't overwriting any temporary files. + + :param set save_files: Set of files about to be saved. + + :raises certbot.errors.ReverterError: + when save is attempting to overwrite a temporary file. + + """ + protected_files = [] + + # Get temp modified files + temp_path = os.path.join(self.config.temp_checkpoint_dir, "FILEPATHS") + if os.path.isfile(temp_path): + with open(temp_path, "r") as protected_fd: + protected_files.extend(protected_fd.read().splitlines()) + + # Get temp new files + new_path = os.path.join(self.config.temp_checkpoint_dir, "NEW_FILES") + if os.path.isfile(new_path): + with open(new_path, "r") as protected_fd: + protected_files.extend(protected_fd.read().splitlines()) + + # Verify no save_file is in protected_files + for filename in protected_files: + if filename in save_files: + raise errors.ReverterError( + "Attempting to overwrite challenge " + "file - %s" % filename) + + def register_file_creation(self, temporary, *files): + r"""Register the creation of all files during certbot execution. + + Call this method before writing to the file to make sure that the + file will be cleaned up if the program exits unexpectedly. + (Before a save occurs) + + :param bool temporary: If the file creation registry is for + a temp or permanent save. + :param \*files: file paths (str) to be registered + + :raises certbot.errors.ReverterError: If + call does not contain necessary parameters or if the file creation + is unable to be registered. + + """ + # Make sure some files are provided... as this is an error + # Made this mistake in my initial implementation of apache.dvsni.py + if not files: + raise errors.ReverterError("Forgot to provide files to registration call") + + cp_dir = self._get_cp_dir(temporary) + + # Append all new files (that aren't already registered) + new_fd = None + try: + new_fd, ex_files = self._read_and_append(os.path.join(cp_dir, "NEW_FILES")) + + for path in files: + if path not in ex_files: + new_fd.write("{0}\n".format(path)) + except (IOError, OSError): + logger.error("Unable to register file creation(s) - %s", files) + raise errors.ReverterError( + "Unable to register file creation(s) - {0}".format(files)) + finally: + if new_fd is not None: + new_fd.close() + + def register_undo_command(self, temporary, command): + """Register a command to be run to undo actions taken. + + .. warning:: This function does not enforce order of operations in terms + of file modification vs. command registration. All undo commands + are run first before all normal files are reverted to their previous + state. If you need to maintain strict order, you may create + checkpoints before and after the the command registration. This + function may be improved in the future based on demand. + + :param bool temporary: Whether the command should be saved in the + IN_PROGRESS or TEMPORARY checkpoints. + :param command: Command to be run. + :type command: list of str + + """ + commands_fp = os.path.join(self._get_cp_dir(temporary), "COMMANDS") + command_file = None + # It is strongly advised to set newline = '' on Python 3 with CSV, + # and it fixes problems on Windows. + kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} + try: + if os.path.isfile(commands_fp): + command_file = open(commands_fp, "a", **kwargs) # type: ignore + else: + command_file = open(commands_fp, "w", **kwargs) # type: ignore + + csvwriter = csv.writer(command_file) + csvwriter.writerow(command) + + except (IOError, OSError): + logger.error("Unable to register undo command") + raise errors.ReverterError( + "Unable to register undo command.") + finally: + if command_file is not None: + command_file.close() + + def _get_cp_dir(self, temporary): + """Return the proper reverter directory.""" + if temporary: + cp_dir = self.config.temp_checkpoint_dir + else: + cp_dir = self.config.in_progress_dir + + util.make_or_verify_dir( + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) + + return cp_dir + + def recovery_routine(self): + """Revert configuration to most recent finalized checkpoint. + + Remove all changes (temporary and permanent) that have not been + finalized. This is useful to protect against crashes and other + execution interruptions. + + :raises .errors.ReverterError: If unable to recover the configuration + + """ + # First, any changes found in IConfig.temp_checkpoint_dir are removed, + # then IN_PROGRESS changes are removed The order is important. + # IN_PROGRESS is unable to add files that are already added by a TEMP + # change. Thus TEMP must be rolled back first because that will be the + # 'latest' occurrence of the file. + self.revert_temporary_config() + if os.path.isdir(self.config.in_progress_dir): + try: + self._recover_checkpoint(self.config.in_progress_dir) + except errors.ReverterError: + # We have a partial or incomplete recovery + logger.critical("Incomplete or failed recovery for IN_PROGRESS " + "checkpoint - %s", + self.config.in_progress_dir) + raise errors.ReverterError( + "Incomplete or failed recovery for IN_PROGRESS checkpoint " + "- %s" % self.config.in_progress_dir) + + def _remove_contained_files(self, file_list): # pylint: disable=no-self-use + """Erase all files contained within file_list. + + :param str file_list: file containing list of file paths to be deleted + + :returns: Success + :rtype: bool + + :raises certbot.errors.ReverterError: If + all files within file_list cannot be removed + + """ + # Check to see that file exists to differentiate can't find file_list + # and can't remove filepaths within file_list errors. + if not os.path.isfile(file_list): + return False + try: + with open(file_list, "r") as list_fd: + filepaths = list_fd.read().splitlines() + for path in filepaths: + # Files are registered before they are added... so + # check to see if file exists first + if os.path.lexists(path): + os.remove(path) + else: + logger.warning( + "File: %s - Could not be found to be deleted\n" + " - Certbot probably shut down unexpectedly", + path) + except (IOError, OSError): + logger.critical( + "Unable to remove filepaths contained within %s", file_list) + raise errors.ReverterError( + "Unable to remove filepaths contained within " + "{0}".format(file_list)) + + return True + + def finalize_checkpoint(self, title): + """Finalize the checkpoint. + + Timestamps and permanently saves all changes made through the use + of :func:`~add_to_checkpoint` and :func:`~register_file_creation` + + :param str title: Title describing checkpoint + + :raises certbot.errors.ReverterError: when the + checkpoint is not able to be finalized. + + """ + # Check to make sure an "in progress" directory exists + if not os.path.isdir(self.config.in_progress_dir): + return + + changes_since_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE") + changes_since_tmp_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE.tmp") + + if not os.path.exists(changes_since_path): + logger.info("Rollback checkpoint is empty (no changes made?)") + with open(changes_since_path, 'w') as f: + f.write("No changes\n") + + # Add title to self.config.in_progress_dir CHANGES_SINCE + try: + with open(changes_since_tmp_path, "w") as changes_tmp: + changes_tmp.write("-- %s --\n" % title) + with open(changes_since_path, "r") as changes_orig: + changes_tmp.write(changes_orig.read()) + + # Move self.config.in_progress_dir to Backups directory + shutil.move(changes_since_tmp_path, changes_since_path) + except (IOError, OSError): + logger.error("Unable to finalize checkpoint - adding title") + logger.debug("Exception was:\n%s", traceback.format_exc()) + raise errors.ReverterError("Unable to add title") + + # rename the directory as a timestamp + self._timestamp_progress_dir() + + def _checkpoint_timestamp(self): + "Determine the timestamp of the checkpoint, enforcing monotonicity." + timestamp = str(time.time()) + others = glob.glob(os.path.join(self.config.backup_dir, "[0-9]*")) + others = [os.path.basename(d) for d in others] + others.append(timestamp) + others.sort() + if others[-1] != timestamp: + timetravel = str(float(others[-1]) + 1) + logger.warning("Current timestamp %s does not correspond to newest reverter " + "checkpoint; your clock probably jumped. Time travelling to %s", + timestamp, timetravel) + timestamp = timetravel + elif len(others) > 1 and others[-2] == timestamp: + # It is possible if the checkpoints are made extremely quickly + # that will result in a name collision. + logger.debug("Race condition with timestamp %s, incrementing by 0.01", timestamp) + timetravel = str(float(others[-1]) + 0.01) + timestamp = timetravel + return timestamp + + def _timestamp_progress_dir(self): + """Timestamp the checkpoint.""" + # It is possible save checkpoints faster than 1 per second resulting in + # collisions in the naming convention. + + for _ in six.moves.range(2): + timestamp = self._checkpoint_timestamp() + final_dir = os.path.join(self.config.backup_dir, timestamp) + try: + filesystem.replace(self.config.in_progress_dir, final_dir) + return + except OSError: + logger.warning("Extreme, unexpected race condition, retrying (%s)", timestamp) + + # After 10 attempts... something is probably wrong here... + logger.error( + "Unable to finalize checkpoint, %s -> %s", + self.config.in_progress_dir, final_dir) + raise errors.ReverterError( + "Unable to finalize checkpoint renaming") diff --git a/certbot/certbot/ssl-dhparams.pem b/certbot/certbot/ssl-dhparams.pem new file mode 100644 index 000000000..9b182b720 --- /dev/null +++ b/certbot/certbot/ssl-dhparams.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/certbot/certbot/tests/__init__.py b/certbot/certbot/tests/__init__.py new file mode 100644 index 000000000..82290ca0b --- /dev/null +++ b/certbot/certbot/tests/__init__.py @@ -0,0 +1 @@ +"""Utilities for running Certbot tests""" diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py new file mode 100644 index 000000000..c88fcd706 --- /dev/null +++ b/certbot/certbot/tests/acme_util.py @@ -0,0 +1,98 @@ +"""ACME utilities for testing.""" +import datetime + +import josepy as jose +import six + +from acme import challenges +from acme import messages + +from certbot._internal import auth_handler + +from certbot.tests import util + + +JWK = jose.JWK.load(util.load_vector('rsa512_key.pem')) +KEY = util.load_rsa_private_key('rsa512_key.pem') + +# Challenges +HTTP01 = challenges.HTTP01( + token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") +DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a") +DNS01_2 = challenges.DNS01(token=b"cafecafecafecafecafecafe0feedbac") + +CHALLENGES = [HTTP01, DNS01] + + +def gen_combos(challbs): + """Generate natural combinations for challbs.""" + # completing a single DV challenge satisfies the CA + return tuple((i,) for i, _ in enumerate(challbs)) + + +def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name + """Return ChallengeBody from Challenge.""" + kwargs = { + "chall": chall, + "uri": chall.typ + "_uri", + "status": status, + } + + if status == messages.STATUS_VALID: + kwargs.update({"validated": datetime.datetime.now()}) + + return messages.ChallengeBody(**kwargs) + + +# Pending ChallengeBody objects +HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) +DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING) +DNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING) + +CHALLENGES_P = [HTTP01_P, DNS01_P] + + +# AnnotatedChallenge objects +HTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, "example.com") +DNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, "example.org") +DNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, "esimerkki.example.org") + +ACHALLENGES = [HTTP01_A, DNS01_A] + + +def gen_authzr(authz_status, domain, challs, statuses, combos=True): + """Generate an authorization resource. + + :param authz_status: Status object + :type authz_status: :class:`acme.messages.Status` + :param list challs: Challenge objects + :param list statuses: status of each challenge object + :param bool combos: Whether or not to add combinations + + """ + # pylint: disable=redefined-outer-name + challbs = tuple( + chall_to_challb(chall, status) + for chall, status in six.moves.zip(challs, statuses) + ) + authz_kwargs = { + "identifier": messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value=domain), + "challenges": challbs, + } + if combos: + authz_kwargs.update({"combinations": gen_combos(challbs)}) + if authz_status == messages.STATUS_VALID: + authz_kwargs.update({ + "status": authz_status, + "expires": datetime.datetime.now() + datetime.timedelta(days=31), + }) + else: + authz_kwargs.update({ + "status": authz_status, + }) + + return messages.AuthorizationResource( + uri="https://trusted.ca/new-authz-resource", + body=messages.Authorization(**authz_kwargs) + ) diff --git a/certbot/certbot/tests/testdata/README b/certbot/certbot/tests/testdata/README new file mode 100644 index 000000000..867215916 --- /dev/null +++ b/certbot/certbot/tests/testdata/README @@ -0,0 +1,11 @@ +The following command has been used to generate test keys: + + for x in 256 512 2048; do openssl genrsa -out rsa${k}_key.pem $k; done + +and for the CSR PEM (Certificate Signing Request): + + openssl req -new -out csr-Xsans_X.pem -key rsa512_key.pem [-config csr-Xsans_X.conf | -subj '/CN=example.com'] [-outform DER > csr_X.der] + +and for the certificate: + + openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der] \ No newline at end of file diff --git a/certbot/certbot/tests/testdata/cert-5sans_512.pem b/certbot/certbot/tests/testdata/cert-5sans_512.pem new file mode 100644 index 000000000..5de7cc6cb --- /dev/null +++ b/certbot/certbot/tests/testdata/cert-5sans_512.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjugAwIBAgIJAJNbfABWQ8bbMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMScwJQYDVQQKDB5FbGVjdHJvbmljIEZyb250aWVyIEZvdW5kYXRpb24xFDAS +BgNVBAMMC2V4YW1wbGUuY29tMB4XDTE2MDYwOTIzMDEzNloXDTE2MDcwOTIzMDEz +NloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xJzAlBgNVBAoMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91 +bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANL +ADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE +30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4GlMIGiMB0GA1UdDgQWBBQmz8jt +S9eUsuQlA1gkjwTAdNWXijAfBgNVHSMEGDAWgBQmz8jtS9eUsuQlA1gkjwTAdNWX +ijAMBgNVHRMEBTADAQH/MFIGA1UdEQRLMEmCDWEuZXhhbXBsZS5jb22CDWIuZXhh +bXBsZS5jb22CDWMuZXhhbXBsZS5jb22CDWQuZXhhbXBsZS5jb22CC2V4YW1wbGUu +Y29tMA0GCSqGSIb3DQEBCwUAA0EAVXmZxB+IJdgFvY2InOYeytTD1QmouDZRtj/T +H/HIpSdsfO7qr4d/ZprI2IhLRxp2S4BiU5Qc5HUkeADcpNd06A== +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert-nosans_nistp256.pem b/certbot/certbot/tests/testdata/cert-nosans_nistp256.pem new file mode 100644 index 000000000..4ec3f24ce --- /dev/null +++ b/certbot/certbot/tests/testdata/cert-nosans_nistp256.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoDCCAUYCCQDCnzfUZ7TQdDAKBggqhkjOPQQDAjBYMQswCQYDVQQGEwJVUzER +MA8GA1UECAwITWljaGlnYW4xEjAQBgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwD +RUZGMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xODA1MTUxNzIyMzlaFw0xODA2 +MTQxNzIyMzlaMFgxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjESMBAG +A1UEBwwJQW5uIEFyYm9yMQwwCgYDVQQKDANFRkYxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPl0JauSZukvAUWv4l5VNLAY +QXhuPXYQBf4dVET3s0E5q9ZCbSe+pNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tzAK +BggqhkjOPQQDAgNIADBFAiEAv8S2GXmWJqZ+j3DBfm72E1YK+HkOf+TOUHsbVR+O +Z1oCIFWNt1SPdIgRp4QAyzVk2pcTF8jDNajEMLWETDtxgRvM +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert-san_512.pem b/certbot/certbot/tests/testdata/cert-san_512.pem new file mode 100644 index 000000000..dcb835994 --- /dev/null +++ b/certbot/certbot/tests/testdata/cert-san_512.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF +nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 +RDjyGMKy5ZgM2w== +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert_2048.pem b/certbot/certbot/tests/testdata/cert_2048.pem new file mode 100644 index 000000000..e02f18e91 --- /dev/null +++ b/certbot/certbot/tests/testdata/cert_2048.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert_512.pem b/certbot/certbot/tests/testdata/cert_512.pem new file mode 100644 index 000000000..96c55cbf4 --- /dev/null +++ b/certbot/certbot/tests/testdata/cert_512.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll +vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn +B/o= +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert_512_bad.pem b/certbot/certbot/tests/testdata/cert_512_bad.pem new file mode 100644 index 000000000..d868dc445 --- /dev/null +++ b/certbot/certbot/tests/testdata/cert_512_bad.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYzCCAg2gAwIBAgIJAPvqv4TcAtuFMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD +VQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEMMAoG +A1UECgwDRUZGMRYwFAYDVQQLDA1UZWNoIFByb2plY3RzMQ4wDAYDVQQDDAVZb21u +YTEjMCEGCSqGSIb3DQEJARYUeW9tbmEubmFzc2VyQGVmZi5vcmcwHhcNMTcwMzI0 +MjIzMjUxWhcNNDgwMTI0MjIzMjUxWjCBjDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgM +B09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xDDAKBgNVBAoMA0VGRjEWMBQGA1UE +CwwNVGVjaCBQcm9qZWN0czEOMAwGA1UEAwwFWW9tbmExIzAhBgkqhkiG9w0BCQEW +FHlvbW5hLm5hc3NlckBlZmYub3JnMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1 +c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvO +jm0c+pVE6K+EdE/twuUCAwEAAaNQME4wHQYDVR0OBBYEFCbPyO1L15Sy5CUDWCSP +BMB01ZeKMB8GA1UdIwQYMBaAFCbPyO1L15Sy5CUDWCSPBMB01ZeKMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADQQAeWDdcrJOolFHr3m8TrlDJ/Ca4SfJya2jb +K1wahbX83sC42834HbDOQASGBhoLYDhC1cMPbKDDjMbR9rjYuf7T +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cert_fullchain_2048.pem b/certbot/certbot/tests/testdata/cert_fullchain_2048.pem new file mode 100644 index 000000000..422d221fb --- /dev/null +++ b/certbot/certbot/tests/testdata/cert_fullchain_2048.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/cli.ini b/certbot/certbot/tests/testdata/cli.ini new file mode 100644 index 000000000..8ef506071 --- /dev/null +++ b/certbot/certbot/tests/testdata/cli.ini @@ -0,0 +1 @@ +agree-dev-preview = True diff --git a/certbot/certbot/tests/testdata/csr-6sans_512.conf b/certbot/certbot/tests/testdata/csr-6sans_512.conf new file mode 100644 index 000000000..fa7b3edc2 --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-6sans_512.conf @@ -0,0 +1,29 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +C=US +C_default = US +ST=Michigan +ST_default=Michigan +L=Ann Arbor +L_default=Ann Arbor +O=EFF +O_default=EFF +OU=University of Michigan +OU_default=University of Michigan +CN=example.com +CN_default=example.com + + +[ v3_req ] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = example.com +DNS.2 = example.org +DNS.3 = example.net +DNS.4 = example.info +DNS.5 = subdomain.example.com +DNS.6 = other.subdomain.example.com \ No newline at end of file diff --git a/certbot/certbot/tests/testdata/csr-6sans_512.pem b/certbot/certbot/tests/testdata/csr-6sans_512.pem new file mode 100644 index 000000000..f72c0541d --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-6sans_512.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoIGGMIGDBgkqhkiG +9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL +ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t +ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQA+ +sU6T30n3SsdnHlj0Va8eECOWK7Lf8nUfxxgjPMQ7BoU8gbAnGfDmOlwDronTRqf1 +Me+nlYJU4TX1OiX10DYu +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/csr-nonames_512.pem b/certbot/certbot/tests/testdata/csr-nonames_512.pem new file mode 100644 index 000000000..abe1029ca --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-nonames_512.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF +AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+ +6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD +QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH +lKWVQ8+xwYMscGWK0NApHGco +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/csr-nosans_512.conf b/certbot/certbot/tests/testdata/csr-nosans_512.conf new file mode 100644 index 000000000..1026cf9ad --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-nosans_512.conf @@ -0,0 +1,16 @@ +[req] +distinguished_name = req_distinguished_name + +[req_distinguished_name] +C=US +C_default = US +ST=Michigan +ST_default=Michigan +L=Ann Arbor +L_default=Ann Arbor +O=EFF +O_default=EFF +OU=University of Michigan +OU_default=University of Michigan +CN=example.com +CN_default=example.com \ No newline at end of file diff --git a/certbot/certbot/tests/testdata/csr-nosans_512.pem b/certbot/certbot/tests/testdata/csr-nosans_512.pem new file mode 100644 index 000000000..5f02d7e97 --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-nosans_512.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBMzCB3gIBADB5MQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ +BgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMR8wHQYDVQQLDBZVbml2ZXJz +aXR5IHBmIE1pY2hpZ2FuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3 +DQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db0Cx1GxS4dQEEEOUfA2VF3d+n +nzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLlAgMBAAGgADANBgkqhkiG9w0B +AQsFAANBAG06jIPvSC6wiGLy7sUTaEX4UCE6Cztp3vh/uXN7Q++CGn6KiXNs/BRW +eFlcFPbvxbVG/ZZFR5aPs+Oy6RhqOjg= +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/csr-nosans_nistp256.pem b/certbot/certbot/tests/testdata/csr-nosans_nistp256.pem new file mode 100644 index 000000000..2f0a671ed --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-nosans_nistp256.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBFDCBugIBADBYMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ +BgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMRQwEgYDVQQDDAtleGFtcGxl +LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDz5dCWrkmbpLwFFr+JeVTSw +GEF4bj12EAX+HVRE97NBOavWQm0nvqTVG5KPRfkxZLnO11Y0D7H5A24dCPZw9Leg +ADAKBggqhkjOPQQDAgNJADBGAiEAuoZHrYA5sy2DRTdLAxJTBNHKFFKbtaGt+QaJ +A62qa8sCIQCUkSgSAiNaEnJ7r5fKphdjeORHqhpl6flYkLE3lGmGdg== +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/csr-san_512.pem b/certbot/certbot/tests/testdata/csr-san_512.pem new file mode 100644 index 000000000..a7128e35c --- /dev/null +++ b/certbot/certbot/tests/testdata/csr-san_512.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN +AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t +MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy +tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/csr_512.der b/certbot/certbot/tests/testdata/csr_512.der new file mode 100644 index 000000000..5c03f3a11 Binary files /dev/null and b/certbot/certbot/tests/testdata/csr_512.der differ diff --git a/certbot/certbot/tests/testdata/csr_512.pem b/certbot/certbot/tests/testdata/csr_512.pem new file mode 100644 index 000000000..c62224ca7 --- /dev/null +++ b/certbot/certbot/tests/testdata/csr_512.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtFeGFt +cGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db +0Cx1GxS4dQEEEOUfA2VF3d+nnzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLl +AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAAceUlq4La8qaiK0DeDP3M19BIVzMmz2 +oemG2fOvPiwNCB90ctSWQ6bMpUMV85ShcFi31C5vlntPfztehhq6YuE= +-----END CERTIFICATE REQUEST----- diff --git a/certbot/certbot/tests/testdata/nistp256_key.pem b/certbot/certbot/tests/testdata/nistp256_key.pem new file mode 100644 index 000000000..4be37e49b --- /dev/null +++ b/certbot/certbot/tests/testdata/nistp256_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOvXH384CyNNv2lfxvjc7hg2f7ScYoLvlk/VpINLJlGBoAoGCCqGSM49 +AwEHoUQDQgAEPPl0JauSZukvAUWv4l5VNLAYQXhuPXYQBf4dVET3s0E5q9ZCbSe+ +pNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tw== +-----END EC PRIVATE KEY----- diff --git a/certbot/certbot/tests/testdata/ocsp_certificate.pem b/certbot/certbot/tests/testdata/ocsp_certificate.pem new file mode 100644 index 000000000..471844859 --- /dev/null +++ b/certbot/certbot/tests/testdata/ocsp_certificate.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGYDCCBEigAwIBAgIKcjrC4hZcebbtODANBgkqhkiG9w0BAQsFADBRMQswCQYD +VQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNVBAMM +GkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDUxMjE1NTgyMVoXDTE5 +MTEwODIyNTkwMFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9P +b+YhJPypm4ui+AZUHPrJ6IsB9R/6Wvgec2G/GuW/UNQFktIhU10HOHAbiJeYLqNZ +1Cia8JD6NXXGbprOjIbZWvjulYTaLSlClcK0H7HZrcgrK60OeIGEtur27ga68RML +hs1FG7TNyWVysifOtwW9Oo1mZQQtxViiE2Yb+Q4QqIxitnbrnFmKrVJSUHVXi8/I +BK1yLrJiRBZMIw0wvAWcWEG2Gpp9PAbemlb11Zx8sm/RSGh7u60rmETbB2Pu941s +XJCSQRtq5yKdtjIJTIgbe12SPkknqTqa3aUh7hgho0IymlDSeeocL60SUiUAsPEr +QRWleodOR1ChXz5mFokCAwEAAaOCAokwggKFMAkGA1UdEwQCMAAwHwYDVR0jBBgw +FoAUd9nQBpFm2N0ZJo1JrNowL2p7YrEwHQYDVR0OBBYEFExS23I6sLCeO6KIxzoc +tr9s+HmiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwIAYDVR0gBBkwFzALBglghEIBGgEAAgcwCAYGZ4EMAQIBMEIGA1UdHwQ7 +MDkwN6A1oDOGMWh0dHA6Ly9jcmwudGVzdDQuYnV5cGFzcy5uby9jcmwvQlBDbGFz +czJUNENBNS5jcmwwIQYDVR0RAQH/BBcwFYITYnV5cGFzcy5wYWNhbGlzLm5ldDB4 +BggrBgEFBQcBAQRsMGowKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1 +eXBhc3MuY29tMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnRlc3Q0LmJ1eXBhc3Mu +bm8vY3J0L0JQQ2xhc3MyVDRDQTUuY2VyMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDw +AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsTLGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMA +RzBFAiEA1oWB4c6q7+tqGA4HhLNACOemr9c2aIUuWxeQE7/PlSYCIEolZ7pWVs1J +VyQW/AqeuXGB7qScwUgLh9C1uOJoeRe6AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsT +LGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMARzBFAiAoLaNvIwMDifsDAXJBsAKHlYx7 +QPLXL8onYKm8f+Sf1wIhAMepo2GX84UR7WtooqzkBZLG+PaBy1zMuUAG6mwnroF9 +MA0GCSqGSIb3DQEBCwUAA4ICAQAPWLdjNS5lLL5SEtghYebtDmNj2968NYSDvb1L +1/uFwg3LCVRR1Xb3z1Hc/sc1W0IFXU0zOqEQiuP8jkVP7UqkaWuK5Eu0eP0zPI83 +WBZM0+eBwxwzIMK/Q7fYKTu1+vg/FlH0WhtV43DQSik66366zvPi2Tfag9IPvRei +DOjbSOBF0o4er2oCrtI0lK5YrHOdWtD7xwQIuA606P9ucuufMf+JcmduRJsVZ2Zu +3K32SMDdAnyjvQWZNbt1ex3G8vuFQEi690UBhPcha/SO8QvLS89wcaLJnyMIWdv7 +54cbw+fa1nLKM7qph6Mk1yb0qpomPqLmKw4T6WX36c0vDlFSpexJLGgWDFqLUxPN +qV7cJz4mi1qaYfdWXRrnyU4bl55pHTTgEzbohV7apsmytkCe1uFNrpcTh8jzAhGN +PQqarX9UoESR56B/ufbBGlBWi0pkV49BFks6Ue0GVKo7djoxuV6+SsmYSE+6MNPv +IUsm54TSnwxjA8WyG7pl14g1hkGFQ4NRYJMiVqK3DMABaPxVmT7NRxUQQiM0mmM7 +EKNzLBeWHJF5ecdDR1MiIF3ayn+RiZb0r8aSQBMLwN1YwUZw+hSYz1eCd7bHN1gC +1ksxP61f8LBz0SwDoyOTr8wY++wqF26KfoYuKQ3LjLeHvuUtL3EMnAhiyuej8ZOZ +22spng== +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem b/certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem new file mode 100644 index 000000000..4f894ae4b --- /dev/null +++ b/certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem @@ -0,0 +1,38 @@ +22spng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGMzCCBBugAwIBAgIJMvsa+ZFQCj8nMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEmMCQGA1UEAwwd +QnV5cGFzcyBDbGFzcyAyIFRlc3Q0IFJvb3QgQ0EwHhcNMTcwMjEzMTY1MjQ2WhcN +MjcwMjEzMTY1MjQ2WjBRMQswCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBB +Uy05ODMxNjMzMjcxIzAhBgNVBAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi/vpgO2sbUQZsoxWd6us +QvT/59kvw5ehoJABBXFs1J1AV1/K2hjhDXit/sNGKjzDvkfE9PJqXMnhKpPFkUzC +z/NmDK++d6aRflnDvJrxlPVpp0QGbe3qOErByFjWiHoobuVItlpRO/BaBdlgGvmQ +LeZFBXs/ZrLNFUKBcE+DZIyJH7vy2EB5dNNVn2mx0n+371InpKsYUaHNlxPpp+uj +TOL+e4OjWTBwDaI7rVzpavozb8SPzFxjpxLLVH/j+8VPwoe3lmxr8ATyI178iRdA +uxYfaKURSfu7PWjnDNTnq26E3pwW3E5zUbsADgUMh/PzoJAcszL1eHGUQaAGBP85 +PlLmHr+nsPMHXOUyl7Ts6KGkZlvjnVshKwUxYAqjAC7/BY0iI0xc406NK9heeVDk +NiFA8/To6mQ09vO/TBxQtkfNk2yuxiixa101peSg4/+E4VhwYv6MJxS/oVqBd2d3 +wemYW/JUVeJg9wXGq1e/c09/UjGwUGwU9s5LNFEgj4v1tcvWnONzWNXkyMrs5g4e +U8L/DQ3XgNrcA9zrfFq0cQhSJonj/VI/jbBYyB2yEuQAIjAN6eDIOoLmHGIIvZtE +0LL5jaZC3W518jB1OF7QSvaFtaFl0VqDy6LMXL50elMVC+hr9KpDnN0t8gaSiPyZ +wEC9SMdQ7SLVOUK1Xdh3dh0CAwEAAaOCAQkwggEFMA8GA1UdEwEB/wQFMAMBAf8w +HwYDVR0jBBgwFoAU0aT+MaGsc75ZynH0up0oH+tVHh4wHQYDVR0OBBYEFHfZ0AaR +ZtjdGSaNSazaMC9qe2KxMA4GA1UdDwEB/wQEAwIBBjAgBgNVHSAEGTAXMAsGCWCE +QgEaAQACBzAIBgZngQwBAgEwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC50 +ZXN0NC5idXlwYXNzLm5vL2NybC9CUENsYXNzMlQ0Um9vdENBLmNybDA5BggrBgEF +BQcBAQQtMCswKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1eXBhc3Mu +Y29tMA0GCSqGSIb3DQEBCwUAA4ICAQBOgxedV31NCpZQRc8yFxoqQNgBnY1UeH/h +/s/9fGQzyGnTWZldEi5MGJKF6ulcYnklitlg/jic9au3xSoqP/i2smUHByX2wMrC +mDpLCwio2x2p/0Wscj5asqzJE2cCWqob2iHxo36nsr3Jdd2GIlzhZ0wm8rMZxsQG +FgbgHYIer79S+PIdHoZuUnCJhsJ+1PRUmm2t7vcmZpu8l4CeL0XJX98l2L8kbBds +MGo1EazGAEirZnSfQKCARhUcEdavsKl067+irsGGcK4+L78Vl9S1/QPfKG30L5fv +nM1X1qAdhsbjwVdrhLkjpzabT0icsW6W17HLh8UBYdA7k4GclA6h+mNrXAt7JAeZ +PzMFq0I7vVJNEdolZHTVCqT0sdJiTj+phS1ztK86Wb1R/5d5B1VSb789zSdJfrwV +ppXgPtZq5x3GQi6ooteWyuWj3cBcNu9TU1D8u1F0XI5gw4Y0VpxlDxysUgFQJlo4 +VYmMpgr442o/35UgwzkIC7x/6dkvMZvM4jYB5JZJXjynR35XawXB/hzybermJ8BB +DsY0MCOwxhpsTbyEC4wfxZ08B4JtORkToOt4OWuejovsr68Ht6ytOPj7dquoPPNM +9eGNSp94nEIiZ2n75ZMg0gIQArXU9OCV6B2TXxB7w2YB0y0teDgVhoM3IY/ltqJ/ +PJrUUjM8OQ== +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/ocsp_responder_certificate.pem b/certbot/certbot/tests/testdata/ocsp_responder_certificate.pem new file mode 100644 index 000000000..53bc4a92a --- /dev/null +++ b/certbot/certbot/tests/testdata/ocsp_responder_certificate.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpjCCAo6gAwIBAgINARMIGYlEsD1LTt6D7zANBgkqhkiG9w0BAQsFADBRMQsw +CQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNV +BAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDQwNTEwMDAwMFoX +DTE5MDcwNDEwMDAwMFowSTELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3Mg +QVMtOTgzMTYzMzI3MRswGQYDVQQDDBJCdXlwYXNzIFRlc3Q0IE9DU1AwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKGF+kYNd1fbhYT7Vf9xouZlx+4w45 +Y5EowPoaSKFo4uUDDxkj4PwmMiH4w9Q2bGrCbZRrDrvlNVY/kwzLu4CIk6Ip0dgm +VZGNFB3Xo9nai7rI5pn/YVvVnDIQXh1LRbekzLVyHvhRgMpRb19xN/iYsxaOJDph +8eAgbTKf6eitvfbvn/zXHj4KGKycuULI4+mwlfV3uioT4ulbT7PTVJetgi/XXFDO +xMjbqx6I1ZMmzKJ6LNaFlfx6GdZsaLRDCidHzGp8Fm4ZdV+UPvMZcVDQO6rvQ3wU +iGyCqgfE5e0aFvfeLoBPBtaoT0Ht1CvGdTfVet6PXrF6gh40fdEH5Ob5AgMBAAGj +gYQwgYEwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBR32dAGkWbY3RkmjUms2jAvanti +sTAdBgNVHQ4EFgQU3VlR+sSIVpmXklieP7IlpVUcXIowDgYDVR0PAQH/BAQDAgeA +MBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GCSsGAQUFBzABBQQCBQAwDQYJKoZIhvcN +AQELBQADggIBAFBRLVsBadNFAoFi0HOrfxYsiqggZGJLlgxGyi/0NBIgduG4kcpM +THvplwBwMQEqyp5511pSEbLPAFj8EqC5c46hXZXmT49xlfRvr2Bo+qtTPV9szuWr +8muEIejwRrkATpqWPZWR2zVTXfB90mU2oGuRvxUVmnW4v+FrCChJo7+9yTocZJKx +p4vxYfPMeggomdGAAUz94+0ppSjOLDzs3MA8uOcR0zJ2Y7UHb7PBf/HiM3GO2uKB +sRgdDaGIf/PNpav0xJ/abGNNNwvXzHiMgqqImsuv/JoncPQWbClNurhXpdN7xt9C +HcLX2AdggabcogjWm4guBFuFTsL1i0l8Bsu/6iPJ7ddCeANfYzf7h6AcQq12uFl3 +070F29DtPh8D3FPWgRZZsxoANFjXErxfj4a4+DR+jhhkb9YM/wI0vCOM7W6PKxVn +ZK5kHGOQTcQMj7RCX52gEf27M33zC7HVam+kKhGvwq7D9Bs5hZclzcbjpR4eIxT7 +tzuiy5VpPh1DRLPrphPUB4xsA1dy6zbkg8OqddG6NxD++ja/iZyzSB3SeWyO02qA +QoK2FzDasxpZ9rT3ioAcms3wVNe4lcd4OP8gHZONuat/gvxk6OZvAld6cnIrQZYB +Tbu89ZWvhsyI3p4YC/15pUvA95j9Y0te+G+CF22Eoyb+rtz6mMletnUB +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/os-release b/certbot/certbot/tests/testdata/os-release new file mode 100644 index 000000000..15bc5fb3c --- /dev/null +++ b/certbot/certbot/tests/testdata/os-release @@ -0,0 +1,7 @@ +NAME="SystemdOS" +VERSION="42.42.42 LTS, Unreal" +ID=systemdos +ID_LIKE="something nonexistent debian" +VERSION_ID="42" +HOME_URL="http://www.example.com/" +SUPPORT_URL="http://help.example.com/" diff --git a/certbot/certbot/tests/testdata/rsa2048_key.pem b/certbot/certbot/tests/testdata/rsa2048_key.pem new file mode 100644 index 000000000..33a68c74d --- /dev/null +++ b/certbot/certbot/tests/testdata/rsa2048_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm1WIecnHjL4Fs +JvxDP27GyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop ++D7s+oh0apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3 +DaNokGn7r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf +5QU5pFx60a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDV +MJ3mIB8FOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf +3nCJFk1fAgMBAAECggEAJkhbVntagfgd+cbZbXm2sIdKQGlwXk92/Zxd3tZMcuNY +rU+/C2bJ5uTEm+0R/V9f3FXlsCagGde2t7ExFnJScSRAGCuFRxudMMI/wNvUvnpR +O9vN3HxrRo2rZqBkqHIZCR0d2Bxs/0cvGqTLZgsVWKV4xM07TThcE7DtvsNGegRn +WFxfsRcRypkIvZoba1HagvCituRBEa07R7mQp8kRhP9ZeRq3bZws9qBmqzj1cylG +q8QA4Foq7sK8P78bpIhrcOFBDAr+Vr1ZGY6u01J0w13MUtl6iIx4VCjQKt4NkzsK +dj2q+GAMwhReR2ZS42o8LiyGpwusj+dKIFfFekgK2QKBgQD4wwmRDgvt85brQTNF +Tkhui0eToz5oXt8mVDb58nwkpojFQOv87ZyNsEqm7S0t/3RtEViVio2aymTMsrz4 +21vRq46dvhINQ3DoMok6xIchEOEgMeonOilkURWtrMjD/Kn297Asv7zOqI5BCNiP +3FFcRqf+CaqbhnOgMkcI5z6b7QKBgQDtjM1otFFHyS7ctyLRuMeFyxWUSbWHvi8U +xjUW256c6wpQ2DBLSVB61VQjfrSjkZ5DJVFGnbw42HxSDafL11mzTbY1vDbgtgLK +YiuVHG7OYZJTLaZoM68BseX4xHN8FztnvvP1ttuk5oFb+vD8q6ODZSEawRd3PvtX +D7RtNouc+wKBgQDiwBWGTUF+gt18T5BGilbnvLlf0Btg06mgrH74UpnqZoqhEs6J +XKWpWZqSkfruxL4BdSBEH2l4QSiklgA+7uTBOBnlm42k3WaboQUJtn5eG5651AXV +/+Qe9vJFvwu56iObZKcIAzY9QdN5YHDWoULgU99pZrJG1cWrrmilqvOc+QKBgQCB +iOslslY0N+926eJxzDn4qkJtJzh2+e1AfcjLWx0F4mEwroK/Ow5IvPVxmZE1NJ3B +baMBR9gwg1RfhhS+4gKG9NRsPuMJ7BZfd+LeH7AImEorU1RPtAc1fGW0HqP+wchi +DU2I6pqhNBTMLG2myo2Sg93mce6y1sRFuEmh2EGPawKBgQC3uUEdjQekXaxXfYHi +1Dk3Ht1a9t8XxwoCVRqicE7lqlwDtS2y9lHAeUP7JNy8ZGNjx8srRZpkYVMztugo +Ecw26UA7FbNqJP5OPkGjfiFqtOq70h9vlfLdiAPmoqyOx//RkgiNXt9m5xcDzzdB +7EtBK59KSiQkB8fHtooy7Ipiiw== +-----END PRIVATE KEY----- diff --git a/certbot/certbot/tests/testdata/rsa256_key.pem b/certbot/certbot/tests/testdata/rsa256_key.pem new file mode 100644 index 000000000..659274d1d --- /dev/null +++ b/certbot/certbot/tests/testdata/rsa256_key.pem @@ -0,0 +1,6 @@ +-----BEGIN RSA PRIVATE KEY----- +MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh +AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N +E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 +rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt +-----END RSA PRIVATE KEY----- diff --git a/certbot/certbot/tests/testdata/rsa512_key.pem b/certbot/certbot/tests/testdata/rsa512_key.pem new file mode 100644 index 000000000..610c8d315 --- /dev/null +++ b/certbot/certbot/tests/testdata/rsa512_key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 +vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn +elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc +mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp +Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj +8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq +6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ +-----END RSA PRIVATE KEY----- diff --git a/certbot/certbot/tests/testdata/sample-archive/cert1.pem b/certbot/certbot/tests/testdata/sample-archive/cert1.pem new file mode 100644 index 000000000..4010000ef --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-archive/cert1.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF +ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5 +MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+ +slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN +NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h +A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx +UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP +r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC +AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8 +L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE +bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy +eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0 +c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB +8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw +Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl +cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy +dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl +IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0 +b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy +KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve +jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2 +Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU ++ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf +rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ== +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/sample-archive/chain1.pem b/certbot/certbot/tests/testdata/sample-archive/chain1.pem new file mode 100644 index 000000000..760417fe9 --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-archive/chain1.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV +BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw +NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i +8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8 +tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj +7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8 +BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD +HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj +UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7 +eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB +vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl +zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo +vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L +oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW +rFo4Uv1EnkKJm3vJFe50eJGhEKlx +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/sample-archive/fullchain1.pem b/certbot/certbot/tests/testdata/sample-archive/fullchain1.pem new file mode 100644 index 000000000..6e24d6038 --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-archive/fullchain1.pem @@ -0,0 +1,47 @@ +-----BEGIN CERTIFICATE----- +MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF +ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5 +MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+ +slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN +NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h +A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx +UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP +r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC +AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8 +L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE +bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy +eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0 +c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB +8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw +Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl +cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy +dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl +IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0 +b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy +KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve +jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2 +Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU ++ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf +rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV +BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw +NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i +8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8 +tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj +7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8 +BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD +HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj +UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7 +eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB +vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl +zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo +vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L +oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW +rFo4Uv1EnkKJm3vJFe50eJGhEKlx +-----END CERTIFICATE----- diff --git a/certbot/certbot/tests/testdata/sample-archive/privkey1.pem b/certbot/certbot/tests/testdata/sample-archive/privkey1.pem new file mode 100644 index 000000000..f03fdd0a3 --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-archive/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8rnaiynCHVmeV +WitX7oZRA22bb7NhPrJZuHZkktZxb3uzHZWevStIHbZ4ZIj0NMBkE5YoHg2fH2PI +i/OxmTtLaeX+0vkCTTWAomMOo+KqeSQcv8xttpCc9ULru/71zGC8y7yusemIsYlg +Fsqqr5VFQS0ta+Ft4QOWfhdRCLF3ss+0yXt5QMyFgAMaXx/Tr4iM9qxA6LbgnVO+ +9VEiyzUsOKiaIzZU8VKWRupRhbPChm/LakTl5J2jChOD7zd0iE4sUlExW5LbdVs+ +tJqFmzXSzCr4JM7lD6+L4jtj+EjubJTQGNGXhLLD6VQHpTdxL6wHBGbaCphuX50A +1aPLlWoNAgMBAAECggEAfKKWFWS6PnwSAnNErFoQeZVVItb/XB5JO8EA2+CvLNFi +mefR/MCixYlzDkYCvaXW7ISPrMJlZxYaGNBx0oAQzfkPB2wfNqj/zY/29SXGxast +8puzk0mEb1oHsaZGfeFaiXvfkFpPlI8J2uJTT7qaVNv/1sArciSv9QonpsyiRhlB +yqT49juNVoR1tJHyXzkkRfHKTG8OlJd4kuFOl3fM9dTFPQ/ft0kTNAQ/B4SFvSwF +RJsbLbsbFGsUdV9ekE6UX6oWD/Ah707rvgtCyS0Bc+0O3t2EKwmm3RXPRUMHCVxE +bKdTxRB4etbjMVXMuVhB8Y4GbfrtMCy+qxZQ6znCAQKBgQDr7bcYAZVZp/nBMVB+ +lBO9w73J6lnEWm6bZ9728KlGAKETaRhxZQSi6TN6MWwNwnk6rinyz4uVwVr9ZRCs +WkB1TbvW0JNcWdr3YClwsKXAt8X22bjGe0LagDJHG6r1TPS+MdovOS2M6IMaxlbT +rzFhSJ8ojLX3tqnOsmc7YAFLjQKBgQDMu8E9hoJt82lQzOGrjHmGzGEu2GLx9WKO +e4nkj335kX6fIhMMqSXBFbTJZwXoYvk5J8ZnaARbYG0m5nxDCwRjX5HWa8q0B2Po +ta53w01sKKznzlPjUhsdhEthun7MCFfLZpgvcZ9xVzOXo3/Zfn2+RrsPSjrVDqBy +hj+k5mW4gQKBgHFWKf3LTO7cBdvsD8ou4mjn7nVgMi1kb/wR4wdnxzmMtdR4STi4 +GYkVVBhgQ5M8mDY7UoWFdH3FfCt8cI0Lcimn5ROl8RSNSeZKeL3c7lNtNRmHr/8R +WaVTrlOAlBjxFiWEF1dWNW6ah9jF7RIV+DfOxj6ZkhTk2CAmjfb1AMpFAoGABf96 +KdNG/vGipDtcYSo8ZTaXoke0nmISARqdb5TEnAsnKoJVDInoEUARi9T411YO9x2z +MlRZzFOG3xzhhxVLi53BKAcAaUXOJ4MrGVcfbYvDhQcGbiJ5qOO3UaWlEVUtPUhE +LR+nDCsB1+9yT2zlQi3QTSJflt5W1QQZ2TrmwAECgYEAvQ7+sTcHs1K9yKj7koEu +A19FbMA0IwvrVRcV/VqmlsoW6e6wW2YND+GtaDbKdD0aBPivqLJwpNFrsRA+W0iB +vzmML6sKhhL+j7tjSgq+iQdBkKz0j9PyReuhe9CRnljMmyun+4qKEk0KUvxBrjPY +Skn+ML18qyUoEPnmbpfHxCs= +-----END PRIVATE KEY----- diff --git a/certbot/certbot/tests/testdata/sample-renewal-ancient.conf b/certbot/certbot/tests/testdata/sample-renewal-ancient.conf new file mode 100644 index 000000000..9586d5492 --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-renewal-ancient.conf @@ -0,0 +1,73 @@ +cert = MAGICDIR/live/sample-renewal-ancient/cert.pem +privkey = MAGICDIR/live/sample-renewal-ancient/privkey.pem +chain = MAGICDIR/live/sample-renewal-ancient/chain.pem +fullchain = MAGICDIR/live/sample-renewal-ancient/fullchain.pem +renew_before_expiry = 1 year + +# Options and defaults used in the renewal process +[renewalparams] +no_self_upgrade = False +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +apache_handle_modules = True +uir = None +installer = None +nginx_ctl = nginx +config_dir = MAGICDIR +text_mode = False +func = +staging = True +prepare = False +work_dir = /var/lib/letsencrypt +tos = False +init = False +http01_port = 80 +duplicate = False +noninteractive_mode = True +key_path = None +nginx = False +nginx_server_root = /etc/nginx +fullchain_path = /home/ubuntu/letsencrypt/chain.pem +email = None +csr = None +agree_dev_preview = None +redirect = None +verb = certonly +verbose_count = -3 +config_file = None +renew_by_default = False +hsts = False +apache_handle_sites = True +authenticator = webroot +domains = isnot.org, +rsa_key_size = 2048 +apache_challenge_location = /etc/apache2 +checkpoints = 1 +manual_test_mode = False +apache = False +cert_path = /home/ubuntu/letsencrypt/cert.pem +webroot_path = /var/www/ +reinstall = False +expand = False +strict_permissions = False +apache_server_root = /etc/apache2 +account = None +dry_run = False +manual_public_ip_logging_ok = False +chain_path = /home/ubuntu/letsencrypt/chain.pem +break_my_certs = False +standalone = True +manual = False +server = https://acme-staging.api.letsencrypt.org/directory +webroot = True +os_packages_only = False +apache_init_script = None +user_agent = None +apache_le_vhost_ext = -le-ssl.conf +debug = False +logs_dir = /var/log/letsencrypt +apache_vhost_root = /etc/apache2/sites-available +configurator = None diff --git a/certbot/certbot/tests/testdata/sample-renewal.conf b/certbot/certbot/tests/testdata/sample-renewal.conf new file mode 100644 index 000000000..936c5c0e0 --- /dev/null +++ b/certbot/certbot/tests/testdata/sample-renewal.conf @@ -0,0 +1,75 @@ +cert = MAGICDIR/live/sample-renewal/cert.pem +privkey = MAGICDIR/live/sample-renewal/privkey.pem +chain = MAGICDIR/live/sample-renewal/chain.pem +fullchain = MAGICDIR/live/sample-renewal/fullchain.pem +renew_before_expiry = 4 years + +# Options and defaults used in the renewal process +[renewalparams] +no_self_upgrade = False +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +apache_handle_modules = True +uir = None +installer = None +nginx_ctl = nginx +config_dir = MAGICDIR +text_mode = False +func = +staging = True +prepare = False +work_dir = /var/lib/letsencrypt +tos = False +init = False +http01_port = 80 +duplicate = False +noninteractive_mode = True +key_path = None +nginx = False +nginx_server_root = /etc/nginx +fullchain_path = /home/ubuntu/letsencrypt/chain.pem +email = None +csr = None +agree_dev_preview = None +redirect = None +verb = certonly +verbose_count = -3 +config_file = None +renew_by_default = False +hsts = False +apache_handle_sites = True +authenticator = standalone +domains = isnot.org, +rsa_key_size = 2048 +apache_challenge_location = /etc/apache2 +checkpoints = 1 +manual_test_mode = False +apache = False +cert_path = /home/ubuntu/letsencrypt/cert.pem +webroot_path = None +reinstall = False +expand = False +strict_permissions = False +apache_server_root = /etc/apache2 +account = None +dry_run = False +manual_public_ip_logging_ok = False +chain_path = /home/ubuntu/letsencrypt/chain.pem +break_my_certs = False +standalone = True +manual = False +server = https://acme-staging-v02.api.letsencrypt.org/directory +webroot = False +os_packages_only = False +apache_init_script = None +user_agent = None +apache_le_vhost_ext = -le-ssl.conf +debug = False +logs_dir = /var/log/letsencrypt +apache_vhost_root = /etc/apache2/sites-available +configurator = None +must_staple = True +[[webroot_map]] diff --git a/certbot/certbot/tests/testdata/webrootconftest.ini b/certbot/certbot/tests/testdata/webrootconftest.ini new file mode 100644 index 000000000..de3bd98a6 --- /dev/null +++ b/certbot/certbot/tests/testdata/webrootconftest.ini @@ -0,0 +1,3 @@ +webroot +webroot-path = /tmp +domains = eg.com, eg2.com diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py new file mode 100644 index 000000000..d9ff18f1c --- /dev/null +++ b/certbot/certbot/tests/util.py @@ -0,0 +1,399 @@ +"""Test utilities. + +.. warning:: This module is not part of the public API. + +""" +import logging +import shutil +import sys +import tempfile +import unittest +from multiprocessing import Process, Event + +import OpenSSL +import josepy as jose +import mock +import pkg_resources +import six +from six.moves import reload_module # pylint: disable=import-error +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization + +from certbot._internal import configuration +from certbot._internal import constants +from certbot import interfaces +from certbot._internal import lock +from certbot._internal import storage +from certbot import util +from certbot.compat import os +from certbot.compat import filesystem +from certbot.display import util as display_util + + +def vector_path(*names): + """Path to a test vector.""" + return pkg_resources.resource_filename( + __name__, os.path.join('testdata', *names)) + + +def load_vector(*names): + """Load contents of a test vector.""" + # luckily, resource_string opens file in binary mode + data = pkg_resources.resource_string( + __name__, os.path.join('testdata', *names)) + # Try at most to convert CRLF to LF when data is text + try: + return data.decode().replace('\r\n', '\n').encode() + except ValueError: + # Failed to process the file with standard encoding. + # Most likely not a text file, return its bytes untouched. + return data + + +def _guess_loader(filename, loader_pem, loader_der): + _, ext = os.path.splitext(filename) + if ext.lower() == '.pem': + return loader_pem + elif ext.lower() == '.der': + return loader_der + else: # pragma: no cover + raise ValueError("Loader could not be recognized based on extension") + + +def load_cert(*names): + """Load certificate.""" + loader = _guess_loader( + names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) + return OpenSSL.crypto.load_certificate(loader, load_vector(*names)) + + +def load_csr(*names): + """Load certificate request.""" + loader = _guess_loader( + names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) + return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names)) + + +def load_comparable_csr(*names): + """Load ComparableX509 certificate request.""" + return jose.ComparableX509(load_csr(*names)) + + +def load_rsa_private_key(*names): + """Load RSA private key.""" + loader = _guess_loader(names[-1], serialization.load_pem_private_key, + serialization.load_der_private_key) + return jose.ComparableRSAKey(loader( + load_vector(*names), password=None, backend=default_backend())) + + +def load_pyopenssl_private_key(*names): + """Load pyOpenSSL private key.""" + loader = _guess_loader( + names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) + return OpenSSL.crypto.load_privatekey(loader, load_vector(*names)) + + +def make_lineage(config_dir, testfile): + """Creates a lineage defined by testfile. + + This creates the archive, live, and renewal directories if + necessary and creates a simple lineage. + + :param str config_dir: path to the configuration directory + :param str testfile: configuration file to base the lineage on + + :returns: path to the renewal conf file for the created lineage + :rtype: str + + """ + lineage_name = testfile[:-len('.conf')] + + conf_dir = os.path.join( + config_dir, constants.RENEWAL_CONFIGS_DIR) + archive_dir = os.path.join( + config_dir, constants.ARCHIVE_DIR, lineage_name) + live_dir = os.path.join( + config_dir, constants.LIVE_DIR, lineage_name) + + for directory in (archive_dir, conf_dir, live_dir,): + if not os.path.exists(directory): + filesystem.makedirs(directory) + + sample_archive = vector_path('sample-archive') + for kind in os.listdir(sample_archive): + shutil.copyfile(os.path.join(sample_archive, kind), + os.path.join(archive_dir, kind)) + + for kind in storage.ALL_FOUR: + os.symlink(os.path.join(archive_dir, '{0}1.pem'.format(kind)), + os.path.join(live_dir, '{0}.pem'.format(kind))) + + conf_path = os.path.join(config_dir, conf_dir, testfile) + with open(vector_path(testfile)) as src: + with open(conf_path, 'w') as dst: + dst.writelines( + line.replace('MAGICDIR', config_dir) for line in src) + + return conf_path + + +def patch_get_utility(target='zope.component.getUtility'): + """Patch zope.component.getUtility to use a special mock IDisplay. + + The mock IDisplay works like a regular mock object, except it also + also asserts that methods are called with valid arguments. + + :param str target: path to patch + + :returns: mock zope.component.getUtility + :rtype: mock.MagicMock + + """ + return mock.patch(target, new_callable=_create_get_utility_mock) + + +def patch_get_utility_with_stdout(target='zope.component.getUtility', + stdout=None): + """Patch zope.component.getUtility to use a special mock IDisplay. + + The mock IDisplay works like a regular mock object, except it also + also asserts that methods are called with valid arguments. + + The `message` argument passed to the IDisplay methods is passed to + stdout's write method. + + :param str target: path to patch + :param object stdout: object to write standard output to; it is + expected to have a `write` method + + :returns: mock zope.component.getUtility + :rtype: mock.MagicMock + + """ + stdout = stdout if stdout else six.StringIO() + + freezable_mock = _create_get_utility_mock_with_stdout(stdout) + return mock.patch(target, new=freezable_mock) + + +class FreezableMock(object): + """Mock object with the ability to freeze attributes. + + This class works like a regular mock.MagicMock object, except + attributes and behavior set before the object is frozen cannot + be changed during tests. + + If a func argument is provided to the constructor, this function + is called first when an instance of FreezableMock is called, + followed by the usual behavior defined by MagicMock. The return + value of func is ignored. + + """ + def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT): + self._frozen_set = set() if frozen else {'freeze', } + self._func = func + self._mock = mock.MagicMock() + if return_value != mock.sentinel.DEFAULT: + self.return_value = return_value + self._frozen = frozen + + def freeze(self): + """Freeze object preventing further changes.""" + self._frozen = True + + def __call__(self, *args, **kwargs): + if self._func is not None: + self._func(*args, **kwargs) + return self._mock(*args, **kwargs) + + def __getattribute__(self, name): + if name == '_frozen': + try: + return object.__getattribute__(self, name) + except AttributeError: + return False + elif name in ('return_value', 'side_effect',): + return getattr(object.__getattribute__(self, '_mock'), name) + elif name == '_frozen_set' or name in self._frozen_set: + return object.__getattribute__(self, name) + else: + return getattr(object.__getattribute__(self, '_mock'), name) + + def __setattr__(self, name, value): + """ Before it is frozen, attributes are set on the FreezableMock + instance and added to the _frozen_set. Attributes in the _frozen_set + cannot be changed after the FreezableMock is frozen. In this case, + they are set on the underlying _mock. + + In cases of return_value and side_effect, these attributes are always + passed through to the instance's _mock and added to the _frozen_set + before the object is frozen. + + """ + if self._frozen: + if name in self._frozen_set: + raise AttributeError('Cannot change frozen attribute ' + name) + else: + return setattr(self._mock, name, value) + + if name != '_frozen_set': + self._frozen_set.add(name) + + if name in ('return_value', 'side_effect'): + return setattr(self._mock, name, value) + + return object.__setattr__(self, name, value) + + +def _create_get_utility_mock(): + display = FreezableMock() + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + if name != 'notification': + frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call) + setattr(display, name, frozen_mock) + display.freeze() + return FreezableMock(frozen=True, return_value=display) + + +def _create_get_utility_mock_with_stdout(stdout): + def _write_msg(message, *unused_args, **unused_kwargs): + """Write to message to stdout. + """ + if message: + stdout.write(message) + + def mock_method(*args, **kwargs): + """ + Mock function for IDisplay methods. + """ + _assert_valid_call(args, kwargs) + _write_msg(*args, **kwargs) + + + display = FreezableMock() + # Use pylint code for disable to keep on single line under line length limit + for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + if name == 'notification': + frozen_mock = FreezableMock(frozen=True, + func=_write_msg) + setattr(display, name, frozen_mock) + else: + frozen_mock = FreezableMock(frozen=True, + func=mock_method) + setattr(display, name, frozen_mock) + display.freeze() + + return FreezableMock(frozen=True, return_value=display) + + +def _assert_valid_call(*args, **kwargs): + assert_args = [args[0] if args else kwargs['message']] + + assert_kwargs = {} + assert_kwargs['default'] = kwargs.get('default', None) + assert_kwargs['cli_flag'] = kwargs.get('cli_flag', None) + assert_kwargs['force_interactive'] = kwargs.get('force_interactive', False) + + display_util.assert_valid_call(*assert_args, **assert_kwargs) + + +class TempDirTestCase(unittest.TestCase): + """Base test class which sets up and tears down a temporary directory""" + + def setUp(self): + """Execute before test""" + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + """Execute after test""" + # Cleanup opened resources after a test. This is usually done through atexit handlers in + # Certbot, but during tests, atexit will not run registered functions before tearDown is + # called and instead will run them right before the entire test process exits. + # It is a problem on Windows, that does not accept to clean resources before closing them. + logging.shutdown() + # Remove logging handlers that have been closed so they won't be + # accidentally used in future tests. + logging.getLogger().handlers = [] + util._release_locks() # pylint: disable=protected-access + + shutil.rmtree(self.tempdir) + + +class ConfigTestCase(TempDirTestCase): + """Test class which sets up a NamespaceConfig object.""" + def setUp(self): + super(ConfigTestCase, self).setUp() + self.config = configuration.NamespaceConfig( + mock.MagicMock(**constants.CLI_DEFAULTS) + ) + self.config.verb = "certonly" + self.config.config_dir = os.path.join(self.tempdir, 'config') + self.config.work_dir = os.path.join(self.tempdir, 'work') + self.config.logs_dir = os.path.join(self.tempdir, 'logs') + self.config.cert_path = constants.CLI_DEFAULTS['auth_cert_path'] + self.config.fullchain_path = constants.CLI_DEFAULTS['auth_chain_path'] + self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path'] + self.config.server = "https://example.com" + + +def _handle_lock(event_in, event_out, path): + """ + Acquire a file lock on given path, then wait to release it. This worker is coordinated + using events to signal when the lock should be acquired and released. + :param multiprocessing.Event event_in: event object to signal when to release the lock + :param multiprocessing.Event event_out: event object to signal when the lock is acquired + :param path: the path to lock + """ + if os.path.isdir(path): + my_lock = lock.lock_dir(path) + else: + my_lock = lock.LockFile(path) + try: + event_out.set() + assert event_in.wait(timeout=20), 'Timeout while waiting to release the lock.' + finally: + my_lock.release() + + +def lock_and_call(callback, path_to_lock): + """ + Grab a lock on path_to_lock from a foreign process then execute the callback. + :param callable callback: object to call after acquiring the lock + :param str path_to_lock: path to file or directory to lock + """ + # Reload certbot.util module to reset internal _LOCKS dictionary. + reload_module(util) + + emit_event = Event() + receive_event = Event() + process = Process(target=_handle_lock, args=(emit_event, receive_event, path_to_lock)) + process.start() + + # Wait confirmation that lock is acquired + assert receive_event.wait(timeout=10), 'Timeout while waiting to acquire the lock.' + # Execute the callback + callback() + # Trigger unlock from foreign process + emit_event.set() + + # Wait for process termination + process.join(timeout=10) + assert process.exitcode == 0 + + +def skip_on_windows(reason): + """Decorator to skip permanently a test on Windows. A reason is required.""" + def wrapper(function): + """Wrapped version""" + return unittest.skipIf(sys.platform == 'win32', reason)(function) + return wrapper + + +def temp_join(path): + """ + Return the given path joined to the tempdir path for the current platform + Eg.: 'cert' => /tmp/cert (Linux) or 'C:\\Users\\currentuser\\AppData\\Temp\\cert' (Windows) + """ + return os.path.join(tempfile.gettempdir(), path) diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py new file mode 100644 index 000000000..5d8aa8f22 --- /dev/null +++ b/certbot/certbot/util.py @@ -0,0 +1,603 @@ +"""Utilities for all Certbot.""" +import argparse +import atexit +import collections +from collections import OrderedDict +# distutils.version under virtualenv confuses pylint +# For more info, see: https://github.com/PyCQA/pylint/issues/73 +import distutils.version # pylint: disable=import-error,no-name-in-module +import errno +import logging +import platform +import re +import socket +import subprocess +import sys + +import configargparse +import six + +from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module + +from certbot._internal import constants +from certbot import errors +from certbot._internal import lock +from certbot.compat import os +from certbot.compat import filesystem + +if sys.platform.startswith('linux'): + import distro + _USE_DISTRO = True +else: + _USE_DISTRO = False + +logger = logging.getLogger(__name__) + + +Key = collections.namedtuple("Key", "file pem") +# Note: form is the type of data, "pem" or "der" +CSR = collections.namedtuple("CSR", "file data form") + + +# ANSI SGR escape codes +# Formats text as bold or with increased intensity +ANSI_SGR_BOLD = '\033[1m' +# Colors text red +ANSI_SGR_RED = "\033[31m" +# Resets output format +ANSI_SGR_RESET = "\033[0m" + + +PERM_ERR_FMT = os.linesep.join(( + "The following error was encountered:", "{0}", + "Either run as root, or set --config-dir, " + "--work-dir, and --logs-dir to writeable paths.")) + + +# Stores importing process ID to be used by atexit_register() +_INITIAL_PID = os.getpid() +# Maps paths to locked directories to their lock object. All locks in +# the dict are attempted to be cleaned up at program exit. If the +# program exits before the lock is cleaned up, it is automatically +# released, but the file isn't deleted. +_LOCKS = OrderedDict() # type: OrderedDict[str, lock.LockFile] + + +def run_script(params, log=logger.error): + """Run the script with the given params. + + :param list params: List of parameters to pass to Popen + :param callable log: Logger method to use for errors + + """ + try: + proc = subprocess.Popen(params, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + + except (OSError, ValueError): + msg = "Unable to run the command: %s" % " ".join(params) + log(msg) + raise errors.SubprocessError(msg) + + stdout, stderr = proc.communicate() + + if proc.returncode != 0: + msg = "Error while running %s.\n%s\n%s" % ( + " ".join(params), stdout, stderr) + # Enter recovery routine... + log(msg) + raise errors.SubprocessError(msg) + + return stdout, stderr + + +def exe_exists(exe): + """Determine whether path/name refers to an executable. + + :param str exe: Executable path or name + + :returns: If exe is a valid executable + :rtype: bool + + """ + path, _ = os.path.split(exe) + if path: + return filesystem.is_executable(exe) + else: + for path in os.environ["PATH"].split(os.pathsep): + if filesystem.is_executable(os.path.join(path, exe)): + return True + + return False + + +def lock_dir_until_exit(dir_path): + """Lock the directory at dir_path until program exit. + + :param str dir_path: path to directory + + :raises errors.LockError: if the lock is held by another process + + """ + if not _LOCKS: # this is the first lock to be released at exit + atexit_register(_release_locks) + + if dir_path not in _LOCKS: + _LOCKS[dir_path] = lock.lock_dir(dir_path) + + +def _release_locks(): + for dir_lock in six.itervalues(_LOCKS): + try: + dir_lock.release() + except: # pylint: disable=bare-except + msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock) + logger.debug(msg, exc_info=True) + _LOCKS.clear() + + +def set_up_core_dir(directory, mode, strict): + """Ensure directory exists with proper permissions and is locked. + + :param str directory: Path to a directory. + :param int mode: Directory mode. + :param bool strict: require directory to be owned by current user + + :raises .errors.LockError: if the directory cannot be locked + :raises .errors.Error: if the directory cannot be made or verified + + """ + try: + make_or_verify_dir(directory, mode, strict) + lock_dir_until_exit(directory) + except OSError as error: + logger.debug("Exception was:", exc_info=True) + raise errors.Error(PERM_ERR_FMT.format(error)) + + +def make_or_verify_dir(directory, mode=0o755, strict=False): + """Make sure directory exists with proper permissions. + + :param str directory: Path to a directory. + :param int mode: Directory mode. + :param bool strict: require directory to be owned by current user + + :raises .errors.Error: if a directory already exists, + but has wrong permissions or owner + + :raises OSError: if invalid or inaccessible file names and + paths, or other arguments that have the correct type, + but are not accepted by the operating system. + + """ + try: + filesystem.makedirs(directory, mode) + except OSError as exception: + if exception.errno == errno.EEXIST: + if strict and not filesystem.check_permissions(directory, mode): + raise errors.Error( + "%s exists, but it should be owned by current user with" + " permissions %s" % (directory, oct(mode))) + else: + raise + + +def safe_open(path, mode="w", chmod=None): + """Safely open a file. + + :param str path: Path to a file. + :param str mode: Same os `mode` for `open`. + :param int chmod: Same as `mode` for `filesystem.open`, uses Python defaults + if ``None``. + + """ + open_args = () # type: Union[Tuple[()], Tuple[int]] + if chmod is not None: + open_args = (chmod,) + fdopen_args = () # type: Union[Tuple[()], Tuple[int]] + fd = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) + return os.fdopen(fd, mode, *fdopen_args) + + +def _unique_file(path, filename_pat, count, chmod, mode): + while True: + current_path = os.path.join(path, filename_pat(count)) + try: + return safe_open(current_path, chmod=chmod, mode=mode),\ + os.path.abspath(current_path) + except OSError as err: + # "File exists," is okay, try a different name. + if err.errno != errno.EEXIST: + raise + count += 1 + + +def unique_file(path, chmod=0o777, mode="w"): + """Safely finds a unique file. + + :param str path: path/filename.ext + :param int chmod: File mode + :param str mode: Open mode + + :returns: tuple of file object and file name + + """ + path, tail = os.path.split(path) + return _unique_file( + path, filename_pat=(lambda count: "%04d_%s" % (count, tail)), + count=0, chmod=chmod, mode=mode) + + +def unique_lineage_name(path, filename, chmod=0o644, mode="w"): + """Safely finds a unique file using lineage convention. + + :param str path: directory path + :param str filename: proposed filename + :param int chmod: file mode + :param str mode: open mode + + :returns: tuple of file object and file name (which may be modified + from the requested one by appending digits to ensure uniqueness) + + :raises OSError: if writing files fails for an unanticipated reason, + such as a full disk or a lack of permission to write to + specified location. + + """ + preferred_path = os.path.join(path, "%s.conf" % (filename)) + try: + return safe_open(preferred_path, chmod=chmod), preferred_path + except OSError as err: + if err.errno != errno.EEXIST: + raise + return _unique_file( + path, filename_pat=(lambda count: "%s-%04d.conf" % (filename, count)), + count=1, chmod=chmod, mode=mode) + + +def safely_remove(path): + """Remove a file that may not exist.""" + try: + os.remove(path) + except OSError as err: + if err.errno != errno.ENOENT: + raise + + +def get_filtered_names(all_names): + """Removes names that aren't considered valid by Let's Encrypt. + + :param set all_names: all names found in the configuration + + :returns: all found names that are considered valid by LE + :rtype: set + + """ + filtered_names = set() + for name in all_names: + try: + filtered_names.add(enforce_le_validity(name)) + except errors.ConfigurationError: + logger.debug('Not suggesting name "%s"', name, exc_info=True) + return filtered_names + +def get_os_info(): + """ + Get OS name and version + + :returns: (os_name, os_version) + :rtype: `tuple` of `str` + """ + + return get_python_os_info(pretty=False) + +def get_os_info_ua(): + """ + Get OS name and version string for User Agent + + :returns: os_ua + :rtype: `str` + """ + if _USE_DISTRO: + os_info = distro.name(pretty=True) + + if not _USE_DISTRO or not os_info: + return " ".join(get_python_os_info(pretty=True)) + return os_info + +def get_systemd_os_like(): + """ + Get a list of strings that indicate the distribution likeness to + other distributions. + + :returns: List of distribution acronyms + :rtype: `list` of `str` + """ + + if _USE_DISTRO: + return distro.like().split(" ") + return [] + +def get_var_from_file(varname, filepath="/etc/os-release"): + """ + Get single value from a file formatted like systemd /etc/os-release + + :param str varname: Name of variable to fetch + :param str filepath: File path of os-release file + :returns: requested value + :rtype: `str` + """ + + var_string = varname+"=" + if not os.path.isfile(filepath): + return "" + with open(filepath, 'r') as fh: + contents = fh.readlines() + + for line in contents: + if line.strip().startswith(var_string): + # Return the value of var, normalized + return _normalize_string(line.strip()[len(var_string):]) + return "" + +def _normalize_string(orig): + """ + Helper function for get_var_from_file() to remove quotes + and whitespaces + """ + return orig.replace('"', '').replace("'", "").strip() + +def get_python_os_info(pretty=False): + """ + Get Operating System type/distribution and major version + using python platform module + + :param bool pretty: If the returned OS name should be in longer (pretty) form + + :returns: (os_name, os_version) + :rtype: `tuple` of `str` + """ + info = platform.system_alias( + platform.system(), + platform.release(), + platform.version() + ) + os_type, os_ver, _ = info + os_type = os_type.lower() + if os_type.startswith('linux') and _USE_DISTRO: + info = distro.linux_distribution(pretty) + # On arch, distro.linux_distribution() is reportedly ('','',''), + # so handle it defensively + if info[0]: + os_type = info[0] + if info[1]: + os_ver = info[1] + elif os_type.startswith('darwin'): + try: + proc = subprocess.Popen( + ["/usr/bin/sw_vers", "-productVersion"], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + except OSError: + proc = subprocess.Popen( + ["sw_vers", "-productVersion"], + stdout=subprocess.PIPE, + universal_newlines=True, + ) + os_ver = proc.communicate()[0].rstrip('\n') + elif os_type.startswith('freebsd'): + # eg "9.3-RC3-p1" + os_ver = os_ver.partition("-")[0] + os_ver = os_ver.partition(".")[0] + elif platform.win32_ver()[1]: + os_ver = platform.win32_ver()[1] + else: + # Cases known to fall here: Cygwin python + os_ver = '' + return os_type, os_ver + +# Just make sure we don't get pwned... Make sure that it also doesn't +# start with a period or have two consecutive periods <- this needs to +# be done in addition to the regex +EMAIL_REGEX = re.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+$") + + +def safe_email(email): + """Scrub email address before using it.""" + if EMAIL_REGEX.match(email) is not None: + return not email.startswith(".") and ".." not in email + logger.warning("Invalid email address: %s.", email) + return False + + +class _ShowWarning(argparse.Action): + """Action to log a warning when an argument is used.""" + def __call__(self, unused1, unused2, unused3, option_string=None): + logger.warning("Use of %s is deprecated.", option_string) + + +def add_deprecated_argument(add_argument, argument_name, nargs): + """Adds a deprecated argument with the name argument_name. + + Deprecated arguments are not shown in the help. If they are used on + the command line, a warning is shown stating that the argument is + deprecated and no other action is taken. + + :param callable add_argument: Function that adds arguments to an + argument parser/group. + :param str argument_name: Name of deprecated argument. + :param nargs: Value for nargs when adding the argument to argparse. + + """ + if _ShowWarning not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE: + # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was + # changed from a set to a tuple. + if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set): + # pylint: disable=no-member + configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add( + _ShowWarning) + else: + configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += ( + _ShowWarning,) + add_argument(argument_name, action=_ShowWarning, + help=argparse.SUPPRESS, nargs=nargs) + + +def enforce_le_validity(domain): + """Checks that Let's Encrypt will consider domain to be valid. + + :param str domain: FQDN to check + :type domain: `str` or `unicode` + :returns: The domain cast to `str`, with ASCII-only contents + :rtype: str + :raises ConfigurationError: for invalid domains and cases where Let's + Encrypt currently will not issue certificates + + """ + domain = enforce_domain_sanity(domain) + if not re.match("^[A-Za-z0-9.-]*$", domain): + raise errors.ConfigurationError( + "{0} contains an invalid character. " + "Valid characters are A-Z, a-z, 0-9, ., and -.".format(domain)) + + labels = domain.split(".") + if len(labels) < 2: + raise errors.ConfigurationError( + "{0} needs at least two labels".format(domain)) + for label in labels: + if label.startswith("-"): + raise errors.ConfigurationError( + 'label "{0}" in domain "{1}" cannot start with "-"'.format( + label, domain)) + if label.endswith("-"): + raise errors.ConfigurationError( + 'label "{0}" in domain "{1}" cannot end with "-"'.format( + label, domain)) + return domain + +def enforce_domain_sanity(domain): + """Method which validates domain value and errors out if + the requirements are not met. + + :param domain: Domain to check + :type domain: `str` or `unicode` + :raises ConfigurationError: for invalid domains and cases where Let's + Encrypt currently will not issue certificates + + :returns: The domain cast to `str`, with ASCII-only contents + :rtype: str + """ + # Unicode + try: + if isinstance(domain, six.binary_type): + domain = domain.decode('utf-8') + domain.encode('ascii') + except UnicodeError: + raise errors.ConfigurationError("Non-ASCII domain names not supported. " + "To issue for an Internationalized Domain Name, use Punycode.") + + domain = domain.lower() + + # Remove trailing dot + domain = domain[:-1] if domain.endswith(u'.') else domain + + # Separately check for odd "domains" like "http://example.com" to fail + # fast and provide a clear error message + for scheme in ["http", "https"]: # Other schemes seem unlikely + if domain.startswith("{0}://".format(scheme)): + raise errors.ConfigurationError( + "Requested name {0} appears to be a URL, not a FQDN. " + "Try again without the leading \"{1}://\".".format( + domain, scheme + ) + ) + + # Explain separately that IP addresses aren't allowed (apart from not + # being FQDNs) because hope springs eternal concerning this point + try: + socket.inet_aton(domain) + raise errors.ConfigurationError( + "Requested name {0} is an IP address. The Let's Encrypt " + "certificate authority will not issue certificates for a " + "bare IP address.".format(domain)) + except socket.error: + # It wasn't an IP address, so that's good + pass + + # FQDN checks according to RFC 2181: domain name should be less than 255 + # octets (inclusive). And each label is 1 - 63 octets (inclusive). + # https://tools.ietf.org/html/rfc2181#section-11 + msg = "Requested domain {0} is not a FQDN because".format(domain) + if len(domain) > 255: + raise errors.ConfigurationError("{0} it is too long.".format(msg)) + labels = domain.split('.') + for l in labels: + if not l: + raise errors.ConfigurationError("{0} it contains an empty label.".format(msg)) + elif len(l) > 63: + raise errors.ConfigurationError("{0} label {1} is too long.".format(msg, l)) + + return domain + + +def is_wildcard_domain(domain): + """"Is domain a wildcard domain? + + :param domain: domain to check + :type domain: `bytes` or `str` or `unicode` + + :returns: True if domain is a wildcard, otherwise, False + :rtype: bool + + """ + if isinstance(domain, six.text_type): + wildcard_marker = u"*." + else: + wildcard_marker = b"*." + + return domain.startswith(wildcard_marker) + + +def get_strict_version(normalized): + """Converts a normalized version to a strict version. + + :param str normalized: normalized version string + + :returns: An equivalent strict version + :rtype: distutils.version.StrictVersion + + """ + # strict version ending with "a" and a number designates a pre-release + # pylint: disable=no-member + return distutils.version.StrictVersion(normalized.replace(".dev", "a")) + + +def is_staging(srv): + """ + Determine whether a given ACME server is a known test / staging server. + + :param str srv: the URI for the ACME server + :returns: True iff srv is a known test / staging server + :rtype bool: + """ + return srv == constants.STAGING_URI or "staging" in srv + + +def atexit_register(func, *args, **kwargs): + """Sets func to be called before the program exits. + + Special care is taken to ensure func is only called when the process + that first imports this module exits rather than any child processes. + + :param function func: function to be called in case of an error + + """ + atexit.register(_atexit_call, func, *args, **kwargs) + + +def _atexit_call(func, *args, **kwargs): + if _INITIAL_PID == os.getpid(): + func(*args, **kwargs) diff --git a/certbot/compat/__init__.py b/certbot/compat/__init__.py deleted file mode 100644 index 74451131a..000000000 --- a/certbot/compat/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Compatibility layer to run certbot both on Linux and Windows. - -This package contains all logic that needs to be implemented specifically for Linux and for Windows. -Then the rest of certbot code relies on this module to be platform agnostic. -""" diff --git a/certbot/compat/_path.py b/certbot/compat/_path.py deleted file mode 100644 index fe2d2d1d2..000000000 --- a/certbot/compat/_path.py +++ /dev/null @@ -1,31 +0,0 @@ -"""This compat module wraps os.path to forbid some functions.""" -# pylint: disable=function-redefined -from __future__ import absolute_import - -# First round of wrapping: we import statically all public attributes exposed by the os.path -# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path -# members are available in certbot.compat.path. -from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden - -# Second round of wrapping: we import dynamically all attributes from the os.path module that have -# not yet been imported by the first round (static star import). -import os.path as std_os_path # pylint: disable=os-module-forbidden -import sys as std_sys - -ourselves = std_sys.modules[__name__] -for attribute in dir(std_os_path): - # Check if the attribute does not already exist in our module. It could be internal attributes - # of the module (__name__, __doc__), or attributes from standard os.path already imported with - # `from os.path import *`. - if not hasattr(ourselves, attribute): - setattr(ourselves, attribute, getattr(std_os_path, attribute)) - -# Clean all remaining importables that are not from the core os.path module. -del ourselves, std_os_path, std_sys - - -# Function os.path.realpath is broken on some versions of Python for Windows. -def realpath(*unused_args, **unused_kwargs): - """Method os.path.realpath() is forbidden""" - raise RuntimeError('Usage of os.path.realpath() is forbidden. ' - 'Use certbot.compat.filesystem.realpath() instead.') diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py deleted file mode 100644 index 5fba440cc..000000000 --- a/certbot/compat/filesystem.py +++ /dev/null @@ -1,602 +0,0 @@ -"""Compat module to handle files security on Windows and Linux""" -from __future__ import absolute_import - -import errno -import os # pylint: disable=os-module-forbidden -import stat - -try: - # pylint: disable=import-error - import ntsecuritycon - import win32security - import win32con - import win32api - import win32file - import pywintypes - import winerror - # pylint: enable=import-error -except ImportError: - POSIX_MODE = True -else: - POSIX_MODE = False - -from acme.magic_typing import List, Union, Tuple # pylint: disable=unused-import, no-name-in-module - - -def chmod(file_path, mode): - # type: (str, int) -> None - """ - Apply a POSIX mode on given file_path: - * for Linux, the POSIX mode will be directly applied using chmod, - * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for - Certbot context, and applied to the file using kernel calls. - - The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, - is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the - method _generate_windows_flags(). - - :param str file_path: Path of the file - :param int mode: POSIX mode to apply - """ - if POSIX_MODE: - os.chmod(file_path, mode) - else: - _apply_win_mode(file_path, mode) - - -# One could ask why there is no copy_ownership() function, or even a reimplementation -# of os.chown() that would modify the ownership of file without touching the mode itself. -# This is because on Windows, it would require recalculating the existing DACL against -# the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically -# the current owner of a file. This action would be necessary to keep consistency between -# the POSIX mode applied to the file and the current owner of this file. -# Since copying and editing arbitrary DACL is very difficult, and since we actually know -# the mode to apply at the time the owner of a file should change, it is easier to just -# change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does. -def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): - # type: (str, str, int, bool, bool) -> None - """ - Copy ownership (user and optionally group on Linux) from the source to the - destination, then apply given mode in compatible way for Linux and Windows. - This replaces the os.chown command. - :param str src: Path of the source file - :param str dst: Path of the destination file - :param int mode: Permission mode to apply on the destination file - :param bool copy_user: Copy user if `True` - :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) - """ - if POSIX_MODE: - stats = os.stat(src) - user_id = stats.st_uid if copy_user else -1 - group_id = stats.st_gid if copy_group else -1 - os.chown(dst, user_id, group_id) - elif copy_user: - # There is no group handling in Windows - _copy_win_ownership(src, dst) - - chmod(dst, mode) - - -def check_mode(file_path, mode): - # type: (str, int) -> bool - """ - Check if the given mode matches the permissions of the given file. - On Linux, will make a direct comparison, on Windows, mode will be compared against - the security model. - :param str file_path: Path of the file - :param int mode: POSIX mode to test - :rtype: bool - :return: True if the POSIX mode matches the file permissions - """ - if POSIX_MODE: - return stat.S_IMODE(os.stat(file_path).st_mode) == mode - - return _check_win_mode(file_path, mode) - - -def check_owner(file_path): - # type: (str) -> bool - """ - Check if given file is owned by current user. - :param str file_path: File path to check - :rtype: bool - :return: True if given file is owned by current user, False otherwise. - """ - if POSIX_MODE: - return os.stat(file_path).st_uid == os.getuid() - - # Get owner sid of the file - security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) - user = security.GetSecurityDescriptorOwner() - - # Compare sids - return _get_current_user() == user - - -def check_permissions(file_path, mode): - # type: (str, int) -> bool - """ - Check if given file has the given mode and is owned by current user. - :param str file_path: File path to check - :param int mode: POSIX mode to check - :rtype: bool - :return: True if file has correct mode and owner, False otherwise. - """ - return check_owner(file_path) and check_mode(file_path, mode) - - -def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin - # type: (str, int, int) -> int - """ - Wrapper of original os.open function, that will ensure on Windows that given mode - is correctly applied. - :param str file_path: The file path to open - :param int flags: Flags to apply on file while opened - :param int mode: POSIX mode to apply on file when opened, - Python defaults will be applied if ``None`` - :returns: the file descriptor to the opened file - :rtype: int - :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set, - OSError(errno.EACCES) on Windows if the file already exists and is a directory, and - os.O_CREAT is set. - """ - if POSIX_MODE: - # On Linux, invoke os.open directly. - return os.open(file_path, flags, mode) - - # Windows: handle creation of the file atomically with proper permissions. - if flags & os.O_CREAT: - # If os.O_EXCL is set, we will use the "CREATE_NEW", that will raise an exception if - # file exists, matching the API contract of this bit flag. Otherwise, we use - # "CREATE_ALWAYS" that will always create the file whether it exists or not. - disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS - - attributes = win32security.SECURITY_ATTRIBUTES() - security = attributes.SECURITY_DESCRIPTOR - user = _get_current_user() - dacl = _generate_dacl(user, mode) - # We set second parameter to 0 (`False`) to say that this security descriptor is - # NOT constructed from a default mechanism, but is explicitly set by the user. - # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long - security.SetSecurityDescriptorOwner(user, 0) - # We set first parameter to 1 (`True`) to say that this security descriptor contains - # a DACL. Otherwise second and third parameters are ignored. - # We set third parameter to 0 (`False`) to say that this security descriptor is - # NOT constructed from a default mechanism, but is explicitly set by the user. - # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long - security.SetSecurityDescriptorDacl(1, dacl, 0) - - handle = None - try: - handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, - win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, - attributes, disposition, 0, None) - except pywintypes.error as err: - # Handle native windows errors into python errors to be consistent with the API - # of os.open in the situation of a file already existing or locked. - if err.winerror == winerror.ERROR_FILE_EXISTS: - raise OSError(errno.EEXIST, err.strerror) - if err.winerror == winerror.ERROR_SHARING_VIOLATION: - raise OSError(errno.EACCES, err.strerror) - raise err - finally: - if handle: - handle.Close() - - # At this point, the file that did not exist has been created with proper permissions, - # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to - # avoid a FileExists exception before calling os.open. - return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL) - - # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine. - handle = os.open(file_path, flags) - chmod(file_path, mode) - return handle - - -def makedirs(file_path, mode=0o777): - # type: (str, int) -> None - """ - Rewrite of original os.makedirs function, that will ensure on Windows that given mode - is correctly applied. - :param str file_path: The file path to open - :param int mode: POSIX mode to apply on leaf directory when created, Python defaults - will be applied if ``None`` - """ - if POSIX_MODE: - return os.makedirs(file_path, mode) - - orig_mkdir_fn = os.mkdir - try: - # As we know that os.mkdir is called internally by os.makedirs, we will swap the function in - # os module for the time of makedirs execution on Windows. - os.mkdir = mkdir # type: ignore - return os.makedirs(file_path, mode) - finally: - os.mkdir = orig_mkdir_fn - - -def mkdir(file_path, mode=0o777): - # type: (str, int) -> None - """ - Rewrite of original os.mkdir function, that will ensure on Windows that given mode - is correctly applied. - :param str file_path: The file path to open - :param int mode: POSIX mode to apply on directory when created, Python defaults - will be applied if ``None`` - """ - if POSIX_MODE: - return os.mkdir(file_path, mode) - - attributes = win32security.SECURITY_ATTRIBUTES() - security = attributes.SECURITY_DESCRIPTOR - user = _get_current_user() - dacl = _generate_dacl(user, mode) - security.SetSecurityDescriptorOwner(user, False) - security.SetSecurityDescriptorDacl(1, dacl, 0) - - try: - win32file.CreateDirectory(file_path, attributes) - except pywintypes.error as err: - # Handle native windows error into python error to be consistent with the API - # of os.mkdir in the situation of a directory already existing. - if err.winerror == winerror.ERROR_ALREADY_EXISTS: - raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror) - raise err - - return None - - -def replace(src, dst): - # type: (str, str) -> None - """ - Rename a file to a destination path and handles situations where the destination exists. - :param str src: The current file path. - :param str dst: The new file path. - """ - if hasattr(os, 'replace'): - # Use replace if possible. On Windows, only Python >= 3.4 is supported - # so we can assume that os.replace() is always available for this platform. - getattr(os, 'replace')(src, dst) - else: - # Otherwise, use os.rename() that behaves like os.replace() on Linux. - os.rename(src, dst) - - -def realpath(file_path): - # type: (str) -> str - """ - Find the real path for the given path. This method resolves symlinks, including - recursive symlinks, and is protected against symlinks that creates an infinite loop. - """ - original_path = file_path - - if POSIX_MODE: - path = os.path.realpath(file_path) - if os.path.islink(path): - # If path returned by realpath is still a link, it means that it failed to - # resolve the symlink because of a loop. - # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py - raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) - return path - - inspected_paths = [] # type: List[str] - while os.path.islink(file_path): - link_path = file_path - file_path = os.readlink(file_path) - if not os.path.isabs(file_path): - file_path = os.path.join(os.path.dirname(link_path), file_path) - if file_path in inspected_paths: - raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) - inspected_paths.append(file_path) - - return os.path.abspath(file_path) - - -# On Windows is_executable run from an unprivileged shell may claim that a path is -# executable when it is excutable only if run from a privileged shell. This result -# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights -# without taking into consideration if the target user has currently required the -# elevated privileges or not. However this is not a problem since certbot always -# requires to be run under a privileged shell, so the user will always benefit -# from the highest (privileged one) set of permissions on a given file. -def is_executable(path): - # type: (str) -> bool - """ - Is path an executable file? - :param str path: path to test - :return: True if path is an executable file - :rtype: bool - """ - if POSIX_MODE: - return os.path.isfile(path) and os.access(path, os.X_OK) - - return _win_is_executable(path) - - -def has_world_permissions(path): - # type: (str) -> bool - """ - Check if everybody/world has any right (read/write/execute) on a file given its path - :param str path: path to test - :return: True if everybody/world has any right to the file - :rtype: bool - """ - if POSIX_MODE: - return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO) - - security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) - dacl = security.GetSecurityDescriptorDacl() - - return bool(dacl.GetEffectiveRightsFromAcl({ - 'TrusteeForm': win32security.TRUSTEE_IS_SID, - 'TrusteeType': win32security.TRUSTEE_IS_USER, - 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'), - })) - - -def compute_private_key_mode(old_key, base_mode): - # type: (str, int) -> int - """ - Calculate the POSIX mode to apply to a private key given the previous private key - :param str old_key: path to the previous private key - :param int base_mode: the minimum modes to apply to a private key - :return: the POSIX mode to apply - :rtype: int - """ - if POSIX_MODE: - # On Linux, we keep read/write/execute permissions - # for group and read permissions for everybody. - old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) & - (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH)) - return base_mode | old_mode - - # On Windows, the mode returned by os.stat is not reliable, - # so we do not keep any permission from the previous private key. - return base_mode - - -def has_same_ownership(path1, path2): - # type: (str, str) -> bool - """ - Return True if the ownership of two files given their respective path is the same. - On Windows, ownership is checked against owner only, since files do not have a group owner. - :param str path1: path to the first file - :param str path2: path to the second file - :return: True if both files have the same ownership, False otherwise - :rtype: bool - - """ - if POSIX_MODE: - stats1 = os.stat(path1) - stats2 = os.stat(path2) - return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid) - - security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION) - user1 = security1.GetSecurityDescriptorOwner() - - security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION) - user2 = security2.GetSecurityDescriptorOwner() - - return user1 == user2 - - -def has_min_permissions(path, min_mode): - # type: (str, int) -> bool - """ - Check if a file given its path has at least the permissions defined by the given minimal mode. - On Windows, group permissions are ignored since files do not have a group owner. - :param str path: path to the file to check - :param int min_mode: the minimal permissions expected - :return: True if the file matches the minimal permissions expectations, False otherwise - :rtype: bool - """ - if POSIX_MODE: - st_mode = os.stat(path).st_mode - return st_mode == st_mode | min_mode - - # Resolve symlinks, to get a consistent result with os.stat on Linux, - # that follows symlinks by default. - path = realpath(path) - - # Get owner sid of the file - security = win32security.GetFileSecurity( - path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) - user = security.GetSecurityDescriptorOwner() - dacl = security.GetSecurityDescriptorDacl() - min_dacl = _generate_dacl(user, min_mode) - - for index in range(min_dacl.GetAceCount()): - min_ace = min_dacl.GetAce(index) - - # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID. - # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html - mask = min_ace[1] - user = min_ace[2] - - effective_mask = dacl.GetEffectiveRightsFromAcl({ - 'TrusteeForm': win32security.TRUSTEE_IS_SID, - 'TrusteeType': win32security.TRUSTEE_IS_USER, - 'Identifier': user, - }) - - if effective_mask != effective_mask | mask: - return False - - return True - - -def _win_is_executable(path): - if not os.path.isfile(path): - return False - - security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) - dacl = security.GetSecurityDescriptorDacl() - - mode = dacl.GetEffectiveRightsFromAcl({ - 'TrusteeForm': win32security.TRUSTEE_IS_SID, - 'TrusteeType': win32security.TRUSTEE_IS_USER, - 'Identifier': _get_current_user(), - }) - - return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE - - -def _apply_win_mode(file_path, mode): - """ - This function converts the given POSIX mode into a Windows ACL list, and applies it to the - file given its path. If the given path is a symbolic link, it will resolved to apply the - mode on the targeted file. - """ - file_path = realpath(file_path) - # Get owner sid of the file - security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) - user = security.GetSecurityDescriptorOwner() - - # New DACL, that will overwrite existing one (including inherited permissions) - dacl = _generate_dacl(user, mode) - - # Apply the new DACL - security.SetSecurityDescriptorDacl(1, dacl, 0) - win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) - - -def _generate_dacl(user_sid, mode): - analysis = _analyze_mode(mode) - - # Get standard accounts from "well-known" sid - # See the list here: - # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - system = win32security.ConvertStringSidToSid('S-1-5-18') - admins = win32security.ConvertStringSidToSid('S-1-5-32-544') - everyone = win32security.ConvertStringSidToSid('S-1-1-0') - - # New dacl, without inherited permissions - dacl = win32security.ACL() - - # If user is already system or admins, any ACE defined here would be superseded by - # the full control ACE that will be added after. - if user_sid not in [system, admins]: - # Handle user rights - user_flags = _generate_windows_flags(analysis['user']) - if user_flags: - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) - - # Handle everybody rights - everybody_flags = _generate_windows_flags(analysis['all']) - if everybody_flags: - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) - - # Handle administrator rights - full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) - - return dacl - - -def _analyze_mode(mode): - return { - 'user': { - 'read': mode & stat.S_IRUSR, - 'write': mode & stat.S_IWUSR, - 'execute': mode & stat.S_IXUSR, - }, - 'all': { - 'read': mode & stat.S_IROTH, - 'write': mode & stat.S_IWOTH, - 'execute': mode & stat.S_IXOTH, - }, - } - - -def _copy_win_ownership(src, dst): - security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION) - user_src = security_src.GetSecurityDescriptorOwner() - - security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION) - # Second parameter indicates, if `False`, that the owner of the file is not provided by some - # default mechanism, but is explicitly set instead. This is obviously what we are doing here. - security_dst.SetSecurityDescriptorOwner(user_src, False) - - win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst) - - -def _generate_windows_flags(rights_desc): - # Some notes about how each POSIX right is interpreted. - # - # For the rights read and execute, we have a pretty bijective relation between - # POSIX flags and their generic counterparts on Windows, so we use them directly - # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). - # - # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a - # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or - # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. - # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS - # substracted of the rights corresponding to POSIX read and POSIX execute. - # - # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, - # so a "Full Control" on the file. - # - # A complete list of the rights defined on NTFS can be found here: - # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders - flag = 0 - if rights_desc['read']: - flag = flag | ntsecuritycon.FILE_GENERIC_READ - if rights_desc['write']: - flag = flag | (ntsecuritycon.FILE_ALL_ACCESS - ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE) - if rights_desc['execute']: - flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE - - return flag - - -def _check_win_mode(file_path, mode): - # Resolve symbolic links - file_path = realpath(file_path) - # Get current dacl file - security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION - | win32security.DACL_SECURITY_INFORMATION) - dacl = security.GetSecurityDescriptorDacl() - - # Get current file owner sid - user = security.GetSecurityDescriptorOwner() - - if not dacl: - # No DACL means full control to everyone - # This is not a deterministic permissions set. - return False - - # Calculate the target dacl - ref_dacl = _generate_dacl(user, mode) - - return _compare_dacls(dacl, ref_dacl) - - -def _compare_dacls(dacl1, dacl2): - """ - This method compare the two given DACLs to check if they are identical. - Identical means here that they contains the same set of ACEs in the same order. - """ - return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] == - [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())]) - - -def _get_current_user(): - """ - Return the pySID corresponding to the current user. - """ - # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx, - # because this function returns nonsense values when Certbot is run under NT AUTHORITY\SYSTEM. - # To run Certbot under NT AUTHORITY\SYSTEM, you can open a shell using the instructions here: - # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/ - account_name = r"{0}\{1}".format(win32api.GetDomainName(), win32api.GetUserName()) - # LookupAccountName() expects the system name as first parameter. By passing None to it, - # we instruct Windows to first search the matching account in the machine local accounts, - # then into the primary domain accounts, if the machine has joined a domain, then finally - # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows - # if there is no reason to use a specific lookup mechanism. - # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea - return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py deleted file mode 100644 index a8fbf2c96..000000000 --- a/certbot/compat/misc.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -This compat module handles various platform specific calls that do not fall into one -particular category. -""" -from __future__ import absolute_import - -import select -import sys - -try: - from win32com.shell import shell as shellwin32 # pylint: disable=import-error - POSIX_MODE = False -except ImportError: # pragma: no cover - POSIX_MODE = True - -from certbot import errors -from certbot.compat import os - - -# For Linux: define OS specific standard binary directories -STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] - - -def raise_for_non_administrative_windows_rights(): - # type: () -> None - """ - On Windows, raise if current shell does not have the administrative rights. - Do nothing on Linux. - - :raises .errors.Error: If the current shell does not have administrative rights on Windows. - """ - if not POSIX_MODE and shellwin32.IsUserAnAdmin() == 0: # pragma: no cover - raise errors.Error('Error, certbot must be run on a shell with administrative rights.') - - -def readline_with_timeout(timeout, prompt): - # type: (float, str) -> str - """ - Read user input to return the first line entered, or raise after specified timeout. - - :param float timeout: The timeout in seconds given to the user. - :param str prompt: The prompt message to display to the user. - - :returns: The first line entered by the user. - :rtype: str - - """ - try: - # Linux specific - # - # Call to select can only be done like this on UNIX - rlist, _, _ = select.select([sys.stdin], [], [], timeout) - if not rlist: - raise errors.Error( - "Timed out waiting for answer to prompt '{0}'".format(prompt)) - return rlist[0].readline() - except OSError: - # Windows specific - # - # No way with select to make a timeout to the user input on Windows, - # as select only supports socket in this case. - # So no timeout on Windows for now. - return sys.stdin.readline() - - -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): - # type: (str) -> str - """ - 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 os.name != 'nt': - # Linux specific - return LINUX_DEFAULT_FOLDERS[folder_type] - # Windows specific - return WINDOWS_DEFAULT_FOLDERS[folder_type] - - -def underscores_for_unsupported_characters_in_path(path): - # type: (str) -> str - """ - Replace unsupported characters in path for current OS by underscores. - :param str path: the path to normalize - :return: the normalized path - :rtype: str - """ - if os.name != 'nt': - # Linux specific - return path - - # Windows specific - drive, tail = os.path.splitdrive(path) - return drive + tail.replace(':', '_') diff --git a/certbot/compat/os.py b/certbot/compat/os.py deleted file mode 100644 index e5438f365..000000000 --- a/certbot/compat/os.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -This compat modules is a wrapper of the core os module that forbids usage of specific operations -(e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. -This module is intended to replace standard os module throughout certbot projects (except acme). -""" -# pylint: disable=function-redefined -from __future__ import absolute_import - -# First round of wrapping: we import statically all public attributes exposed by the os module -# This allows in particular to have pylint, mypy, IDEs be aware that most of os members are -# available in certbot.compat.os. -from os import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden - -# Second round of wrapping: we import dynamically all attributes from the os module that have not -# yet been imported by the first round (static import). This covers in particular the case of -# specific python 3.x versions where not all public attributes are in the special __all__ of os, -# and so not in `from os import *`. -import os as std_os # pylint: disable=os-module-forbidden -import sys as std_sys - -ourselves = std_sys.modules[__name__] -for attribute in dir(std_os): - # Check if the attribute does not already exist in our module. It could be internal attributes - # of the module (__name__, __doc__), or attributes from standard os already imported with - # `from os import *`. - if not hasattr(ourselves, attribute): - setattr(ourselves, attribute, getattr(std_os, attribute)) - -# Import our internal path module, then allow certbot.compat.os.path -# to behave as a module (similarly to os.path). -from certbot.compat import _path as path # type: ignore # pylint: disable=wrong-import-position -std_sys.modules[__name__ + '.path'] = path - -# Clean all remaining importables that are not from the core os module. -del ourselves, std_os, std_sys - - -# Chmod is the root of all evil for our security model on Windows. With the default implementation -# of os.chmod on Windows, almost all bits on mode will be ignored, and only a general RO or RW will -# be applied. The DACL, the inner mechanism to control file access on Windows, will stay on its -# default definition, giving effectively at least read permissions to any one, as the default -# permissions on root path will be inherit by the file (as NTFS state), and root path can be read -# by anyone. So the given mode needs to be translated into a secured and not inherited DACL that -# will be applied to this file using filesystem.chmod, calling internally the win32security -# module to construct and apply the DACL. Complete security model to translate a POSIX mode into -# a suitable DACL on Windows for Certbot can be found here: -# https://github.com/certbot/certbot/issues/6356 -# Basically, it states that appropriate permissions will be set for the owner, nothing for the -# group, appropriate permissions for the "Everyone" group, and all permissions to the -# "Administrators" group + "System" user, as they can do everything anyway. -def chmod(*unused_args, **unused_kwargs): - """Method os.chmod() is forbidden""" - raise RuntimeError('Usage of os.chmod() is forbidden. ' - 'Use certbot.compat.filesystem.chmod() instead.') - - -# Because uid is not a concept on Windows, chown is useless. In fact, it is not even available -# on Python for Windows. So to be consistent on both platforms for Certbot, this method is -# always forbidden. -def chown(*unused_args, **unused_kwargs): - """Method os.chown() is forbidden""" - raise RuntimeError('Usage of os.chown() is forbidden.' - 'Use certbot.compat.filesystem.copy_ownership_and_apply_mode() instead.') - - -# The os.open function on Windows has the same effect as a call to os.chown concerning the file -# modes: these modes lack the correct control over the permissions given to the file. Instead, -# filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are -# atomically set in case of file creation, or invokes filesystem.chmod to properly set the -# permissions for the other cases. -def open(*unused_args, **unused_kwargs): - """Method os.open() is forbidden""" - raise RuntimeError('Usage of os.open() is forbidden. ' - 'Use certbot.compat.filesystem.open() instead.') - - -# Very similarly to os.open, os.mkdir has the same effects on Windows and creates an unsecured -# folder. So a similar mitigation to security.chmod is provided on this platform. -def mkdir(*unused_args, **unused_kwargs): - """Method os.mkdir() is forbidden""" - raise RuntimeError('Usage of os.mkdir() is forbidden. ' - 'Use certbot.compat.filesystem.mkdir() instead.') - - -# As said above, os.makedirs would call the original os.mkdir function recursively on Windows, -# creating the same flaws for every actual folder created. This method is modified to ensure -# that our modified os.mkdir is called on Windows, by monkey patching temporarily the mkdir method -# on the original os module, executing the modified logic to correctly protect newly created -# folders, then restoring original mkdir method in the os module. -def makedirs(*unused_args, **unused_kwargs): - """Method os.makedirs() is forbidden""" - raise RuntimeError('Usage of os.makedirs() is forbidden. ' - 'Use certbot.compat.filesystem.makedirs() instead.') - - -# Because of the blocking strategy on file handlers on Windows, rename does not behave as expected -# with POSIX systems: an exception will be raised if dst already exists. -def rename(*unused_args, **unused_kwargs): - """Method os.rename() is forbidden""" - raise RuntimeError('Usage of os.rename() is forbidden. ' - 'Use certbot.compat.filesystem.replace() instead.') - - -# Behavior of os.replace is consistent between Windows and Linux. However, it is not supported on -# Python 2.x. So, as for os.rename, we forbid it in favor of filesystem.replace. -def replace(*unused_args, **unused_kwargs): - """Method os.replace() is forbidden""" - raise RuntimeError('Usage of os.replace() is forbidden. ' - 'Use certbot.compat.filesystem.replace() instead.') - - -# Results given by os.access are inconsistent or partial on Windows, because this platform is not -# following the POSIX approach. -def access(*unused_args, **unused_kwargs): - """Method os.access() is forbidden""" - raise RuntimeError('Usage of os.access() is forbidden. ' - 'Use certbot.compat.filesystem.check_mode() or ' - 'certbot.compat.filesystem.is_executable() instead.') - - -# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or -# meaningless. We need to use specialized functions from the certbot.compat.filesystem module. -def stat(*unused_args, **unused_kwargs): - """Method os.stat() is forbidden""" - raise RuntimeError('Usage of os.stat() is forbidden. ' - 'Use certbot.compat.filesystem functions instead ' - '(eg. has_min_permissions, has_same_ownership).') - - -# Method os.fstat has the same problem than os.stat, since it is the same function, -# but accepting a file descriptor instead of a path. -def fstat(*unused_args, **unused_kwargs): - """Method os.stat() is forbidden""" - raise RuntimeError('Usage of os.fstat() is forbidden. ' - 'Use certbot.compat.filesystem functions instead ' - '(eg. has_min_permissions, has_same_ownership).') diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py deleted file mode 100644 index 12291af38..000000000 --- a/certbot/crypto_util.py +++ /dev/null @@ -1,491 +0,0 @@ -"""Certbot client crypto utility functions. - -.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server - is capable of handling the signatures. - -""" -import hashlib -import logging -import warnings - -import pyrfc3339 -import six -import zope.component -from OpenSSL import SSL # type: ignore -from OpenSSL import crypto -# https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography import x509 # type: ignore -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric.ec import ECDSA -from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey -from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey - -from acme import crypto_util as acme_crypto_util -from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import os - -logger = logging.getLogger(__name__) - - -# High level functions -def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): - """Initializes and saves a privkey. - - Inits key and saves it in PEM format on the filesystem. - - .. note:: keyname is the attempted filename, it may be different if a file - already exists at the path. - - :param int key_size: RSA key size in bits - :param str key_dir: Key save directory. - :param str keyname: Filename of key - - :returns: Key - :rtype: :class:`certbot.util.Key` - - :raises ValueError: If unable to generate the key given key_size. - - """ - try: - key_pem = make_key(key_size) - except ValueError as err: - logger.error("", exc_info=True) - raise err - - config = zope.component.getUtility(interfaces.IConfig) - # Save file - util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions) - key_f, key_path = util.unique_file( - os.path.join(key_dir, keyname), 0o600, "wb") - with key_f: - key_f.write(key_pem) - logger.debug("Generating key (%d bits): %s", key_size, key_path) - - return util.Key(key_path, key_pem) - - -def init_save_csr(privkey, names, path): - """Initialize a CSR with the given private key. - - :param privkey: Key to include in the CSR - :type privkey: :class:`certbot.util.Key` - - :param set names: `str` names to include in the CSR - - :param str path: Certificate save directory. - - :returns: CSR - :rtype: :class:`certbot.util.CSR` - - """ - config = zope.component.getUtility(interfaces.IConfig) - - csr_pem = acme_crypto_util.make_csr( - privkey.pem, names, must_staple=config.must_staple) - - # Save CSR - util.make_or_verify_dir(path, 0o755, config.strict_permissions) - csr_f, csr_filename = util.unique_file( - os.path.join(path, "csr-certbot.pem"), 0o644, "wb") - with csr_f: - csr_f.write(csr_pem) - logger.debug("Creating CSR: %s", csr_filename) - - return util.CSR(csr_filename, csr_pem, "pem") - - -# WARNING: the csr and private key file are possible attack vectors for TOCTOU -# We should either... -# A. Do more checks to verify that the CSR is trusted/valid -# B. Audit the parsing code for vulnerabilities - -def valid_csr(csr): - """Validate CSR. - - Check if `csr` is a valid CSR for the given domains. - - :param str csr: CSR in PEM. - - :returns: Validity of CSR. - :rtype: bool - - """ - try: - req = crypto.load_certificate_request( - crypto.FILETYPE_PEM, csr) - return req.verify(req.get_pubkey()) - except crypto.Error: - logger.debug("", exc_info=True) - return False - - -def csr_matches_pubkey(csr, privkey): - """Does private key correspond to the subject public key in the CSR? - - :param str csr: CSR in PEM. - :param str privkey: Private key file contents (PEM) - - :returns: Correspondence of private key to CSR subject public key. - :rtype: bool - - """ - req = crypto.load_certificate_request( - crypto.FILETYPE_PEM, csr) - pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey) - try: - return req.verify(pkey) - except crypto.Error: - logger.debug("", exc_info=True) - return False - - -def import_csr_file(csrfile, data): - """Import a CSR file, which can be either PEM or DER. - - :param str csrfile: CSR filename - :param str data: contents of the CSR file - - :returns: (`crypto.FILETYPE_PEM`, - util.CSR object representing the CSR, - list of domains requested in the CSR) - :rtype: tuple - - """ - PEM = crypto.FILETYPE_PEM - load = crypto.load_certificate_request - try: - # Try to parse as DER first, then fall back to PEM. - csr = load(crypto.FILETYPE_ASN1, data) - except crypto.Error: - try: - csr = load(PEM, data) - except crypto.Error: - raise errors.Error("Failed to parse CSR file: {0}".format(csrfile)) - - domains = _get_names_from_loaded_cert_or_req(csr) - # Internally we always use PEM, so re-encode as PEM before returning. - data_pem = crypto.dump_certificate_request(PEM, csr) - return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains - - -def make_key(bits): - """Generate PEM encoded RSA key. - - :param int bits: Number of bits, at least 1024. - - :returns: new RSA key in PEM form with specified number of bits - :rtype: str - - """ - assert bits >= 1024 # XXX - key = crypto.PKey() - key.generate_key(crypto.TYPE_RSA, bits) - return crypto.dump_privatekey(crypto.FILETYPE_PEM, key) - - -def valid_privkey(privkey): - """Is valid RSA private key? - - :param str privkey: Private key file contents in PEM - - :returns: Validity of private key. - :rtype: bool - - """ - try: - return crypto.load_privatekey( - crypto.FILETYPE_PEM, privkey).check() - except (TypeError, crypto.Error): - return False - - -def verify_renewable_cert(renewable_cert): - """For checking that your certs were not corrupted on disk. - - Several things are checked: - 1. Signature verification for the cert. - 2. That fullchain matches cert and chain when concatenated. - 3. Check that the private key matches the certificate. - - :param `.storage.RenewableCert` renewable_cert: cert to verify - - :raises errors.Error: If verification fails. - """ - verify_renewable_cert_sig(renewable_cert) - verify_fullchain(renewable_cert) - verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey) - - -def verify_renewable_cert_sig(renewable_cert): - """Verifies the signature of a `.storage.RenewableCert` object. - - :param `.storage.RenewableCert` renewable_cert: cert to verify - - :raises errors.Error: If signature verification fails. - """ - try: - with open(renewable_cert.chain, 'rb') as chain_file: # type: IO[bytes] - chain = x509.load_pem_x509_certificate(chain_file.read(), default_backend()) - with open(renewable_cert.cert, 'rb') as cert_file: # type: IO[bytes] - cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) - pk = chain.public_key() - with warnings.catch_warnings(): - verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes, - cert.signature_hash_algorithm) - except (IOError, ValueError, InvalidSignature) as e: - error_str = "verifying the signature of the cert located at {0} has failed. \ - Details: {1}".format(renewable_cert.cert, e) - logger.exception(error_str) - raise errors.Error(error_str) - - -def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm): - """Check the signature of a payload. - - :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature - :param bytes signature: the signature bytes - :param bytes payload: the payload bytes - :param cryptography.hazmat.primitives.hashes.HashAlgorithm - signature_hash_algorithm: algorithm used to hash the payload - - :raises InvalidSignature: If signature verification fails. - :raises errors.Error: If public key type is not supported - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - if isinstance(public_key, RSAPublicKey): - # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi - verifier = public_key.verifier( # type: ignore - signature, PKCS1v15(), signature_hash_algorithm - ) - verifier.update(payload) - verifier.verify() - elif isinstance(public_key, EllipticCurvePublicKey): - verifier = public_key.verifier( - signature, ECDSA(signature_hash_algorithm) - ) - verifier.update(payload) - verifier.verify() - else: - raise errors.Error("Unsupported public key type") - - -def verify_cert_matches_priv_key(cert_path, key_path): - """ Verifies that the private key and cert match. - - :param str cert_path: path to a cert in PEM format - :param str key_path: path to a private key file - - :raises errors.Error: If they don't match. - """ - try: - context = SSL.Context(SSL.SSLv23_METHOD) - context.use_certificate_file(cert_path) - context.use_privatekey_file(key_path) - context.check_privatekey() - except (IOError, SSL.Error) as e: - error_str = "verifying the cert located at {0} matches the \ - private key located at {1} has failed. \ - Details: {2}".format(cert_path, - key_path, e) - logger.exception(error_str) - raise errors.Error(error_str) - - -def verify_fullchain(renewable_cert): - """ Verifies that fullchain is indeed cert concatenated with chain. - - :param `.storage.RenewableCert` renewable_cert: cert to verify - - :raises errors.Error: If cert and chain do not combine to fullchain. - """ - try: - with open(renewable_cert.chain) as chain_file: # type: IO[str] - chain = chain_file.read() - with open(renewable_cert.cert) as cert_file: # type: IO[str] - cert = cert_file.read() - with open(renewable_cert.fullchain) as fullchain_file: # type: IO[str] - fullchain = fullchain_file.read() - if (cert + chain) != fullchain: - error_str = "fullchain does not match cert + chain for {0}!" - error_str = error_str.format(renewable_cert.lineagename) - raise errors.Error(error_str) - except IOError as e: - error_str = "reading one of cert, chain, or fullchain has failed: {0}".format(e) - logger.exception(error_str) - raise errors.Error(error_str) - except errors.Error as e: - raise e - - -def pyopenssl_load_certificate(data): - """Load PEM/DER certificate. - - :raises errors.Error: - - """ - - openssl_errors = [] - - for file_type in (crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1): - try: - return crypto.load_certificate(file_type, data), file_type - except crypto.Error as error: # TODO: other errors? - openssl_errors.append(error) - raise errors.Error("Unable to load: {0}".format(",".join( - str(error) for error in openssl_errors))) - - -def _load_cert_or_req(cert_or_req_str, load_func, - typ=crypto.FILETYPE_PEM): - try: - return load_func(typ, cert_or_req_str) - except crypto.Error: - logger.error("", exc_info=True) - raise - - -def _get_sans_from_cert_or_req(cert_or_req_str, load_func, - typ=crypto.FILETYPE_PEM): - # pylint: disable=protected-access - return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req( - cert_or_req_str, load_func, typ)) - - -def get_sans_from_cert(cert, typ=crypto.FILETYPE_PEM): - """Get a list of Subject Alternative Names from a certificate. - - :param str cert: Certificate (encoded). - :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1` - - :returns: A list of Subject Alternative Names. - :rtype: list - - """ - return _get_sans_from_cert_or_req( - cert, crypto.load_certificate, typ) - - -def _get_names_from_cert_or_req(cert_or_req, load_func, typ): - loaded_cert_or_req = _load_cert_or_req(cert_or_req, load_func, typ) - return _get_names_from_loaded_cert_or_req(loaded_cert_or_req) - - -def _get_names_from_loaded_cert_or_req(loaded_cert_or_req): - # pylint: disable=protected-access - return acme_crypto_util._pyopenssl_cert_or_req_all_names(loaded_cert_or_req) - - -def get_names_from_cert(csr, typ=crypto.FILETYPE_PEM): - """Get a list of domains from a cert, including the CN if it is set. - - :param str cert: Certificate (encoded). - :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1` - - :returns: A list of domain names. - :rtype: list - - """ - return _get_names_from_cert_or_req( - csr, crypto.load_certificate, typ) - - -def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM): - """Dump certificate chain into a bundle. - - :param list chain: List of `crypto.X509` (or wrapped in - :class:`josepy.util.ComparableX509`). - - """ - # XXX: returns empty string when no chain is available, which - # shuts up RenewableCert, but might not be the best solution... - return acme_crypto_util.dump_pyopenssl_chain(chain, filetype) - - -def notBefore(cert_path): - """When does the cert at cert_path start being valid? - - :param str cert_path: path to a cert in PEM format - - :returns: the notBefore value from the cert at cert_path - :rtype: :class:`datetime.datetime` - - """ - return _notAfterBefore(cert_path, crypto.X509.get_notBefore) - - -def notAfter(cert_path): - """When does the cert at cert_path stop being valid? - - :param str cert_path: path to a cert in PEM format - - :returns: the notAfter value from the cert at cert_path - :rtype: :class:`datetime.datetime` - - """ - return _notAfterBefore(cert_path, crypto.X509.get_notAfter) - - -def _notAfterBefore(cert_path, method): - """Internal helper function for finding notbefore/notafter. - - :param str cert_path: path to a cert in PEM format - :param function method: one of ``crypto.X509.get_notBefore`` - or ``crypto.X509.get_notAfter`` - - :returns: the notBefore or notAfter value from the cert at cert_path - :rtype: :class:`datetime.datetime` - - """ - # pylint: disable=redefined-outer-name - with open(cert_path) as f: - x509 = crypto.load_certificate(crypto.FILETYPE_PEM, - f.read()) - # pyopenssl always returns bytes - timestamp = method(x509) - reformatted_timestamp = [timestamp[0:4], b"-", timestamp[4:6], b"-", - timestamp[6:8], b"T", timestamp[8:10], b":", - timestamp[10:12], b":", timestamp[12:]] - timestamp_str = b"".join(reformatted_timestamp) - # pyrfc3339 uses "native" strings. That is, bytes on Python 2 and unicode - # on Python 3 - if six.PY3: - timestamp_str = timestamp_str.decode('ascii') - return pyrfc3339.parse(timestamp_str) - - -def sha256sum(filename): - """Compute a sha256sum of a file. - - NB: In given file, platform specific newlines characters will be converted - into their equivalent unicode counterparts before calculating the hash. - - :param str filename: path to the file whose hash will be computed - - :returns: sha256 digest of the file in hexadecimal - :rtype: str - """ - sha256 = hashlib.sha256() - with open(filename, 'r') as file_d: - sha256.update(file_d.read().encode('UTF-8')) - return sha256.hexdigest() - -def cert_and_chain_from_fullchain(fullchain_pem): - """Split fullchain_pem into cert_pem and chain_pem - - :param str fullchain_pem: concatenated cert + chain - - :returns: tuple of string cert_pem and chain_pem - :rtype: tuple - - """ - cert = crypto.dump_certificate(crypto.FILETYPE_PEM, - crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode() - chain = fullchain_pem[len(cert):].lstrip() - return (cert, chain) diff --git a/certbot/display/__init__.py b/certbot/display/__init__.py deleted file mode 100644 index 9d39dce92..000000000 --- a/certbot/display/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot display utilities.""" diff --git a/certbot/display/ops.py b/certbot/display/ops.py deleted file mode 100644 index 1a36744a5..000000000 --- a/certbot/display/ops.py +++ /dev/null @@ -1,368 +0,0 @@ -"""Contains UI methods for LE user operations.""" -import logging - -import zope.component - -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import misc -from certbot.compat import os -from certbot.display import util as display_util - -logger = logging.getLogger(__name__) - -# Define a helper function to avoid verbose code -z_util = zope.component.getUtility - - -def get_email(invalid=False, optional=True): - """Prompt for valid email address. - - :param bool invalid: True if an invalid address was provided by the user - :param bool optional: True if the user can use - --register-unsafely-without-email to avoid providing an e-mail - - :returns: e-mail address - :rtype: str - - :raises errors.Error: if the user cancels - - """ - invalid_prefix = "There seem to be problems with that address. " - msg = "Enter email address (used for urgent renewal and security notices)" - 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 " - "{0}\n\n".format(os.path.join( - misc.get_default_folder('config'), 'accounts'))) - if optional: - if invalid: - msg += unsafe_suggestion - suggest_unsafe = False - else: - suggest_unsafe = True - else: - suggest_unsafe = False - - while True: - try: - code, email = z_util(interfaces.IDisplay).input( - invalid_prefix + msg if invalid else msg, - force_interactive=True) - except errors.MissingCommandlineFlag: - msg = ("You should register before running non-interactively, " - "or provide --agree-tos and --email flags.") - raise errors.MissingCommandlineFlag(msg) - - if code != display_util.OK: - if optional: - raise errors.Error( - "An e-mail address or " - "--register-unsafely-without-email must be provided.") - else: - raise errors.Error("An e-mail address must be provided.") - elif util.safe_email(email): - return email - elif suggest_unsafe: - msg += unsafe_suggestion - suggest_unsafe = False # add this message at most once - - invalid = bool(email) - - -def choose_account(accounts): - """Choose an account. - - :param list accounts: Containing at least one - :class:`~certbot._internal.account.Account` - - """ - # Note this will get more complicated once we start recording authorizations - labels = [acc.slug for acc in accounts] - - code, index = z_util(interfaces.IDisplay).menu( - "Please choose an account", labels, force_interactive=True) - if code == display_util.OK: - return accounts[index] - return None - -def choose_values(values, question=None): - """Display screen to let user pick one or multiple values from the provided - list. - - :param list values: Values to select from - - :returns: List of selected values - :rtype: list - """ - code, items = z_util(interfaces.IDisplay).checklist( - question, tags=values, force_interactive=True) - if code == display_util.OK and items: - return items - return [] - -def choose_names(installer, question=None): - """Display screen to select domains to validate. - - :param installer: An installer object - :type installer: :class:`certbot.interfaces.IInstaller` - - :param `str` question: Overriding dialog question to ask the user if asked - to choose from domain names. - - :returns: List of selected names - :rtype: `list` of `str` - - """ - if installer is None: - logger.debug("No installer, picking names manually") - return _choose_names_manually() - - domains = list(installer.get_all_names()) - names = get_valid_domains(domains) - - if not names: - return _choose_names_manually( - "No names were found in your configuration files. ") - - code, names = _filter_names(names, question) - if code == display_util.OK and names: - return names - return [] - - -def get_valid_domains(domains): - """Helper method for choose_names that implements basic checks - on domain names - - :param list domains: Domain names to validate - :return: List of valid domains - :rtype: list - """ - valid_domains = [] - for domain in domains: - try: - valid_domains.append(util.enforce_domain_sanity(domain)) - except errors.ConfigurationError: - continue - return valid_domains - -def _sort_names(FQDNs): - """Sort FQDNs by SLD (and if many, by their subdomains) - - :param list FQDNs: list of domain names - - :returns: Sorted list of domain names - :rtype: list - """ - return sorted(FQDNs, key=lambda fqdn: fqdn.split('.')[::-1][1:]) - - -def _filter_names(names, override_question=None): - """Determine which names the user would like to select from a list. - - :param list names: domain names - - :returns: tuple of the form (`code`, `names`) where - `code` - str display exit code - `names` - list of names selected - :rtype: tuple - - """ - #Sort by domain first, and then by subdomain - sorted_names = _sort_names(names) - if override_question: - question = override_question - else: - question = "Which names would you like to activate HTTPS for?" - code, names = z_util(interfaces.IDisplay).checklist( - question, tags=sorted_names, cli_flag="--domains", force_interactive=True) - return code, [str(s) for s in names] - - -def _choose_names_manually(prompt_prefix=""): - """Manually input names for those without an installer. - - :param str prompt_prefix: string to prepend to prompt for domains - - :returns: list of provided names - :rtype: `list` of `str` - - """ - code, input_ = z_util(interfaces.IDisplay).input( - prompt_prefix + - "Please enter in your domain name(s) (comma and/or space separated) ", - cli_flag="--domains", force_interactive=True) - - if code == display_util.OK: - invalid_domains = dict() - retry_message = "" - try: - domain_list = display_util.separate_list_input(input_) - except UnicodeEncodeError: - domain_list = [] - retry_message = ( - "Internationalized domain names are not presently " - "supported.{0}{0}Would you like to re-enter the " - "names?{0}").format(os.linesep) - - for i, domain in enumerate(domain_list): - try: - domain_list[i] = util.enforce_domain_sanity(domain) - except errors.ConfigurationError as e: - invalid_domains[domain] = str(e) - - if invalid_domains: - retry_message = ( - "One or more of the entered domain names was not valid:" - "{0}{0}").format(os.linesep) - for domain in invalid_domains: - retry_message = retry_message + "{1}: {2}{0}".format( - os.linesep, domain, invalid_domains[domain]) - retry_message = retry_message + ( - "{0}Would you like to re-enter the names?{0}").format( - os.linesep) - - if retry_message: - # We had error in input - retry = z_util(interfaces.IDisplay).yesno(retry_message, - force_interactive=True) - if retry: - return _choose_names_manually() - else: - return domain_list - return [] - - -def success_installation(domains): - """Display a box confirming the installation of HTTPS. - - :param list domains: domain names which were enabled - - """ - z_util(interfaces.IDisplay).notification( - "Congratulations! You have successfully enabled {0}{1}{1}" - "You should test your configuration at:{1}{2}".format( - _gen_https_names(domains), - os.linesep, - os.linesep.join(_gen_ssl_lab_urls(domains))), - pause=False) - - -def success_renewal(domains): - """Display a box confirming the renewal of an existing certificate. - - :param list domains: domain names which were renewed - - """ - z_util(interfaces.IDisplay).notification( - "Your existing certificate has been successfully renewed, and the " - "new certificate has been installed.{1}{1}" - "The new certificate covers the following domains: {0}{1}{1}" - "You should test your configuration at:{1}{2}".format( - _gen_https_names(domains), - os.linesep, - os.linesep.join(_gen_ssl_lab_urls(domains))), - pause=False) - -def success_revocation(cert_path): - """Display a box confirming a certificate has been revoked. - - :param list cert_path: path to certificate which was revoked. - - """ - z_util(interfaces.IDisplay).notification( - "Congratulations! You have successfully revoked the certificate " - "that was located at {0}{1}{1}".format( - cert_path, - os.linesep), - pause=False) - - -def _gen_ssl_lab_urls(domains): - """Returns a list of urls. - - :param list domains: Each domain is a 'str' - - """ - return ["https://www.ssllabs.com/ssltest/analyze.html?d=%s" % dom for dom in domains] - - -def _gen_https_names(domains): - """Returns a string of the https domains. - - Domains are formatted nicely with https:// prepended to each. - - :param list domains: Each domain is a 'str' - - """ - if len(domains) == 1: - return "https://{0}".format(domains[0]) - elif len(domains) == 2: - return "https://{dom[0]} and https://{dom[1]}".format(dom=domains) - elif len(domains) > 2: - return "{0}{1}{2}".format( - ", ".join("https://%s" % dom for dom in domains[:-1]), - ", and https://", - domains[-1]) - - return "" - - -def _get_validated(method, validator, message, default=None, **kwargs): - if default is not None: - try: - validator(default) - except errors.Error as error: - logger.debug('Encountered invalid default value "%s" when prompting for "%s"', - default, - message, - exc_info=True) - raise AssertionError('Invalid default "{0}"'.format(default)) - - while True: - code, raw = method(message, default=default, **kwargs) - if code == display_util.OK: - try: - validator(raw) - return code, raw - except errors.Error as error: - logger.debug('Validator rejected "%s" when prompting for "%s"', - raw, - message, - exc_info=True) - zope.component.getUtility(interfaces.IDisplay).notification(str(error), pause=False) - else: - return code, raw - - -def validated_input(validator, *args, **kwargs): - """Like `~certbot.interfaces.IDisplay.input`, but with validation. - - :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its - text will be displayed and the user will be re-prompted. - :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. - :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. - :return: as `~certbot.interfaces.IDisplay.input` - :rtype: tuple - """ - return _get_validated(zope.component.getUtility(interfaces.IDisplay).input, - validator, *args, **kwargs) - - -def validated_directory(validator, *args, **kwargs): - """Like `~certbot.interfaces.IDisplay.directory_select`, but with validation. - - :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its - text will be displayed and the user will be re-prompted. - :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`. - :param dict `**kwargs`: Arguments to be passed to - `~certbot.interfaces.IDisplay.directory_select`. - :return: as `~certbot.interfaces.IDisplay.directory_select` - :rtype: tuple - """ - return _get_validated(zope.component.getUtility(interfaces.IDisplay).directory_select, - validator, *args, **kwargs) diff --git a/certbot/display/util.py b/certbot/display/util.py deleted file mode 100644 index b79ba338f..000000000 --- a/certbot/display/util.py +++ /dev/null @@ -1,598 +0,0 @@ -"""Certbot display.""" -import logging -import sys -import textwrap - -import zope.interface - -from certbot._internal import constants -from certbot import errors -from certbot import interfaces -from certbot.compat import misc -from certbot.compat import os -from certbot._internal.display import completer - -logger = logging.getLogger(__name__) - -WIDTH = 72 - -# Display exit codes -OK = "ok" -"""Display exit code indicating user acceptance.""" - -CANCEL = "cancel" -"""Display exit code for a user canceling the display.""" - -HELP = "help" -"""Display exit code when for when the user requests more help. (UNUSED)""" - -ESC = "esc" -"""Display exit code when the user hits Escape (UNUSED)""" - -# Display constants -SIDE_FRAME = ("- " * 39) + "-" -"""Display boundary (alternates spaces, so when copy-pasted, markdown doesn't interpret -it as a heading)""" - -def _wrap_lines(msg): - """Format lines nicely to 80 chars. - - :param str msg: Original message - - :returns: Formatted message respecting newlines in message - :rtype: str - - """ - lines = msg.splitlines() - fixed_l = [] - - for line in lines: - fixed_l.append(textwrap.fill( - line, - 80, - break_long_words=False, - break_on_hyphens=False)) - - return '\n'.join(fixed_l) - - -def input_with_timeout(prompt=None, timeout=36000.0): - """Get user input with a timeout. - - Behaves the same as six.moves.input, however, an error is raised if - a user doesn't answer after timeout seconds. The default timeout - value was chosen to place it just under 12 hours for users following - our advice and running Certbot twice a day. - - :param str prompt: prompt to provide for input - :param float timeout: maximum number of seconds to wait for input - - :returns: user response - :rtype: str - - :raises errors.Error if no answer is given before the timeout - - """ - # use of sys.stdin and sys.stdout to mimic six.moves.input based on - # https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129 - if prompt: - sys.stdout.write(prompt) - sys.stdout.flush() - - line = misc.readline_with_timeout(timeout, prompt) - - if not line: - raise EOFError - return line.rstrip('\n') - - -@zope.interface.implementer(interfaces.IDisplay) -class FileDisplay(object): - """File-based display.""" - # see https://github.com/certbot/certbot/issues/3915 - - def __init__(self, outfile, force_interactive): - super(FileDisplay, self).__init__() - self.outfile = outfile - self.force_interactive = force_interactive - self.skipped_interaction = False - - def notification(self, message, pause=True, - wrap=True, force_interactive=False): - """Displays a notification and waits for user acceptance. - - :param str message: Message to display - :param bool pause: Whether or not the program should pause for the - user's confirmation - :param bool wrap: Whether or not the application should wrap text - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - """ - if wrap: - message = _wrap_lines(message) - self.outfile.write( - "{line}{frame}{line}{msg}{line}{frame}{line}".format( - line='\n', frame=SIDE_FRAME, msg=message)) - self.outfile.flush() - if pause: - if self._can_interact(force_interactive): - input_with_timeout("Press Enter to Continue") - else: - logger.debug("Not pausing for user confirmation") - - def menu(self, message, choices, ok_label=None, cancel_label=None, # pylint: disable=unused-argument - help_label=None, default=None, # pylint: disable=unused-argument - cli_flag=None, force_interactive=False, **unused_kwargs): - """Display a menu. - - .. todo:: This doesn't enable the help label/button (I wasn't sold on - any interface I came up with for this). It would be a nice feature - - :param str message: title of menu - :param choices: Menu lines, len must be > 0 - :type choices: list of tuples (tag, item) or - list of descriptions (tags will be enumerated) - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of (`code`, `index`) where - `code` - str display exit code - `index` - int index of the user's selection - - :rtype: tuple - - """ - if self._return_default(message, default, cli_flag, force_interactive): - return OK, default - - self._print_menu(message, choices) - - code, selection = self._get_valid_int_ans(len(choices)) - - return code, selection - 1 - - def input(self, message, default=None, - cli_flag=None, force_interactive=False, **unused_kwargs): - """Accept input from the user. - - :param str message: message to display to the user - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of (`code`, `input`) where - `code` - str display exit code - `input` - str of the user's input - :rtype: tuple - - """ - if self._return_default(message, default, cli_flag, force_interactive): - return OK, default - - # Trailing space must be added outside of _wrap_lines to be preserved - message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " " - ans = input_with_timeout(message) - - if ans == "c" or ans == "C": - return CANCEL, "-1" - return OK, ans - - def yesno(self, message, yes_label="Yes", no_label="No", default=None, - cli_flag=None, force_interactive=False, **unused_kwargs): - """Query the user with a yes/no question. - - Yes and No label must begin with different letters, and must contain at - least one letter each. - - :param str message: question for the user - :param str yes_label: Label of the "Yes" parameter - :param str no_label: Label of the "No" parameter - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: True for "Yes", False for "No" - :rtype: bool - - """ - if self._return_default(message, default, cli_flag, force_interactive): - return default - - message = _wrap_lines(message) - - self.outfile.write("{0}{frame}{msg}{0}{frame}".format( - os.linesep, frame=SIDE_FRAME + os.linesep, msg=message)) - self.outfile.flush() - - while True: - ans = input_with_timeout("{yes}/{no}: ".format( - yes=_parens_around_char(yes_label), - no=_parens_around_char(no_label))) - - # Couldn't get pylint indentation right with elif - # elif doesn't matter in this situation - if (ans.startswith(yes_label[0].lower()) or - ans.startswith(yes_label[0].upper())): - return True - if (ans.startswith(no_label[0].lower()) or - ans.startswith(no_label[0].upper())): - return False - - def checklist(self, message, tags, default=None, - cli_flag=None, force_interactive=False, **unused_kwargs): - """Display a checklist. - - :param str message: Message to display to user - :param list tags: `str` tags to select, len(tags) > 0 - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of (`code`, `tags`) where - `code` - str display exit code - `tags` - list of selected tags - :rtype: tuple - - """ - if self._return_default(message, default, cli_flag, force_interactive): - return OK, default - - while True: - self._print_menu(message, tags) - - code, ans = self.input("Select the appropriate numbers separated " - "by commas and/or spaces, or leave input " - "blank to select all options shown", - force_interactive=True) - - if code == OK: - if not ans.strip(): - ans = " ".join(str(x) for x in range(1, len(tags)+1)) - indices = separate_list_input(ans) - selected_tags = self._scrub_checklist_input(indices, tags) - if selected_tags: - return code, selected_tags - else: - self.outfile.write( - "** Error - Invalid selection **%s" % os.linesep) - self.outfile.flush() - else: - return code, [] - - def _return_default(self, prompt, default, cli_flag, force_interactive): - """Should we return the default instead of prompting the user? - - :param str prompt: prompt for the user - :param default: default answer to prompt - :param str cli_flag: command line option for setting an answer - to this question - :param bool force_interactive: if interactivity is forced by the - IDisplay call - - :returns: True if we should return the default without prompting - :rtype: bool - - """ - # assert_valid_call(prompt, default, cli_flag, force_interactive) - if self._can_interact(force_interactive): - return False - elif default is None: - msg = "Unable to get an answer for the question:\n{0}".format(prompt) - if cli_flag: - msg += ( - "\nYou can provide an answer on the " - "command line with the {0} flag.".format(cli_flag)) - raise errors.Error(msg) - else: - logger.debug( - "Falling back to default %s for the prompt:\n%s", - default, prompt) - return True - - def _can_interact(self, force_interactive): - """Can we safely interact with the user? - - :param bool force_interactive: if interactivity is forced by the - IDisplay call - - :returns: True if the display can interact with the user - :rtype: bool - - """ - if (self.force_interactive or force_interactive or - sys.stdin.isatty() and self.outfile.isatty()): - return True - elif not self.skipped_interaction: - logger.warning( - "Skipped user interaction because Certbot doesn't appear to " - "be running in a terminal. You should probably include " - "--non-interactive or %s on the command line.", - constants.FORCE_INTERACTIVE_FLAG) - self.skipped_interaction = True - return False - - def directory_select(self, message, default=None, cli_flag=None, - force_interactive=False, **unused_kwargs): - """Display a directory selection screen. - - :param str message: prompt to give the user - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of the form (`code`, `string`) where - `code` - display exit code - `string` - input entered by the user - - """ - with completer.Completer(): - return self.input(message, default, cli_flag, force_interactive) - - def _scrub_checklist_input(self, indices, tags): - # pylint: disable=no-self-use - """Validate input and transform indices to appropriate tags. - - :param list indices: input - :param list tags: Original tags of the checklist - - :returns: valid tags the user selected - :rtype: :class:`list` of :class:`str` - - """ - # They should all be of type int - try: - indices = [int(index) for index in indices] - except ValueError: - return [] - - # Remove duplicates - indices = list(set(indices)) - - # Check all input is within range - for index in indices: - if index < 1 or index > len(tags): - return [] - # Transform indices to appropriate tags - return [tags[index - 1] for index in indices] - - def _print_menu(self, message, choices): - """Print a menu on the screen. - - :param str message: title of menu - :param choices: Menu lines - :type choices: list of tuples (tag, item) or - list of descriptions (tags will be enumerated) - - """ - # Can take either tuples or single items in choices list - if choices and isinstance(choices[0], tuple): - choices = ["%s - %s" % (c[0], c[1]) for c in choices] - - # Write out the message to the user - self.outfile.write( - "{new}{msg}{new}".format(new=os.linesep, msg=message)) - self.outfile.write(SIDE_FRAME + os.linesep) - - # Write out the menu choices - for i, desc in enumerate(choices, 1): - msg = "{num}: {desc}".format(num=i, desc=desc) - self.outfile.write(_wrap_lines(msg)) - - # Keep this outside of the textwrap - self.outfile.write(os.linesep) - - self.outfile.write(SIDE_FRAME + os.linesep) - self.outfile.flush() - - def _get_valid_int_ans(self, max_): - """Get a numerical selection. - - :param int max: The maximum entry (len of choices), must be positive - - :returns: tuple of the form (`code`, `selection`) where - `code` - str display exit code ('ok' or cancel') - `selection` - int user's selection - :rtype: tuple - - """ - selection = -1 - if max_ > 1: - input_msg = ("Select the appropriate number " - "[1-{max_}] then [enter] (press 'c' to " - "cancel): ".format(max_=max_)) - else: - input_msg = ("Press 1 [enter] to confirm the selection " - "(press 'c' to cancel): ") - while selection < 1: - ans = input_with_timeout(input_msg) - if ans.startswith("c") or ans.startswith("C"): - return CANCEL, -1 - try: - selection = int(ans) - if selection < 1 or selection > max_: - selection = -1 - raise ValueError - - except ValueError: - self.outfile.write( - "{0}** Invalid input **{0}".format(os.linesep)) - self.outfile.flush() - - return OK, selection - - -def assert_valid_call(prompt, default, cli_flag, force_interactive): - """Verify that provided arguments is a valid IDisplay call. - - :param str prompt: prompt for the user - :param default: default answer to prompt - :param str cli_flag: command line option for setting an answer - to this question - :param bool force_interactive: if interactivity is forced by the - IDisplay call - - """ - msg = "Invalid IDisplay call for this prompt:\n{0}".format(prompt) - if cli_flag: - msg += ("\nYou can set an answer to " - "this prompt with the {0} flag".format(cli_flag)) - assert default is not None or force_interactive, msg - - -@zope.interface.implementer(interfaces.IDisplay) -class NoninteractiveDisplay(object): - """An iDisplay implementation that never asks for interactive user input""" - - def __init__(self, outfile, *unused_args, **unused_kwargs): - super(NoninteractiveDisplay, self).__init__() - self.outfile = outfile - - def _interaction_fail(self, message, cli_flag, extra=""): - "Error out in case of an attempt to interact in noninteractive mode" - msg = "Missing command line flag or config entry for this setting:\n" - msg += message - if extra: - msg += "\n" + extra - if cli_flag: - msg += "\n\n(You can set this with the {0} flag)".format(cli_flag) - raise errors.MissingCommandlineFlag(msg) - - def notification(self, message, pause=False, wrap=True, **unused_kwargs): # pylint: disable=unused-argument - """Displays a notification without waiting for user acceptance. - - :param str message: Message to display to stdout - :param bool pause: The NoninteractiveDisplay waits for no keyboard - :param bool wrap: Whether or not the application should wrap text - - """ - if wrap: - message = _wrap_lines(message) - self.outfile.write( - "{line}{frame}{line}{msg}{line}{frame}{line}".format( - line=os.linesep, frame=SIDE_FRAME, msg=message)) - self.outfile.flush() - - def menu(self, message, choices, ok_label=None, cancel_label=None, - help_label=None, default=None, cli_flag=None, **unused_kwargs): - # pylint: disable=unused-argument - """Avoid displaying a menu. - - :param str message: title of menu - :param choices: Menu lines, len must be > 0 - :type choices: list of tuples (tag, item) or - list of descriptions (tags will be enumerated) - :param int default: the default choice - :param dict kwargs: absorbs various irrelevant labelling arguments - - :returns: tuple of (`code`, `index`) where - `code` - str display exit code - `index` - int index of the user's selection - :rtype: tuple - :raises errors.MissingCommandlineFlag: if there was no default - - """ - if default is None: - self._interaction_fail(message, cli_flag, "Choices: " + repr(choices)) - - return OK, default - - def input(self, message, default=None, cli_flag=None, **unused_kwargs): - """Accept input from the user. - - :param str message: message to display to the user - - :returns: tuple of (`code`, `input`) where - `code` - str display exit code - `input` - str of the user's input - :rtype: tuple - :raises errors.MissingCommandlineFlag: if there was no default - - """ - if default is None: - self._interaction_fail(message, cli_flag) - return OK, default - - def yesno(self, message, yes_label=None, no_label=None, # pylint: disable=unused-argument - default=None, cli_flag=None, **unused_kwargs): - """Decide Yes or No, without asking anybody - - :param str message: question for the user - :param dict kwargs: absorbs yes_label, no_label - - :raises errors.MissingCommandlineFlag: if there was no default - :returns: True for "Yes", False for "No" - :rtype: bool - - """ - if default is None: - self._interaction_fail(message, cli_flag) - return default - - def checklist(self, message, tags, default=None, - cli_flag=None, **unused_kwargs): - """Display a checklist. - - :param str message: Message to display to user - :param list tags: `str` tags to select, len(tags) > 0 - :param dict kwargs: absorbs default_status arg - - :returns: tuple of (`code`, `tags`) where - `code` - str display exit code - `tags` - list of selected tags - :rtype: tuple - - """ - if default is None: - self._interaction_fail(message, cli_flag, "? ".join(tags)) - return OK, default - - def directory_select(self, message, default=None, - cli_flag=None, **unused_kwargs): - """Simulate prompting the user for a directory. - - This function returns default if it is not ``None``, otherwise, - an exception is raised explaining the problem. If cli_flag is - not ``None``, the error message will include the flag that can - be used to set this value with the CLI. - - :param str message: prompt to give the user - :param default: default value to return (if one exists) - :param str cli_flag: option used to set this value with the CLI - - :returns: tuple of the form (`code`, `string`) where - `code` - int display exit code - `string` - input entered by the user - - """ - return self.input(message, default, cli_flag) - - -def separate_list_input(input_): - """Separate a comma or space separated list. - - :param str input_: input from the user - - :returns: strings - :rtype: list - - """ - no_commas = input_.replace(",", " ") - # Each string is naturally unicode, this causes problems with M2Crypto SANs - # TODO: check if above is still true when M2Crypto is gone ^ - return [str(string) for string in no_commas.split()] - - -def _parens_around_char(label): - """Place parens around first character of label. - - :param str label: Must contain at least one character - - """ - return "({first}){rest}".format(first=label[0], rest=label[1:]) diff --git a/certbot/docs/.gitignore b/certbot/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/certbot/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/certbot/docs/Makefile b/certbot/docs/Makefile new file mode 100644 index 000000000..415eebca1 --- /dev/null +++ b/certbot/docs/Makefile @@ -0,0 +1,183 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LetsEncrypt.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LetsEncrypt.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/LetsEncrypt" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LetsEncrypt" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/certbot/docs/_static/.gitignore b/certbot/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/certbot/docs/_templates/footer.html b/certbot/docs/_templates/footer.html new file mode 100644 index 000000000..8fd0f127d --- /dev/null +++ b/certbot/docs/_templates/footer.html @@ -0,0 +1,52 @@ +
+ {% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %} + + {% endif %} + +
+ +
+

+ + © Copyright 2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license. + +
+
+ + Let's Encrypt Status + + + {%- if build_id and build_url %} + {% trans build_url=build_url, build_id=build_id %} + + Build + {{ build_id }}. + + {% endtrans %} + {%- elif commit %} + {% trans commit=commit %} + + Revision {{ commit }}. + + {% endtrans %} + {%- elif last_updated %} + {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} + {%- endif %} + +

+
+ + {%- if show_sphinx %} + {% trans %}Built with Sphinx using a theme provided by Read the Docs{% endtrans %}. + {%- endif %} + + {%- block extrafooter %} {% endblock %} + +
diff --git a/certbot/docs/api.rst b/certbot/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/certbot/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/certbot/docs/api/achallenges.rst b/certbot/docs/api/achallenges.rst new file mode 100644 index 000000000..90dda3f06 --- /dev/null +++ b/certbot/docs/api/achallenges.rst @@ -0,0 +1,5 @@ +:mod:`certbot.achallenges` +------------------------------ + +.. automodule:: certbot.achallenges + :members: diff --git a/certbot/docs/api/crypto_util.rst b/certbot/docs/api/crypto_util.rst new file mode 100644 index 000000000..2f473944c --- /dev/null +++ b/certbot/docs/api/crypto_util.rst @@ -0,0 +1,5 @@ +:mod:`certbot.crypto_util` +------------------------------ + +.. automodule:: certbot.crypto_util + :members: diff --git a/certbot/docs/api/display.rst b/certbot/docs/api/display.rst new file mode 100644 index 000000000..70038786c --- /dev/null +++ b/certbot/docs/api/display.rst @@ -0,0 +1,17 @@ +:mod:`certbot.display` +-------------------------- + +.. automodule:: certbot.display + :members: + +:mod:`certbot.display.util` +=============================== + +.. automodule:: certbot.display.util + :members: + +:mod:`certbot.display.ops` +============================== + +.. automodule:: certbot.display.ops + :members: diff --git a/certbot/docs/api/errors.rst b/certbot/docs/api/errors.rst new file mode 100644 index 000000000..a9324765b --- /dev/null +++ b/certbot/docs/api/errors.rst @@ -0,0 +1,5 @@ +:mod:`certbot.errors` +------------------------- + +.. automodule:: certbot.errors + :members: diff --git a/certbot/docs/api/index.rst b/certbot/docs/api/index.rst new file mode 100644 index 000000000..be94214c9 --- /dev/null +++ b/certbot/docs/api/index.rst @@ -0,0 +1,5 @@ +:mod:`certbot` +------------------ + +.. automodule:: certbot + :members: diff --git a/certbot/docs/api/interfaces.rst b/certbot/docs/api/interfaces.rst new file mode 100644 index 000000000..2988b3b87 --- /dev/null +++ b/certbot/docs/api/interfaces.rst @@ -0,0 +1,5 @@ +:mod:`certbot.interfaces` +----------------------------- + +.. automodule:: certbot.interfaces + :members: diff --git a/certbot/docs/api/main.rst b/certbot/docs/api/main.rst new file mode 100644 index 000000000..d9dda841d --- /dev/null +++ b/certbot/docs/api/main.rst @@ -0,0 +1,5 @@ +:mod:`certbot.main` +------------------------------ + +.. automodule:: certbot.main + :members: diff --git a/certbot/docs/api/plugins/common.rst b/certbot/docs/api/plugins/common.rst new file mode 100644 index 000000000..7cfaf8d70 --- /dev/null +++ b/certbot/docs/api/plugins/common.rst @@ -0,0 +1,5 @@ +:mod:`certbot.plugins.common` +--------------------------------- + +.. automodule:: certbot.plugins.common + :members: diff --git a/certbot/docs/api/plugins/dns_common.rst b/certbot/docs/api/plugins/dns_common.rst new file mode 100644 index 000000000..ee3945e74 --- /dev/null +++ b/certbot/docs/api/plugins/dns_common.rst @@ -0,0 +1,5 @@ +:mod:`certbot.plugins.dns_common` +--------------------------------- + +.. automodule:: certbot.plugins.dns_common + :members: diff --git a/certbot/docs/api/plugins/dns_common_lexicon.rst b/certbot/docs/api/plugins/dns_common_lexicon.rst new file mode 100644 index 000000000..a48166828 --- /dev/null +++ b/certbot/docs/api/plugins/dns_common_lexicon.rst @@ -0,0 +1,5 @@ +:mod:`certbot.plugins.dns_common_lexicon` +----------------------------------------- + +.. automodule:: certbot.plugins.dns_common_lexicon + :members: diff --git a/certbot/docs/api/plugins/util.rst b/certbot/docs/api/plugins/util.rst new file mode 100644 index 000000000..30ab3d49f --- /dev/null +++ b/certbot/docs/api/plugins/util.rst @@ -0,0 +1,5 @@ +:mod:`certbot.plugins.util` +------------------------------- + +.. automodule:: certbot.plugins.util + :members: diff --git a/certbot/docs/api/reverter.rst b/certbot/docs/api/reverter.rst new file mode 100644 index 000000000..3e0ac750b --- /dev/null +++ b/certbot/docs/api/reverter.rst @@ -0,0 +1,5 @@ +:mod:`certbot.reverter` +--------------------------- + +.. automodule:: certbot.reverter + :members: diff --git a/certbot/docs/api/util.rst b/certbot/docs/api/util.rst new file mode 100644 index 000000000..7d0e33501 --- /dev/null +++ b/certbot/docs/api/util.rst @@ -0,0 +1,5 @@ +:mod:`certbot.util` +-------------------------- + +.. automodule:: certbot.util + :members: diff --git a/certbot/docs/challenges.rst b/certbot/docs/challenges.rst new file mode 100644 index 000000000..ee8bb8e61 --- /dev/null +++ b/certbot/docs/challenges.rst @@ -0,0 +1,70 @@ +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 +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) +* 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 +changes itself, or runs another program that does so), or *manually* (Certbot tells you to make a +certain change, and you edit a configuration file of some kind in order to accomplish it). Certbot's +design favors performing challenges automatically, and this is the normal case for most users of Certbot. + +Some plugins offer an *authenticator*, meaning that they can satisfy challenges: + +* 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: (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, + and are prepared to repeat these steps every time the certificate needs to be renewed. + +Tips for Challenges +------------------- +General tips: + +* Run Certbot on your web server, not on your laptop or another server. It’s usually the easiest way to get a certificate. +* Use a tool like the DNSchecker at dnsstuff.com to check your DNS records to make sure + there are no serious errors. A DNS error can prevent a certificate authority from + issuing a certificate, even if it does not prevent your site from loading in a browser. +* If you are using Apache or NGINX plugins, make sure the configuration of your Apache or NGINX server is correct. + +HTTP-01 Challenge +~~~~~~~~~~~~~~~~~ + +* Make sure the domain name exists and is already pointed to the public IP address of the server where + you’re requesting the certificate. +* Make sure port 80 is open, publicly reachable from the Internet, and not blocked by a router or firewall. +* When using the Webroot plugin or the manual plugin, make sure the the webroot directory exists and that you + specify it properly. If you set the webroot directory for example.com to `/var/www/example.com` + then a file placed in `/var/www/example.com/.well-known/acme-challenge/testfile` should appear on + your web site at `http://example.com/.well-known/acme-challenge/testfile` (A redirection to HTTPS + is OK here and should not stop the challenge from working.) +* In some web server configurations, all pages are dynamically generated by some kind of framework, + usually using a database backend. In this case, there might not be a particular directory + from which the web server can serve filesdirectly. Using the Webroot plugin in this case + requires making a change to your web server configuration first. +* Make sure your web server serves files properly from the directory where the challenge + file is placed (e. g. `/.well-known/acme-challenge`) to the expected location on the + website without adding a header or footer. +* 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. + +DNS-01 Challenge +~~~~~~~~~~~~~~~~ + +* When using the manual plugin, make sure your DNS records are correctly updated; + you must be able to make appropriate changes to your DNS zone in order to pass the challenge. + diff --git a/certbot/docs/ciphers.rst b/certbot/docs/ciphers.rst new file mode 100644 index 000000000..04b24b526 --- /dev/null +++ b/certbot/docs/ciphers.rst @@ -0,0 +1,339 @@ +============ +Ciphersuites +============ + +.. contents:: Table of Contents + :local: + + +.. _ciphersuites: + +Introduction +============ + +Autoupdates +----------- + +Within certain limits, TLS server software can choose what kind of +cryptography to use when a client connects. These choices can affect +security, compatibility, and performance in complex ways. Most of +these options are independent of a particular certificate. Certbot +tries to provide defaults that we think are most useful to our users. + +As described below, Certbot will default to modifying +server software's cryptographic settings to keep these up-to-date with +what we think are appropriate defaults when new versions of the Certbot +are installed (for example, by an operating system package manager). + +When this feature is implemented, this document will be updated +to describe how to disable these automatic changes. + + +Cryptographic choices +--------------------- + +Software that uses cryptography must inevitably make choices about what +kind of cryptography to use and how. These choices entail assumptions +about how well particular cryptographic mechanisms resist attack, and what +trade-offs are available and appropriate. The choices are constrained +by compatibility issues (in order to interoperate with other software, +an implementation must agree to use cryptographic mechanisms that the +other side also supports) and protocol issues (cryptographic mechanisms +must be specified in protocols and there must be a way to agree to use +them in a particular context). + +The best choices for a particular application may change over time in +response to new research, new standardization events, changes in computer +hardware, and changes in the prevalence of legacy software. Much important +research on cryptanalysis and cryptographic vulnerabilities is unpublished +because many researchers have been working in the interest of improving +some entities' communications security while weakening, or failing to +improve, others' security. But important information that improves our +understanding of the state of the art is published regularly. + +When enabling TLS support in a compatible web server (which is a separate +step from obtaining a certificate), Certbot has the ability to +update that web server's TLS configuration. Again, this is *different +from the cryptographic particulars of the certificate itself*; the +certificate as of the initial release will be RSA-signed using one of +Let's Encrypt's 2048-bit RSA keys, and will describe the subscriber's +RSA public key ("subject public key") of at least 2048 bits, which is +used for key establishment. + +Note that the subscriber's RSA public key can be used in a wide variety +of key establishment methods, most of which do not use RSA directly +for key exchange, but only for authenticating the server! For example, +in DHE and ECDHE key exchanges, the subject public key is just used to +sign other parameters for authentication. You do not have to "use RSA" +for other purposes just because you're using an RSA key for authentication. + +The certificate doesn't specify other cryptographic or ciphersuite +particulars; for example, it doesn't say whether or not parties should +use a particular symmetric algorithm like 3DES, or what cipher modes +they should use. All of these details are negotiated between client +and server independent of the content of the ciphersuite. The +Let's Encrypt project hopes to provide useful defaults that reflect +good security choices with respect to the publicly-known state of the +art. However, the Let's Encrypt certificate authority does *not* +dictate end-users' security policy, and any site is welcome to change +its preferences in accordance with its own policy or its administrators' +preferences, and use different cryptographic mechanisms or parameters, +or a different priority order, than the defaults provided by Certbot. + +If you don't use Certbot to configure your server directly, because the +client doesn't integrate with your server software or because you chose +not to use this integration, then the cryptographic defaults haven't been +modified, and the cryptography chosen by the server will still be whatever +the default for your software was. For example, if you obtain a +certificate using *standalone* mode and then manually install it in an IMAP +or LDAP server, your cryptographic settings will not be modified by the +client in any way. + + +Sources of defaults +------------------- + +Initially, Certbot will configure users' servers to use the cryptographic +defaults recommended by the Mozilla project. These settings are well-reasoned +recommendations that carefully consider client software compatibility. They +are described at + +https://wiki.mozilla.org/Security/Server_Side_TLS + +and the version implemented by Certbot will be the +version that was most current as of the release date of each client +version. Mozilla offers three separate sets of cryptographic options, +which trade off security and compatibility differently. These are +referred to as the "Modern", "Intermediate", and "Old" configurations +(in order from most secure to least secure, and least-backwards compatible +to most-backwards compatible). The client will follow the Mozilla defaults +for the *Intermediate* configuration by default, at least with regards to +ciphersuites and TLS versions. Mozilla's web site describes which client +software will be compatible with each configuration. You can also use +the Qualys SSL Labs site, which Certbot will suggest +when installing a certificate, to test your server and see whether it +will be compatible with particular software versions. + +It will be possible to ask Certbot to instead apply (and track) Modern +or Old configurations. + +The Let's Encrypt project expects to follow the Mozilla recommendations +in the future as those recommendations are updated. (For example, some +users have proposed prioritizing a new ciphersuite known as ``0xcc13`` +which uses the ChaCha and Poly1305 algorithms, and which is already +implemented by the Chrome browser. Mozilla has delayed recommending +``0xcc13`` over compatibility and standardization concerns, but is likely +to recommend it in the future once these concerns have been addressed. At +that point, Certbot would likely follow the Mozilla recommendations and favor +the use of this ciphersuite as well.) + +The Let's Encrypt project may deviate from the Mozilla recommendations +in the future if good cause is shown and we believe our users' +priorities would be well-served by doing so. In general, please address +relevant proposals for changing priorities to the Mozilla security +team first, before asking the Certbot developers to change +Certbot's priorities. The Mozilla security team is likely to have more +resources and expertise to bring to bear on evaluating reasons why its +recommendations should be updated. + +The Let's Encrypt project will entertain proposals to create a *very* +small number of alternative configurations (apart from Modern, +Intermediate, and Old) that there's reason to believe would be widely +used by sysadmins; this would usually be a preferable course to modifying +an existing configuration. For example, if many sysadmins want their +servers configured to track a different expert recommendation, Certbot +could add an option to do so. + + +Resources for recommendations +----------------------------- + +In the course of considering how to handle this issue, we received +recommendations with sources of expert guidance on ciphersuites and other +cryptographic parameters. We're grateful to everyone who contributed +suggestions. The recommendations we received are available under Feedback_. + +Certbot users are welcome to review these authorities to +better inform their own cryptographic parameter choices. We also +welcome suggestions of other resources to add to this list. Please keep +in mind that different recommendations may reflect different priorities +or evaluations of trade-offs, especially related to compatibility! + + +Changing your settings +---------------------- + +This will probably look something like + +.. code-block:: shell + + certbot --cipher-recommendations mozilla-secure + certbot --cipher-recommendations mozilla-intermediate + certbot --cipher-recommendations mozilla-old + +to track Mozilla's *Secure*, *Intermediate*, or *Old* recommendations, +and + +.. code-block:: shell + + certbot --update-ciphers on + +to enable updating ciphers with each new Certbot release, or + +.. code-block:: shell + + certbot --update-ciphers off + +to disable automatic configuration updates. These features have not yet +been implemented and this syntax may change when they are implemented. + + +TODO +---- + +The status of this feature is tracked as part of issue #1123 in our +bug tracker. + +https://github.com/certbot/certbot/issues/1123 + +Prior to implementation of #1123, the client does not actually modify +ciphersuites (this is intended to be implemented as a "configuration +enhancement", but the only configuration enhancement implemented +so far is redirecting HTTP requests to HTTPS in web servers, the +"redirect" enhancement). The changes here would probably be either a new +"ciphersuite" enhancement in each plugin that provides an installer, +or a family of enhancements, one per selectable ciphersuite configuration. + +Feedback +======== +We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers. + +Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions. + +Some people provided rationale information mostly having to do with compatibility of particular user-agents (especially UAs that don't support ECC, or that don't support DH groups > 1024 bits). Some ciphersuite configurations have been chosen to try to increase compatibility with older UAs while allowing newer UAs to negotiate stronger crypto. For example, some configurations forego forward secrecy entirely for connections from old UAs, like by offering ECDHE and RSA key exchange, but no DHE at all. (There are UAs that can fail the negotiation completely if a DHE ciphersuite with prime > 1024 bits is offered.) + +References +---------- + +RFC 7575 +~~~~~~~~ + +IETF has published a BCP document, RFC 7525, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)" + +https://datatracker.ietf.org/doc/rfc7525/ + +BetterCrypto.org +~~~~~~~~~~~~~~~~ + +BetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, "Applied Crypto Hardening" + +https://bettercrypto.org/ + +FF-DHE Internet-Draft +~~~~~~~~~~~~~~~~~~~~~ + +Gillmor's Internet-Draft "Negotiated Discrete Log Diffie-Hellman Ephemeral Parameters for TLS" is being developed at the IETF TLS WG. It advocates using *standardized* DH groups in all cases, not individually-chosen ones (mostly because of the Triple Handshake attack which can involve maliciously choosing invalid DH groups). The draft provides a list of recommended groups, with primes beginning at 2048 bits and going up from there. It also has a new protocol mechanism for agreeing to use these groups, with the possibility of backwards compatibility (and use of weaker DH groups) for older clients and servers that don't know about this mechanism. + +https://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10 + +Mozilla +~~~~~~~ + +Mozilla's general server configuration guidance is available at https://wiki.mozilla.org/Security/Server_Side_TLS + +Mozilla has also produced a configuration generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/ + +Dutch National Cyber Security Centre +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Dutch National Cyber Security Centre has published guidance on "ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)" ("IT Security Guidelines for Transport Layer Security (TLS)"). These are available only in Dutch at + +https://web.archive.org/web/20190516085116/https://www.ncsc.nl/actueel/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html + +I have access to an English-language summary of the recommendations. + +Keylength.com +~~~~~~~~~~~~~ + +Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2017. + +http://www.keylength.com/ + +NIST +~~~~ +NISA published its "NIST Special Publication 800-52 Revision 1: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations" + +http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r1.pdf + +and its "NIST Special Publication 800-57: Recommendation for Key Management – Part 1: General (Revision 3)" + +http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf + +ENISA +~~~~~ + +ENISA published its "Algorithms, Key Sizes and Parameters Report - 2013" + +https://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/algorithms-key-sizes-and-parameters-report + +WeakDH/Logjam +------------- + +The WeakDH/Logjam research has thrown into question the safety of some existing practice using DH ciphersuites, especially the use of standardized groups with a prime ≤ 1024 bits. The authors provided detailed guidance, including ciphersuite lists, at + +https://weakdh.org/sysadmin.html + +These lists may have been derived from Mozilla's recommendations. +One of the authors clarified his view of the priorities for various changes as a result of the research at + +https://web.archive.org/web/20150526022820/https://www.ietf.org/mail-archive/web/tls/current/msg16496.html + +In particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn't clear from the group's original recommendations). + +Particular sites' opinions or configurations +-------------------------------------------- + +Amazon ELB +~~~~~~~~~~ + +Amazon ELB explains its current ciphersuite choices at + +https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html + +U.S. Government 18F +~~~~~~~~~~~~~~~~~~~ + +The 18F site (https://18f.gsa.gov/) is using + +:: + + ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED'; + +Duraconf +~~~~~~~~ + +The Duraconf project collects particular configuration files, with an apparent focus on avoiding the use of obsolete symmetric ciphers and hash functions, and favoring forward secrecy while not requiring it. + +https://github.com/ioerror/duraconf + +Site scanning or rating tools +----------------------------- + +Qualys SSL Labs +~~~~~~~~~~~~~~~ + +Qualys offers the best-known TLS security scanner, maintained by Ivan Ristić. + +https://www.ssllabs.com/ + +Dutch NCSC +~~~~~~~~~~ + +The Dutch NCSC, mentioned above, has also made available its own site security scanner which indicates how well sites comply with the recommendations. + +https://en.internet.nl/ + +Java compatibility issue +------------------------ + +A lot of backward-compatibility concerns have to do with Java hard-coding DHE primes to a 1024-bit limit, accepting DHE ciphersuites in negotiation, and then aborting the connection entirely if a prime > 1024 bits is presented. The simple summary is that servers offering a Java-compatible DHE ciphersuite in preference to other Java-compatible ciphersuites, and then presenting a DH group with a prime > 1024 bits, will be completely incompatible with clients running some versions of Java. (This may also be the case with very old MSIE versions...?) There are various strategies for dealing with this, and maybe we can document the options here. diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt new file mode 100644 index 000000000..efade498c --- /dev/null +++ b/certbot/docs/cli-help.txt @@ -0,0 +1,718 @@ +usage: + certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... + +Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, +it will attempt to use a webserver both for obtaining and installing the +certificate. The most common SUBCOMMANDS and flags are: + +obtain, install, and renew certificates: + (default) run Obtain & install a certificate in your current webserver + certonly Obtain or renew a certificate, but do not install it + renew Renew all previously obtained certificates that are near expiry + enhance Add security enhancements to your existing configuration + -d DOMAINS Comma-separated list of domains to obtain a certificate for + + --apache Use the Apache plugin for authentication & installation + --standalone Run a standalone webserver for authentication + --nginx Use the Nginx plugin for authentication & installation + --webroot Place files in a server's webroot folder for authentication + --manual Obtain certificates interactively, or using shell script hooks + + -n Run non-interactively + --test-cert Obtain a test certificate from a staging server + --dry-run Test "renew" or "certonly" without saving any certificates to disk + +manage certificates: + certificates Display information about certificates you have from Certbot + revoke Revoke a certificate (supply --cert-path or --cert-name) + delete Delete a certificate + +manage your account: + register Create an ACME account + unregister Deactivate an ACME account + update_account Update an ACME account + --agree-tos Agree to the ACME server's Subscriber Agreement + -m EMAIL Email address for important account notifications + +optional arguments: + -h, --help show this help message and exit + -c CONFIG_FILE, --config CONFIG_FILE + path to config file (default: /etc/letsencrypt/cli.ini + and ~/.config/letsencrypt/cli.ini) + -v, --verbose This flag can be used multiple times to incrementally + increase the verbosity of output, e.g. -vvv. (default: + -2) + --max-log-backups MAX_LOG_BACKUPS + Specifies the maximum number of backup logs that + should be kept by Certbot's built in log rotation. + Setting this flag to 0 disables log rotation entirely, + causing Certbot to always append to the same log file. + (default: 1000) + -n, --non-interactive, --noninteractive + Run without ever asking for user input. This may + require additional command line flags; the client will + try to explain which ones are required if it finds one + missing (default: False) + --force-interactive Force Certbot to be interactive even if it detects + it's not being run in a terminal. This flag cannot be + used with the renew subcommand. (default: False) + -d DOMAIN, --domains DOMAIN, --domain DOMAIN + Domain names to apply. For multiple domains you can + use multiple -d flags or enter a comma separated list + of domains as a parameter. The first domain provided + will be the subject CN of the certificate, and all + domains will be Subject Alternative Names on the + certificate. The first domain will also be used in + some software user interfaces and as the file paths + for the certificate and related material unless + otherwise 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) + --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 + certificate names, run 'certbot certificates'. When + creating a new certificate, specifies the new + certificate's name. (default: the first provided + domain or the name of an existing certificate on your + system for the same domains) + --dry-run Perform a test run of the client, obtaining test + (invalid) certificates but not saving them to disk. + This can currently only be used with the 'certonly' + and 'renew' subcommands. Note: Although --dry-run + tries to avoid making any persistent changes on a + system, it is not completely side-effect free: if used + with webserver authenticator plugins like apache and + nginx, it makes and then reverts temporary config + changes in order to obtain test certificates, and + reloads webservers to deploy and then roll back those + changes. It also calls --pre-hook and --post-hook + commands if they are defined because they may be + necessary to accurately simulate renewal. --deploy- + hook commands are not called. (default: False) + --debug-challenges After setting up challenges, wait for user input + before submitting to CA (default: False) + --preferred-challenges PREF_CHALLS + A sorted, comma delimited list of the preferred + challenge to use during authorization with the most + preferred challenge listed first (Eg, "dns" or + "http,dns"). Not all plugins support all challenges. + See https://certbot.eff.org/docs/using.html#plugins + for details. ACME Challenges are versioned, but if you + pick "http" rather than "http-01", Certbot will select + the latest version automatically. (default: []) + --user-agent USER_AGENT + Set a custom user agent string for the client. User + agent strings allow the CA to collect high level + statistics about success rates by OS, plugin and use + 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.40.1 + (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX + Installer/YYY (SUBCOMMAND; flags: FLAGS) + Py/major.minor.patchlevel). The flags encoded in the + user agent are: --duplicate, --force-renew, --allow- + subset-of-names, -n, and whether any hooks are set. + --user-agent-comment USER_AGENT_COMMENT + Add a comment to the default user agent string. May be + used when repackaging Certbot or calling it from + another tool to allow additional statistical data to + be collected. Ignored if --user-agent is set. + (Example: Foo-Wrapper/1.0) (default: None) + +automation: + Flags for automating execution & other tweaks + + --keep-until-expiring, --keep, --reinstall + If the requested certificate matches an existing + certificate, always keep the existing one until it is + due for renewal (for the 'run' subcommand this means + reinstall the existing certificate). (default: Ask) + --expand If an existing certificate is a strict subset of the + requested names, always expand and replace it with the + additional names. (default: Ask) + --version show program's version number and exit + --force-renewal, --renew-by-default + If a certificate already exists for the requested + domains, renew it now, regardless of whether it is + near expiry. (Often --keep-until-expiring is more + appropriate). Also implies --expand. (default: False) + --renew-with-new-domains + If a certificate already exists for the requested + certificate name but does not match the requested + domains, renew it now, regardless of whether it is + near expiry. (default: False) + --reuse-key When renewing, use the same private key as the + existing certificate. (default: False) + --allow-subset-of-names + When performing domain validation, do not consider it + a failure if authorizations can not be obtained for a + strict subset of the requested domains. This may be + useful for allowing renewals for multiple domains to + succeed even if some domains no longer point at this + system. This option cannot be used with --csr. + (default: False) + --agree-tos Agree to the ACME Subscriber Agreement (default: Ask) + --duplicate Allow making a certificate lineage that duplicates an + existing one (both can be renewed in parallel) + (default: False) + --os-packages-only (certbot-auto only) install OS package dependencies + and then stop (default: False) + --no-self-upgrade (certbot-auto only) prevent the certbot-auto script + from upgrading itself to newer released versions + (default: Upgrade automatically) + --no-bootstrap (certbot-auto only) prevent the certbot-auto script + from installing OS-level dependencies (default: Prompt + to install OS-wide dependencies, but exit if the user + says 'No') + --no-permissions-check + (certbot-auto only) skip the check on the file system + permissions of the certbot-auto script (default: + False) + -q, --quiet Silence all output except errors. Useful for + automation via cron. Implies --non-interactive. + (default: False) + +security: + Security parameters & server settings + + --rsa-key-size N Size of the RSA key. (default: 2048) + --must-staple Adds the OCSP Must Staple extension to the + certificate. Autoconfigures OCSP Stapling for + supported setups (Apache version >= 2.3.3 ). (default: + False) + --redirect Automatically redirect all HTTP traffic to HTTPS for + the newly authenticated vhost. (default: Ask) + --no-redirect Do not automatically redirect all HTTP traffic to + HTTPS for the newly authenticated vhost. (default: + Ask) + --hsts Add the Strict-Transport-Security header to every HTTP + response. Forcing browser to always use SSL for the + domain. Defends against SSL Stripping. (default: None) + --uir Add the "Content-Security-Policy: upgrade-insecure- + requests" header to every HTTP response. Forcing the + browser to use https:// for every http:// resource. + (default: None) + --staple-ocsp Enables OCSP Stapling. A valid OCSP response is + stapled to the certificate that the server offers + during TLS. (default: None) + --strict-permissions Require that all configuration files are owned by the + current user; only needed if your config is somewhere + unsafe like /tmp/ (default: False) + --auto-hsts Gradually increasing max-age value for HTTP Strict + Transport Security security header (default: False) + +testing: + The following flags are meant for testing and integration purposes only. + + --test-cert, --staging + Use the staging server to obtain or revoke test + (invalid) certificates; equivalent to --server https + ://acme-staging-v02.api.letsencrypt.org/directory + (default: False) + --debug Show tracebacks in case of errors, and allow certbot- + auto execution on experimental platforms (default: + False) + --no-verify-ssl Disable verification of the ACME server's certificate. + (default: False) + --http-01-port HTTP01_PORT + Port used in the http-01 challenge. This only affects + the port Certbot listens on. A conforming ACME server + will still attempt to connect on port 80. (default: + 80) + --http-01-address HTTP01_ADDRESS + The address the server listens to during http-01 + challenge. (default: ) + --https-port HTTPS_PORT + Port used to serve HTTPS. This affects which port + Nginx will listen on after a LE certificate is + installed. (default: 443) + --break-my-certs Be willing to replace or renew valid certificates with + invalid (testing/staging) certificates (default: + False) + +paths: + Flags for changing execution paths & servers + + --cert-path CERT_PATH + Path to where certificate is saved (with auth --csr), + installed from, or revoked. (default: None) + --key-path KEY_PATH Path to private key for certificate installation or + revocation (if account key is missing) (default: None) + --fullchain-path FULLCHAIN_PATH + Accompanying path to a full certificate chain + (certificate plus chain). (default: None) + --chain-path CHAIN_PATH + Accompanying path to a certificate chain. (default: + None) + --config-dir CONFIG_DIR + Configuration directory. (default: /etc/letsencrypt) + --work-dir WORK_DIR Working directory. (default: /var/lib/letsencrypt) + --logs-dir LOGS_DIR Logs directory. (default: /var/log/letsencrypt) + --server SERVER ACME Directory Resource URI. (default: + https://acme-v02.api.letsencrypt.org/directory) + +manage: + Various subcommands and flags are available for managing your + certificates: + + certificates List certificates managed by Certbot + delete Clean up all files related to a certificate + renew Renew all certificates (or one specified with --cert- + name) + revoke Revoke a certificate specified with --cert-path or + --cert-name + update_symlinks Recreate symlinks in your /etc/letsencrypt/live/ + directory + +run: + Options for obtaining & installing certificates + +certonly: + Options for modifying how a certificate is obtained + + --csr CSR Path to a Certificate Signing Request (CSR) in DER or + PEM format. Currently --csr only works with the + 'certonly' subcommand. (default: None) + +renew: + The 'renew' subcommand will attempt to renew all certificates (or more + precisely, certificate lineages) you have previously obtained if they are + close to expiry, and print a summary of the results. By default, 'renew' + will reuse the options used to create obtain or most recently successfully + renew each certificate lineage. You can try it with `--dry-run` first. For + more fine-grained control, you can renew individual lineages with the + `certonly` subcommand. Hooks are available to run commands before and + after renewal; see https://certbot.eff.org/docs/using.html#renewal for + more information on these. + + --pre-hook PRE_HOOK Command to be run in a shell before obtaining any + certificates. Intended primarily for renewal, where it + can be used to temporarily shut down a webserver that + might conflict with the standalone plugin. This will + only be called if a certificate is actually to be + obtained/renewed. When renewing several certificates + that have identical pre-hooks, only the first will be + executed. (default: None) + --post-hook POST_HOOK + Command to be run in a shell after attempting to + obtain/renew certificates. Can be used to deploy + renewed certificates, or to restart any servers that + were stopped by --pre-hook. This is only run if an + attempt was made to obtain/renew a certificate. If + multiple renewed certificates have identical post- + hooks, only one will be run. (default: None) + --deploy-hook DEPLOY_HOOK + Command to be run in a shell once for each + successfully issued certificate. For this command, the + shell variable $RENEWED_LINEAGE will point to the + config live subdirectory (for example, + "/etc/letsencrypt/live/example.com") containing the + new certificates and keys; the shell variable + $RENEWED_DOMAINS will contain a space-delimited list + of renewed certificate domains (for example, + "example.com www.example.com" (default: None) + --disable-hook-validation + Ordinarily the commands specified for --pre-hook + /--post-hook/--deploy-hook will be checked for + validity, to see if the programs being run are in the + $PATH, so that mistakes can be caught early, even when + the hooks aren't being run just yet. The validation is + rather simplistic and fails if you use more advanced + shell constructs, so you can use this switch to + disable it. (default: False) + --no-directory-hooks Disable running executables found in Certbot's hook + directories during renewal. (default: False) + --disable-renew-updates + Disable automatic updates to your server configuration + that would otherwise be done by the selected installer + plugin, and triggered when the user executes "certbot + renew", regardless of if the certificate is renewed. + This setting does not apply to important TLS + configuration updates. (default: False) + --no-autorenew Disable auto renewal of certificates. (default: True) + +certificates: + List certificates managed by Certbot + +delete: + Options for deleting a certificate + +revoke: + Options for revocation of certificates + + --reason {unspecified,keycompromise,affiliationchanged,superseded,cessationofoperation} + Specify reason for revoking certificate. (default: + unspecified) + --delete-after-revoke + Delete certificates after revoking them, along with + all previous and later versions of those certificates. + (default: None) + --no-delete-after-revoke + Do not delete certificates after revoking them. This + option should be used with caution because the 'renew' + subcommand will attempt to renew undeleted revoked + certificates. (default: None) + +register: + Options for account registration + + --register-unsafely-without-email + Specifying this flag enables registering an account + with no email address. This is strongly discouraged, + because in the event of key loss or account compromise + you will irrevocably lose access to your account. You + will also be unable to receive notice about impending + expiration or revocation of your certificates. Updates + to the Subscriber Agreement will still affect you, and + will be effective 14 days after posting an update to + the web site. (default: False) + -m EMAIL, --email EMAIL + Email used for registration and recovery contact. Use + comma to register multiple emails, ex: + u1@example.com,u2@example.com. (default: Ask). + --eff-email Share your e-mail address with EFF (default: None) + --no-eff-email Don't share your e-mail address with EFF (default: + None) + +update_account: + Options for account modification + +unregister: + Options for account deactivation. + + --account ACCOUNT_ID Account ID to use (default: None) + +install: + Options for modifying how a certificate is deployed + +rollback: + Options for rolling back server configuration changes + + --checkpoints N Revert configuration N number of checkpoints. + (default: 1) + +plugins: + Options for the "plugins" subcommand + + --init Initialize plugins. (default: False) + --prepare Initialize and prepare plugins. (default: False) + --authenticators Limit to authenticator plugins only. (default: None) + --installers Limit to installer plugins only. (default: None) + +update_symlinks: + Recreates certificate and key symlinks in /etc/letsencrypt/live, if you + changed them by hand or edited a renewal configuration file + +enhance: + Helps to harden the TLS configuration by adding security enhancements to + already existing configuration. + +plugins: + Plugin Selection: Certbot client supports an extensible plugins + architecture. See 'certbot plugins' for a list of all installed plugins + and their names. You can force a particular plugin by setting options + provided below. Running --help will list flags specific to + that plugin. + + --configurator CONFIGURATOR + Name of the plugin that is both an authenticator and + an installer. Should not be used together with + --authenticator or --installer. (default: Ask) + -a AUTHENTICATOR, --authenticator AUTHENTICATOR + Authenticator plugin name. (default: None) + -i INSTALLER, --installer INSTALLER + Installer plugin name (also used to find domains). + (default: None) + --apache Obtain and install certificates using Apache (default: + False) + --nginx Obtain and install certificates using Nginx (default: + False) + --standalone Obtain certificates using a "standalone" webserver. + (default: False) + --manual Provide laborious manual instructions for obtaining a + certificate (default: False) + --webroot Obtain certificates by placing files in a webroot + directory. (default: False) + --dns-cloudflare Obtain certificates using a DNS TXT record (if you are + using Cloudflare for DNS). (default: False) + --dns-cloudxns Obtain certificates using a DNS TXT record (if you are + using CloudXNS for DNS). (default: False) + --dns-digitalocean Obtain certificates using a DNS TXT record (if you are + using DigitalOcean for DNS). (default: False) + --dns-dnsimple Obtain certificates using a DNS TXT record (if you are + using DNSimple for DNS). (default: False) + --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you are + using DNS Made Easy for DNS). (default: False) + --dns-gehirn Obtain certificates using a DNS TXT record (if you are + using Gehirn Infrastracture Service for DNS). + (default: False) + --dns-google Obtain certificates using a DNS TXT record (if you are + using Google Cloud DNS). (default: False) + --dns-linode Obtain certificates using a DNS TXT record (if you are + using Linode for DNS). (default: False) + --dns-luadns Obtain certificates using a DNS TXT record (if you are + using LuaDNS for DNS). (default: False) + --dns-nsone Obtain certificates using a DNS TXT record (if you are + using NS1 for DNS). (default: False) + --dns-ovh Obtain certificates using a DNS TXT record (if you are + using OVH for DNS). (default: False) + --dns-rfc2136 Obtain certificates using a DNS TXT record (if you are + using BIND for DNS). (default: False) + --dns-route53 Obtain certificates using a DNS TXT record (if you are + using Route53 for DNS). (default: False) + --dns-sakuracloud Obtain certificates using a DNS TXT record (if you are + using Sakura Cloud for DNS). (default: False) + +apache: + Apache Web Server plugin (Please note that the default values of the + Apache plugin options change depending on the operating system Certbot is + run on.) + + --apache-enmod APACHE_ENMOD + Path to the Apache 'a2enmod' binary (default: None) + --apache-dismod APACHE_DISMOD + Path to the Apache 'a2dismod' binary (default: None) + --apache-le-vhost-ext APACHE_LE_VHOST_EXT + SSL vhost configuration extension (default: -le- + ssl.conf) + --apache-server-root APACHE_SERVER_ROOT + Apache server root directory (default: /etc/apache2) + --apache-vhost-root APACHE_VHOST_ROOT + Apache server VirtualHost configuration root (default: + None) + --apache-logs-root APACHE_LOGS_ROOT + Apache server logs directory (default: + /var/log/apache2) + --apache-challenge-location APACHE_CHALLENGE_LOCATION + Directory path for challenge configuration (default: + /etc/apache2) + --apache-handle-modules APACHE_HANDLE_MODULES + Let installer handle enabling required modules for you + (Only Ubuntu/Debian currently) (default: False) + --apache-handle-sites APACHE_HANDLE_SITES + Let installer handle enabling sites for you (Only + Ubuntu/Debian currently) (default: False) + --apache-ctl APACHE_CTL + Full path to Apache control script (default: + apache2ctl) + +dns-cloudflare: + Obtain certificates using a DNS TXT record (if you are using Cloudflare + for DNS). + + --dns-cloudflare-propagation-seconds DNS_CLOUDFLARE_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 10) + --dns-cloudflare-credentials DNS_CLOUDFLARE_CREDENTIALS + Cloudflare credentials INI file. (default: None) + +dns-cloudxns: + Obtain certificates using a DNS TXT record (if you are using CloudXNS for + DNS). + + --dns-cloudxns-propagation-seconds DNS_CLOUDXNS_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-cloudxns-credentials DNS_CLOUDXNS_CREDENTIALS + CloudXNS credentials INI file. (default: None) + +dns-digitalocean: + Obtain certs using a DNS TXT record (if you are using DigitalOcean for + DNS). + + --dns-digitalocean-propagation-seconds DNS_DIGITALOCEAN_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 10) + --dns-digitalocean-credentials DNS_DIGITALOCEAN_CREDENTIALS + DigitalOcean credentials INI file. (default: None) + +dns-dnsimple: + Obtain certificates using a DNS TXT record (if you are using DNSimple for + DNS). + + --dns-dnsimple-propagation-seconds DNS_DNSIMPLE_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-dnsimple-credentials DNS_DNSIMPLE_CREDENTIALS + DNSimple credentials INI file. (default: None) + +dns-dnsmadeeasy: + Obtain certificates using a DNS TXT record (if you are using DNS Made Easy + for DNS). + + --dns-dnsmadeeasy-propagation-seconds DNS_DNSMADEEASY_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 60) + --dns-dnsmadeeasy-credentials DNS_DNSMADEEASY_CREDENTIALS + DNS Made Easy credentials INI file. (default: None) + +dns-gehirn: + Obtain certificates using a DNS TXT record (if you are using Gehirn + Infrastracture Service for DNS). + + --dns-gehirn-propagation-seconds DNS_GEHIRN_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-gehirn-credentials DNS_GEHIRN_CREDENTIALS + Gehirn Infrastracture Service credentials file. + (default: None) + +dns-google: + Obtain certificates using a DNS TXT record (if you are using Google Cloud + DNS for DNS). + + --dns-google-propagation-seconds DNS_GOOGLE_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 60) + --dns-google-credentials DNS_GOOGLE_CREDENTIALS + Path to Google Cloud DNS service account JSON file. + (See https://developers.google.com/identity/protocols/ + OAuth2ServiceAccount#creatinganaccount forinformation + about creating a service account and + https://cloud.google.com/dns/access- + control#permissions_and_roles for information about + therequired permissions.) (default: None) + +dns-linode: + Obtain certs using a DNS TXT record (if you are using Linode for DNS). + + --dns-linode-propagation-seconds DNS_LINODE_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 1200) + --dns-linode-credentials DNS_LINODE_CREDENTIALS + Linode credentials INI file. (default: None) + +dns-luadns: + Obtain certificates using a DNS TXT record (if you are using LuaDNS for + DNS). + + --dns-luadns-propagation-seconds DNS_LUADNS_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-luadns-credentials DNS_LUADNS_CREDENTIALS + LuaDNS credentials INI file. (default: None) + +dns-nsone: + Obtain certificates using a DNS TXT record (if you are using NS1 for DNS). + + --dns-nsone-propagation-seconds DNS_NSONE_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-nsone-credentials DNS_NSONE_CREDENTIALS + NS1 credentials file. (default: None) + +dns-ovh: + Obtain certificates using a DNS TXT record (if you are using OVH for DNS). + + --dns-ovh-propagation-seconds DNS_OVH_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 30) + --dns-ovh-credentials DNS_OVH_CREDENTIALS + OVH credentials INI file. (default: None) + +dns-rfc2136: + Obtain certificates using a DNS TXT record (if you are using BIND for + DNS). + + --dns-rfc2136-propagation-seconds DNS_RFC2136_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 60) + --dns-rfc2136-credentials DNS_RFC2136_CREDENTIALS + RFC 2136 credentials INI file. (default: None) + +dns-route53: + Obtain certificates using a DNS TXT record (if you are using AWS Route53 + for DNS). + + --dns-route53-propagation-seconds DNS_ROUTE53_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 10) + +dns-sakuracloud: + Obtain certificates using a DNS TXT record (if you are using Sakura Cloud + for DNS). + + --dns-sakuracloud-propagation-seconds DNS_SAKURACLOUD_PROPAGATION_SECONDS + The number of seconds to wait for DNS to propagate + before asking the ACME server to verify the DNS + record. (default: 90) + --dns-sakuracloud-credentials DNS_SAKURACLOUD_CREDENTIALS + Sakura Cloud credentials file. (default: None) + +manual: + Authenticate through manual configuration or custom shell scripts. When + using shell scripts, an authenticator script must be provided. The + environment variables available to this script depend on the type of + challenge. $CERTBOT_DOMAIN will always contain the domain being + authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION is the + validation string, and $CERTBOT_TOKEN is the filename of the resource + requested when performing an HTTP-01 challenge. An additional cleanup + script can also be provided and can use the additional variable + $CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth + script. + + --manual-auth-hook MANUAL_AUTH_HOOK + Path or command to execute for the authentication + script (default: None) + --manual-cleanup-hook MANUAL_CLEANUP_HOOK + Path or command to execute for the cleanup script + (default: None) + --manual-public-ip-logging-ok + Automatically allows public IP logging (default: Ask) + +nginx: + Nginx Web Server plugin + + --nginx-server-root NGINX_SERVER_ROOT + Nginx server root directory. (default: /etc/nginx or + /usr/local/etc/nginx) + --nginx-ctl NGINX_CTL + Path to the 'nginx' binary, used for 'configtest' and + retrieving nginx version number. (default: nginx) + +null: + Null Installer + +standalone: + Spin up a temporary webserver + +webroot: + Place files in webroot directory + + --webroot-path WEBROOT_PATH, -w WEBROOT_PATH + public_html / webroot path. This can be specified + multiple times to handle different domains; each + domain will have the webroot path that preceded it. + For instance: `-w /var/www/example -d example.com -d + www.example.com -w /var/www/thing -d thing.net -d + m.thing.net` (default: Ask) + --webroot-map WEBROOT_MAP + JSON dictionary mapping domains to webroot paths; this + implies -d for each entry. You may need to escape this + from your shell. E.g.: --webroot-map + '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' + This option is merged with, but takes precedence over, + -w / -d entries. At present, if you put webroot-map in + a config file, it needs to be on a single line, like: + webroot-map = {"example.com":"/var/www"}. (default: + {}) diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py new file mode 100644 index 000000000..c72d1c1cf --- /dev/null +++ b/certbot/docs/conf.py @@ -0,0 +1,323 @@ +# -*- coding: utf-8 -*- +# +# Certbot documentation build configuration file, created by +# sphinx-quickstart on Sun Nov 23 20:35:21 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import codecs +import os +import re +import sys + +import sphinx + + +here = os.path.abspath(os.path.dirname(__file__)) + +# read version number (and other metadata) from package init +init_fn = os.path.join(here, '..', 'certbot', '__init__.py') +with codecs.open(init_fn, encoding='utf8') as fd: + meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", fd.read())) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +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.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.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + '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'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Certbot' +# this is now overridden by the footer.html template +#copyright = u'2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '.'.join(meta['version'].split('.')[:2]) +# The full version, including alpha/beta/rc tags. +release = meta['version'] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + +suppress_warnings = ['image.nonlocal_uri'] + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Certbotdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + #'preamble': '', + + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Certbot.tex', u'Certbot Documentation', + u'Certbot Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'certbot', u'Certbot Documentation', + [project], 7), + ('man/certbot', 'certbot', u'certbot script documentation', + [project], 1), +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Certbot', u'Certbot Documentation', + u'Certbot Project', 'Certbot', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), +} diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst new file mode 100644 index 000000000..d38dfc121 --- /dev/null +++ b/certbot/docs/contributing.rst @@ -0,0 +1,596 @@ +=============== +Developer Guide +=============== + +.. contents:: Table of Contents + :local: + + +.. _getting_started: + +Getting Started +=============== + +Certbot has the same :ref:`system requirements ` when set +up for development. While the section below will help you install Certbot and +its dependencies, Certbot needs to be run on a UNIX-like OS so if you're using +Windows, you'll need to set up a (virtual) machine running an OS such as Linux +and continue with these instructions on that UNIX-like OS. + +.. _local copy: + +Running a local copy of the client +---------------------------------- + +Running the client in developer mode from your local tree is a little different +than running Certbot as a user. To get set up, clone our git repository by +running: + +.. code-block:: shell + + git clone https://github.com/certbot/certbot + +If you're on macOS, we recommend you skip the rest of this section and instead +run Certbot in Docker. You can find instructions for how to do this :ref:`here +`. If you're running on Linux, you can run the following commands to +install dependencies and set up a virtual environment where you can run +Certbot. + +Install the OS system dependencies required to run Certbot. + +.. code-block:: shell + + # For APT-based distributions (e.g. Debian, Ubuntu ...) + sudo apt update + sudo apt install python3-dev python3-venv gcc libaugeas0 libssl-dev \ + libffi-dev ca-certificates openssl + # For RPM-based distributions (e.g. Fedora, CentOS ...) + # NB1: old distributions will use yum instead of dnf + # NB2: RHEL-based distributions use python3X-devel instead of python3-devel (e.g. python36-devel) + sudo dnf install python3-devel gcc augeas-libs openssl-devel libffi-devel \ + redhat-rpm-config ca-certificates openssl + +Set up the Python virtual environment that will host your Certbot local instance. + +.. code-block:: shell + + cd certbot + python tools/venv3.py + +.. note:: You may need to repeat this when + Certbot's dependencies change or when a new plugin is introduced. + +You can now run the copy of Certbot from git either by executing +``venv3/bin/certbot``, or by activating the virtual environment. You can do the +latter by running: + +.. code-block:: shell + + source venv3/bin/activate + +After running this command, ``certbot`` and development tools like ``ipdb``, +``ipython``, ``pytest``, and ``tox`` are available in the shell where you ran +the command. These tools are installed in the virtual environment and are kept +separate from your global Python installation. This works by setting +environment variables so the right executables are found and Python can pull in +the versions of various packages needed by Certbot. More information can be +found in the `virtualenv docs`_. + +.. _`virtualenv docs`: https://virtualenv.pypa.io + +Find issues to work on +---------------------- + +You can find the open issues in the `github issue tracker`_. Comparatively +easy ones are marked `good first issue`_. If you're starting work on +something, post a comment to let others know and seek feedback on your plan +where appropriate. + +Once you've got a working branch, you can open a pull request. All changes in +your pull request must have thorough unit test coverage, pass our +tests, and be compliant with the :ref:`coding style `. + +.. _github issue tracker: https://github.com/certbot/certbot/issues +.. _good first issue: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 + +.. _testing: + +Testing +------- + +You can test your code in several ways: + +- running the `automated unit`_ tests, +- running the `automated integration`_ tests +- running an *ad hoc* `manual integration`_ test + +.. _automated unit: + +Running automated unit tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you are working in a file ``foo.py``, there should also be a file ``foo_test.py`` +either in the same directory as ``foo.py`` or in the ``tests`` subdirectory +(if there isn't, make one). While you are working on your code and tests, run +``python foo_test.py`` to run the relevant tests. + +For debugging, we recommend putting +``import ipdb; ipdb.set_trace()`` statements inside the source code. + +Once you are done with your code changes, and the tests in ``foo_test.py`` pass, +run all of the unittests for Certbot with ``tox -e py27`` (this uses Python +2.7). + +Once all the unittests pass, check for sufficient test coverage using ``tox -e +py27-cover``, and then check for code style with ``tox -e lint`` (all files) or +``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). + +Once all of the above is successful, you may run the full test suite using +``tox --skip-missing-interpreters``. We recommend running the commands above +first, because running all tests like this is very slow, and the large amount +of output can make it hard to find specific failures when they happen. + +.. warning:: The full test suite may attempt to modify your system's Apache + config if your user has sudo permissions, so it should not be run on a + production Apache server. + +.. _automated integration: + +Running automated integration tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Generally it is sufficient to open a pull request and let Github and Travis run +integration tests for you. However, you may want to run them locally before submitting +your pull request. You need Docker and docker-compose installed and working. + +The tox environment `integration` will setup `Pebble`_, the Let's Encrypt ACME CA server +for integration testing, then launch the Certbot integration tests. + +With a user allowed to access your local Docker daemon, run: + +.. code-block:: shell + + tox -e integration + +Tests will be run using pytest. A test report and a code coverage report will be +displayed at the end of the integration tests execution. + +.. _Pebble: https://github.com/letsencrypt/pebble + +.. _manual integration: + +Running manual integration tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also manually execute Certbot against a local instance of the `Pebble`_ ACME server. +This is useful to verify that the modifications done to the code makes Certbot behave as expected. + +To do so you need: + +- Docker installed, and a user with access to the Docker client, +- an available `local copy`_ of Certbot. + +The virtual environment set up with `python tools/venv.py` contains two commands +that can be used once the virtual environment is activated: + +.. code-block:: shell + + run_acme_server + +- Starts a local instance of Pebble and runs in the foreground printing its logs. +- Press CTRL+C to stop this instance. +- This instance is configured to validate challenges against certbot executed locally. + +.. code-block:: shell + + certbot_test [ARGS...] + +- Execute certbot with the provided arguments and other arguments useful for testing purposes, + such as: verbose output, full tracebacks in case Certbot crashes, *etc.* +- Execution is preconfigured to interact with the Pebble CA started with ``run_acme_server``. +- Any arguments can be passed as they would be to Certbot (eg. ``certbot_test certonly -d test.example.com``). + +Here is a typical workflow to verify that Certbot successfully issued a certificate +using an HTTP-01 challenge on a machine with Python 3: + +.. code-block:: shell + + python tools/venv3.py + source venv3/bin/activate + run_acme_server & + certbot_test certonly --standalone -d test.example.com + # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C + +Code components and layout +========================== + +The following components of the Certbot repository are distributed to users: + +acme + contains all protocol specific code +certbot + main client code +certbot-apache and certbot-nginx + client code to configure specific web servers +certbot-dns-* + client code to configure DNS providers +certbot-auto and letsencrypt-auto + shell scripts to install Certbot and its dependencies on UNIX systems +windows installer + Installs Certbot on Windows and is built using the files in windows-installer/ + +Plugin-architecture +------------------- + +Certbot has a plugin architecture to facilitate support for +different webservers, other TLS servers, and operating systems. +The interfaces available for plugins to implement are defined in +`interfaces.py`_ and `plugins/common.py`_. + +The main two plugin interfaces are `~certbot.interfaces.IAuthenticator`, which +implements various ways of proving domain control to a certificate authority, +and `~certbot.interfaces.IInstaller`, which configures a server to use a +certificate once it is issued. Some plugins, like the built-in Apache and Nginx +plugins, implement both interfaces and perform both tasks. Others, like the +built-in Standalone authenticator, implement just one interface. + +There are also `~certbot.interfaces.IDisplay` plugins, +which can change how prompts are displayed to a user. + +.. _interfaces.py: https://github.com/certbot/certbot/blob/master/certbot/interfaces.py +.. _plugins/common.py: https://github.com/certbot/certbot/blob/master/certbot/plugins/common.py#L34 + + +Authenticators +-------------- + +Authenticators are plugins that prove control of a domain name by solving a +challenge provided by the ACME server. ACME currently defines several types of +challenges: HTTP, TLS-ALPN, 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 +`get_chall_pref(domain)` to return a sorted list of challenge types in +preference order. + +An Authenticator must also implement `perform(achalls)`, which "performs" a list +of challenges by, for instance, provisioning a file on an HTTP server, or +setting a TXT record in DNS. Once all challenges have succeeded or failed, +Certbot will call the plugin's `cleanup(achalls)` method to remove any files or +DNS records that were needed only during authentication. + +Installer +--------- + +Installers plugins exist to actually setup the certificate in a server, +possibly tweak the security configuration to make it more correct and secure +(Fix some mixed content problems, turn on HSTS, redirect to HTTPS, etc). +Installer plugins tell the main client about their abilities to do the latter +via the :meth:`~.IInstaller.supported_enhancements` call. We currently +have two Installers in the tree, the `~.ApacheConfigurator`. and the +`~.NginxConfigurator`. External projects have made some progress toward +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 80, but it cannot install certs; a postfix plugin would +be an installer but not an authenticator). + +Installers and Authenticators are kept separate because +it should be possible to use the `~.StandaloneAuthenticator` (it sets +up its own Python server to perform challenges) with a program that +cannot solve challenges itself (Such as MTA installers). + + +Installer Development +--------------------- + +There are a few existing classes that may be beneficial while +developing a new `~certbot.interfaces.IInstaller`. +Installers aimed to reconfigure UNIX servers may use Augeas for +configuration parsing and can inherit from `~.AugeasConfigurator` class +to handle much of the interface. Installers that are unable to use +Augeas may still find the `~.Reverter` class helpful in handling +configuration checkpoints and rollback. + + +.. _dev-plugin: + +Writing your own plugin +~~~~~~~~~~~~~~~~~~~~~~~ + +Certbot client supports dynamic discovery of plugins through the +`setuptools entry points`_ using the `certbot.plugins` group. This +way you can, for example, create a custom implementation of +`~certbot.interfaces.IAuthenticator` or the +`~certbot.interfaces.IInstaller` without having to merge it +with the core upstream source code. An example is provided in +``examples/plugins/`` directory. + +While developing, you can install your plugin into a Certbot development +virtualenv like this: + +.. code-block:: shell + + . venv/bin/activate + pip install -e examples/plugins/ + certbot_test plugins + +Your plugin should show up in the output of the last command. If not, +it was not installed properly. + +Once you've finished your plugin and published it, you can have your +users install it system-wide with `pip install`. Note that this will +only work for users who have Certbot installed from OS packages or via +pip. Users who run `certbot-auto` are currently unable to use third-party +plugins. It's technically possible to install third-party plugins into +the virtualenv used by `certbot-auto`, but they will be wiped away when +`certbot-auto` upgrades. + +.. warning:: Please be aware though that as this client is still in a + developer-preview stage, the API may undergo a few changes. If you + believe the plugin will be beneficial to the community, please + consider submitting a pull request to the repo and we will update + it with any necessary API changes. + +.. _`setuptools entry points`: + http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points + +.. _coding-style: + +Coding style +============ + +Please: + +1. **Be consistent with the rest of the code**. + +2. Read `PEP 8 - Style Guide for Python Code`_. + +3. Follow the `Google Python Style Guide`_, with the exception that we + use `Sphinx-style`_ documentation:: + + def foo(arg): + """Short description. + + :param int arg: Some number. + + :returns: Argument + :rtype: int + + """ + return arg + +4. Remember to use ``pylint``. + +.. _Google Python Style Guide: + https://google.github.io/styleguide/pyguide.html +.. _Sphinx-style: http://sphinx-doc.org/ +.. _PEP 8 - Style Guide for Python Code: + https://www.python.org/dev/peps/pep-0008 + +Use ``certbot.compat.os`` instead of ``os`` +=========================================== + + +Python's standard library ``os`` module lacks full support for several Windows +security features about file permissions (eg. DACLs). However several files +handled by Certbot (eg. private keys) need strongly restricted access +on both Linux and Windows. + +To help with this, the ``certbot.compat.os`` module wraps the standard +``os`` module, and forbids usage of methods that lack support for these Windows +security features. + +As a developer, when working on Certbot or its plugins, you must use ``certbot.compat.os`` +in every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of +``import os``). Otherwise the tests will fail when your PR is submitted. + +.. _type annotations: + +Mypy type annotations +===================== + +Certbot uses the `mypy`_ static type checker. Python 3 natively supports official type annotations, +which can then be tested for consistency using mypy. Python 2 doesn’t, but type annotations can +be `added in comments`_. Mypy does some type checks even without type annotations; we can find +bugs in Certbot even without a fully annotated codebase. + +Certbot supports both Python 2 and 3, so we’re using Python 2-style annotations. + +Zulip wrote a `great guide`_ to using mypy. It’s useful, but you don’t have to read the whole thing +to start contributing to Certbot. + +To run mypy on Certbot, use ``tox -e mypy`` on a machine that has Python 3 installed. + +Note that instead of just importing ``typing``, due to packaging issues, in Certbot we import from +``acme.magic_typing`` and have to add some comments for pylint like this: + +.. code-block:: python + + from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +Also note that OpenSSL, which we rely on, has type definitions for crypto but not SSL. We use both. +Those imports should look like this: + +.. code-block:: python + + from OpenSSL import crypto + from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 + +.. _mypy: https://mypy.readthedocs.io +.. _added in comments: https://mypy.readthedocs.io/en/latest/cheat_sheet.html +.. _great guide: https://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/ + +Submitting a pull request +========================= + +Steps: + +1. Write your code! When doing this, you should add :ref:`mypy type annotations + ` for any functions you add or modify. You can check that + you've done this correctly by running ``tox -e mypy`` on a machine that has + Python 3 installed. +2. Make sure your environment is set up properly and that you're in your + virtualenv. You can do this by following the instructions in the + :ref:`Getting Started ` section. +3. Run ``tox -e lint`` to check for pylint errors. Fix any errors. +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. 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 `_ 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. + +.. _ask for help: + +Asking for help +=============== + +If you have any questions while working on a Certbot issue, don't hesitate to +ask for help! You can do this in the Certbot channel in EFF's Mattermost +instance for its open source projects as described below. + +You can get involved with several of EFF's software projects such as Certbot at +the `EFF Open Source Contributor Chat Platform +`_. +By signing up for the EFF Open Source Contributor Chat Platform, you consent to +share your personal information with the Electronic Frontier Foundation, which +is the operator and data controller for this platform. The channels will be +available both to EFF, and to other users of EFFOSCCP, who may use or disclose +information in these channels outside of EFFOSCCP. EFF will use your +information, according to the `Privacy Policy `_, +to further the mission of EFF, including hosting and moderating the discussions +on this platform. + +Use of EFFOSCCP is subject to the `EFF Code of Conduct +`_. When investigating an alleged Code of +Conduct violation, EFF may review discussion channels or direct messages. + +Updating certbot-auto and letsencrypt-auto +========================================== + +.. note:: We are currently only accepting changes to certbot-auto that fix + regressions on platforms where certbot-auto is the recommended installation + method at https://certbot.eff.org/instructions. If you are unsure if a change + you want to make qualifies, don't hesitate to `ask for help`_! + +Updating the scripts +-------------------- +Developers should *not* modify the ``certbot-auto`` and ``letsencrypt-auto`` files +in the root directory of the repository. Rather, modify the +``letsencrypt-auto.template`` and associated platform-specific shell scripts in +the ``letsencrypt-auto-source`` and +``letsencrypt-auto-source/pieces/bootstrappers`` directory, respectively. + +Building letsencrypt-auto-source/letsencrypt-auto +------------------------------------------------- +Once changes to any of the aforementioned files have been made, the +``letsencrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of +manually updating this script, run the build script, which lives at +``letsencrypt-auto-source/build.py``: + +.. code-block:: shell + + python letsencrypt-auto-source/build.py + +Running ``build.py`` will update the ``letsencrypt-auto-source/letsencrypt-auto`` +script. Note that the ``certbot-auto`` and ``letsencrypt-auto`` scripts in the root +directory of the repository will remain **unchanged** after this script is run. +Your changes will be propagated to these files during the next release of +Certbot. + +Opening a PR +------------ +When opening a PR, ensure that the following files are committed: + +1. ``letsencrypt-auto-source/letsencrypt-auto.template`` and + ``letsencrypt-auto-source/pieces/bootstrappers/*`` +2. ``letsencrypt-auto-source/letsencrypt-auto`` (generated by ``build.py``) + +It might also be a good idea to double check that **no** changes were +inadvertently made to the ``certbot-auto`` or ``letsencrypt-auto`` scripts in the +root of the repository. These scripts will be updated by the core developers +during the next release. + + +Updating the documentation +========================== + +In order to generate the Sphinx documentation, run the following +commands: + +.. code-block:: shell + + make -C docs clean html man + +This should generate documentation in the ``docs/_build/html`` +directory. + +.. note:: If you skipped the "Getting Started" instructions above, + run ``pip install -e "certbot[docs]"`` to install Certbot's docs extras modules. + + +.. _docker-dev: + +Running the client with Docker +============================== + +You can use Docker Compose to quickly set up an environment for running and +testing Certbot. To install Docker Compose, follow the instructions at +https://docs.docker.com/compose/install/. + +.. note:: Linux users can simply run ``pip install docker-compose`` to get + Docker Compose after installing Docker Engine and activating your shell as + described in the :ref:`Getting Started ` section. + +Now you can develop on your host machine, but run Certbot and test your changes +in Docker. When using ``docker-compose`` make sure you are inside your clone of +the Certbot repository. As an example, you can run the following command to +check for linting errors:: + + docker-compose run --rm --service-ports development bash -c 'tox -e lint' + +You can also leave a terminal open running a shell in the Docker container and +modify Certbot code in another window. The Certbot repo on your host machine is +mounted inside of the container so any changes you make immediately take +effect. To do this, run:: + + docker-compose run --rm --service-ports development bash + +Now running the check for linting errors described above is as easy as:: + + tox -e lint + +.. _prerequisites: + +Notes on OS dependencies +======================== + +OS-level dependencies can be installed like so: + +.. code-block:: shell + + ./certbot-auto --debug --os-packages-only + +In general... + +* ``sudo`` is required as a suggested way of running privileged process +* `Python`_ 2.7 or 3.4+ is required +* `Augeas`_ is required for the Python bindings +* ``virtualenv`` is used for managing other Python library dependencies + +.. _Python: https://wiki.python.org/moin/BeginnersGuide/Download +.. _Augeas: http://augeas.net/ +.. _Virtualenv: https://virtualenv.pypa.io + + +FreeBSD +------- + +FreeBSD by default uses ``tcsh``. In order to activate virtualenv (see +above), you will need a compatible shell, e.g. ``pkg install bash && +bash``. diff --git a/certbot/docs/index.rst b/certbot/docs/index.rst new file mode 100644 index 000000000..17cde1adf --- /dev/null +++ b/certbot/docs/index.rst @@ -0,0 +1,26 @@ +Welcome to the Certbot documentation! +================================================== + +.. toctree:: + :maxdepth: 2 + + intro + what + install + using + contributing + packaging + resources + +.. toctree:: + :maxdepth: 1 + + api + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/certbot/docs/install.rst b/certbot/docs/install.rst new file mode 100644 index 000000000..42d46c33e --- /dev/null +++ b/certbot/docs/install.rst @@ -0,0 +1,336 @@ +===================== +Get Certbot +===================== + +.. contents:: Table of Contents + :local: + + +About Certbot +============= + +*Certbot is meant to be run directly on a web server*, normally by a system administrator. In most cases, running Certbot on your personal computer is not a useful option. The instructions below relate to installing and running Certbot on a server. + +System administrators can use Certbot directly to request certificates; they should *not* allow unprivileged users to run arbitrary Certbot commands as ``root``, because Certbot allows its user to specify arbitrary file locations and run arbitrary scripts. + +Certbot is packaged for many common operating systems and web servers. Check whether +``certbot`` (or ``letsencrypt``) is packaged for your web server's OS by visiting +certbot.eff.org_, where you will also find the correct installation instructions for +your system. + +.. Note:: Unless you have very specific requirements, we kindly suggest that you use the Certbot packages provided by your package manager (see certbot.eff.org_). If such packages are not available, we recommend using ``certbot-auto``, which automates the process of installing Certbot on your system. + +.. _certbot.eff.org: https://certbot.eff.org + + +.. _system_requirements: + +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 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 +client as root, either `letsencrypt-nosudo +`_ or `simp_le +`_ are more appropriate choices. + +The Apache plugin currently requires an OS with augeas version 1.0; currently `it +supports +`_ +modern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin. + + +Additional integrity verification of certbot-auto script can be done by verifying its digital signature. +This requires a local installation of gpg2, which comes packaged in many Linux distributions under name gnupg or gnupg2. + + +Installing with ``certbot-auto`` requires 512MB of RAM in order to build some +of the dependencies. Installing from pre-built OS packages avoids this +requirement. You can also temporarily set a swap file. See "Problems with +Python virtual environment" below for details. + + +Alternate installation methods +================================ + +If you are offline or your operating system doesn't provide a package, you can use +an alternate method for installing ``certbot``. + +.. _certbot-auto: + +Certbot-Auto +------------ + +The ``certbot-auto`` wrapper script installs Certbot, obtaining some dependencies +from your web server OS and putting others in a python virtual environment. You can +download and run it as follows:: + + user@webserver:~$ wget https://dl.eff.org/certbot-auto + user@webserver:~$ sudo mv certbot-auto /usr/local/bin/certbot-auto + user@webserver:~$ sudo chown root /usr/local/bin/certbot-auto + user@webserver:~$ chmod 0755 /usr/local/bin/certbot-auto + user@webserver:~$ /usr/local/bin/certbot-auto --help + +To check the integrity of the ``certbot-auto`` script, +you can use these steps:: + + + user@webserver:~$ wget -N https://dl.eff.org/certbot-auto.asc + user@webserver:~$ gpg2 --keyserver pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2 + user@webserver:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto + + + +The output of the last command should look something like:: + + + gpg: Signature made Wed 02 May 2018 05:29:12 AM IST + gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2 + gpg: key 4D17C995CD9775F2 marked as ultimately trusted + gpg: checking the trustdb + gpg: marginals needed: 3 completes needed: 1 trust model: pgp + gpg: depth: 0 valid: 2 signed: 2 trust: 0-, 0q, 0n, 0m, 0f, 2u + gpg: depth: 1 valid: 2 signed: 0 trust: 2-, 0q, 0n, 0m, 0f, 0u + gpg: next trustdb check due at 2027-11-22 + gpg: Good signature from "Let's Encrypt Client Team " [ultimate] + + + +The ``certbot-auto`` command updates to the latest client release automatically. +Since ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly +the same command line flags and arguments. For more information, see +`Certbot command-line options `_. + +For full command line help, you can type:: + + /usr/local/bin/certbot-auto --help all + +Problems with Python virtual environment +---------------------------------------- + +On a low memory system such as VPS with less than 512MB of RAM, the required dependencies of Certbot will fail to build. +This can be identified if the pip outputs contains something like ``internal compiler error: Killed (program cc1)``. +You can workaround this restriction by creating a temporary swapfile:: + + user@webserver:~$ sudo fallocate -l 1G /tmp/swapfile + user@webserver:~$ sudo chmod 600 /tmp/swapfile + user@webserver:~$ sudo mkswap /tmp/swapfile + user@webserver:~$ sudo swapon /tmp/swapfile + +Disable and remove the swapfile once the virtual environment is constructed:: + + user@webserver:~$ sudo swapoff /tmp/swapfile + user@webserver:~$ sudo rm /tmp/swapfile + +.. _docker-user: + +Running with Docker +------------------- + +Docker_ is an amazingly simple and quick way to obtain a +certificate. However, this mode of operation is unable to install +certificates or configure your webserver, because our installer +plugins cannot reach your webserver from inside the Docker container. + +Most users should use the operating system packages (see instructions at +certbot.eff.org_) or, as a fallback, ``certbot-auto``. You should only +use Docker if you are sure you know what you are doing and have a +good reason to do so. + +You should definitely read the :ref:`where-certs` section, in order to +know how to manage the certs +manually. `Our ciphersuites page `__ +provides some information about recommended ciphersuites. If none of +these make much sense to you, you should definitely use the +certbot-auto_ method, which enables you to use installer plugins +that cover both of those hard topics. + +If you're still not convinced and have decided to use this method, from +the server that the domain you're requesting a certficate for resolves +to, `install Docker`_, then issue a command like the one found below. If +you are using Certbot with the :ref:`Standalone` plugin, you will need +to make the port it uses accessible from outside of the container by +including something like ``-p 80:80`` or ``-p 443:443`` on the command +line before ``certbot/certbot``. + +.. code-block:: shell + + sudo docker run -it --rm --name certbot \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + certbot/certbot certonly + +Running Certbot with the ``certonly`` command will obtain a certificate and place it in the directory +``/etc/letsencrypt/live`` on your system. Because Certonly cannot install the certificate from +within Docker, you must install the certificate manually according to the procedure +recommended by the provider of your webserver. + +There are also Docker images for each of Certbot's DNS plugins available +at https://hub.docker.com/u/certbot which automate doing domain +validation over DNS for popular providers. To use one, just replace +``certbot/certbot`` in the command above with the name of the image you +want to use. For example, to use Certbot's plugin for Amazon Route 53, +you'd use ``certbot/dns-route53``. You may also need to add flags to +Certbot and/or mount additional directories to provide access to your +DNS API credentials as specified in the :ref:`DNS plugin documentation +`. If you would like to obtain a wildcard certificate from +Let's Encrypt's ACMEv2 server, you'll need to include ``--server +https://acme-v02.api.letsencrypt.org/directory`` on the command line as +well. + +For more information about the layout +of the ``/etc/letsencrypt`` directory, see :ref:`where-certs`. + +.. _Docker: https://docker.com +.. _`install Docker`: https://docs.docker.com/engine/installation/ + +Operating System Packages +------------------------- + +**Arch Linux** + +.. code-block:: shell + + sudo pacman -S certbot + +**Debian** + +If you run Debian Buster or Debian testing/Sid, you can easily install certbot +packages through commands like: + +.. code-block:: shell + + sudo apt-get update + sudo apt-get install certbot + +If you run Debian Stretch, we recommend you use the packages in Debian +backports repository. First you'll have to follow the instructions at +https://backports.debian.org/Instructions/ to enable the Stretch backports repo, +if you have not already done so. Then run: + +.. code-block:: shell + + sudo apt-get install certbot -t stretch-backports + +In all of these cases, there also packages available to help Certbot integrate +with Apache, nginx, or various DNS services. If you are using Apache or nginx, +we strongly recommend that you install the ``python-certbot-apache`` or +``python-certbot-nginx`` package so that Certbot can fully automate HTTPS +configuration for your server. A full list of these packages can be found +through a command like: + +.. code-block:: shell + + apt search 'python-certbot*' + +They can be installed by running the same installation command above but +replacing ``certbot`` with the name of the desired package. + +There are no Certbot packages available for Debian Jessie and Jessie users +should instead use certbot-auto_. + +**Ubuntu** + +If you run Ubuntu Trusty, Xenial, or Bionic, certbot is available through the official PPA, +that can be installed as followed: + +.. code-block:: shell + + sudo apt-get update + sudo apt-get install software-properties-common + sudo add-apt-repository universe + sudo add-apt-repository ppa:certbot/certbot + sudo apt-get update + +Then, certbot can be installed using: + +.. code-block:: shell + + sudo apt-get install certbot + +Optionally to install the Certbot Apache plugin, you can use: + +.. code-block:: shell + + sudo apt-get install python-certbot-apache + +**Fedora** + +.. code-block:: shell + + sudo dnf install certbot python2-certbot-apache + +**FreeBSD** + + * Port: ``cd /usr/ports/security/py-certbot && make install clean`` + * Package: ``pkg install py27-certbot`` + +**Gentoo** + +The official Certbot client is available in Gentoo Portage. If you +want to use the Apache plugin, it has to be installed separately: + +.. code-block:: shell + + emerge -av app-crypt/certbot + emerge -av app-crypt/certbot-apache + +When using the Apache plugin, you will run into a "cannot find an +SSLCertificateFile directive" or "cannot find an SSLCertificateKeyFile +directive for certificate" error if you're sporting the default Gentoo +``httpd.conf``. You can fix this by commenting out two lines in +``/etc/apache2/httpd.conf`` as follows: + +Change + +.. code-block:: shell + + + LoadModule ssl_module modules/mod_ssl.so + + +to + +.. code-block:: shell + + # + LoadModule ssl_module modules/mod_ssl.so + # + +For the time being, this is the only way for the Apache plugin to recognise +the appropriate directives when installing the certificate. +Note: this change is not required for the other plugins. + +**NetBSD** + + * Build from source: ``cd /usr/pkgsrc/security/py-certbot && make install clean`` + * Install pre-compiled package: ``pkg_add py27-certbot`` + +**OpenBSD** + + * Port: ``cd /usr/ports/security/letsencrypt/client && make install clean`` + * Package: ``pkg_add letsencrypt`` + +**Other Operating Systems** + +OS packaging is an ongoing effort. If you'd like to package +Certbot for your distribution of choice please have a +look at the :doc:`packaging`. + +Installing from source +---------------------- + +Installation from source is only supported for developers and the +whole process is described in the :doc:`contributing`. + +.. warning:: Please do **not** use ``python certbot/setup.py install``, ``python pip + install certbot``, or ``easy_install certbot``. Please do **not** attempt the + installation commands as superuser/root and/or without virtual environment, + e.g. ``sudo python certbot/setup.py install``, ``sudo pip install``, ``sudo + ./venv/bin/...``. These modes of operation might corrupt your operating + system and are **not supported** by the Certbot team! diff --git a/certbot/docs/intro.rst b/certbot/docs/intro.rst new file mode 100644 index 000000000..2d4abdc2d --- /dev/null +++ b/certbot/docs/intro.rst @@ -0,0 +1,10 @@ +===================== +Introduction +===================== + +.. note:: + To get started quickly, use the `interactive installation guide `_. + +.. include:: ../README.rst + :start-after: tag:intro-begin + :end-before: tag:intro-end diff --git a/certbot/docs/make.bat b/certbot/docs/make.bat new file mode 100644 index 000000000..198e864c3 --- /dev/null +++ b/certbot/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LetsEncrypt.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LetsEncrypt.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/certbot/docs/man/certbot.rst b/certbot/docs/man/certbot.rst new file mode 100644 index 000000000..d03f3eed4 --- /dev/null +++ b/certbot/docs/man/certbot.rst @@ -0,0 +1 @@ +.. literalinclude:: ../cli-help.txt diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst new file mode 100644 index 000000000..7b0b1d41a --- /dev/null +++ b/certbot/docs/packaging.rst @@ -0,0 +1,49 @@ +=============== +Packaging Guide +=============== + +Releases +======== + +We release packages and upload them to PyPI (wheels and source tarballs). + +- https://pypi.python.org/pypi/acme +- https://pypi.python.org/pypi/certbot +- https://pypi.python.org/pypi/certbot-apache +- https://pypi.python.org/pypi/certbot-nginx +- https://pypi.python.org/pypi/certbot-dns-cloudflare +- https://pypi.python.org/pypi/certbot-dns-cloudxns +- https://pypi.python.org/pypi/certbot-dns-digitalocean +- https://pypi.python.org/pypi/certbot-dns-dnsimple +- https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy +- https://pypi.python.org/pypi/certbot-dns-google +- https://pypi.python.org/pypi/certbot-dns-linode +- https://pypi.python.org/pypi/certbot-dns-luadns +- https://pypi.python.org/pypi/certbot-dns-nsone +- https://pypi.python.org/pypi/certbot-dns-ovh +- https://pypi.python.org/pypi/certbot-dns-rfc2136 +- https://pypi.python.org/pypi/certbot-dns-route53 + +The following scripts are used in the process: + +- https://github.com/certbot/certbot/blob/master/tools/release.sh + +We use git tags to identify releases, using `Semantic Versioning`_. For +example: `v0.11.1`. + +.. _`Semantic Versioning`: http://semver.org/ + +Notes for package maintainers +============================= + +0. Please use our tagged releases, not ``master``! + +1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally. + +2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins. + +3. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. + +4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. + +5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. diff --git a/certbot/docs/resources.rst b/certbot/docs/resources.rst new file mode 100644 index 000000000..459d8a829 --- /dev/null +++ b/certbot/docs/resources.rst @@ -0,0 +1,7 @@ +===================== +Resources +===================== + +.. include:: ../README.rst + :start-after: tag:links-begin + :end-before: tag:links-end diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst new file mode 100644 index 000000000..a67d28c89 --- /dev/null +++ b/certbot/docs/using.rst @@ -0,0 +1,990 @@ +========== +User Guide +========== + +.. contents:: Table of Contents + :local: + +Certbot Commands +================ + +Certbot uses a number of different commands (also referred +to as "subcommands") to request specific actions such as +obtaining, renewing, or revoking certificates. The most important +and commonly-used commands will be discussed throughout this +document; an exhaustive list also appears near the end of the document. + +The ``certbot`` script on your web server might be named ``letsencrypt`` if your system uses an older package, or ``certbot-auto`` if you used an alternate installation method. Throughout the docs, whenever you see ``certbot``, swap in the correct name as needed. + +.. _plugins: + +Getting certificates (and choosing plugins) +=========================================== + +The Certbot client supports two types of plugins for +obtaining and installing certificates: authenticators and installers. + +Authenticators are plugins used with the ``certonly`` command to obtain a certificate. +The authenticator validates that you +control the domain(s) you are requesting a certificate for, obtains a certificate for the specified +domain(s), and places the certificate in the ``/etc/letsencrypt`` directory on your +machine. The authenticator does not install the certificate (it does not edit any of your server's configuration files to serve the +obtained certificate). If you specify multiple domains to authenticate, they will +all be listed in a single certificate. To obtain multiple separate certificates +you will need to run Certbot multiple times. + +Installers are Plugins used with the ``install`` command to install a certificate. +These plugins can modify your webserver's configuration to +serve your website over HTTPS using certificates obtained by certbot. + +Plugins that do both can be used with the ``certbot run`` command, which is the default +when no command is specified. The ``run`` subcommand can also be used to specify +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. http-01_ (80) +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. +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) + | modifying DNS records to prove you have control over a + | 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) 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 ` + +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) +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``. + +There are also many third-party-plugins_ available. Below we describe in more detail +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 +.. _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 + +Apache +------ + +The Apache plugin currently `supports +`_ +modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. +This automates both obtaining *and* installing certificates on an Apache +webserver. To specify this plugin on the command line, simply include +``--apache``. + +Webroot +------- + +If you're running a local webserver for which you have the ability +to modify the content being served, and you'd prefer not to stop the +webserver during the certificate issuance process, you can use the webroot +plugin to obtain a certificate by including ``certonly`` and ``--webroot`` on +the command line. In addition, you'll need to specify ``--webroot-path`` +or ``-w`` with the top-level directory ("web root") containing the files +served by your webserver. For example, ``--webroot-path /var/www/html`` +or ``--webroot-path /usr/share/nginx/html`` are two common webroot paths. + +If you're getting a certificate for many domains at once, the plugin +needs to know where each domain's files are served from, which could +potentially be a separate directory for each domain. When requesting a +certificate for multiple domains, each domain will use the most recently +specified ``--webroot-path``. So, for instance, + +:: + + certbot certonly --webroot -w /var/www/example -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net + +would obtain a single certificate for all of those names, using the +``/var/www/example`` webroot directory for the first two, and +``/var/www/other`` for the second two. + +The webroot plugin works by creating a temporary file for each of your requested +domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's Encrypt +validation server makes HTTP requests to validate that the DNS for each +requested domain resolves to the server running certbot. An example request +made to your web server would look like: + +:: + + 66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" + +Note that to use the webroot plugin, your server must be configured to serve +files from hidden directories. If ``/.well-known`` is treated specially by +your webserver configuration, you might need to modify the configuration +to ensure that files inside ``/.well-known/acme-challenge`` are served by +the webserver. + +Nginx +----- + +The Nginx plugin should work for most configurations. We recommend backing up +Nginx configurations before using it (though you can also revert changes to +configurations with ``certbot --nginx rollback``). You can use it by providing +the ``--nginx`` flag on the commandline. + +:: + + certbot --nginx + +.. _standalone: + +Standalone +---------- + +Use standalone mode to obtain a certificate if you don't want to use (or don't currently have) +existing server software. The standalone plugin does not rely on any other server +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 in +order to perform domain validation, so you may need to stop your +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. + +By default, Certbot first attempts to bind to the port for all interfaces using +IPv6 and then bind to that port using IPv4; Certbot continues so long as at +least one bind succeeds. On most Linux systems, IPv4 traffic will be routed to +the bound IPv6 port and the failure during the second bind is expected. + +Use ``---address`` to explicitly tell Certbot which interface +(and protocol) to bind. + +.. _dns_plugins: + +DNS Plugins +----------- + +If you'd like to obtain a wildcard certificate from Let's Encrypt or run +``certbot`` on a machine other than your target webserver, you can use one of +Certbot's DNS plugins. + +These plugins are not included in a default Certbot installation and must be +installed separately. While the DNS plugins cannot currently be used with +``certbot-auto``, they are available in many OS package managers and as Docker +images. Visit https://certbot.eff.org to learn the best way to use the DNS +plugins on your system. + +Once installed, you can find documentation on how to use each plugin at: + +* `certbot-dns-cloudflare `_ +* `certbot-dns-cloudxns `_ +* `certbot-dns-digitalocean `_ +* `certbot-dns-dnsimple `_ +* `certbot-dns-dnsmadeeasy `_ +* `certbot-dns-google `_ +* `certbot-dns-linode `_ +* `certbot-dns-luadns `_ +* `certbot-dns-nsone `_ +* `certbot-dns-ovh `_ +* `certbot-dns-rfc2136 `_ +* `certbot-dns-route53 `_ + +Manual +------ + +If you'd like to obtain a certificate running ``certbot`` on a machine +other than your target webserver or perform the steps for domain +validation yourself, you can use the manual plugin. While hidden from +the UI, you can use the plugin to obtain a certificate by specifying +``certonly`` and ``--manual`` on the command line. This requires you +to copy and paste commands into another terminal session, which may +be on a different computer. + +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 +specific content in the ``/.well-known/acme-challenge/`` directory directly +in the top-level directory (“web rootâ€) containing the files served by your +webserver. In essence it's the same as the webroot_ plugin, but not automated. + +When using the ``dns`` challenge, ``certbot`` will ask you to place a TXT DNS +record with specific contents under the domain name consisting of the hostname +for which you want a certificate issued, prepended by ``_acme-challenge``. + +For example, for the domain ``example.com``, a zone file entry would look like: + +:: + + _acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM" + + +Additionally you can specify scripts to prepare for validation and +perform the authentication procedure and/or clean up after it by using +the ``--manual-auth-hook`` and ``--manual-cleanup-hook`` flags. This is +described in more depth in the hooks_ section. + +.. _combination: + +Combining plugins +----------------- + +Sometimes you may want to specify a combination of distinct authenticator and +installer plugins. To do so, specify the authenticator plugin with +``--authenticator`` or ``-a`` and the installer plugin with ``--installer`` or +``-i``. + +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 +------------------- + +There are also a number of third-party plugins for the client, provided by +other developers. Many are beta/experimental, but some are already in +widespread use: + +================== ==== ==== =============================================================== +Plugin Auth Inst Notes +================== ==== ==== =============================================================== +haproxy_ Y Y Integration with the HAProxy load balancer +s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets +gandi_ Y N Obtain certificates via the Gandi LiveDNS API +varnish_ Y N Obtain certificates via a Varnish server +external_ Y N A plugin for convenient scripting (See also ticket 2782_) +pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers +proxmox_ N Y Install certificates in Proxmox Virtualization servers +dns-standalone_ Y N Obtain certificates via an integrated DNS server +dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server +================== ==== ==== =============================================================== + +.. _haproxy: https://github.com/greenhost/certbot-haproxy +.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front +.. _gandi: https://github.com/obynio/certbot-plugin-gandi +.. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin +.. _2782: https://github.com/certbot/certbot/issues/2782 +.. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl +.. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox +.. _external: https://github.com/marcan/letsencrypt-external +.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone +.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig + +If you're interested, you can also :ref:`write your own plugin `. + +.. _managing-certs: + +Managing certificates +===================== + +To view a list of the certificates Certbot knows about, run +the ``certificates`` subcommand: + +``certbot certificates`` + +This returns information in the following format:: + + Found the following certs: + Certificate Name: example.com + Domains: example.com, www.example.com + Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days) + Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem + Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem + +``Certificate Name`` shows the name of the certificate. Pass this name +using the ``--cert-name`` flag to specify a particular certificate for the ``run``, +``certonly``, ``certificates``, ``renew``, and ``delete`` commands. Example:: + + certbot certonly --cert-name example.com + +.. _updating_certs: + +Re-creating and Updating Existing Certificates +---------------------------------------------- + +You can use ``certonly`` or ``run`` subcommands to request +the creation of a single new certificate even if you already have an +existing certificate with some of the same domain names. + +If a certificate is requested with ``run`` or ``certonly`` specifying a +certificate name that already exists, Certbot updates +the existing certificate. Otherwise a new certificate +is created and assigned the specified name. + +The ``--force-renewal``, ``--duplicate``, and ``--expand`` options +control Certbot's behavior when re-creating +a certificate with the same name as an existing certificate. +If you don't specify a requested behavior, Certbot may ask you what you intended. + + +``--force-renewal`` tells Certbot to request a new certificate +with the same domains as an existing certificate. Each domain +must be explicitly specified via ``-d``. If successful, this certificate +is saved alongside the earlier one and symbolic links (the "``live``" +reference) will be updated to point to the new certificate. This is a +valid method of renewing a specific individual +certificate. + +``--duplicate`` tells Certbot to create a separate, unrelated certificate +with the same domains as an existing certificate. This certificate is +saved completely separately from the prior one. Most users will not +need to issue this command in normal circumstances. + +``--expand`` tells Certbot to update an existing certificate with a new +certificate that contains all of the old domains and one or more additional +new domains. With the ``--expand`` option, use the ``-d`` option to specify +all existing domains and one or more new domains. + +Example: + +.. code-block:: none + + certbot --expand -d existing.com,example.com,newdomain.com + +If you prefer, you can specify the domains individually like this: + +.. code-block:: none + + certbot --expand -d existing.com -d example.com -d newdomain.com + +Consider using ``--cert-name`` instead of ``--expand``, as it gives more control +over which certificate is modified and it lets you remove domains as well as adding them. + + +``--allow-subset-of-names`` tells Certbot to continue with certificate generation if +only some of the specified domain authorizations can be obtained. This may +be useful if some domains specified in a certificate no longer point at this +system. + +Whenever you obtain a new certificate in any of these ways, the new +certificate exists alongside any previously obtained certificates, whether +or not the previous certificates have expired. The generation of a new +certificate counts against several rate limits that are intended to prevent +abuse of the ACME protocol, as described +`here `__. + +.. _changing: + +Changing a Certificate's Domains +================================ + +The ``--cert-name`` flag can also be used to modify the domains a certificate contains, +by specifying new domains using the ``-d`` or ``--domains`` flag. If certificate ``example.com`` +previously contained ``example.com`` and ``www.example.com``, it can be modified to only +contain ``example.com`` by specifying only ``example.com`` with the ``-d`` or ``--domains`` flag. Example:: + + certbot certonly --cert-name example.com -d example.com + +The same format can be used to expand the set of domains a certificate contains, or to +replace that set entirely:: + + certbot certonly --cert-name example.com -d example.org,www.example.org + + +Revoking certificates +--------------------- + +If your account key has been compromised or you otherwise need to revoke a certificate, +use the ``revoke`` command to do so. Note that the ``revoke`` command takes the certificate path +(ending in ``cert.pem``), not a certificate name or domain. Example:: + + certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem + +You can also specify the reason for revoking your certificate by using the ``reason`` flag. +Reasons include ``unspecified`` which is the default, as well as ``keycompromise``, +``affiliationchanged``, ``superseded``, and ``cessationofoperation``:: + + certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem --reason keycompromise + +Additionally, if a certificate +is a test certificate obtained via the ``--staging`` or ``--test-cert`` flag, that flag must be passed to the +``revoke`` subcommand. +Once a certificate is revoked (or for other certificate management tasks), all of a certificate's +relevant files can be removed from the system with the ``delete`` subcommand:: + + certbot delete --cert-name example.com + +.. note:: If you don't use ``delete`` to remove the certificate completely, it will be renewed automatically at the next renewal event. + +.. note:: Revoking a certificate will have no effect on the rate limit imposed by the Let's Encrypt server. + +.. _renewal: + +Renewing certificates +--------------------- + +.. note:: Let's Encrypt CA issues short-lived certificates (90 + days). Make sure you renew the certificates at least once in 3 + months. + +.. seealso:: Many of the certbot clients obtained through a + distribution come with automatic renewal out of the box, + such as Debian and Ubuntu versions installed through `apt`, + CentOS/RHEL 7 through EPEL, etc. See `Automated Renewals`_ + for more details. + +As of version 0.10.0, Certbot supports a ``renew`` action to check +all installed certificates for impending expiry and attempt to renew +them. The simplest form is simply + +``certbot renew`` + +This command attempts to renew any previously-obtained certificates that +expire in less than 30 days. The same plugin and options that were used +at the time the certificate was originally issued will be used for the +renewal attempt, unless you specify other plugins or options. Unlike ``certonly``, ``renew`` acts on +multiple certificates and always takes into account whether each one is near +expiry. Because of this, ``renew`` is suitable (and designed) for automated use, +to allow your system to automatically renew each certificate when appropriate. +Since ``renew`` only renews certificates that are near expiry it can be +run as frequently as you want - since it will usually take no action. + +The ``renew`` command includes hooks for running commands or scripts before or after a certificate is +renewed. For example, if you have a single certificate obtained using +the standalone_ plugin, you might need to stop the webserver +before renewing so standalone can bind to the necessary ports, and +then restart it after the plugin is finished. Example:: + + certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start" + +If a hook exits with a non-zero exit code, the error will be printed +to ``stderr`` but renewal will be attempted anyway. A failing hook +doesn't directly cause Certbot to exit with a non-zero exit code, but +since Certbot exits with a non-zero exit code when renewals fail, a +failed hook causing renewal failures will indirectly result in a +non-zero exit code. Hooks will only be run if a certificate is due for +renewal, so you can run the above command frequently without +unnecessarily stopping your webserver. + +When Certbot detects that a certificate is due for renewal, ``--pre-hook`` +and ``--post-hook`` hooks run before and after each attempt to renew it. +If you want your hook to run only after a successful renewal, use +``--deploy-hook`` in a command like this. + +``certbot renew --deploy-hook /path/to/deploy-hook-script`` + +For example, if you have a daemon that does not read its certificates as the +root user, a deploy hook like this can copy them to the correct location and +apply appropriate file permissions. + +/path/to/deploy-hook-script + +.. code-block:: none + + #!/bin/sh + + set -e + + for domain in $RENEWED_DOMAINS; do + case $domain in + example.com) + daemon_cert_root=/etc/some-daemon/certs + + # Make sure the certificate and private key files are + # never world readable, even just for an instant while + # we're copying them into daemon_cert_root. + umask 077 + + cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert" + cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key" + + # Apply the proper file ownership and permissions for + # the daemon to read its certificate and key. + chown some-daemon "$daemon_cert_root/$domain.cert" \ + "$daemon_cert_root/$domain.key" + chmod 400 "$daemon_cert_root/$domain.cert" \ + "$daemon_cert_root/$domain.key" + + service some-daemon restart >/dev/null + ;; + esac + done + +You can also specify hooks by placing files in subdirectories of Certbot's +configuration directory. Assuming your configuration directory is +``/etc/letsencrypt``, any executable files found in +``/etc/letsencrypt/renewal-hooks/pre``, +``/etc/letsencrypt/renewal-hooks/deploy``, and +``/etc/letsencrypt/renewal-hooks/post`` will be run as pre, deploy, and post +hooks respectively when any certificate is renewed with the ``renew`` +subcommand. These hooks are run in alphabetical order and are not run for other +subcommands. (The order the hooks are run is determined by the byte value of +the characters in their filenames and is not dependent on your locale.) + +Hooks specified in the command line, :ref:`configuration file +`, or :ref:`renewal configuration files ` are +run as usual after running all hooks in these directories. One minor exception +to this is if a hook specified elsewhere is simply the path to an executable +file in the hook directory of the same type (e.g. your pre-hook is the path to +an executable in ``/etc/letsencrypt/renewal-hooks/pre``), the file is not run a +second time. You can stop Certbot from automatically running executables found +in these directories by including ``--no-directory-hooks`` on the command line. + +More information about hooks can be found by running +``certbot --help renew``. + +If you're sure that this command executes successfully without human +intervention, you can add the command to ``crontab`` (since certificates +are only renewed when they're determined to be near expiry, the command +can run on a regular basis, like every week or every day). In that case, +you are likely to want to use the ``-q`` or ``--quiet`` quiet flag to +silence all output except errors. + +If you are manually renewing all of your certificates, the +``--force-renewal`` flag may be helpful; it causes the expiration time of +the certificate(s) to be ignored when considering renewal, and attempts to +renew each and every installed certificate regardless of its age. (This +form is not appropriate to run daily because each certificate will be +renewed every day, which will quickly run into the certificate authority +rate limit.) + +Note that options provided to ``certbot renew`` will apply to +*every* certificate for which renewal is attempted; for example, +``certbot renew --rsa-key-size 4096`` would try to replace every +near-expiry certificate with an equivalent certificate using a 4096-bit +RSA public key. If a certificate is successfully renewed using +specified options, those options will be saved and used for future +renewals of that certificate. + +An alternative form that provides for more fine-grained control over the +renewal process (while renewing specified certificates one at a time), +is ``certbot certonly`` with the complete set of subject domains of +a specific certificate specified via `-d` flags. You may also want to +include the ``-n`` or ``--noninteractive`` flag to prevent blocking on +user input (which is useful when running the command from cron). + +``certbot certonly -n -d example.com -d www.example.com`` + +All of the domains covered by the certificate must be specified in +this case in order to renew and replace the old certificate rather +than obtaining a new one; don't forget any `www.` domains! Specifying +a subset of the domains creates a new, separate certificate containing +only those domains, rather than replacing the original certificate. +When run with a set of domains corresponding to an existing certificate, +the ``certonly`` command attempts to renew that specific certificate. + +Please note that the CA will send notification emails to the address +you provide if you do not renew certificates that are about to expire. + +Certbot is working hard to improve the renewal process, and we +apologize for any inconvenience you encounter in integrating these +commands into your individual environment. + +.. note:: ``certbot renew`` exit status will only be 1 if a renewal attempt failed. + This means ``certbot renew`` exit status will be 0 if no certificate needs to be updated. + If you write a custom script and expect to run a command only after a certificate was actually renewed + you will need to use the ``--deploy-hook`` since the exit status will be 0 both on successful renewal + and when renewal is not necessary. + +.. _renewal-config-file: + + +Modifying the Renewal Configuration File +---------------------------------------- + +When a certificate is issued, by default Certbot creates a renewal configuration file that +tracks the options that were selected when Certbot was run. This allows Certbot +to use those same options again when it comes time for renewal. These renewal +configuration files are located at ``/etc/letsencrypt/renewal/CERTNAME``. + +For advanced certificate management tasks, it is possible to manually modify the certificate's +renewal configuration file, but this is discouraged since it can easily break Certbot's +ability to renew your certificates. If you choose to modify the renewal configuration file +we advise you to test its validity with the ``certbot renew --dry-run`` command. + +.. warning:: Modifying any files in ``/etc/letsencrypt`` can damage them so Certbot can no longer properly manage its certificates, and we do not recommend doing so. + +For most tasks, it is safest to limit yourself to pointing symlinks at the files there, or using +``--deploy-hook`` to copy / make new files based upon those files, if your operational situation requires it +(for instance, combining certificates and keys in different way, or having copies of things with different +specific permissions that are demanded by other programs). + +If the contents of ``/etc/letsencrypt/archive/CERTNAME`` are moved to a new folder, first specify +the new folder's name in the renewal configuration file, then run ``certbot update_symlinks`` to +point the symlinks in ``/etc/letsencrypt/live/CERTNAME`` to the new folder. + +If you would like the live certificate files whose symlink location Certbot updates on each run to +reside in a different location, first move them to that location, then specify the full path of +each of the four files in the renewal configuration file. Since the symlinks are relative links, +you must follow this with an invocation of ``certbot update_symlinks``. + +For example, say that a certificate's renewal configuration file previously contained the following +directives:: + + archive_dir = /etc/letsencrypt/archive/example.com + cert = /etc/letsencrypt/live/example.com/cert.pem + privkey = /etc/letsencrypt/live/example.com/privkey.pem + chain = /etc/letsencrypt/live/example.com/chain.pem + fullchain = /etc/letsencrypt/live/example.com/fullchain.pem + +The following commands could be used to specify where these files are located:: + + mv /etc/letsencrypt/archive/example.com /home/user/me/certbot/example_archive + sed -i 's,/etc/letsencrypt/archive/example.com,/home/user/me/certbot/example_archive,' /etc/letsencrypt/renewal/example.com.conf + mv /etc/letsencrypt/live/example.com/*.pem /home/user/me/certbot/ + sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf + certbot update_symlinks + +Automated Renewals +------------------ + +Many Linux distributions provide automated renewal when you use the +packages installed through their system package manager. The +following table is an *incomplete* list of distributions which do so, +as well as their methods for doing so. + +If you are not sure whether or not your system has this already +automated, refer to your distribution's documentation, or check your +system's crontab (typically in `/etc/crontab/` and `/etc/cron.*/*` and +systemd timers (`systemctl list-timers`). + +.. csv-table:: Distributions with Automated Renewal + :header: "Distribution Name", "Distribution Version", "Automation Method" + + "CentOS", "EPEL 7", "systemd" + "Debian", "jessie", "cron, systemd" + "Debian", "stretch", "cron, systemd" + "Debian", "testing/sid", "cron, systemd" + "Fedora", "26", "systemd" + "Fedora", "27", "systemd" + "RHEL", "EPEL 7", "systemd" + "Ubuntu", "17.10", "cron, systemd" + "Ubuntu", "certbot PPA", "cron, systemd" + +.. _where-certs: + +Where are my certificates? +========================== + +All generated keys and issued certificates can be found in +``/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. + +.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys`` + contain all previous keys and certificates, while + ``/etc/letsencrypt/live`` symlinks to the latest versions. + +The following files are available: + +``privkey.pem`` + Private key for the certificate. + + .. warning:: This **must be kept secret at all times**! Never share + it with anyone, including Certbot developers. You cannot + 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 + `_, + and Nginx for `ssl_certificate_key + `_. + +``fullchain.pem`` + All certificates, **including** server certificate (aka leaf certificate or + end-entity certificate). The server certificate is the first one in this file, + followed by any intermediates. + + This is what Apache >= 2.4.8 needs for `SSLCertificateFile + `_, + and what Nginx needs for `ssl_certificate + `_. + +``cert.pem`` and ``chain.pem`` (less common) + ``cert.pem`` contains the server certificate by itself, and + ``chain.pem`` contains the additional intermediate certificate or + certificates that web browsers will need in order to validate the + server certificate. If you provide one of these files to your web + server, you **must** provide both of them, or some browsers will show + "This Connection is Untrusted" errors for your site, `some of the time + `_. + + Apache < 2.4.8 needs these for `SSLCertificateFile + `_. + and `SSLCertificateChainFile + `_, + respectively. + + If you're using OCSP stapling with Nginx >= 1.3.7, ``chain.pem`` should be + provided as the `ssl_trusted_certificate + `_ + to validate OCSP responses. + +.. note:: All files are PEM-encoded. + If you need other format, such as DER or PFX, then you + could convert using ``openssl``. You can automate that with + ``--deploy-hook`` if you're using automatic renewal_. + +.. _hooks: + +Pre and Post Validation Hooks +============================= + +Certbot allows for the specification of pre and post validation hooks when run +in manual mode. The flags to specify these scripts are ``--manual-auth-hook`` +and ``--manual-cleanup-hook`` respectively and can be used as follows: + +:: + + certbot certonly --manual --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com + +This will run the ``authenticator.sh`` script, attempt the validation, and then run +the ``cleanup.sh`` script. Additionally certbot will pass relevant environment +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) + +Additionally for cleanup: + +- ``CERTBOT_AUTH_OUTPUT``: Whatever the auth script wrote to stdout + +Example usage for HTTP-01: + +:: + + certbot certonly --manual --preferred-challenges=http --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com + +/path/to/http/authenticator.sh + +.. code-block:: none + + #!/bin/bash + echo $CERTBOT_VALIDATION > /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN + +/path/to/http/cleanup.sh + +.. code-block:: none + + #!/bin/bash + rm -f /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN + +Example usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not use as-is) + +:: + + certbot certonly --manual --preferred-challenges=dns --manual-auth-hook /path/to/dns/authenticator.sh --manual-cleanup-hook /path/to/dns/cleanup.sh -d secure.example.com + +/path/to/dns/authenticator.sh + +.. code-block:: none + + #!/bin/bash + + # Get your API key from https://www.cloudflare.com/a/account/my-account + API_KEY="your-api-key" + EMAIL="your.email@example.com" + + # Strip only the top domain to get the zone id + DOMAIN=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)') + + # Get the Cloudflare zone id + ZONE_EXTRA_PARAMS="status=active&page=1&per_page=20&order=status&direction=desc&match=all" + ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN&$ZONE_EXTRA_PARAMS" \ + -H "X-Auth-Email: $EMAIL" \ + -H "X-Auth-Key: $API_KEY" \ + -H "Content-Type: application/json" | python -c "import sys,json;print(json.load(sys.stdin)['result'][0]['id'])") + + # Create TXT record + CREATE_DOMAIN="_acme-challenge.$CERTBOT_DOMAIN" + RECORD_ID=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ + -H "X-Auth-Email: $EMAIL" \ + -H "X-Auth-Key: $API_KEY" \ + -H "Content-Type: application/json" \ + --data '{"type":"TXT","name":"'"$CREATE_DOMAIN"'","content":"'"$CERTBOT_VALIDATION"'","ttl":120}' \ + | python -c "import sys,json;print(json.load(sys.stdin)['result']['id'])") + # Save info for cleanup + if [ ! -d /tmp/CERTBOT_$CERTBOT_DOMAIN ];then + mkdir -m 0700 /tmp/CERTBOT_$CERTBOT_DOMAIN + fi + echo $ZONE_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID + echo $RECORD_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID + + # Sleep to make sure the change has time to propagate over to DNS + sleep 25 + +/path/to/dns/cleanup.sh + +.. code-block:: none + + #!/bin/bash + + # Get your API key from https://www.cloudflare.com/a/account/my-account + API_KEY="your-api-key" + EMAIL="your.email@example.com" + + if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID ]; then + ZONE_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID) + rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID + fi + + if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID ]; then + RECORD_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID) + rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID + fi + + # Remove the challenge TXT record from the zone + if [ -n "${ZONE_ID}" ]; then + if [ -n "${RECORD_ID}" ]; then + curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ + -H "X-Auth-Email: $EMAIL" \ + -H "X-Auth-Key: $API_KEY" \ + -H "Content-Type: application/json" + fi + fi + +.. _lock-files: + +Changing the ACME Server +======================== + +By default, Certbot uses Let's Encrypt's initial production server at +https://acme-v01.api.letsencrypt.org/. You can tell Certbot to use a +different CA by providing ``--server`` on the command line or in a +:ref:`configuration file ` with the URL of the server's +ACME directory. For example, if you would like to use Let's Encrypt's +new ACMEv2 server, you would add ``--server +https://acme-v02.api.letsencrypt.org/directory`` to the command line. +Certbot will automatically select which version of the ACME protocol to +use based on the contents served at the provided URL. + +If you use ``--server`` to specify an ACME CA that implements a newer +version of the spec, you may be able to obtain a certificate for a +wildcard domain. Some CAs (such as Let's Encrypt) require that domain +validation for wildcard domains must be done through modifications to +DNS records which means that the dns-01_ challenge type must be used. To +see a list of Certbot plugins that support this challenge type and how +to use them, see plugins_. + +Lock Files +========== + +When processing a validation Certbot writes a number of lock files on your system +to prevent multiple instances from overwriting each other's changes. This means +that by default two instances of Certbot will not be able to run in parallel. + +Since the directories used by Certbot are configurable, Certbot +will write a lock file for all of the directories it uses. This include Certbot's +``--work-dir``, ``--logs-dir``, and ``--config-dir``. By default these are +``/var/lib/letsencrypt``, ``/var/log/letsencrypt``, and ``/etc/letsencrypt`` +respectively. Additionally if you are using Certbot with Apache or nginx it will +lock the configuration folder for that program, which are typically also in the +``/etc`` directory. + +Note that these lock files will only prevent other instances of Certbot from +using those directories, not other processes. If you'd like to run multiple +instances of Certbot simultaneously you should specify different directories +as the ``--work-dir``, ``--logs-dir``, and ``--config-dir`` for each instance +of Certbot that you would like to run. + +.. _config-file: + +Configuration file +================== + +Certbot accepts a global configuration file that applies its options to all invocations +of Certbot. Certificate specific configuration choices should be set in the ``.conf`` +files that can be found in ``/etc/letsencrypt/renewal``. + +By default no cli.ini file is created (though it may exist already if you installed Certbot +via a package manager, for instance). +After creating one it is possible to specify the location of this configuration file with +``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An +example configuration file is shown below: + +.. include:: ../examples/cli.ini + :code: ini + +By default, the following locations are searched: + +- ``/etc/letsencrypt/cli.ini`` +- ``$XDG_CONFIG_HOME/letsencrypt/cli.ini`` (or + ``~/.config/letsencrypt/cli.ini`` if ``$XDG_CONFIG_HOME`` is not + set). + +Since this configuration file applies to all invocations of certbot it is incorrect +to list domains in it. Listing domains in cli.ini may prevent renewal from working. +Additionally due to how arguments in cli.ini are parsed, options which wish to +not be set should not be listed. Options set to false will instead be read +as being set to true by older versions of Certbot, since they have been listed +in the config file. + +.. keep it up to date with constants.py + +.. _log-rotation: + +Log Rotation +============ + +By default certbot stores status logs in ``/var/log/letsencrypt``. By default +certbot will begin rotating logs once there are 1000 logs in the log directory. +Meaning that once 1000 files are in ``/var/log/letsencrypt`` Certbot will delete +the oldest one to make room for new logs. The number of subsequent logs can be +changed by passing the desired number to the command line flag +``--max-log-backups``. + +.. note:: Some distributions, including Debian and Ubuntu, disable + certbot's internal log rotation in favor of a more traditional + logrotate script. If you are using a distribution's packages and + want to alter the log rotation, check `/etc/logrotate.d/` for a + certbot rotation script. + +.. _command-line: + +Certbot command-line options +============================ + +Certbot supports a lot of command line options. Here's the full list, from +``certbot --help all``: + +.. literalinclude:: cli-help.txt + +Getting help +============ + +If you're having problems, we recommend posting on the Let's Encrypt +`Community Forum `_. + +If you find a bug in the software, please do report it in our `issue +tracker `_. Remember to +give us as much information as possible: + +- copy and paste exact command line used and the output (though mind + that the latter might include some personally identifiable + information, including your email and domains) +- copy and paste logs from ``/var/log/letsencrypt`` (though mind they + also might contain personally identifiable information) +- copy and paste ``certbot --version`` output +- your operating system, including specific version +- specify which installation method you've chosen diff --git a/certbot/docs/what.rst b/certbot/docs/what.rst new file mode 100644 index 000000000..3d33346c2 --- /dev/null +++ b/certbot/docs/what.rst @@ -0,0 +1,31 @@ +====================== +What is a Certificate? +====================== + +A public key or digital *certificate* (formerly called an SSL certificate) uses a public key +and a private key to enable secure communication between a client program (web browser, email client, +etc.) and a server over an encrypted SSL (secure socket layer) or TLS (transport layer security) connection. +The certificate is used both to encrypt the initial stage of communication (secure key exchange) +and to identify the server. The certificate +includes information about the key, information about the server identity, and the digital signature +of the certificate issuer. If the issuer is trusted by the software that initiates the communication, +and the signature is valid, then the key can be used to communicate securely with the server identified by +the certificate. Using a certificate is a good way to prevent "man-in-the-middle" attacks, in which +someone in between you and the server you think you are talking to is able to insert their own (harmful) +content. + +You can use Certbot to easily obtain and configure a free certificate from Let's Encrypt, a +joint project of EFF, Mozilla, and many other sponsors. + +Certificates and Lineages +========================= + +Certbot introduces the concept of a *lineage,* which is a collection of all the versions of a certificate +plus Certbot configuration information maintained for that certificate from +renewal to renewal. Whenever you renew a certificate, Certbot keeps the same configuration unless +you explicitly change it, for example by adding or removing domains. If you add domains, you can +either add them to an existing lineage or create +a new one. + +See also: +:ref:`updating_certs` diff --git a/certbot/errors.py b/certbot/errors.py deleted file mode 100644 index 48aebc267..000000000 --- a/certbot/errors.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Certbot client errors.""" - - -class Error(Exception): - """Generic Certbot client error.""" - - -class AccountStorageError(Error): - """Generic `.AccountStorage` error.""" - - -class AccountNotFound(AccountStorageError): - """Account not found error.""" - - -class ReverterError(Error): - """Certbot Reverter error.""" - - -class SubprocessError(Error): - """Subprocess handling error.""" - - -class CertStorageError(Error): - """Generic `.CertStorage` error.""" - - -class HookCommandNotFound(Error): - """Failed to find a hook command in the PATH.""" - - -class SignalExit(Error): - """A Unix signal was received while in the ErrorHandler context manager.""" - -class OverlappingMatchFound(Error): - """Multiple lineages matched what should have been a unique result.""" - -class LockError(Error): - """File locking error.""" - - -# Auth Handler Errors -class AuthorizationError(Error): - """Authorization error.""" - - -class FailedChallenges(AuthorizationError): - """Failed challenges error. - - :ivar set failed_achalls: Failed `.AnnotatedChallenge` instances. - - """ - def __init__(self, failed_achalls): - assert failed_achalls - self.failed_achalls = failed_achalls - super(FailedChallenges, self).__init__() - - def __str__(self): - return "Failed authorization procedure. {0}".format( - ", ".join( - "{0} ({1}): {2}".format(achall.domain, achall.typ, achall.error) - for achall in self.failed_achalls if achall.error is not None)) - - -# Plugin Errors -class PluginError(Error): - """Certbot Plugin error.""" - - -class PluginEnhancementAlreadyPresent(Error): - """ Enhancement was already set """ - - -class PluginSelectionError(Error): - """A problem with plugin/configurator selection or setup""" - - -class NoInstallationError(PluginError): - """Certbot No Installation error.""" - - -class MisconfigurationError(PluginError): - """Certbot Misconfiguration error.""" - - -class NotSupportedError(PluginError): - """Certbot Plugin function not supported error.""" - - -class PluginStorageError(PluginError): - """Certbot Plugin Storage error.""" - - -class StandaloneBindError(Error): - """Standalone plugin bind error.""" - - def __init__(self, socket_error, port): - super(StandaloneBindError, self).__init__( - "Problem binding to port {0}: {1}".format(port, socket_error)) - self.socket_error = socket_error - self.port = port - - -class ConfigurationError(Error): - """Configuration sanity error.""" - -# NoninteractiveDisplay iDisplay plugin error: - -class MissingCommandlineFlag(Error): - """A command line argument was missing in noninteractive usage""" diff --git a/certbot/examples/.gitignore b/certbot/examples/.gitignore new file mode 100644 index 000000000..abaf425d1 --- /dev/null +++ b/certbot/examples/.gitignore @@ -0,0 +1,3 @@ +# generate-csr.sh: +/key.pem +/csr.der \ No newline at end of file diff --git a/certbot/examples/cli.ini b/certbot/examples/cli.ini new file mode 100644 index 000000000..4215fda5b --- /dev/null +++ b/certbot/examples/cli.ini @@ -0,0 +1,22 @@ +# This is an example of the kind of things you can do in a configuration file. +# All flags used by the client can be configured here. Run Certbot with +# "--help" to learn more about the available options. +# +# Note that these options apply automatically to all use of Certbot for +# obtaining or renewing certificates, so options specific to a single +# certificate on a system with several certificates should not be placed +# here. + +# Use a 4096 bit RSA key instead of 2048 +rsa-key-size = 4096 + +# Uncomment and update to register with the specified e-mail address +# email = foo@example.com + +# Uncomment to use the standalone authenticator on port 443 +# authenticator = standalone + +# Uncomment to use the webroot authenticator. Replace webroot-path with the +# path to the public_html / webroot folder being served by your web server. +# authenticator = webroot +# webroot-path = /usr/share/nginx/html diff --git a/certbot/examples/dev-cli.ini b/certbot/examples/dev-cli.ini new file mode 100644 index 000000000..a405a0aef --- /dev/null +++ b/certbot/examples/dev-cli.ini @@ -0,0 +1,20 @@ +# Always use the staging/testing server - avoids rate limiting +server = https://acme-staging-v02.api.letsencrypt.org/directory + +# This is an example configuration file for developers +config-dir = /tmp/le/conf +work-dir = /tmp/le/conf +logs-dir = /tmp/le/logs + +# make sure to use a valid email and domains! +email = foo@example.com +domains = example.com + +text = True +agree-tos = True +debug = True +# Unfortunately, it's not possible to specify "verbose" multiple times +# (correspondingly to -vvvvvv) +verbose = True + +authenticator = standalone diff --git a/certbot/examples/generate-csr.sh b/certbot/examples/generate-csr.sh new file mode 100755 index 000000000..55f6c7b9f --- /dev/null +++ b/certbot/examples/generate-csr.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# This script generates a simple SAN CSR to be used with Let's Encrypt +# CA. Mostly intended for "auth --csr" testing, but, since it's easily +# auditable, feel free to adjust it and use it on your production web +# server. + +if [ "$#" -lt 1 ] +then + echo "Usage: $0 domain [domain...]" >&2 + exit 1 +fi + +domains="DNS:$1" +shift +for x in "$@" +do + domains="$domains,DNS:$x" +done + +SAN="$domains" openssl req -config "${OPENSSL_CNF:-openssl.cnf}" \ + -new -nodes -subj '/' -reqexts san \ + -out "${CSR_PATH:-csr.der}" \ + -keyout "${KEY_PATH:-key.pem}" \ + -newkey rsa:2048 \ + -outform DER +# 512 or 1024 too low for Boulder, 2048 is smallest for tests + +echo "You can now run: certbot auth --csr ${CSR_PATH:-csr.der}" diff --git a/certbot/examples/openssl.cnf b/certbot/examples/openssl.cnf new file mode 100644 index 000000000..a3e6f3895 --- /dev/null +++ b/certbot/examples/openssl.cnf @@ -0,0 +1,5 @@ +[ req ] +distinguished_name = req_distinguished_name +[ req_distinguished_name ] +[ san ] +subjectAltName=${ENV::SAN} diff --git a/certbot/examples/plugins/certbot_example_plugins.py b/certbot/examples/plugins/certbot_example_plugins.py new file mode 100644 index 000000000..9dec2e108 --- /dev/null +++ b/certbot/examples/plugins/certbot_example_plugins.py @@ -0,0 +1,31 @@ +"""Example Certbot plugins. + +For full examples, see `certbot.plugins`. + +""" +import zope.interface + +from certbot import interfaces +from certbot.plugins import common + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(common.Plugin): + """Example Authenticator.""" + + description = "Example Authenticator plugin" + + # Implement all methods from IAuthenticator, remembering to add + # "self" as first argument, e.g. def prepare(self)... + + +@zope.interface.implementer(interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class Installer(common.Plugin): + """Example Installer.""" + + description = "Example Installer plugin" + + # Implement all methods from IInstaller, remembering to add + # "self" as first argument, e.g. def get_all_names(self)... diff --git a/certbot/examples/plugins/setup.py b/certbot/examples/plugins/setup.py new file mode 100644 index 000000000..4538e83b8 --- /dev/null +++ b/certbot/examples/plugins/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup + + +setup( + name='certbot-example-plugins', + package='certbot_example_plugins.py', + install_requires=[ + 'certbot', + 'zope.interface', + ], + entry_points={ + 'certbot.plugins': [ + 'example_authenticator = certbot_example_plugins:Authenticator', + 'example_installer = certbot_example_plugins:Installer', + ], + }, +) diff --git a/certbot/interfaces.py b/certbot/interfaces.py deleted file mode 100644 index edf71e63f..000000000 --- a/certbot/interfaces.py +++ /dev/null @@ -1,604 +0,0 @@ -"""Certbot client interfaces.""" -import abc -import six -import zope.interface - -# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class - - -@six.add_metaclass(abc.ABCMeta) -class AccountStorage(object): - """Accounts storage interface.""" - - @abc.abstractmethod - def find_all(self): # pragma: no cover - """Find all accounts. - - :returns: All found accounts. - :rtype: list - - """ - raise NotImplementedError() - - @abc.abstractmethod - def load(self, account_id): # pragma: no cover - """Load an account by its id. - - :raises .AccountNotFound: if account could not be found - :raises .AccountStorageError: if account could not be loaded - - """ - raise NotImplementedError() - - @abc.abstractmethod - def save(self, account, client): # pragma: no cover - """Save account. - - :raises .AccountStorageError: if account could not be saved - - """ - raise NotImplementedError() - - -class IPluginFactory(zope.interface.Interface): - """IPlugin factory. - - Objects providing this interface will be called without satisfying - any entry point "extras" (extra dependencies) you might have defined - for your plugin, e.g (excerpt from ``setup.py`` script):: - - setup( - ... - entry_points={ - 'certbot.plugins': [ - 'name=example_project.plugin[plugin_deps]', - ], - }, - extras_require={ - 'plugin_deps': ['dep1', 'dep2'], - } - ) - - Therefore, make sure such objects are importable and usable without - extras. This is necessary, because CLI does the following operations - (in order): - - - loads an entry point, - - calls `inject_parser_options`, - - requires an entry point, - - creates plugin instance (`__call__`). - - """ - - description = zope.interface.Attribute("Short plugin description") - - def __call__(config, name): # pylint: disable=signature-differs - """Create new `IPlugin`. - - :param IConfig config: Configuration. - :param str name: Unique plugin name. - - """ - - def inject_parser_options(parser, name): - """Inject argument parser options (flags). - - 1. Be nice and prepend all options and destinations with - `~.common.option_namespace` and `~common.dest_namespace`. - - 2. Inject options (flags) only. Positional arguments are not - allowed, as this would break the CLI. - - :param ArgumentParser parser: (Almost) top-level CLI parser. - :param str name: Unique plugin name. - - """ - - -class IPlugin(zope.interface.Interface): - """Certbot plugin.""" - - def prepare(): # type: ignore - """Prepare the plugin. - - Finish up any additional initialization. - - :raises .PluginError: - when full initialization cannot be completed. - :raises .MisconfigurationError: - when full initialization cannot be completed. Plugin will - be displayed on a list of available plugins. - :raises .NoInstallationError: - when the necessary programs/files cannot be located. Plugin - will NOT be displayed on a list of available plugins. - :raises .NotSupportedError: - when the installation is recognized, but the version is not - currently supported. - - """ - - def more_info(): # type: ignore - """Human-readable string to help the user. - - Should describe the steps taken and any relevant info to help the user - decide which plugin to use. - - :rtype str: - - """ - - -class IAuthenticator(IPlugin): - """Generic Certbot Authenticator. - - Class represents all possible tools processes that have the - ability to perform challenges and attain a certificate. - - """ - - def get_chall_pref(domain): - """Return `collections.Iterable` of challenge preferences. - - :param str domain: Domain for which challenge preferences are sought. - - :returns: `collections.Iterable` of challenge types (subclasses of - :class:`acme.challenges.Challenge`) with the most - preferred challenges first. If a type is not specified, it means the - Authenticator cannot perform the challenge. - :rtype: `collections.Iterable` - - """ - - def perform(achalls): - """Perform the given challenge. - - :param list achalls: Non-empty (guaranteed) list of - :class:`~certbot.achallenges.AnnotatedChallenge` - instances, such that it contains types found within - :func:`get_chall_pref` only. - - :returns: `collections.Iterable` of ACME - :class:`~acme.challenges.ChallengeResponse` instances corresponding to each provided - :class:`~acme.challenges.Challenge`. - :rtype: :class:`collections.Iterable` of - :class:`acme.challenges.ChallengeResponse`, - where responses are required to be returned in - the same order as corresponding input challenges - - :raises .PluginError: If some or all challenges cannot be performed - - """ - - def cleanup(achalls): - """Revert changes and shutdown after challenges complete. - - This method should be able to revert all changes made by - perform, even if perform exited abnormally. - - :param list achalls: Non-empty (guaranteed) list of - :class:`~certbot.achallenges.AnnotatedChallenge` - instances, a subset of those previously passed to :func:`perform`. - - :raises PluginError: if original configuration cannot be restored - - """ - - -class IConfig(zope.interface.Interface): - """Certbot user-supplied configuration. - - .. warning:: The values stored in the configuration have not been - filtered, stripped or sanitized. - - """ - server = zope.interface.Attribute("ACME Directory Resource URI.") - email = zope.interface.Attribute( - "Email used for registration and recovery contact. Use comma to " - "register multiple emails, ex: u1@example.com,u2@example.com. " - "(default: Ask).") - rsa_key_size = zope.interface.Attribute("Size of the RSA key.") - must_staple = zope.interface.Attribute( - "Adds the OCSP Must Staple extension to the certificate. " - "Autoconfigures OCSP Stapling for supported setups " - "(Apache version >= 2.3.3 ).") - - config_dir = zope.interface.Attribute("Configuration directory.") - work_dir = zope.interface.Attribute("Working directory.") - - accounts_dir = zope.interface.Attribute( - "Directory where all account information is stored.") - backup_dir = zope.interface.Attribute("Configuration backups directory.") - csr_dir = zope.interface.Attribute( - "Directory where newly generated Certificate Signing Requests " - "(CSRs) are saved.") - in_progress_dir = zope.interface.Attribute( - "Directory used before a permanent checkpoint is finalized.") - key_dir = zope.interface.Attribute("Keys storage.") - temp_checkpoint_dir = zope.interface.Attribute( - "Temporary checkpoint directory.") - - no_verify_ssl = zope.interface.Attribute( - "Disable verification of the ACME server's certificate.") - - http01_port = zope.interface.Attribute( - "Port used in the http-01 challenge. " - "This only affects the port Certbot listens on. " - "A conforming ACME server will still attempt to connect on port 80.") - - http01_address = zope.interface.Attribute( - "The address the server listens to during http-01 challenge.") - - https_port = zope.interface.Attribute( - "Port used to serve HTTPS. " - "This affects which port Nginx will listen on after a LE certificate " - "is installed.") - - pref_challs = zope.interface.Attribute( - "Sorted user specified preferred challenges" - "type strings with the most preferred challenge listed first") - - allow_subset_of_names = zope.interface.Attribute( - "When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This may be useful for allowing renewals for " - "multiple domains to succeed even if some domains no longer point " - "at this system. This is a boolean") - - strict_permissions = zope.interface.Attribute( - "Require that all configuration files are owned by the current " - "user; only needed if your config is somewhere unsafe like /tmp/." - "This is a boolean") - - disable_renew_updates = zope.interface.Attribute( - "If updates provided by installer enhancements when Certbot is being run" - " with \"renew\" verb should be disabled.") - -class IInstaller(IPlugin): - """Generic Certbot Installer Interface. - - Represents any server that an X509 certificate can be placed. - - It is assumed that :func:`save` is the only method that finalizes a - checkpoint. This is important to ensure that checkpoints are - restored in a consistent manner if requested by the user or in case - of an error. - - Using :class:`certbot.reverter.Reverter` to implement checkpoints, - rollback, and recovery can dramatically simplify plugin development. - - """ - - def get_all_names(): # type: ignore - """Returns all names that may be authenticated. - - :rtype: `collections.Iterable` of `str` - - """ - - def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path): - """Deploy certificate. - - :param str domain: domain to deploy certificate file - :param str cert_path: absolute path to the certificate file - :param str key_path: absolute path to the private key file - :param str chain_path: absolute path to the certificate chain file - :param str fullchain_path: absolute path to the certificate fullchain - file (cert plus chain) - - :raises .PluginError: when cert cannot be deployed - - """ - - def enhance(domain, enhancement, options=None): - """Perform a configuration enhancement. - - :param str domain: domain for which to provide enhancement - :param str enhancement: An enhancement as defined in - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - :param options: Flexible options parameter for enhancement. - Check documentation of - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - for expected options for each enhancement. - - :raises .PluginError: If Enhancement is not supported, or if - an error occurs during the enhancement. - - """ - - def supported_enhancements(): # type: ignore - """Returns a `collections.Iterable` of supported enhancements. - - :returns: supported enhancements which should be a subset of - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - :rtype: :class:`collections.Iterable` of :class:`str` - - """ - - def save(title=None, temporary=False): - """Saves all changes to the configuration files. - - Both title and temporary are needed because a save may be - intended to be permanent, but the save is not ready to be a full - checkpoint. - - It is assumed that at most one checkpoint is finalized by this - method. Additionally, if an exception is raised, it is assumed a - new checkpoint was not finalized. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. `title` has no effect if temporary is true. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (challenges) - - :raises .PluginError: when save is unsuccessful - - """ - - def rollback_checkpoints(rollback=1): - """Revert `rollback` number of configuration checkpoints. - - :raises .PluginError: when configuration cannot be fully reverted - - """ - - def recovery_routine(): # type: ignore - """Revert configuration to most recent finalized checkpoint. - - Remove all changes (temporary and permanent) that have not been - finalized. This is useful to protect against crashes and other - execution interruptions. - - :raises .errors.PluginError: If unable to recover the configuration - - """ - - def config_test(): # type: ignore - """Make sure the configuration is valid. - - :raises .MisconfigurationError: when the config is not in a usable state - - """ - - def restart(): # type: ignore - """Restart or refresh the server content. - - :raises .PluginError: when server cannot be restarted - - """ - - -class IDisplay(zope.interface.Interface): - """Generic display.""" - # see https://github.com/certbot/certbot/issues/3915 - - def notification(message, pause, wrap=True, force_interactive=False): - """Displays a string message - - :param str message: Message to display - :param bool pause: Whether or not the application should pause for - confirmation (if available) - :param bool wrap: Whether or not the application should wrap text - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - """ - - def menu(message, choices, ok_label=None, - cancel_label=None, help_label=None, - default=None, cli_flag=None, force_interactive=False): - """Displays a generic menu. - - When not setting force_interactive=True, you must provide a - default value. - - :param str message: message to display - - :param choices: choices - :type choices: :class:`list` of :func:`tuple` or :class:`str` - - :param str ok_label: label for OK button (UNUSED) - :param str cancel_label: label for Cancel button (UNUSED) - :param str help_label: label for Help button (UNUSED) - :param int default: default (non-interactive) choice from the menu - :param str cli_flag: to automate choice from the menu, eg "--keep" - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of (`code`, `index`) where - `code` - str display exit code - `index` - int index of the user's selection - - :raises errors.MissingCommandlineFlag: if called in non-interactive - mode without a default set - - """ - - def input(message, default=None, cli_args=None, force_interactive=False): - """Accept input from the user. - - When not setting force_interactive=True, you must provide a - default value. - - :param str message: message to display to the user - :param str default: default (non-interactive) response to prompt - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of (`code`, `input`) where - `code` - str display exit code - `input` - str of the user's input - :rtype: tuple - - :raises errors.MissingCommandlineFlag: if called in non-interactive - mode without a default set - - """ - - def yesno(message, yes_label="Yes", no_label="No", default=None, - cli_args=None, force_interactive=False): - """Query the user with a yes/no question. - - Yes and No label must begin with different letters. - - When not setting force_interactive=True, you must provide a - default value. - - :param str message: question for the user - :param str default: default (non-interactive) choice from the menu - :param str cli_flag: to automate choice from the menu, eg "--redirect / --no-redirect" - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: True for "Yes", False for "No" - :rtype: bool - - :raises errors.MissingCommandlineFlag: if called in non-interactive - mode without a default set - - """ - - def checklist(message, tags, default=None, cli_args=None, force_interactive=False): - """Allow for multiple selections from a menu. - - When not setting force_interactive=True, you must provide a - default value. - - :param str message: message to display to the user - :param list tags: where each is of type :class:`str` len(tags) > 0 - :param str default: default (non-interactive) state of the checklist - :param str cli_flag: to automate choice from the menu, eg "--domains" - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of the form (code, list_tags) where - `code` - int display exit code - `list_tags` - list of str tags selected by the user - :rtype: tuple - - :raises errors.MissingCommandlineFlag: if called in non-interactive - mode without a default set - - """ - - def directory_select(self, message, default=None, - cli_flag=None, force_interactive=False): - """Display a directory selection screen. - - When not setting force_interactive=True, you must provide a - default value. - - :param str message: prompt to give the user - :param default: the default value to return, if one exists, when - using the NoninteractiveDisplay - :param str cli_flag: option used to set this value with the CLI, - if one exists, to be included in error messages given by - NoninteractiveDisplay - :param bool force_interactive: True if it's safe to prompt the user - because it won't cause any workflow regressions - - :returns: tuple of the form (`code`, `string`) where - `code` - int display exit code - `string` - input entered by the user - - """ - - -class IReporter(zope.interface.Interface): - """Interface to collect and display information to the user.""" - - HIGH_PRIORITY = zope.interface.Attribute( - "Used to denote high priority messages") - MEDIUM_PRIORITY = zope.interface.Attribute( - "Used to denote medium priority messages") - LOW_PRIORITY = zope.interface.Attribute( - "Used to denote low priority messages") - - def add_message(self, msg, priority, on_crash=True): - """Adds msg to the list of messages to be printed. - - :param str msg: Message to be displayed to the user. - - :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or - LOW_PRIORITY. - - :param bool on_crash: Whether or not the message should be printed if - the program exits abnormally. - - """ - - def print_messages(self): - """Prints messages to the user and clears the message queue.""" - - -# Updater interfaces -# -# When "certbot renew" is run, Certbot will iterate over each lineage and check -# if the selected installer for that lineage is a subclass of each updater -# class. If it is and the update of that type is configured to be run for that -# lineage, the relevant update function will be called for it. These functions -# are never called for other subcommands, so if an installer wants to perform -# an update during the run or install subcommand, it should do so when -# :func:`IInstaller.deploy_cert` is called. - -@six.add_metaclass(abc.ABCMeta) -class GenericUpdater(object): - """Interface for update types not currently specified by Certbot. - - This class allows plugins to perform types of updates that Certbot hasn't - defined (yet). - - To make use of this interface, the installer should implement the interface - methods, and interfaces.GenericUpdater.register(InstallerClass) should - be called from the installer code. - - The plugins implementing this enhancement are responsible of handling - the saving of configuration checkpoints as well as other calls to - interface methods of `interfaces.IInstaller` such as prepare() and restart() - """ - - @abc.abstractmethod - def generic_updates(self, lineage, *args, **kwargs): - """Perform any update types defined by the installer. - - If an installer is a subclass of the class containing this method, this - function will always be called when "certbot renew" is run. If the - update defined by the installer should be run conditionally, the - installer needs to handle checking the conditions itself. - - This method is called once for each lineage. - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - """ - - -@six.add_metaclass(abc.ABCMeta) -class RenewDeployer(object): - """Interface for update types run when a lineage is renewed - - This class allows plugins to perform types of updates that need to run at - lineage renewal that Certbot hasn't defined (yet). - - To make use of this interface, the installer should implement the interface - methods, and interfaces.RenewDeployer.register(InstallerClass) should - be called from the installer code. - """ - - @abc.abstractmethod - def renew_deploy(self, lineage, *args, **kwargs): - """Perform updates defined by installer when a certificate has been renewed - - If an installer is a subclass of the class containing this method, this - function will always be called when a certficate has been renewed by - running "certbot renew". For example if a plugin needs to copy a - certificate over, or change configuration based on the new certificate. - - This method is called once for each lineage renewed - - :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert - - """ diff --git a/certbot/local-oldest-requirements.txt b/certbot/local-oldest-requirements.txt new file mode 100644 index 000000000..f6d158890 --- /dev/null +++ b/certbot/local-oldest-requirements.txt @@ -0,0 +1,2 @@ +# Remember to update setup.py to match the package versions below. +acme[dev]==0.40.0 diff --git a/certbot/plugins/__init__.py b/certbot/plugins/__init__.py deleted file mode 100644 index 7831eab61..000000000 --- a/certbot/plugins/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot plugins.""" diff --git a/certbot/plugins/common.py b/certbot/plugins/common.py deleted file mode 100644 index 843e27a1b..000000000 --- a/certbot/plugins/common.py +++ /dev/null @@ -1,458 +0,0 @@ -"""Plugin common functions.""" -import logging -import re -import shutil -import sys -import tempfile -import warnings - -import pkg_resources -import zope.interface - -from josepy import util as jose_util - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import achallenges # pylint: disable=unused-import -from certbot._internal import constants -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot import reverter -from certbot.compat import os -from certbot.compat import filesystem -from certbot.plugins.storage import PluginStorage - -logger = logging.getLogger(__name__) - - -def option_namespace(name): - """ArgumentParser options namespace (prefix of all options).""" - return name + "-" - - -def dest_namespace(name): - """ArgumentParser dest namespace (prefix of all destinations).""" - return name.replace("-", "_") + "_" - -private_ips_regex = re.compile( - r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|" - r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)") -hostname_regex = re.compile( - r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE) - - -@zope.interface.implementer(interfaces.IPlugin) -class Plugin(object): - """Generic plugin.""" - # provider is not inherited, subclasses must define it on their own - # @zope.interface.provider(interfaces.IPluginFactory) - - def __init__(self, config, name): - self.config = config - self.name = name - - @jose_util.abstractclassmethod - def add_parser_arguments(cls, add): - """Add plugin arguments to the CLI argument parser. - - NOTE: If some of your flags interact with others, you can - use cli.report_config_interaction to register this to ensure - values are correctly saved/overridable during renewal. - - :param callable add: Function that proxies calls to - `argparse.ArgumentParser.add_argument` prepending options - with unique plugin name prefix. - - """ - - @classmethod - def inject_parser_options(cls, parser, name): - """Inject parser options. - - See `~.IPlugin.inject_parser_options` for docs. - - """ - # dummy function, doesn't check if dest.startswith(self.dest_namespace) - def add(arg_name_no_prefix, *args, **kwargs): - # pylint: disable=missing-docstring - return parser.add_argument( - "--{0}{1}".format(option_namespace(name), arg_name_no_prefix), - *args, **kwargs) - return cls.add_parser_arguments(add) - - @property - def option_namespace(self): - """ArgumentParser options namespace (prefix of all options).""" - return option_namespace(self.name) - - def option_name(self, name): - """Option name (include plugin namespace).""" - return self.option_namespace + name - - @property - def dest_namespace(self): - """ArgumentParser dest namespace (prefix of all destinations).""" - return dest_namespace(self.name) - - def dest(self, var): - """Find a destination for given variable ``var``.""" - # this should do exactly the same what ArgumentParser(arg), - # does to "arg" to compute "dest" - return self.dest_namespace + var.replace("-", "_") - - def conf(self, var): - """Find a configuration value for variable ``var``.""" - return getattr(self.config, self.dest(var)) - - -class Installer(Plugin): - """An installer base class with reverter and ssl_dhparam methods defined. - - Installer plugins do not have to inherit from this class. - - """ - def __init__(self, *args, **kwargs): - super(Installer, self).__init__(*args, **kwargs) - self.storage = PluginStorage(self.config, self.name) - self.reverter = reverter.Reverter(self.config) - - def add_to_checkpoint(self, save_files, save_notes, temporary=False): - """Add files to a checkpoint. - - :param set save_files: set of filepaths to save - :param str save_notes: notes about changes during the save - :param bool temporary: True if the files should be added to a - temporary checkpoint rather than a permanent one. This is - usually used for changes that will soon be reverted. - - :raises .errors.PluginError: when unable to add to checkpoint - - """ - if temporary: - checkpoint_func = self.reverter.add_to_temp_checkpoint - else: - checkpoint_func = self.reverter.add_to_checkpoint - - try: - checkpoint_func(save_files, save_notes) - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - - def finalize_checkpoint(self, title): - """Timestamp and save changes made through the reverter. - - :param str title: Title describing checkpoint - - :raises .errors.PluginError: when an error occurs - - """ - try: - self.reverter.finalize_checkpoint(title) - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - - def recovery_routine(self): - """Revert all previously modified files. - - Reverts all modified files that have not been saved as a checkpoint - - :raises .errors.PluginError: If unable to recover the configuration - - """ - try: - self.reverter.recovery_routine() - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - - def revert_temporary_config(self): - """Rollback temporary checkpoint. - - :raises .errors.PluginError: when unable to revert config - - """ - try: - self.reverter.revert_temporary_config() - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - - """ - try: - self.reverter.rollback_checkpoints(rollback) - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - - @property - def ssl_dhparams(self): - """Full absolute path to ssl_dhparams file.""" - return os.path.join(self.config.config_dir, constants.SSL_DHPARAMS_DEST) - - @property - def updated_ssl_dhparams_digest(self): - """Full absolute path to digest of updated ssl_dhparams file.""" - return os.path.join(self.config.config_dir, constants.UPDATED_SSL_DHPARAMS_DIGEST) - - def install_ssl_dhparams(self): - """Copy Certbot's ssl_dhparams file into the system's config dir if required.""" - return install_version_controlled_file( - self.ssl_dhparams, - self.updated_ssl_dhparams_digest, - constants.SSL_DHPARAMS_SRC, - constants.ALL_SSL_DHPARAMS_HASHES) - - -class Addr(object): - r"""Represents an virtual host address. - - :param str addr: addr part of vhost address - :param str port: port number or \*, or "" - - """ - def __init__(self, tup, ipv6=False): - self.tup = tup - self.ipv6 = ipv6 - - @classmethod - def fromstring(cls, str_addr): - """Initialize Addr from string.""" - if str_addr.startswith('['): - # ipv6 addresses starts with [ - endIndex = str_addr.rfind(']') - host = str_addr[:endIndex + 1] - port = '' - if len(str_addr) > endIndex + 2 and str_addr[endIndex + 1] == ':': - port = str_addr[endIndex + 2:] - return cls((host, port), ipv6=True) - else: - tup = str_addr.partition(':') - return cls((tup[0], tup[2])) - - def __str__(self): - if self.tup[1]: - return "%s:%s" % self.tup - return self.tup[0] - - def normalized_tuple(self): - """Normalized representation of addr/port tuple - """ - if self.ipv6: - return (self.get_ipv6_exploded(), self.tup[1]) - return self.tup - - def __eq__(self, other): - if isinstance(other, self.__class__): - # compare normalized to take different - # styles of representation into account - return self.normalized_tuple() == other.normalized_tuple() - - return False - - def __hash__(self): - return hash(self.tup) - - def get_addr(self): - """Return addr part of Addr object.""" - return self.tup[0] - - def get_port(self): - """Return port.""" - return self.tup[1] - - def get_addr_obj(self, port): - """Return new address object with same addr and new port.""" - return self.__class__((self.tup[0], port), self.ipv6) - - def _normalize_ipv6(self, addr): - """Return IPv6 address in normalized form, helper function""" - addr = addr.lstrip("[") - addr = addr.rstrip("]") - return self._explode_ipv6(addr) - - def get_ipv6_exploded(self): - """Return IPv6 in normalized form""" - if self.ipv6: - return ":".join(self._normalize_ipv6(self.tup[0])) - return "" - - def _explode_ipv6(self, addr): - """Explode IPv6 address for comparison""" - result = ['0', '0', '0', '0', '0', '0', '0', '0'] - addr_list = addr.split(":") - if len(addr_list) > len(result): - # too long, truncate - addr_list = addr_list[0:len(result)] - append_to_end = False - for i, block in enumerate(addr_list): - if not block: - # encountered ::, so rest of the blocks should be - # appended to the end - append_to_end = True - continue - elif len(block) > 1: - # remove leading zeros - block = block.lstrip("0") - if not append_to_end: - result[i] = str(block) - else: - # count the location from the end using negative indices - result[i-len(addr_list)] = str(block) - return result - - -class ChallengePerformer(object): - """Abstract base for challenge performers. - - :ivar configurator: Authenticator and installer plugin - :ivar achalls: Annotated challenges - :vartype achalls: `list` of `.KeyAuthorizationAnnotatedChallenge` - :ivar indices: Holds the indices of challenges from a larger array - so the user of the class doesn't have to. - :vartype indices: `list` of `int` - - """ - - def __init__(self, configurator): - self.configurator = configurator - self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge] - self.indices = [] # type: List[int] - - def add_chall(self, achall, idx=None): - """Store challenge to be performed when perform() is called. - - :param .KeyAuthorizationAnnotatedChallenge achall: Annotated - challenge. - :param int idx: index to challenge in a larger array - - """ - self.achalls.append(achall) - if idx is not None: - self.indices.append(idx) - - def perform(self): - """Perform all added challenges. - - :returns: challenge responses - :rtype: `list` of `acme.challenges.KeyAuthorizationChallengeResponse` - - - """ - raise NotImplementedError() - - -def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes): - """Copy a file into an active location (likely the system's config dir) if required. - - :param str dest_path: destination path for version controlled file - :param str digest_path: path to save a digest of the file in - :param str src_path: path to version controlled file found in distribution - :param list all_hashes: hashes of every released version of the file - """ - current_hash = crypto_util.sha256sum(src_path) - - def _write_current_hash(): - with open(digest_path, "w") as f: - f.write(current_hash) - - def _install_current_file(): - shutil.copyfile(src_path, dest_path) - _write_current_hash() - - # Check to make sure options-ssl.conf is installed - if not os.path.isfile(dest_path): - _install_current_file() - return - # there's already a file there. if it's up to date, do nothing. if it's not but - # it matches a known file hash, we can update it. - # otherwise, print a warning once per new version. - active_file_digest = crypto_util.sha256sum(dest_path) - if active_file_digest == current_hash: # already up to date - return - elif active_file_digest in all_hashes: # safe to update - _install_current_file() - else: # has been manually modified, not safe to update - # did they modify the current version or an old version? - if os.path.isfile(digest_path): - with open(digest_path, "r") as f: - saved_digest = f.read() - # they modified it after we either installed or told them about this version, so return - if saved_digest == current_hash: - return - # there's a new version but we couldn't update the file, or they deleted the digest. - # save the current digest so we only print this once, and print a warning - _write_current_hash() - logger.warning("%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.", - dest_path, src_path, dest_path) - - -# test utils used by certbot_apache/certbot_nginx (hence -# "pragma: no cover") TODO: this might quickly lead to dead code (also -# c.f. #383) - -def dir_setup(test_dir, pkg): # pragma: no cover - """Setup the directories necessary for the configurator.""" - def expanded_tempdir(prefix): - """Return the real path of a temp directory with the specified prefix - - Some plugins rely on real paths of symlinks for working correctly. For - example, certbot-apache uses real paths of configuration files to tell - a virtual host from another. On systems where TMP itself is a symbolic - link, (ex: OS X) such plugins will be confused. This function prevents - such a case. - """ - return filesystem.realpath(tempfile.mkdtemp(prefix)) - - temp_dir = expanded_tempdir("temp") - config_dir = expanded_tempdir("config") - work_dir = expanded_tempdir("work") - - filesystem.chmod(temp_dir, constants.CONFIG_DIRS_MODE) - filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE) - filesystem.chmod(work_dir, constants.CONFIG_DIRS_MODE) - - test_configs = pkg_resources.resource_filename( - pkg, os.path.join("testdata", test_dir)) - - shutil.copytree( - test_configs, os.path.join(temp_dir, test_dir), symlinks=True) - - return temp_dir, config_dir, work_dir - - -# This class takes a similar approach to the cryptography project to deprecate attributes -# in public modules. See the _ModuleWithDeprecation class here: -# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 -class _TLSSNI01DeprecationModule(object): - """ - Internal class delegating to a module, and displaying warnings when - attributes related to TLS-SNI-01 are accessed. - """ - def __init__(self, module): - self.__dict__['_module'] = module - - def __getattr__(self, attr): - if attr == 'TLSSNI01': - warnings.warn('TLSSNI01 is deprecated and will be removed soon.', - DeprecationWarning, stacklevel=2) - return getattr(self._module, attr) - - def __setattr__(self, attr, value): # pragma: no cover - setattr(self._module, attr, value) - - def __delattr__(self, attr): # pragma: no cover - delattr(self._module, attr) - - def __dir__(self): # pragma: no cover - return ['_module'] + dir(self._module) - - -# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. -sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/certbot/plugins/common_test.py b/certbot/plugins/common_test.py deleted file mode 100644 index 977500f86..000000000 --- a/certbot/plugins/common_test.py +++ /dev/null @@ -1,356 +0,0 @@ -"""Tests for certbot.plugins.common.""" -import functools -import shutil -import unittest - -import josepy as jose -import mock - -from acme import challenges - -from certbot import achallenges -from certbot import crypto_util -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem -from certbot.tests import acme_util -from certbot.tests import util as test_util - -AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) -ACHALL = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb(challenges.HTTP01(token=b'token1'), - "pending"), - domain="encryption-example.demo", account_key=AUTH_KEY) - -class NamespaceFunctionsTest(unittest.TestCase): - """Tests for certbot.plugins.common.*_namespace functions.""" - - def test_option_namespace(self): - from certbot.plugins.common import option_namespace - self.assertEqual("foo-", option_namespace("foo")) - - def test_dest_namespace(self): - from certbot.plugins.common import dest_namespace - self.assertEqual("foo_", dest_namespace("foo")) - - def test_dest_namespace_with_dashes(self): - from certbot.plugins.common import dest_namespace - self.assertEqual("foo_bar_", dest_namespace("foo-bar")) - - -class PluginTest(unittest.TestCase): - """Test for certbot.plugins.common.Plugin.""" - - def setUp(self): - from certbot.plugins.common import Plugin - - class MockPlugin(Plugin): # pylint: disable=missing-docstring - @classmethod - def add_parser_arguments(cls, add): - add("foo-bar", dest="different_to_foo_bar", x=1, y=None) - - self.plugin_cls = MockPlugin - self.config = mock.MagicMock() - self.plugin = MockPlugin(config=self.config, name="mock") - - def test_init(self): - self.assertEqual("mock", self.plugin.name) - self.assertEqual(self.config, self.plugin.config) - - def test_option_namespace(self): - self.assertEqual("mock-", self.plugin.option_namespace) - - def test_option_name(self): - self.assertEqual("mock-foo_bar", self.plugin.option_name("foo_bar")) - - def test_dest_namespace(self): - self.assertEqual("mock_", self.plugin.dest_namespace) - - def test_dest(self): - self.assertEqual("mock_foo_bar", self.plugin.dest("foo-bar")) - self.assertEqual("mock_foo_bar", self.plugin.dest("foo_bar")) - - def test_conf(self): - self.assertEqual(self.config.mock_foo_bar, self.plugin.conf("foo-bar")) - - def test_inject_parser_options(self): - parser = mock.MagicMock() - self.plugin_cls.inject_parser_options(parser, "mock") - # note that inject_parser_options doesn't check if dest has - # correct prefix - parser.add_argument.assert_called_once_with( - "--mock-foo-bar", dest="different_to_foo_bar", x=1, y=None) - - -class InstallerTest(test_util.ConfigTestCase): - """Tests for certbot.plugins.common.Installer.""" - - def setUp(self): - super(InstallerTest, self).setUp() - filesystem.mkdir(self.config.config_dir) - from certbot.plugins.common import Installer - - self.installer = Installer(config=self.config, - name="Installer") - self.reverter = self.installer.reverter - - def test_add_to_real_checkpoint(self): - files = set(("foo.bar", "baz.qux",)) - save_notes = "foo bar baz qux" - self._test_wrapped_method("add_to_checkpoint", files, save_notes) - - def test_add_to_real_checkpoint2(self): - self._test_add_to_checkpoint_common(False) - - def test_add_to_temporary_checkpoint(self): - self._test_add_to_checkpoint_common(True) - - def _test_add_to_checkpoint_common(self, temporary): - files = set(("foo.bar", "baz.qux",)) - save_notes = "foo bar baz qux" - - installer_func = functools.partial(self.installer.add_to_checkpoint, - temporary=temporary) - - if temporary: - reverter_func_name = "add_to_temp_checkpoint" - else: - reverter_func_name = "add_to_checkpoint" - - self._test_adapted_method(installer_func, reverter_func_name, files, save_notes) - - def test_finalize_checkpoint(self): - self._test_wrapped_method("finalize_checkpoint", "foo") - - def test_recovery_routine(self): - self._test_wrapped_method("recovery_routine") - - def test_revert_temporary_config(self): - self._test_wrapped_method("revert_temporary_config") - - def test_rollback_checkpoints(self): - self._test_wrapped_method("rollback_checkpoints", 42) - - def _test_wrapped_method(self, name, *args, **kwargs): - """Test a wrapped reverter method. - - :param str name: name of the method to test - :param tuple args: position arguments to method - :param dict kwargs: keyword arguments to method - - """ - installer_func = getattr(self.installer, name) - self._test_adapted_method(installer_func, name, *args, **kwargs) - - def _test_adapted_method(self, installer_func, - reverter_func_name, *passed_args, **passed_kwargs): - """Test an adapted reverter method - - :param callable installer_func: installer method to test - :param str reverter_func_name: name of the method on the - reverter that should be called - :param tuple passed_args: positional arguments passed from - installer method to the reverter method - :param dict passed_kargs: keyword arguments passed from - installer method to the reverter method - - """ - with mock.patch.object(self.reverter, reverter_func_name) as reverter_func: - installer_func(*passed_args, **passed_kwargs) - reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) - reverter_func.side_effect = errors.ReverterError - self.assertRaises( - errors.PluginError, installer_func, *passed_args, **passed_kwargs) - - def test_install_ssl_dhparams(self): - self.installer.install_ssl_dhparams() - self.assertTrue(os.path.isfile(self.installer.ssl_dhparams)) - - def _current_ssl_dhparams_hash(self): - from certbot._internal.constants import SSL_DHPARAMS_SRC - return crypto_util.sha256sum(SSL_DHPARAMS_SRC) - - def test_current_file_hash_in_all_hashes(self): - from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES - self.assertTrue(self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES, - "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" - " with the sha256 hash of self.config.ssl_dhparams when it is updated.") - - -class AddrTest(unittest.TestCase): - """Tests for certbot._internal.client.plugins.common.Addr.""" - - def setUp(self): - from certbot.plugins.common import Addr - self.addr1 = Addr.fromstring("192.168.1.1") - self.addr2 = Addr.fromstring("192.168.1.1:*") - self.addr3 = Addr.fromstring("192.168.1.1:80") - self.addr4 = Addr.fromstring("[fe00::1]") - self.addr5 = Addr.fromstring("[fe00::1]:*") - self.addr6 = Addr.fromstring("[fe00::1]:80") - self.addr7 = Addr.fromstring("[fe00::1]:5") - self.addr8 = Addr.fromstring("[fe00:1:2:3:4:5:6:7:8:9]:8080") - - def test_fromstring(self): - self.assertEqual(self.addr1.get_addr(), "192.168.1.1") - self.assertEqual(self.addr1.get_port(), "") - self.assertEqual(self.addr2.get_addr(), "192.168.1.1") - self.assertEqual(self.addr2.get_port(), "*") - self.assertEqual(self.addr3.get_addr(), "192.168.1.1") - self.assertEqual(self.addr3.get_port(), "80") - self.assertEqual(self.addr4.get_addr(), "[fe00::1]") - self.assertEqual(self.addr4.get_port(), "") - self.assertEqual(self.addr5.get_addr(), "[fe00::1]") - self.assertEqual(self.addr5.get_port(), "*") - self.assertEqual(self.addr6.get_addr(), "[fe00::1]") - self.assertEqual(self.addr6.get_port(), "80") - self.assertEqual(self.addr6.get_ipv6_exploded(), - "fe00:0:0:0:0:0:0:1") - self.assertEqual(self.addr1.get_ipv6_exploded(), - "") - self.assertEqual(self.addr7.get_port(), "5") - self.assertEqual(self.addr8.get_ipv6_exploded(), - "fe00:1:2:3:4:5:6:7") - - def test_str(self): - self.assertEqual(str(self.addr1), "192.168.1.1") - self.assertEqual(str(self.addr2), "192.168.1.1:*") - self.assertEqual(str(self.addr3), "192.168.1.1:80") - self.assertEqual(str(self.addr4), "[fe00::1]") - self.assertEqual(str(self.addr5), "[fe00::1]:*") - self.assertEqual(str(self.addr6), "[fe00::1]:80") - - def test_get_addr_obj(self): - self.assertEqual(str(self.addr1.get_addr_obj("443")), "192.168.1.1:443") - self.assertEqual(str(self.addr2.get_addr_obj("")), "192.168.1.1") - self.assertEqual(str(self.addr1.get_addr_obj("*")), "192.168.1.1:*") - self.assertEqual(str(self.addr4.get_addr_obj("443")), "[fe00::1]:443") - self.assertEqual(str(self.addr5.get_addr_obj("")), "[fe00::1]") - self.assertEqual(str(self.addr4.get_addr_obj("*")), "[fe00::1]:*") - - def test_eq(self): - self.assertEqual(self.addr1, self.addr2.get_addr_obj("")) - self.assertNotEqual(self.addr1, self.addr2) - self.assertFalse(self.addr1 == 3333) - - self.assertEqual(self.addr4, self.addr4.get_addr_obj("")) - self.assertNotEqual(self.addr4, self.addr5) - self.assertFalse(self.addr4 == 3333) - from certbot.plugins.common import Addr - self.assertEqual(self.addr4, Addr.fromstring("[fe00:0:0::1]")) - self.assertEqual(self.addr4, Addr.fromstring("[fe00:0::0:0:1]")) - - - def test_set_inclusion(self): - from certbot.plugins.common import Addr - set_a = set([self.addr1, self.addr2]) - addr1b = Addr.fromstring("192.168.1.1") - addr2b = Addr.fromstring("192.168.1.1:*") - set_b = set([addr1b, addr2b]) - - self.assertEqual(set_a, set_b) - - set_c = set([self.addr4, self.addr5]) - addr4b = Addr.fromstring("[fe00::1]") - addr5b = Addr.fromstring("[fe00::1]:*") - set_d = set([addr4b, addr5b]) - - self.assertEqual(set_c, set_d) - - -class ChallengePerformerTest(unittest.TestCase): - """Tests for certbot.plugins.common.ChallengePerformer.""" - - def setUp(self): - configurator = mock.MagicMock() - - from certbot.plugins.common import ChallengePerformer - self.performer = ChallengePerformer(configurator) - - def test_add_chall(self): - self.performer.add_chall(ACHALL, 0) - self.assertEqual(1, len(self.performer.achalls)) - self.assertEqual([0], self.performer.indices) - - def test_perform(self): - self.assertRaises(NotImplementedError, self.performer.perform) - - -class InstallVersionControlledFileTest(test_util.TempDirTestCase): - """Tests for certbot.plugins.common.install_version_controlled_file.""" - - def setUp(self): - super(InstallVersionControlledFileTest, self).setUp() - self.hashes = ["someotherhash"] - self.dest_path = os.path.join(self.tempdir, "options-ssl-dest.conf") - self.hash_path = os.path.join(self.tempdir, ".options-ssl-conf.txt") - self.old_path = os.path.join(self.tempdir, "options-ssl-old.conf") - self.source_path = os.path.join(self.tempdir, "options-ssl-src.conf") - for path in (self.source_path, self.old_path,): - with open(path, "w") as f: - f.write(path) - self.hashes.append(crypto_util.sha256sum(path)) - - def _call(self): - from certbot.plugins.common import install_version_controlled_file - install_version_controlled_file(self.dest_path, - self.hash_path, - self.source_path, - self.hashes) - - def _current_file_hash(self): - return crypto_util.sha256sum(self.source_path) - - def _assert_current_file(self): - self.assertTrue(os.path.isfile(self.dest_path)) - self.assertEqual(crypto_util.sha256sum(self.dest_path), - self._current_file_hash()) - - def test_no_file(self): - self.assertFalse(os.path.isfile(self.dest_path)) - self._call() - self._assert_current_file() - - def test_current_file(self): - # 1st iteration installs the file, the 2nd checks if it needs updating - for _ in range(2): - self._call() - self._assert_current_file() - - def test_prev_file_updates_to_current(self): - shutil.copyfile(self.old_path, self.dest_path) - self._call() - self._assert_current_file() - - def test_manually_modified_current_file_does_not_update(self): - self._call() - with open(self.dest_path, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - self.assertTrue(os.path.isfile(self.dest_path)) - self.assertEqual(crypto_util.sha256sum(self.source_path), - self._current_file_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.dest_path), - self._current_file_hash()) - - def test_manually_modified_past_file_warns(self): - with open(self.dest_path, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with open(self.hash_path, "w") as f: - f.write("hashofanoldversion") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertEqual(mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum(self.source_path), - self._current_file_hash()) - # only print warning once - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/disco_test.py b/certbot/plugins/disco_test.py deleted file mode 100644 index f739512f0..000000000 --- a/certbot/plugins/disco_test.py +++ /dev/null @@ -1,292 +0,0 @@ -"""Tests for certbot._internal.plugins.disco.""" -import functools -import string -import unittest - -import mock -import pkg_resources -import six -import zope.interface - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from certbot import errors -from certbot import interfaces - -from certbot._internal.plugins import standalone -from certbot._internal.plugins import webroot - -EP_SA = pkg_resources.EntryPoint( - "sa", "certbot._internal.plugins.standalone", - attrs=("Authenticator",), - dist=mock.MagicMock(key="certbot")) -EP_WR = pkg_resources.EntryPoint( - "wr", "certbot._internal.plugins.webroot", - attrs=("Authenticator",), - dist=mock.MagicMock(key="certbot")) - - -class PluginEntryPointTest(unittest.TestCase): - """Tests for certbot._internal.plugins.disco.PluginEntryPoint.""" - - def setUp(self): - self.ep1 = pkg_resources.EntryPoint( - "ep1", "p1.ep1", dist=mock.MagicMock(key="p1")) - self.ep1prim = pkg_resources.EntryPoint( - "ep1", "p2.ep2", dist=mock.MagicMock(key="p2")) - # nested - self.ep2 = pkg_resources.EntryPoint( - "ep2", "p2.foo.ep2", dist=mock.MagicMock(key="p2")) - # project name != top-level package name - self.ep3 = pkg_resources.EntryPoint( - "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - - from certbot._internal.plugins.disco import PluginEntryPoint - self.plugin_ep = PluginEntryPoint(EP_SA) - - def test_entry_point_to_plugin_name(self): - from certbot._internal.plugins.disco import PluginEntryPoint - - names = { - self.ep1: "p1:ep1", - self.ep1prim: "p2:ep1", - self.ep2: "p2:ep2", - self.ep3: "p3:ep3", - EP_SA: "sa", - } - - for entry_point, name in six.iteritems(names): - self.assertEqual( - name, PluginEntryPoint.entry_point_to_plugin_name(entry_point)) - - def test_description(self): - self.assertTrue("temporary webserver" in self.plugin_ep.description) - - def test_description_with_name(self): - self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc") - self.assertEqual( - "Desc (sa)", self.plugin_ep.description_with_name) - - def test_long_description(self): - self.plugin_ep.plugin_cls = mock.MagicMock( - long_description="Long desc") - self.assertEqual( - "Long desc", self.plugin_ep.long_description) - - def test_long_description_nonexistent(self): - self.plugin_ep.plugin_cls = mock.MagicMock( - description="Long desc not found", spec=["description"]) - self.assertEqual( - "Long desc not found", self.plugin_ep.long_description) - - def test_ifaces(self): - self.assertTrue(self.plugin_ep.ifaces((interfaces.IAuthenticator,))) - self.assertFalse(self.plugin_ep.ifaces((interfaces.IInstaller,))) - self.assertFalse(self.plugin_ep.ifaces(( - interfaces.IInstaller, interfaces.IAuthenticator))) - - def test__init__(self): - self.assertFalse(self.plugin_ep.initialized) - self.assertFalse(self.plugin_ep.prepared) - self.assertFalse(self.plugin_ep.misconfigured) - self.assertFalse(self.plugin_ep.available) - self.assertTrue(self.plugin_ep.problem is None) - self.assertTrue(self.plugin_ep.entry_point is EP_SA) - self.assertEqual("sa", self.plugin_ep.name) - - self.assertTrue(self.plugin_ep.plugin_cls is standalone.Authenticator) - - def test_init(self): - config = mock.MagicMock() - plugin = self.plugin_ep.init(config=config) - self.assertTrue(self.plugin_ep.initialized) - self.assertTrue(plugin.config is config) - # memoize! - self.assertTrue(self.plugin_ep.init() is plugin) - self.assertTrue(plugin.config is config) - # try to give different config - self.assertTrue(self.plugin_ep.init(123) is plugin) - self.assertTrue(plugin.config is config) - - self.assertFalse(self.plugin_ep.prepared) - self.assertFalse(self.plugin_ep.misconfigured) - self.assertFalse(self.plugin_ep.available) - - def test_verify(self): - iface1 = mock.MagicMock(__name__="iface1") - iface2 = mock.MagicMock(__name__="iface2") - iface3 = mock.MagicMock(__name__="iface3") - # pylint: disable=protected-access - self.plugin_ep._initialized = plugin = mock.MagicMock() - - exceptions = zope.interface.exceptions - with mock.patch("certbot._internal.plugins." - "disco.zope.interface") as mock_zope: - mock_zope.exceptions = exceptions - - def verify_object(iface, obj): # pylint: disable=missing-docstring - assert obj is plugin - assert iface is iface1 or iface is iface2 or iface is iface3 - if iface is iface3: - raise mock_zope.exceptions.BrokenImplementation(None, None) - mock_zope.verify.verifyObject.side_effect = verify_object - self.assertTrue(self.plugin_ep.verify((iface1,))) - self.assertTrue(self.plugin_ep.verify((iface1, iface2))) - self.assertFalse(self.plugin_ep.verify((iface3,))) - self.assertFalse(self.plugin_ep.verify((iface1, iface3))) - - def test_prepare(self): - config = mock.MagicMock() - self.plugin_ep.init(config=config) - self.plugin_ep.prepare() - self.assertTrue(self.plugin_ep.prepared) - self.assertFalse(self.plugin_ep.misconfigured) - - # output doesn't matter that much, just test if it runs - str(self.plugin_ep) - - def test_prepare_misconfigured(self): - plugin = mock.MagicMock() - plugin.prepare.side_effect = errors.MisconfigurationError - # pylint: disable=protected-access - self.plugin_ep._initialized = plugin - self.assertTrue(isinstance(self.plugin_ep.prepare(), - errors.MisconfigurationError)) - self.assertTrue(self.plugin_ep.prepared) - self.assertTrue(self.plugin_ep.misconfigured) - self.assertTrue(isinstance(self.plugin_ep.problem, - errors.MisconfigurationError)) - self.assertTrue(self.plugin_ep.available) - - def test_prepare_no_installation(self): - plugin = mock.MagicMock() - plugin.prepare.side_effect = errors.NoInstallationError - # pylint: disable=protected-access - self.plugin_ep._initialized = plugin - self.assertTrue(isinstance(self.plugin_ep.prepare(), - errors.NoInstallationError)) - self.assertTrue(self.plugin_ep.prepared) - self.assertFalse(self.plugin_ep.misconfigured) - self.assertFalse(self.plugin_ep.available) - - def test_prepare_generic_plugin_error(self): - plugin = mock.MagicMock() - plugin.prepare.side_effect = errors.PluginError - # pylint: disable=protected-access - self.plugin_ep._initialized = plugin - self.assertTrue(isinstance(self.plugin_ep.prepare(), errors.PluginError)) - self.assertTrue(self.plugin_ep.prepared) - self.assertFalse(self.plugin_ep.misconfigured) - self.assertFalse(self.plugin_ep.available) - - def test_repr(self): - self.assertEqual("PluginEntryPoint#sa", repr(self.plugin_ep)) - - -class PluginsRegistryTest(unittest.TestCase): - """Tests for certbot._internal.plugins.disco.PluginsRegistry.""" - - @classmethod - def _create_new_registry(cls, plugins): - from certbot._internal.plugins.disco import PluginsRegistry - return PluginsRegistry(plugins) - - def setUp(self): - self.plugin_ep = mock.MagicMock() - self.plugin_ep.name = "mock" - self.plugin_ep.__hash__.side_effect = TypeError - self.plugins = {self.plugin_ep.name: self.plugin_ep} - self.reg = self._create_new_registry(self.plugins) - - def test_find_all(self): - from certbot._internal.plugins.disco import PluginsRegistry - with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: - mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]), - iter([EP_WR])] - plugins = PluginsRegistry.find_all() - self.assertTrue(plugins["sa"].plugin_cls is standalone.Authenticator) - self.assertTrue(plugins["sa"].entry_point is EP_SA) - self.assertTrue(plugins["wr"].plugin_cls is webroot.Authenticator) - self.assertTrue(plugins["wr"].entry_point is EP_WR) - - def test_getitem(self): - self.assertEqual(self.plugin_ep, self.reg["mock"]) - - def test_iter(self): - self.assertEqual(["mock"], list(self.reg)) - - def test_len(self): - self.assertEqual(0, len(self._create_new_registry({}))) - self.assertEqual(1, len(self.reg)) - - def test_init(self): - self.plugin_ep.init.return_value = "baz" - self.assertEqual(["baz"], self.reg.init("bar")) - self.plugin_ep.init.assert_called_once_with("bar") - - def test_filter(self): - self.assertEqual( - self.plugins, - self.reg.filter(lambda p_ep: p_ep.name.startswith("m"))) - self.assertEqual( - {}, self.reg.filter(lambda p_ep: p_ep.name.startswith("b"))) - - def test_ifaces(self): - self.plugin_ep.ifaces.return_value = True - # pylint: disable=protected-access - self.assertEqual(self.plugins, self.reg.ifaces()._plugins) - self.plugin_ep.ifaces.return_value = False - self.assertEqual({}, self.reg.ifaces()._plugins) - - def test_verify(self): - self.plugin_ep.verify.return_value = True - # pylint: disable=protected-access - self.assertEqual( - self.plugins, self.reg.verify(mock.MagicMock())._plugins) - self.plugin_ep.verify.return_value = False - self.assertEqual({}, self.reg.verify(mock.MagicMock())._plugins) - - def test_prepare(self): - self.plugin_ep.prepare.return_value = "baz" - self.assertEqual(["baz"], self.reg.prepare()) - self.plugin_ep.prepare.assert_called_once_with() - - def test_prepare_order(self): - order = [] # type: List[str] - plugins = dict( - (c, mock.MagicMock(prepare=functools.partial(order.append, c))) - for c in string.ascii_letters) - reg = self._create_new_registry(plugins) - reg.prepare() - # order of prepare calls must be sorted to prevent deadlock - # caused by plugins acquiring locks during prepare - self.assertEqual(order, sorted(string.ascii_letters)) - - def test_available(self): - self.plugin_ep.available = True - # pylint: disable=protected-access - self.assertEqual(self.plugins, self.reg.available()._plugins) - self.plugin_ep.available = False - self.assertEqual({}, self.reg.available()._plugins) - - def test_find_init(self): - self.assertTrue(self.reg.find_init(mock.Mock()) is None) - self.plugin_ep.initialized = True - self.assertTrue( - self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep) - - def test_repr(self): - self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock" - self.assertEqual("PluginsRegistry(PluginEntryPoint#mock)", - repr(self.reg)) - - def test_str(self): - self.assertEqual("No plugins", str(self._create_new_registry({}))) - self.plugin_ep.__str__ = lambda _: "Mock" - self.assertEqual("Mock", str(self.reg)) - plugins = {self.plugin_ep.name: self.plugin_ep, "foo": "Bar"} - reg = self._create_new_registry(plugins) - self.assertEqual("Bar\n\nMock", str(reg)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/dns_common.py b/certbot/plugins/dns_common.py deleted file mode 100644 index 931778b07..000000000 --- a/certbot/plugins/dns_common.py +++ /dev/null @@ -1,335 +0,0 @@ -"""Common code for DNS Authenticator Plugins.""" - -import abc -import logging -from time import sleep - -import configobj -import zope.interface - -from acme import challenges - -from certbot import errors -from certbot import interfaces -from certbot.compat import filesystem -from certbot.compat import os -from certbot.display import ops -from certbot.display import util as display_util -from certbot.plugins import common - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class DNSAuthenticator(common.Plugin): - """Base class for DNS Authenticators""" - - def __init__(self, config, name): - super(DNSAuthenticator, self).__init__(config, name) - - self._attempt_cleanup = False - - @classmethod - def add_parser_arguments(cls, add, default_propagation_seconds=10): # pylint: disable=arguments-differ - add('propagation-seconds', - default=default_propagation_seconds, - type=int, - help='The number of seconds to wait for DNS to propagate before asking the ACME server ' - 'to verify the DNS record.') - - def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use - return [challenges.DNS01] - - def prepare(self): # pylint: disable=missing-docstring - pass - - def perform(self, achalls): # pylint: disable=missing-docstring - self._setup_credentials() - - self._attempt_cleanup = True - - responses = [] - for achall in achalls: - domain = achall.domain - validation_domain_name = achall.validation_domain_name(domain) - validation = achall.validation(achall.account_key) - - self._perform(domain, validation_domain_name, validation) - responses.append(achall.response(achall.account_key)) - - # DNS updates take time to propagate and checking to see if the update has occurred is not - # reliable (the machine this code is running on might be able to see an update before - # the ACME server). So: we sleep for a short amount of time we believe to be long enough. - logger.info("Waiting %d seconds for DNS changes to propagate", - self.conf('propagation-seconds')) - sleep(self.conf('propagation-seconds')) - - return responses - - def cleanup(self, achalls): # pylint: disable=missing-docstring - if self._attempt_cleanup: - for achall in achalls: - domain = achall.domain - validation_domain_name = achall.validation_domain_name(domain) - validation = achall.validation(achall.account_key) - - self._cleanup(domain, validation_domain_name, validation) - - @abc.abstractmethod - def _setup_credentials(self): # pragma: no cover - """ - Establish credentials, prompting if necessary. - """ - raise NotImplementedError() - - @abc.abstractmethod - def _perform(self, domain, validation_name, validation): # pragma: no cover - """ - Performs a dns-01 challenge by creating a DNS TXT record. - - :param str domain: The domain being validated. - :param str validation_domain_name: The validation record domain name. - :param str validation: The validation record content. - :raises errors.PluginError: If the challenge cannot be performed - """ - raise NotImplementedError() - - @abc.abstractmethod - def _cleanup(self, domain, validation_name, validation): # pragma: no cover - """ - Deletes the DNS TXT record which would have been created by `_perform_achall`. - - Fails gracefully if no such record exists. - - :param str domain: The domain being validated. - :param str validation_domain_name: The validation record domain name. - :param str validation: The validation record content. - """ - raise NotImplementedError() - - def _configure(self, key, label): - """ - Ensure that a configuration value is available. - - If necessary, prompts the user and stores the result. - - :param str key: The configuration key. - :param str label: The user-friendly label for this piece of information. - """ - - configured_value = self.conf(key) - if not configured_value: - new_value = self._prompt_for_data(label) - - setattr(self.config, self.dest(key), new_value) - - def _configure_file(self, key, label, validator=None): - """ - Ensure that a configuration value is available for a path. - - If necessary, prompts the user and stores the result. - - :param str key: The configuration key. - :param str label: The user-friendly label for this piece of information. - """ - - configured_value = self.conf(key) - if not configured_value: - new_value = self._prompt_for_file(label, validator) - - setattr(self.config, self.dest(key), os.path.abspath(os.path.expanduser(new_value))) - - def _configure_credentials(self, key, label, required_variables=None, validator=None): - """ - As `_configure_file`, but for a credential configuration file. - - If necessary, prompts the user and stores the result. - - Always stores absolute paths to avoid issues during renewal. - - :param str key: The configuration key. - :param str label: The user-friendly label for this piece of information. - :param dict required_variables: Map of variable which must be present to error to display. - :param callable validator: A method which will be called to validate the - `CredentialsConfiguration` resulting from the supplied input after it has been validated - to contain the `required_variables`. Should throw a `~certbot.errors.PluginError` to - indicate any issue. - """ - - def __validator(filename): - configuration = CredentialsConfiguration(filename, self.dest) - - if required_variables: - configuration.require(required_variables) - - if validator: - validator(configuration) - - self._configure_file(key, label, __validator) - - credentials_configuration = CredentialsConfiguration(self.conf(key), self.dest) - if required_variables: - credentials_configuration.require(required_variables) - - if validator: - validator(credentials_configuration) - - return credentials_configuration - - @staticmethod - def _prompt_for_data(label): - """ - Prompt the user for a piece of information. - - :param str label: The user-friendly label for this piece of information. - :returns: The user's response (guaranteed non-empty). - :rtype: str - """ - - def __validator(i): - if not i: - raise errors.PluginError('Please enter your {0}.'.format(label)) - - code, response = ops.validated_input( - __validator, - 'Input your {0}'.format(label), - force_interactive=True) - - if code == display_util.OK: - return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) - - @staticmethod - def _prompt_for_file(label, validator=None): - """ - Prompt the user for a path. - - :param str label: The user-friendly label for the file. - :param callable validator: A method which will be called to validate the supplied input - after it has been validated to be a non-empty path to an existing file. Should throw a - `~certbot.errors.PluginError` to indicate any issue. - :returns: The user's response (guaranteed to exist). - :rtype: str - """ - - def __validator(filename): - if not filename: - raise errors.PluginError('Please enter a valid path to your {0}.'.format(label)) - - filename = os.path.expanduser(filename) - - validate_file(filename) - - if validator: - validator(filename) - - code, response = ops.validated_directory( - __validator, - 'Input the path to your {0}'.format(label), - force_interactive=True) - - if code == display_util.OK: - return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) - - -class CredentialsConfiguration(object): - """Represents a user-supplied filed which stores API credentials.""" - - def __init__(self, filename, mapper=lambda x: x): - """ - :param str filename: A path to the configuration file. - :param callable mapper: A transformation to apply to configuration key names - :raises errors.PluginError: If the file does not exist or is not a valid format. - """ - validate_file_permissions(filename) - - try: - self.confobj = configobj.ConfigObj(filename) - except configobj.ConfigObjError as e: - logger.debug("Error parsing credentials configuration: %s", e, exc_info=True) - raise errors.PluginError("Error parsing credentials configuration: {0}".format(e)) - - self.mapper = mapper - - def require(self, required_variables): - """Ensures that the supplied set of variables are all present in the file. - - :param dict required_variables: Map of variable which must be present to error to display. - :raises errors.PluginError: If one or more are missing. - """ - messages = [] - - for var in required_variables: - if not self._has(var): - messages.append('Property "{0}" not found (should be {1}).' - .format(self.mapper(var), required_variables[var])) - elif not self._get(var): - messages.append('Property "{0}" not set (should be {1}).' - .format(self.mapper(var), required_variables[var])) - - if messages: - raise errors.PluginError( - 'Missing {0} in credentials configuration file {1}:\n * {2}'.format( - 'property' if len(messages) == 1 else 'properties', - self.confobj.filename, - '\n * '.join(messages) - ) - ) - - def conf(self, var): - """Find a configuration value for variable `var`, as transformed by `mapper`. - - :param str var: The variable to get. - :returns: The value of the variable. - :rtype: str - """ - - return self._get(var) - - def _has(self, var): - return self.mapper(var) in self.confobj - - def _get(self, var): - return self.confobj.get(self.mapper(var)) - - -def validate_file(filename): - """Ensure that the specified file exists.""" - - if not os.path.exists(filename): - raise errors.PluginError('File not found: {0}'.format(filename)) - - if os.path.isdir(filename): - raise errors.PluginError('Path is a directory: {0}'.format(filename)) - - -def validate_file_permissions(filename): - """Ensure that the specified file exists and warn about unsafe permissions.""" - - validate_file(filename) - - if filesystem.has_world_permissions(filename): - logger.warning('Unsafe permissions on credentials configuration file: %s', filename) - - -def base_domain_name_guesses(domain): - """Return a list of progressively less-specific domain names. - - One of these will probably be the domain name known to the DNS provider. - - :Example: - - >>> base_domain_name_guesses('foo.bar.baz.example.com') - ['foo.bar.baz.example.com', 'bar.baz.example.com', 'baz.example.com', 'example.com', 'com'] - - :param str domain: The domain for which to return guesses. - :returns: The a list of less specific domain names. - :rtype: list - """ - - fragments = domain.split('.') - return ['.'.join(fragments[i:]) for i in range(0, len(fragments))] diff --git a/certbot/plugins/dns_common_lexicon.py b/certbot/plugins/dns_common_lexicon.py deleted file mode 100644 index 2c82db030..000000000 --- a/certbot/plugins/dns_common_lexicon.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Common code for DNS Authenticator Plugins built on Lexicon.""" -import logging - -from requests.exceptions import HTTPError, RequestException - -from acme.magic_typing import Union, Dict, Any # pylint: disable=unused-import,no-name-in-module -from certbot import errors -from certbot.plugins import dns_common - -# Lexicon is not declared as a dependency in Certbot itself, -# but in the Certbot plugins backed by Lexicon. -# So we catch import error here to allow this module to be -# always importable, even if it does not make sense to use it -# if Lexicon is not available, obviously. -try: - from lexicon.config import ConfigResolver -except ImportError: - ConfigResolver = None # type: ignore - -logger = logging.getLogger(__name__) - - -class LexiconClient(object): - """ - Encapsulates all communication with a DNS provider via Lexicon. - """ - - def __init__(self): - self.provider = None - - def add_txt_record(self, domain, record_name, record_content): - """ - Add a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :raises errors.PluginError: if an error occurs communicating with the DNS Provider API - """ - self._find_domain_id(domain) - - try: - self.provider.create_record(type='TXT', name=record_name, content=record_content) - except RequestException as e: - logger.debug('Encountered error adding TXT record: %s', e, exc_info=True) - raise errors.PluginError('Error adding TXT record: {0}'.format(e)) - - def del_txt_record(self, domain, record_name, record_content): - """ - Delete a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :raises errors.PluginError: if an error occurs communicating with the DNS Provider API - """ - try: - self._find_domain_id(domain) - except errors.PluginError as e: - logger.debug('Encountered error finding domain_id during deletion: %s', e, - exc_info=True) - return - - try: - self.provider.delete_record(type='TXT', name=record_name, content=record_content) - except RequestException as e: - logger.debug('Encountered error deleting TXT record: %s', e, exc_info=True) - - def _find_domain_id(self, domain): - """ - Find the domain_id for a given domain. - - :param str domain: The domain for which to find the domain_id. - :raises errors.PluginError: if the domain_id cannot be found. - """ - - domain_name_guesses = dns_common.base_domain_name_guesses(domain) - - for domain_name in domain_name_guesses: - try: - if hasattr(self.provider, 'options'): - # For Lexicon 2.x - self.provider.options['domain'] = domain_name - else: - # For Lexicon 3.x - self.provider.domain = domain_name - - self.provider.authenticate() - - return # If `authenticate` doesn't throw an exception, we've found the right name - except HTTPError as e: - result = self._handle_http_error(e, domain_name) - - if result: - raise result - except Exception as e: # pylint: disable=broad-except - result = self._handle_general_error(e, domain_name) - - if result: - raise result - - raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}' - .format(domain, domain_name_guesses)) - - def _handle_http_error(self, e, domain_name): - return errors.PluginError('Error determining zone identifier for {0}: {1}.' - .format(domain_name, e)) - - def _handle_general_error(self, e, domain_name): - if not str(e).startswith('No domain found'): - return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}' - .format(domain_name, e)) - return None - - -def build_lexicon_config(lexicon_provider_name, lexicon_options, provider_options): - # type: (str, Dict, Dict) -> Union[ConfigResolver, Dict] - """ - Convenient function to build a Lexicon 2.x/3.x config object. - :param str lexicon_provider_name: the name of the lexicon provider to use - :param dict lexicon_options: options specific to lexicon - :param dict provider_options: options specific to provider - :return: configuration to apply to the provider - :rtype: ConfigurationResolver or dict - """ - config = {'provider_name': lexicon_provider_name} # type: Dict[str, Any] - config.update(lexicon_options) - if not ConfigResolver: - # Lexicon 2.x - config.update(provider_options) - else: - # Lexicon 3.x - provider_config = {} - provider_config.update(provider_options) - config[lexicon_provider_name] = provider_config - config = ConfigResolver().with_dict(config).with_env() - - return config diff --git a/certbot/plugins/dns_common_lexicon_test.py b/certbot/plugins/dns_common_lexicon_test.py deleted file mode 100644 index 986362ca9..000000000 --- a/certbot/plugins/dns_common_lexicon_test.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Tests for certbot.plugins.dns_common_lexicon.""" - -import unittest - -import mock - -from certbot.plugins import dns_common_lexicon -from certbot.plugins import dns_test_common_lexicon - - -class LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - class _FakeLexiconClient(dns_common_lexicon.LexiconClient): - pass - - def setUp(self): - super(LexiconClientTest, self).setUp() - - self.client = LexiconClientTest._FakeLexiconClient() - self.provider_mock = mock.MagicMock() - - self.client.provider = self.provider_mock - - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/dns_common_test.py b/certbot/plugins/dns_common_test.py deleted file mode 100644 index eba3c89d6..000000000 --- a/certbot/plugins/dns_common_test.py +++ /dev/null @@ -1,230 +0,0 @@ -"""Tests for certbot.plugins.dns_common.""" - -import collections -import logging -import unittest - -import mock - -from certbot import errors -from certbot import util -from certbot.compat import os -from certbot.display import util as display_util -from certbot.plugins import dns_common -from certbot.plugins import dns_test_common -from certbot.tests import util as test_util - - -class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): - # pylint: disable=protected-access - - class _FakeDNSAuthenticator(dns_common.DNSAuthenticator): - _setup_credentials = mock.MagicMock() - _perform = mock.MagicMock() - _cleanup = mock.MagicMock() - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'A fake authenticator for testing.' - - class _FakeConfig(object): - fake_propagation_seconds = 0 - fake_config_key = 1 - fake_other_key = None - fake_file_path = None - - def setUp(self): - super(DNSAuthenticatorTest, self).setUp() - - self.config = DNSAuthenticatorTest._FakeConfig() - - self.auth = DNSAuthenticatorTest._FakeDNSAuthenticator(self.config, "fake") - - def test_perform(self): - self.auth.perform([self.achall]) - - self.auth._perform.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) - - def test_cleanup(self): - self.auth._attempt_cleanup = True - - self.auth.cleanup([self.achall]) - - self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) - - @test_util.patch_get_utility() - def test_prompt(self, mock_get_utility): - mock_display = mock_get_utility() - mock_display.input.side_effect = ((display_util.OK, "",), - (display_util.OK, "value",)) - - self.auth._configure("other_key", "") - self.assertEqual(self.auth.config.fake_other_key, "value") - - @test_util.patch_get_utility() - def test_prompt_canceled(self, mock_get_utility): - mock_display = mock_get_utility() - mock_display.input.side_effect = ((display_util.CANCEL, "c",),) - - self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "") - - @test_util.patch_get_utility() - def test_prompt_file(self, mock_get_utility): - path = os.path.join(self.tempdir, 'file.ini') - open(path, "wb").close() - - mock_display = mock_get_utility() - mock_display.directory_select.side_effect = ((display_util.OK, "",), - (display_util.OK, "not-a-file.ini",), - (display_util.OK, self.tempdir), - (display_util.OK, path,)) - - self.auth._configure_file("file_path", "") - self.assertEqual(self.auth.config.fake_file_path, path) - - @test_util.patch_get_utility() - def test_prompt_file_canceled(self, mock_get_utility): - mock_display = mock_get_utility() - mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),) - - self.assertRaises(errors.PluginError, self.auth._configure_file, "file_path", "") - - def test_configure_credentials(self): - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"fake_test": "value"}, path) - setattr(self.config, "fake_credentials", path) - - credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) - - self.assertEqual(credentials.conf("test"), "value") - - @test_util.patch_get_utility() - def test_prompt_credentials(self, mock_get_utility): - bad_path = os.path.join(self.tempdir, 'bad-file.ini') - dns_test_common.write({"fake_other": "other_value"}, bad_path) - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"fake_test": "value"}, path) - setattr(self.config, "fake_credentials", "") - - mock_display = mock_get_utility() - mock_display.directory_select.side_effect = ((display_util.OK, "",), - (display_util.OK, "not-a-file.ini",), - (display_util.OK, self.tempdir), - (display_util.OK, bad_path), - (display_util.OK, path,)) - - credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) - self.assertEqual(credentials.conf("test"), "value") - - -class CredentialsConfigurationTest(test_util.TempDirTestCase): - class _MockLoggingHandler(logging.Handler): - messages = None - - def __init__(self, *args, **kwargs): - self.reset() - logging.Handler.__init__(self, *args, **kwargs) - - def emit(self, record): - self.messages[record.levelname.lower()].append(record.getMessage()) - - def reset(self): - """Allows the handler to be reset between tests.""" - self.messages = collections.defaultdict(list) - - def test_valid_file(self): - path = os.path.join(self.tempdir, 'too-permissive-file.ini') - - dns_test_common.write({"test": "value", "other": 1}, path) - - credentials_configuration = dns_common.CredentialsConfiguration(path) - self.assertEqual("value", credentials_configuration.conf("test")) - self.assertEqual("1", credentials_configuration.conf("other")) - - def test_nonexistent_file(self): - path = os.path.join(self.tempdir, 'not-a-file.ini') - - self.assertRaises(errors.PluginError, dns_common.CredentialsConfiguration, path) - - def test_valid_file_with_unsafe_permissions(self): - log = self._MockLoggingHandler() - dns_common.logger.addHandler(log) - - path = os.path.join(self.tempdir, 'too-permissive-file.ini') - util.safe_open(path, "wb", 0o744).close() - - dns_common.CredentialsConfiguration(path) - - self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")])) - - -class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): - - def setUp(self): - super(CredentialsConfigurationRequireTest, self).setUp() - - self.path = os.path.join(self.tempdir, 'file.ini') - - def _write(self, values): - dns_test_common.write(values, self.path) - - def test_valid(self): - self._write({"test": "value", "other": 1}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - credentials_configuration.require({"test": "", "other": ""}) - - def test_valid_but_extra(self): - self._write({"test": "value", "other": 1}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - credentials_configuration.require({"test": ""}) - - def test_valid_empty(self): - self._write({}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - credentials_configuration.require({}) - - def test_missing(self): - self._write({}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) - - def test_blank(self): - self._write({"test": ""}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) - - def test_typo(self): - self._write({"tets": "typo!"}) - - credentials_configuration = dns_common.CredentialsConfiguration(self.path) - self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) - - -class DomainNameGuessTest(unittest.TestCase): - - def test_simple_case(self): - self.assertTrue( - 'example.com' in - dns_common.base_domain_name_guesses("example.com") - ) - - def test_sub_domain(self): - self.assertTrue( - 'example.com' in - dns_common.base_domain_name_guesses("foo.bar.baz.example.com") - ) - - def test_second_level_domain(self): - self.assertTrue( - 'example.co.uk' in - dns_common.base_domain_name_guesses("foo.bar.baz.example.co.uk") - ) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/dns_test_common.py b/certbot/plugins/dns_test_common.py deleted file mode 100644 index 0fc0c9a71..000000000 --- a/certbot/plugins/dns_test_common.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Base test class for DNS authenticators.""" - -import configobj -import josepy as jose -import mock -import six - -from acme import challenges - -from certbot import achallenges -from certbot.compat import filesystem -from certbot.tests import acme_util -from certbot.tests import util as test_util - -DOMAIN = 'example.com' -KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) - - -class BaseAuthenticatorTest(object): - """ - A base test class to reduce duplication between test code for DNS Authenticator Plugins. - - Assumes: - * That subclasses also subclass unittest.TestCase - * That the authenticator is stored as self.auth - """ - - achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.DNS01, domain=DOMAIN, account_key=KEY) - - def test_more_info(self): - # pylint: disable=no-member - self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) - - def test_get_chall_pref(self): - # pylint: disable=no-member - self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) - - def test_parser_arguments(self): - m = mock.MagicMock() - - # pylint: disable=no-member - self.auth.add_parser_arguments(m) - - m.assert_any_call('propagation-seconds', type=int, default=mock.ANY, help=mock.ANY) - - -def write(values, path): - """Write the specified values to a config file. - - :param dict values: A map of values to write. - :param str path: Where to write the values. - """ - - config = configobj.ConfigObj() - - for key in values: - config[key] = values[key] - - with open(path, "wb") as f: - config.write(outfile=f) - - filesystem.chmod(path, 0o600) diff --git a/certbot/plugins/dns_test_common_lexicon.py b/certbot/plugins/dns_test_common_lexicon.py deleted file mode 100644 index a221cf1bf..000000000 --- a/certbot/plugins/dns_test_common_lexicon.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Base test class for DNS authenticators built on Lexicon.""" - -import josepy as jose -import mock -from requests.exceptions import HTTPError, RequestException - -from certbot import errors -from certbot.plugins import dns_test_common -from certbot.tests import util as test_util - -DOMAIN = 'example.com' -KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) - -# These classes are intended to be subclassed/mixed in, so not all members are defined. -# pylint: disable=no-member - -class BaseLexiconAuthenticatorTest(dns_test_common.BaseAuthenticatorTest): - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - self.auth._attempt_cleanup = True # _attempt_cleanup | pylint: disable=protected-access - self.auth.cleanup([self.achall]) - - expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - -class BaseLexiconClientTest(object): - DOMAIN_NOT_FOUND = Exception('No domain found') - GENERIC_ERROR = RequestException - LOGIN_ERROR = HTTPError('400 Client Error: ...') - UNKNOWN_LOGIN_ERROR = HTTPError('500 Surprise! Error: ...') - - record_prefix = "_acme-challenge" - record_name = record_prefix + "." + DOMAIN - record_content = "bar" - - def test_add_txt_record(self): - self.client.add_txt_record(DOMAIN, self.record_name, self.record_content) - - self.provider_mock.create_record.assert_called_with(type='TXT', - name=self.record_name, - content=self.record_content) - - def test_add_txt_record_try_twice_to_find_domain(self): - self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, ''] - - self.client.add_txt_record(DOMAIN, self.record_name, self.record_content) - - self.provider_mock.create_record.assert_called_with(type='TXT', - name=self.record_name, - content=self.record_content) - - def test_add_txt_record_fail_to_find_domain(self): - self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, - self.DOMAIN_NOT_FOUND, - self.DOMAIN_NOT_FOUND,] - - self.assertRaises(errors.PluginError, - self.client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_fail_to_authenticate(self): - self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR - - self.assertRaises(errors.PluginError, - self.client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_fail_to_authenticate_with_unknown_error(self): - self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR - - self.assertRaises(errors.PluginError, - self.client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_error_finding_domain(self): - self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR - - self.assertRaises(errors.PluginError, - self.client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_error_adding_record(self): - self.provider_mock.create_record.side_effect = self.GENERIC_ERROR - - self.assertRaises(errors.PluginError, - self.client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record(self): - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - self.provider_mock.delete_record.assert_called_with(type='TXT', - name=self.record_name, - content=self.record_content) - - def test_del_txt_record_fail_to_find_domain(self): - self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, - self.DOMAIN_NOT_FOUND, - self.DOMAIN_NOT_FOUND, ] - - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_fail_to_authenticate(self): - self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR - - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_fail_to_authenticate_with_unknown_error(self): - self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR - - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_error_finding_domain(self): - self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR - - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_error_deleting_record(self): - self.provider_mock.delete_record.side_effect = self.GENERIC_ERROR - - self.client.del_txt_record(DOMAIN, self.record_name, self.record_content) diff --git a/certbot/plugins/enhancements.py b/certbot/plugins/enhancements.py deleted file mode 100644 index d917b0ea4..000000000 --- a/certbot/plugins/enhancements.py +++ /dev/null @@ -1,175 +0,0 @@ -"""New interface style Certbot enhancements""" -import abc -import six - -from certbot._internal import constants - -from acme.magic_typing import Dict, List, Any # pylint: disable=unused-import, no-name-in-module - -ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] -"""List of possible :class:`certbot.interfaces.IInstaller` -enhancements. - -List of expected options parameters: -- redirect: None -- ensure-http-header: name of header (i.e. Strict-Transport-Security) -- ocsp-stapling: certificate chain file path - -""" - -def enabled_enhancements(config): - """ - Generator to yield the enabled new style enhancements. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - """ - for enh in _INDEX: - if getattr(config, enh["cli_dest"]): - yield enh - -def are_requested(config): - """ - Checks if one or more of the requested enhancements are those of the new - enhancement interfaces. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - """ - return any(enabled_enhancements(config)) - -def are_supported(config, installer): - """ - Checks that all of the requested enhancements are supported by the - installer. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :returns: If all the requested enhancements are supported by the installer - :rtype: bool - """ - for enh in enabled_enhancements(config): - if not isinstance(installer, enh["class"]): - return False - return True - -def enable(lineage, domains, installer, config): - """ - Run enable method for each requested enhancement that is supported. - - :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert - - :param domains: List of domains in certificate to enhance - :type domains: str - - :param installer: Installer object - :type installer: interfaces.IInstaller - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - """ - for enh in enabled_enhancements(config): - getattr(installer, enh["enable_function"])(lineage, domains) - -def populate_cli(add): - """ - Populates the command line flags for certbot._internal.cli.HelpfulParser - - :param add: Add function of certbot._internal.cli.HelpfulParser - :type add: func - """ - for enh in _INDEX: - add(enh["cli_groups"], enh["cli_flag"], action=enh["cli_action"], - dest=enh["cli_dest"], default=enh["cli_flag_default"], - help=enh["cli_help"]) - - -@six.add_metaclass(abc.ABCMeta) -class AutoHSTSEnhancement(object): - """ - Enhancement interface that installer plugins can implement in order to - provide functionality that configures the software to have a - 'Strict-Transport-Security' with initially low max-age value that will - increase over time. - - The plugins implementing new style enhancements are responsible of handling - the saving of configuration checkpoints as well as calling possible restarts - of managed software themselves. For update_autohsts method, the installer may - have to call prepare() to finalize the plugin initialization. - - Methods: - enable_autohsts is called when the header is initially installed using a - low max-age value. - - update_autohsts is called every time when Certbot is run using 'renew' - verb. The max-age value should be increased over time using this method. - - deploy_autohsts is called for every lineage that has had its certificate - renewed. A long HSTS max-age value should be set here, as we should be - confident that the user is able to automatically renew their certificates. - - - """ - - @abc.abstractmethod - def update_autohsts(self, lineage, *args, **kwargs): - """ - Gets called for each lineage every time Certbot is run with 'renew' verb. - Implementation of this method should increase the max-age value. - - :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert - - .. note:: prepare() method inherited from `interfaces.IPlugin` might need - to be called manually within implementation of this interface method - to finalize the plugin initialization. - """ - - @abc.abstractmethod - def deploy_autohsts(self, lineage, *args, **kwargs): - """ - Gets called for a lineage when its certificate is successfully renewed. - Long max-age value should be set in implementation of this method. - - :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert - """ - - @abc.abstractmethod - def enable_autohsts(self, lineage, domains, *args, **kwargs): - """ - Enables the AutoHSTS enhancement, installing - Strict-Transport-Security header with a low initial value to be increased - over the subsequent runs of Certbot renew. - - :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert - - :param domains: List of domains in certificate to enhance - :type domains: str - """ - -# This is used to configure internal new style enhancements in Certbot. These -# enhancement interfaces need to be defined in this file. Please do not modify -# this list from plugin code. -_INDEX = [ - { - "name": "AutoHSTS", - "cli_help": "Gradually increasing max-age value for HTTP Strict Transport "+ - "Security security header", - "cli_flag": "--auto-hsts", - "cli_flag_default": constants.CLI_DEFAULTS["auto_hsts"], - "cli_groups": ["security", "enhance"], - "cli_dest": "auto_hsts", - "cli_action": "store_true", - "class": AutoHSTSEnhancement, - "updater_function": "update_autohsts", - "deployer_function": "deploy_autohsts", - "enable_function": "enable_autohsts" - } -] # type: List[Dict[str, Any]] diff --git a/certbot/plugins/enhancements_test.py b/certbot/plugins/enhancements_test.py deleted file mode 100644 index 86482184e..000000000 --- a/certbot/plugins/enhancements_test.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Tests for new style enhancements""" -import unittest -import mock - -from certbot.plugins import enhancements -from certbot._internal.plugins import null - -import certbot.tests.util as test_util - - -class EnhancementTest(test_util.ConfigTestCase): - """Tests for new style enhancements in certbot.plugins.enhancements""" - - def setUp(self): - super(EnhancementTest, self).setUp() - self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) - - - @test_util.patch_get_utility() - def test_enhancement_enabled_enhancements(self, _): - FAKEINDEX = [ - { - "name": "autohsts", - "cli_dest": "auto_hsts", - }, - { - "name": "somethingelse", - "cli_dest": "something", - } - ] - with mock.patch("certbot.plugins.enhancements._INDEX", FAKEINDEX): - self.config.auto_hsts = True - self.config.something = True - enabled = list(enhancements.enabled_enhancements(self.config)) - self.assertEqual(len(enabled), 2) - self.assertTrue([i for i in enabled if i["name"] == "autohsts"]) - self.assertTrue([i for i in enabled if i["name"] == "somethingelse"]) - - def test_are_requested(self): - 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.assertEqual( - len([i for i in enhancements.enabled_enhancements(self.config)]), 1) - self.assertTrue(enhancements.are_requested(self.config)) - - def test_are_supported(self): - self.config.auto_hsts = True - unsupported = null.Installer(self.config, "null") - self.assertTrue(enhancements.are_supported(self.config, self.mockinstaller)) - self.assertFalse(enhancements.are_supported(self.config, unsupported)) - - def test_enable(self): - self.config.auto_hsts = True - domains = ["example.com", "www.example.com"] - lineage = "lineage" - enhancements.enable(lineage, domains, self.mockinstaller, self.config) - self.assertTrue(self.mockinstaller.enable_autohsts.called) - self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0], - (lineage, domains)) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/certbot/plugins/manual_test.py b/certbot/plugins/manual_test.py deleted file mode 100644 index 8796c30f1..000000000 --- a/certbot/plugins/manual_test.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Tests for certbot._internal.plugins.manual""" -import unittest -import sys - -import mock -import six - -from acme import challenges - -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem -from certbot.tests import acme_util -from certbot.tests import util as test_util - - -class AuthenticatorTest(test_util.TempDirTestCase): - """Tests for certbot._internal.plugins.manual.Authenticator.""" - - def setUp(self): - super(AuthenticatorTest, self).setUp() - self.http_achall = acme_util.HTTP01_A - self.dns_achall = acme_util.DNS01_A - self.dns_achall_2 = acme_util.DNS01_A_2 - self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2] - for d in ["config_dir", "work_dir", "in_progress"]: - filesystem.mkdir(os.path.join(self.tempdir, d)) - # "backup_dir" and "temp_checkpoint_dir" get created in - # certbot.util.make_or_verify_dir() during the Reverter - # initialization. - self.config = mock.MagicMock( - http01_port=0, manual_auth_hook=None, manual_cleanup_hook=None, - manual_public_ip_logging_ok=False, noninteractive_mode=False, - validate_hooks=False, - config_dir=os.path.join(self.tempdir, "config_dir"), - work_dir=os.path.join(self.tempdir, "work_dir"), - backup_dir=os.path.join(self.tempdir, "backup_dir"), - temp_checkpoint_dir=os.path.join( - self.tempdir, "temp_checkpoint_dir"), - in_progress_dir=os.path.join(self.tempdir, "in_progess")) - - from certbot._internal.plugins.manual import Authenticator - self.auth = Authenticator(self.config, name='manual') - - def test_prepare_no_hook_noninteractive(self): - self.config.noninteractive_mode = True - self.assertRaises(errors.PluginError, self.auth.prepare) - - def test_prepare_bad_hook(self): - self.config.manual_auth_hook = os.path.abspath(os.sep) # is / on UNIX - self.config.validate_hooks = True - self.assertRaises(errors.HookCommandNotFound, self.auth.prepare) - - def test_more_info(self): - self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) - - def test_get_chall_pref(self): - self.assertEqual(self.auth.get_chall_pref('example.org'), - [challenges.HTTP01, challenges.DNS01]) - - @test_util.patch_get_utility() - def test_ip_logging_not_ok(self, mock_get_utility): - mock_get_utility().yesno.return_value = False - self.assertRaises(errors.PluginError, self.auth.perform, []) - - @test_util.patch_get_utility() - def test_ip_logging_ok(self, mock_get_utility): - mock_get_utility().yesno.return_value = True - self.auth.perform([]) - self.assertTrue(self.config.manual_public_ip_logging_ok) - - def test_script_perform(self): - self.config.manual_public_ip_logging_ok = True - self.config.manual_auth_hook = ( - '{0} -c "from __future__ import print_function;' - 'from certbot.compat import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' - 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' - 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' - .format(sys.executable)) - dns_expected = '{0}\n{1}\n{2}'.format( - self.dns_achall.domain, 'notoken', - self.dns_achall.validation(self.dns_achall.account_key)) - http_expected = '{0}\n{1}\n{2}'.format( - self.http_achall.domain, self.http_achall.chall.encode('token'), - self.http_achall.validation(self.http_achall.account_key)) - - self.assertEqual( - self.auth.perform(self.achalls), - [achall.response(achall.account_key) for achall in self.achalls]) - self.assertEqual( - self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'], - dns_expected) - self.assertEqual( - self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'], - http_expected) - - @test_util.patch_get_utility() - def test_manual_perform(self, mock_get_utility): - self.config.manual_public_ip_logging_ok = True - self.assertEqual( - self.auth.perform(self.achalls), - [achall.response(achall.account_key) for achall in self.achalls]) - for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list): - achall = self.achalls[i] - self.assertTrue( - achall.validation(achall.account_key) in args[0]) - self.assertFalse(kwargs['wrap']) - - def test_cleanup(self): - self.config.manual_public_ip_logging_ok = True - self.config.manual_auth_hook = ('{0} -c "import sys; sys.stdout.write(\'foo\')"' - .format(sys.executable)) - self.config.manual_cleanup_hook = '# cleanup' - self.auth.perform(self.achalls) - - for achall in self.achalls: - self.auth.cleanup([achall]) - self.assertEqual(os.environ['CERTBOT_AUTH_OUTPUT'], 'foo') - self.assertEqual(os.environ['CERTBOT_DOMAIN'], achall.domain) - if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)): - self.assertEqual( - os.environ['CERTBOT_VALIDATION'], - achall.validation(achall.account_key)) - if isinstance(achall.chall, challenges.HTTP01): - self.assertEqual( - os.environ['CERTBOT_TOKEN'], - achall.chall.encode('token')) - else: - self.assertFalse('CERTBOT_TOKEN' in os.environ) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/certbot/plugins/null_test.py b/certbot/plugins/null_test.py deleted file mode 100644 index 41cd45a93..000000000 --- a/certbot/plugins/null_test.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Tests for certbot._internal.plugins.null.""" -import unittest -import six - -import mock - - -class InstallerTest(unittest.TestCase): - """Tests for certbot._internal.plugins.null.Installer.""" - - def setUp(self): - from certbot._internal.plugins.null import Installer - self.installer = Installer(config=mock.MagicMock(), name="null") - - def test_it(self): - self.assertTrue(isinstance(self.installer.more_info(), six.string_types)) - self.assertEqual([], self.installer.get_all_names()) - self.assertEqual([], self.installer.supported_enhancements()) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/selection_test.py b/certbot/plugins/selection_test.py deleted file mode 100644 index 9de7f7941..000000000 --- a/certbot/plugins/selection_test.py +++ /dev/null @@ -1,220 +0,0 @@ -"""Tests for letsencrypt.plugins.selection""" -import sys -import unittest - -import mock -import zope.component - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot import interfaces -from certbot.compat import os -from certbot.display import util as display_util -from certbot._internal.plugins.disco import PluginsRegistry -from certbot.tests import util as test_util - - -class ConveniencePickPluginTest(unittest.TestCase): - """Tests for certbot._internal.plugins.selection.pick_*.""" - - def _test(self, fun, ifaces): - config = mock.Mock() - default = mock.Mock() - plugins = mock.Mock() - - with mock.patch("certbot._internal.plugins.selection.pick_plugin") as mock_p: - mock_p.return_value = "foo" - self.assertEqual("foo", fun(config, default, plugins, "Question?")) - mock_p.assert_called_once_with( - config, default, plugins, "Question?", ifaces) - - def test_authenticator(self): - from certbot._internal.plugins.selection import pick_authenticator - self._test(pick_authenticator, (interfaces.IAuthenticator,)) - - def test_installer(self): - from certbot._internal.plugins.selection import pick_installer - self._test(pick_installer, (interfaces.IInstaller,)) - - def test_configurator(self): - from certbot._internal.plugins.selection import pick_configurator - self._test(pick_configurator, - (interfaces.IAuthenticator, interfaces.IInstaller)) - - -class PickPluginTest(unittest.TestCase): - """Tests for certbot._internal.plugins.selection.pick_plugin.""" - - def setUp(self): - self.config = mock.Mock(noninteractive_mode=False) - self.default = None - self.reg = mock.MagicMock() - self.question = "Question?" - self.ifaces = [] # type: List[interfaces.IPlugin] - - def _call(self): - from certbot._internal.plugins.selection import pick_plugin - return pick_plugin(self.config, self.default, self.reg, - self.question, self.ifaces) - - def test_default_provided(self): - self.default = "foo" - self._call() - self.assertEqual(1, self.reg.filter.call_count) - - def test_no_default(self): - self._call() - self.assertEqual(1, self.reg.visible().ifaces.call_count) - - def test_no_candidate(self): - self.assertTrue(self._call() is None) - - def test_single(self): - plugin_ep = mock.MagicMock() - plugin_ep.init.return_value = "foo" - plugin_ep.misconfigured = False - - self.reg.visible().ifaces().verify().available.return_value = { - "bar": plugin_ep} - self.assertEqual("foo", self._call()) - - def test_single_misconfigured(self): - plugin_ep = mock.MagicMock() - plugin_ep.init.return_value = "foo" - plugin_ep.misconfigured = True - - self.reg.visible().ifaces().verify().available.return_value = { - "bar": plugin_ep} - self.assertTrue(self._call() is None) - - def test_multiple(self): - plugin_ep = mock.MagicMock() - plugin_ep.init.return_value = "foo" - self.reg.visible().ifaces().verify().available.return_value = { - "bar": plugin_ep, - "baz": plugin_ep, - } - with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: - mock_choose.return_value = plugin_ep - self.assertEqual("foo", self._call()) - mock_choose.assert_called_once_with( - [plugin_ep, plugin_ep], self.question) - - def test_choose_plugin_none(self): - self.reg.visible().ifaces().verify().available.return_value = { - "bar": None, - "baz": None, - } - - with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: - mock_choose.return_value = None - self.assertTrue(self._call() is None) - - -class ChoosePluginTest(unittest.TestCase): - """Tests for certbot._internal.plugins.selection.choose_plugin.""" - - def setUp(self): - zope.component.provideUtility(display_util.FileDisplay(sys.stdout, - False)) - self.mock_apache = mock.Mock( - description_with_name="a", misconfigured=True) - self.mock_apache.name = "apache" - self.mock_stand = mock.Mock( - description_with_name="s", misconfigured=False) - self.mock_stand.init().more_info.return_value = "standalone" - self.plugins = [ - self.mock_apache, - self.mock_stand, - ] - - def _call(self): - from certbot._internal.plugins.selection import choose_plugin - return choose_plugin(self.plugins, "Question?") - - @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") - def test_selection(self, mock_util): - mock_util().menu.side_effect = [(display_util.OK, 0), - (display_util.OK, 1)] - self.assertEqual(self.mock_stand, self._call()) - self.assertEqual(mock_util().notification.call_count, 1) - - @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") - def test_more_info(self, mock_util): - mock_util().menu.side_effect = [ - (display_util.OK, 1), - ] - - self.assertEqual(self.mock_stand, self._call()) - - @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") - def test_no_choice(self, mock_util): - mock_util().menu.return_value = (display_util.CANCEL, 0) - self.assertTrue(self._call() is None) - - @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") - def test_new_interaction_avoidance(self, mock_util): - mock_nginx = mock.Mock( - description_with_name="n", misconfigured=False) - mock_nginx.init().more_info.return_value = "nginx plugin" - mock_nginx.name = "nginx" - self.plugins[1] = mock_nginx - mock_util().menu.return_value = (display_util.CANCEL, 0) - - unset_cb_auto = os.environ.get("CERTBOT_AUTO") is None - if unset_cb_auto: - os.environ["CERTBOT_AUTO"] = "foo" - try: - self._call() - finally: - if unset_cb_auto: - del os.environ["CERTBOT_AUTO"] - - self.assertTrue("default" in mock_util().menu.call_args[1]) - -class GetUnpreparedInstallerTest(test_util.ConfigTestCase): - """Tests for certbot._internal.plugins.selection.get_unprepared_installer.""" - - def setUp(self): - super(GetUnpreparedInstallerTest, self).setUp() - self.mock_apache_fail_ep = mock.Mock( - description_with_name="afail") - self.mock_apache_fail_ep.name = "afail" - self.mock_apache_ep = mock.Mock( - description_with_name="apache") - self.mock_apache_ep.name = "apache" - self.mock_apache_plugin = mock.MagicMock() - self.mock_apache_ep.init.return_value = self.mock_apache_plugin - self.plugins = PluginsRegistry({ - "afail": self.mock_apache_fail_ep, - "apache": self.mock_apache_ep, - }) - - def _call(self): - from certbot._internal.plugins.selection import get_unprepared_installer - return get_unprepared_installer(self.config, self.plugins) - - def test_no_installer_defined(self): - self.config.configurator = None - self.assertEqual(self._call(), None) - - def test_no_available_installers(self): - self.config.configurator = "apache" - self.plugins = PluginsRegistry({}) - self.assertRaises(errors.PluginSelectionError, self._call) - - def test_get_plugin(self): - self.config.configurator = "apache" - installer = self._call() - self.assertTrue(installer is self.mock_apache_plugin) - - def test_multiple_installers_returned(self): - self.config.configurator = "apache" - # Two plugins with the same name - self.mock_apache_fail_ep.name = "apache" - self.assertRaises(errors.PluginSelectionError, self._call) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/standalone_test.py b/certbot/plugins/standalone_test.py deleted file mode 100644 index c9dabb8b4..000000000 --- a/certbot/plugins/standalone_test.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Tests for certbot._internal.plugins.standalone.""" -import socket -# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi -from socket import errno as socket_errors # type: ignore -import unittest - -import josepy as jose -import mock -import six - -import OpenSSL.crypto # pylint: disable=unused-import - -from acme import challenges -from acme import standalone as acme_standalone # pylint: disable=unused-import -from acme.magic_typing import Dict, Tuple, Set # pylint: disable=unused-import, no-name-in-module - -from certbot import achallenges -from certbot import errors - -from certbot.tests import acme_util -from certbot.tests import util as test_util - - -class ServerManagerTest(unittest.TestCase): - """Tests for certbot._internal.plugins.standalone.ServerManager.""" - - def setUp(self): - from certbot._internal.plugins.standalone import ServerManager - self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] - self.http_01_resources = {} \ - # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] - self.mgr = ServerManager(self.certs, self.http_01_resources) - - def test_init(self): - self.assertTrue(self.mgr.certs is self.certs) - self.assertTrue( - self.mgr.http_01_resources is self.http_01_resources) - - def _test_run_stop(self, challenge_type): - server = self.mgr.run(port=0, challenge_type=challenge_type) - port = server.getsocknames()[0][1] # pylint: disable=no-member - self.assertEqual(self.mgr.running(), {port: server}) - self.mgr.stop(port=port) - self.assertEqual(self.mgr.running(), {}) - - def test_run_stop_http_01(self): - self._test_run_stop(challenges.HTTP01) - - def test_run_idempotent(self): - server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) - port = server.getsocknames()[0][1] # pylint: disable=no-member - server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) - self.assertEqual(self.mgr.running(), {port: server}) - self.assertTrue(server is server2) - self.mgr.stop(port) - self.assertEqual(self.mgr.running(), {}) - - def test_run_bind_error(self): - some_server = socket.socket(socket.AF_INET6) - some_server.bind(("", 0)) - port = some_server.getsockname()[1] - maybe_another_server = socket.socket() - try: - maybe_another_server.bind(("", port)) - except socket.error: - pass - self.assertRaises( - errors.StandaloneBindError, self.mgr.run, port, - challenge_type=challenges.HTTP01) - self.assertEqual(self.mgr.running(), {}) - some_server.close() - maybe_another_server.close() - - -def get_open_port(): - """Gets an open port number from the OS.""" - open_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - open_socket.bind(("", 0)) - port = open_socket.getsockname()[1] - open_socket.close() - return port - - -class AuthenticatorTest(unittest.TestCase): - """Tests for certbot._internal.plugins.standalone.Authenticator.""" - - def setUp(self): - from certbot._internal.plugins.standalone import Authenticator - - self.config = mock.MagicMock(http01_port=get_open_port()) - self.auth = Authenticator(self.config, name="standalone") - self.auth.servers = mock.MagicMock() - - def test_more_info(self): - self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) - - def test_get_chall_pref(self): - self.assertEqual(self.auth.get_chall_pref(domain=None), - [challenges.HTTP01]) - - def test_perform(self): - achalls = self._get_achalls() - response = self.auth.perform(achalls) - - expected = [achall.response(achall.account_key) for achall in achalls] - self.assertEqual(response, expected) - - @test_util.patch_get_utility() - def test_perform_eaddrinuse_retry(self, mock_get_utility): - mock_utility = mock_get_utility() - errno = socket_errors.EADDRINUSE - error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) - self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()] - mock_yesno = mock_utility.yesno - mock_yesno.return_value = True - - self.test_perform() - self._assert_correct_yesno_call(mock_yesno) - - @test_util.patch_get_utility() - def test_perform_eaddrinuse_no_retry(self, mock_get_utility): - mock_utility = mock_get_utility() - mock_yesno = mock_utility.yesno - mock_yesno.return_value = False - - errno = socket_errors.EADDRINUSE - self.assertRaises(errors.PluginError, self._fail_perform, errno) - self._assert_correct_yesno_call(mock_yesno) - - def _assert_correct_yesno_call(self, mock_yesno): - yesno_args, yesno_kwargs = mock_yesno.call_args - self.assertTrue("in use" in yesno_args[0]) - self.assertFalse(yesno_kwargs.get("default", True)) - - def test_perform_eacces(self): - errno = socket_errors.EACCES - self.assertRaises(errors.PluginError, self._fail_perform, errno) - - def test_perform_unexpected_socket_error(self): - errno = socket_errors.ENOTCONN - self.assertRaises( - errors.StandaloneBindError, self._fail_perform, errno) - - def _fail_perform(self, errno): - error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) - self.auth.servers.run.side_effect = error - self.auth.perform(self._get_achalls()) - - @classmethod - def _get_achalls(cls): - domain = b'localhost' - key = jose.JWK.load(test_util.load_vector('rsa512_key.pem')) - http_01 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.HTTP01_P, domain=domain, account_key=key) - - return [http_01] - - def test_cleanup(self): - self.auth.servers.running.return_value = { - 1: "server1", - 2: "server2", - } - self.auth.served["server1"].add("chall1") - self.auth.served["server2"].update(["chall2", "chall3"]) - - self.auth.cleanup(["chall1"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": set(["chall2", "chall3"])}) - self.auth.servers.stop.assert_called_once_with(1) - - self.auth.servers.running.return_value = { - 2: "server2", - } - self.auth.cleanup(["chall2"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": set(["chall3"])}) - self.assertEqual(1, self.auth.servers.stop.call_count) - - self.auth.cleanup(["chall3"]) - self.assertEqual(self.auth.served, { - "server1": set(), "server2": set([])}) - self.auth.servers.stop.assert_called_with(2) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/storage.py b/certbot/plugins/storage.py deleted file mode 100644 index 294dfa0e8..000000000 --- a/certbot/plugins/storage.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Plugin storage class.""" -import json -import logging - -from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem - -logger = logging.getLogger(__name__) - - -class PluginStorage(object): - """Class implementing storage functionality for plugins""" - - def __init__(self, config, classkey): - """Initializes PluginStorage object storing required configuration - options. - - :param .configuration.NamespaceConfig config: Configuration object - :param str classkey: class name to use as root key in storage file - - """ - - self._config = config - self._classkey = classkey - self._initialized = False - self._data = None - self._storagepath = None - - def _initialize_storage(self): - """Initializes PluginStorage data and reads current state from the disk - if the storage json exists.""" - - self._storagepath = os.path.join(self._config.config_dir, ".pluginstorage.json") - self._load() - self._initialized = True - - def _load(self): - """Reads PluginStorage content from the disk to a dict structure - - :raises .errors.PluginStorageError: when unable to open or read the file - """ - data = dict() # type: Dict[str, Any] - filedata = "" - try: - with open(self._storagepath, 'r') as fh: - filedata = fh.read() - except IOError as e: - errmsg = "Could not read PluginStorage data file: {0} : {1}".format( - self._storagepath, str(e)) - if os.path.isfile(self._storagepath): - # Only error out if file exists, but cannot be read - logger.error(errmsg) - raise errors.PluginStorageError(errmsg) - try: - data = json.loads(filedata) - except ValueError: - if not filedata: - logger.debug("Plugin storage file %s was empty, no values loaded", - self._storagepath) - else: - errmsg = "PluginStorage file {0} is corrupted.".format( - self._storagepath) - logger.error(errmsg) - raise errors.PluginStorageError(errmsg) - self._data = data - - def save(self): - """Saves PluginStorage content to disk - - :raises .errors.PluginStorageError: when unable to serialize the data - or write it to the filesystem - """ - if not self._initialized: - errmsg = "Unable to save, no values have been added to PluginStorage." - logger.error(errmsg) - raise errors.PluginStorageError(errmsg) - - try: - serialized = json.dumps(self._data) - except TypeError as e: - errmsg = "Could not serialize PluginStorage data: {0}".format( - str(e)) - logger.error(errmsg) - raise errors.PluginStorageError(errmsg) - try: - with os.fdopen(filesystem.open( - self._storagepath, - os.O_WRONLY | os.O_CREAT | os.O_TRUNC, - 0o600), 'w') as fh: - fh.write(serialized) - except IOError as e: - errmsg = "Could not write PluginStorage data to file {0} : {1}".format( - self._storagepath, str(e)) - logger.error(errmsg) - raise errors.PluginStorageError(errmsg) - - def put(self, key, value): - """Put configuration value to PluginStorage - - :param str key: Key to store the value to - :param value: Data to store - """ - if not self._initialized: - self._initialize_storage() - - if not self._classkey in self._data.keys(): - self._data[self._classkey] = dict() - self._data[self._classkey][key] = value - - def fetch(self, key): - """Get configuration value from PluginStorage - - :param str key: Key to get value from the storage - - :raises KeyError: If the key doesn't exist in the storage - """ - if not self._initialized: - self._initialize_storage() - - return self._data[self._classkey][key] diff --git a/certbot/plugins/storage_test.py b/certbot/plugins/storage_test.py deleted file mode 100644 index 9d08cc7ef..000000000 --- a/certbot/plugins/storage_test.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Tests for certbot.plugins.storage.PluginStorage""" -import json -import unittest -import mock - -from certbot import errors - -from certbot.compat import os -from certbot.compat import filesystem -from certbot.plugins import common -from certbot.tests import util as test_util - - -class PluginStorageTest(test_util.ConfigTestCase): - """Test for certbot.plugins.storage.PluginStorage""" - - def setUp(self): - super(PluginStorageTest, self).setUp() - self.plugin_cls = common.Installer - filesystem.mkdir(self.config.config_dir) - with mock.patch("certbot.reverter.util"): - self.plugin = self.plugin_cls(config=self.config, name="mockplugin") - - def test_load_errors_cant_read(self): - with open(os.path.join(self.config.config_dir, - ".pluginstorage.json"), "w") as fh: - fh.write("dummy") - # When unable to read file that exists - mock_open = mock.mock_open() - mock_open.side_effect = IOError - self.plugin.storage.storagepath = os.path.join(self.config.config_dir, - ".pluginstorage.json") - with mock.patch("six.moves.builtins.open", mock_open): - with mock.patch('certbot.compat.os.path.isfile', return_value=True): - with mock.patch("certbot.reverter.util"): - self.assertRaises(errors.PluginStorageError, - self.plugin.storage._load) # pylint: disable=protected-access - - def test_load_errors_empty(self): - with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), "w") as fh: - fh.write('') - with mock.patch("certbot.plugins.storage.logger.debug") as mock_log: - # Should not error out but write a debug log line instead - with mock.patch("certbot.reverter.util"): - nocontent = self.plugin_cls(self.config, "mockplugin") - self.assertRaises(KeyError, - nocontent.storage.fetch, "value") - self.assertTrue(mock_log.called) - self.assertTrue("no values loaded" in mock_log.call_args[0][0]) - - def test_load_errors_corrupted(self): - with open(os.path.join(self.config.config_dir, - ".pluginstorage.json"), "w") as fh: - fh.write('invalid json') - with mock.patch("certbot.plugins.storage.logger.error") as mock_log: - with mock.patch("certbot.reverter.util"): - corrupted = self.plugin_cls(self.config, "mockplugin") - self.assertRaises(errors.PluginError, - corrupted.storage.fetch, - "value") - self.assertTrue("is corrupted" in mock_log.call_args[0][0]) - - def test_save_errors_cant_serialize(self): - with mock.patch("certbot.plugins.storage.logger.error") as mock_log: - # Set data as something that can't be serialized - self.plugin.storage._initialized = True # pylint: disable=protected-access - self.plugin.storage.storagepath = "/tmp/whatever" - self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access - self.assertRaises(errors.PluginStorageError, - self.plugin.storage.save) - self.assertTrue("Could not serialize" in mock_log.call_args[0][0]) - - def test_save_errors_unable_to_write_file(self): - mock_open = mock.mock_open() - mock_open.side_effect = IOError - with mock.patch("certbot.compat.filesystem.open", mock_open): - with mock.patch("certbot.plugins.storage.logger.error") as mock_log: - self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access - self.plugin.storage._initialized = True # pylint: disable=protected-access - self.assertRaises(errors.PluginStorageError, - self.plugin.storage.save) - self.assertTrue("Could not write" in mock_log.call_args[0][0]) - - def test_save_uninitialized(self): - with mock.patch("certbot.reverter.util"): - self.assertRaises(errors.PluginStorageError, - self.plugin_cls(self.config, "x").storage.save) - - def test_namespace_isolation(self): - with mock.patch("certbot.reverter.util"): - plugin1 = self.plugin_cls(self.config, "first") - plugin2 = self.plugin_cls(self.config, "second") - plugin1.storage.put("first_key", "first_value") - self.assertRaises(KeyError, - plugin2.storage.fetch, "first_key") - self.assertRaises(KeyError, - plugin2.storage.fetch, "first") - self.assertEqual(plugin1.storage.fetch("first_key"), "first_value") - - - def test_saved_state(self): - self.plugin.storage.put("testkey", "testvalue") - # Write to disk - self.plugin.storage.save() - with mock.patch("certbot.reverter.util"): - another = self.plugin_cls(self.config, "mockplugin") - self.assertEqual(another.storage.fetch("testkey"), "testvalue") - - with open(os.path.join(self.config.config_dir, - ".pluginstorage.json"), 'r') as fh: - psdata = fh.read() - psjson = json.loads(psdata) - self.assertTrue("mockplugin" in psjson.keys()) - self.assertEqual(len(psjson), 1) - self.assertEqual(psjson["mockplugin"]["testkey"], "testvalue") - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/util.py b/certbot/plugins/util.py deleted file mode 100644 index 87eb45fe9..000000000 --- a/certbot/plugins/util.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Plugin utilities.""" -import logging - -from certbot import util -from certbot.compat import os -from certbot.compat.misc import STANDARD_BINARY_DIRS - -logger = logging.getLogger(__name__) - - -def get_prefixes(path): - """Retrieves all possible path prefixes of a path, in descending order - of length. For instance, - (linux) /a/b/c returns ['/a/b/c', '/a/b', '/a', '/'] - (windows) C:\\a\\b\\c returns ['C:\\a\\b\\c', 'C:\\a\\b', 'C:\\a', 'C:'] - :param str path: the path to break into prefixes - - :returns: all possible path prefixes of given path in descending order - :rtype: `list` of `str` - """ - prefix = os.path.normpath(path) - prefixes = [] - while prefix: - prefixes.append(prefix) - prefix, _ = os.path.split(prefix) - # break once we hit the root path - if prefix == prefixes[-1]: - break - return prefixes - - -def path_surgery(cmd): - """Attempt to perform PATH surgery to find cmd - - Mitigates https://github.com/certbot/certbot/issues/1833 - - :param str cmd: the command that is being searched for in the PATH - - :returns: True if the operation succeeded, False otherwise - """ - path = os.environ["PATH"] - added = [] - for d in STANDARD_BINARY_DIRS: - if d not in path: - path += os.pathsep + d - added.append(d) - - if any(added): - logger.debug("Can't find %s, attempting PATH mitigation by adding %s", - cmd, os.pathsep.join(added)) - os.environ["PATH"] = path - - if util.exe_exists(cmd): - return True - expanded = " expanded" if any(added) else "" - logger.debug("Failed to find executable %s in%s PATH: %s", cmd, - expanded, path) - return False diff --git a/certbot/plugins/util_test.py b/certbot/plugins/util_test.py deleted file mode 100644 index c41e55222..000000000 --- a/certbot/plugins/util_test.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Tests for certbot.plugins.util.""" -import unittest - -import mock - -from certbot.compat import os - - -class GetPrefixTest(unittest.TestCase): - """Tests for certbot.plugins.get_prefixes.""" - def test_get_prefix(self): - from certbot.plugins.util import get_prefixes - self.assertEqual( - get_prefixes('/a/b/c'), - [os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']]) - self.assertEqual(get_prefixes('/'), [os.path.normpath('/')]) - self.assertEqual(get_prefixes('a'), ['a']) - - -class PathSurgeryTest(unittest.TestCase): - """Tests for certbot.plugins.path_surgery.""" - - @mock.patch("certbot.plugins.util.logger.debug") - def test_path_surgery(self, mock_debug): - from certbot.plugins.util import path_surgery - all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"} - with mock.patch.dict('os.environ', all_path): - with mock.patch('certbot.util.exe_exists') as mock_exists: - mock_exists.return_value = True - self.assertEqual(path_surgery("eg"), True) - self.assertEqual(mock_debug.call_count, 0) - self.assertEqual(os.environ["PATH"], all_path["PATH"]) - if os.name != 'nt': - # This part is specific to Linux since on Windows no PATH surgery is ever done. - no_path = {"PATH": "/tmp/"} - with mock.patch.dict('os.environ', no_path): - path_surgery("thingy") - self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1) - self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) - self.assertTrue("/usr/local/bin" in os.environ["PATH"]) - self.assertTrue("/tmp" in os.environ["PATH"]) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py deleted file mode 100644 index 70501f812..000000000 --- a/certbot/plugins/webroot_test.py +++ /dev/null @@ -1,318 +0,0 @@ -"""Tests for certbot._internal.plugins.webroot.""" - -from __future__ import print_function - -import argparse -import errno -import json -import shutil -import tempfile -import unittest - -import josepy as jose -import mock -import six - -from acme import challenges - -from certbot import achallenges -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem -from certbot.display import util as display_util -from certbot.tests import acme_util -from certbot.tests import util as test_util - -KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) - - -class AuthenticatorTest(unittest.TestCase): - """Tests for certbot._internal.plugins.webroot.Authenticator.""" - - achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) - - def setUp(self): - from certbot._internal.plugins.webroot import Authenticator - # On Linux directories created by tempfile.mkdtemp inherit their permissions from their - # parent directory. So the actual permissions are inconsistent over various tests env. - # To circumvent this, a dedicated sub-workspace is created under the workspace, using - # filesystem.mkdir to get consistent permissions. - self.workspace = tempfile.mkdtemp() - self.path = os.path.join(self.workspace, 'webroot') - filesystem.mkdir(self.path) - self.partial_root_challenge_path = os.path.join( - self.path, ".well-known") - self.root_challenge_path = os.path.join( - self.path, ".well-known", "acme-challenge") - self.validation_path = os.path.join( - self.root_challenge_path, - "ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ") - self.config = mock.MagicMock(webroot_path=self.path, - webroot_map={"thing.com": self.path}) - self.auth = Authenticator(self.config, "webroot") - - def tearDown(self): - shutil.rmtree(self.path) - - def test_more_info(self): - more_info = self.auth.more_info() - self.assertTrue(isinstance(more_info, six.string_types)) - self.assertTrue(self.path in more_info) - - def test_add_parser_arguments(self): - add = mock.MagicMock() - self.auth.add_parser_arguments(add) - self.assertEqual(2, add.call_count) - - def test_prepare(self): - self.auth.prepare() # shouldn't raise any exceptions - - @test_util.patch_get_utility() - def test_webroot_from_list(self, mock_get_utility): - self.config.webroot_path = [] - self.config.webroot_map = {"otherthing.com": self.path} - mock_display = mock_get_utility() - mock_display.menu.return_value = (display_util.OK, 1,) - - self.auth.perform([self.achall]) - self.assertTrue(mock_display.menu.called) - for call in mock_display.menu.call_args_list: - self.assertTrue(self.achall.domain in call[0][0]) - self.assertTrue(all( - webroot in call[0][1] - for webroot in six.itervalues(self.config.webroot_map))) - self.assertEqual(self.config.webroot_map[self.achall.domain], - self.path) - - @test_util.patch_get_utility() - def test_webroot_from_list_help_and_cancel(self, mock_get_utility): - self.config.webroot_path = [] - self.config.webroot_map = {"otherthing.com": self.path} - - mock_display = mock_get_utility() - mock_display.menu.side_effect = ((display_util.CANCEL, -1),) - self.assertRaises(errors.PluginError, self.auth.perform, [self.achall]) - self.assertTrue(mock_display.menu.called) - for call in mock_display.menu.call_args_list: - self.assertTrue(self.achall.domain in call[0][0]) - self.assertTrue(all( - webroot in call[0][1] - for webroot in six.itervalues(self.config.webroot_map))) - - @test_util.patch_get_utility() - def test_new_webroot(self, mock_get_utility): - self.config.webroot_path = [] - self.config.webroot_map = {"something.com": self.path} - - mock_display = mock_get_utility() - mock_display.menu.return_value = (display_util.OK, 0,) - with mock.patch('certbot.display.ops.validated_directory') as m: - m.side_effect = ((display_util.CANCEL, -1), - (display_util.OK, self.path,)) - - self.auth.perform([self.achall]) - - self.assertEqual(self.config.webroot_map[self.achall.domain], self.path) - - @test_util.patch_get_utility() - def test_new_webroot_empty_map_cancel(self, mock_get_utility): - self.config.webroot_path = [] - self.config.webroot_map = {} - - mock_display = mock_get_utility() - mock_display.menu.return_value = (display_util.OK, 0,) - with mock.patch('certbot.display.ops.validated_directory') as m: - m.return_value = (display_util.CANCEL, -1) - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) - - def test_perform_missing_root(self): - self.config.webroot_path = None - self.config.webroot_map = {} - self.assertRaises(errors.PluginError, self.auth.perform, []) - - def test_perform_reraises_other_errors(self): - self.auth.full_path = os.path.join(self.path, "null") - permission_canary = os.path.join(self.path, "rnd") - with open(permission_canary, "w") as f: - f.write("thingimy") - filesystem.chmod(self.path, 0o000) - try: - open(permission_canary, "r") - print("Warning, running tests as root skips permissions tests...") - except IOError: - # ok, permissions work, test away... - self.assertRaises(errors.PluginError, self.auth.perform, []) - filesystem.chmod(self.path, 0o700) - - @mock.patch("certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode") - def test_failed_chown(self, mock_ownership): - mock_ownership.side_effect = OSError(errno.EACCES, "msg") - self.auth.perform([self.achall]) # exception caught and logged - - @test_util.patch_get_utility() - def test_perform_new_webroot_not_in_map(self, mock_get_utility): - new_webroot = tempfile.mkdtemp() - self.config.webroot_path = [] - self.config.webroot_map = {"whatever.com": self.path} - mock_display = mock_get_utility() - mock_display.menu.side_effect = ((display_util.OK, 0), - (display_util.OK, new_webroot)) - achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.HTTP01_P, domain="something.com", account_key=KEY) - with mock.patch('certbot.display.ops.validated_directory') as m: - m.return_value = (display_util.OK, new_webroot,) - self.auth.perform([achall]) - self.assertEqual(self.config.webroot_map[achall.domain], new_webroot) - - def test_perform_permissions(self): - self.auth.prepare() - - # Remove exec bit from permission check, so that it - # matches the file - self.auth.perform([self.achall]) - self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) - - # Check permissions of the directories - for dirpath, dirnames, _ in os.walk(self.path): - for directory in dirnames: - full_path = os.path.join(dirpath, directory) - self.assertTrue(filesystem.check_mode(full_path, 0o755)) - - self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path)) - - def test_perform_cleanup(self): - self.auth.prepare() - responses = self.auth.perform([self.achall]) - self.assertEqual(1, len(responses)) - self.assertTrue(os.path.exists(self.validation_path)) - with open(self.validation_path) as validation_f: - validation = validation_f.read() - self.assertTrue( - challenges.KeyAuthorizationChallengeResponse( - key_authorization=validation).verify( - self.achall.chall, KEY.public_key())) - - self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) - self.assertFalse(os.path.exists(self.partial_root_challenge_path)) - - def test_perform_cleanup_existing_dirs(self): - filesystem.mkdir(self.partial_root_challenge_path) - self.auth.prepare() - self.auth.perform([self.achall]) - self.auth.cleanup([self.achall]) - - # Ensure we don't "clean up" directories that previously existed - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) - - def test_perform_cleanup_multiple_challenges(self): - bingo_achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=b"bingo"), "pending"), - domain="thing.com", account_key=KEY) - - bingo_validation_path = "YmluZ28" - filesystem.mkdir(self.partial_root_challenge_path) - self.auth.prepare() - self.auth.perform([bingo_achall, self.achall]) - - self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(bingo_validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) - self.auth.cleanup([bingo_achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertFalse(os.path.exists(self.root_challenge_path)) - - def test_cleanup_leftovers(self): - self.auth.prepare() - self.auth.perform([self.achall]) - - leftover_path = os.path.join(self.root_challenge_path, 'leftover') - filesystem.mkdir(leftover_path) - - self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) - - os.rmdir(leftover_path) - - @mock.patch('certbot.compat.os.rmdir') - def test_cleanup_failure(self, mock_rmdir): - self.auth.prepare() - self.auth.perform([self.achall]) - - os_error = OSError() - os_error.errno = errno.EACCES - mock_rmdir.side_effect = os_error - - self.auth.cleanup([self.achall]) - self.assertFalse(os.path.exists(self.validation_path)) - self.assertTrue(os.path.exists(self.root_challenge_path)) - - -class WebrootActionTest(unittest.TestCase): - """Tests for webroot argparse actions.""" - - achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) - - def setUp(self): - from certbot._internal.plugins.webroot import Authenticator - self.path = tempfile.mkdtemp() - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--domains", - action="append", default=[]) - Authenticator.inject_parser_options(self.parser, "webroot") - - def test_webroot_map_action(self): - args = self.parser.parse_args( - ["--webroot-map", json.dumps({'thing.com': self.path})]) - self.assertEqual(args.webroot_map["thing.com"], self.path) - - def test_domain_before_webroot(self): - args = self.parser.parse_args( - "-d {0} -w {1}".format(self.achall.domain, self.path).split()) - config = self._get_config_after_perform(args) - self.assertEqual(config.webroot_map[self.achall.domain], self.path) - - def test_domain_before_webroot_error(self): - self.assertRaises(errors.PluginError, self.parser.parse_args, - "-d foo -w bar -w baz".split()) - self.assertRaises(errors.PluginError, self.parser.parse_args, - "-d foo -w bar -d baz -w qux".split()) - - def test_multiwebroot(self): - args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( - self.path, self.achall.domain, tempfile.mkdtemp()).split()) - self.assertEqual(args.webroot_map[self.achall.domain], self.path) - config = self._get_config_after_perform(args) - self.assertEqual( - config.webroot_map[self.achall.domain], self.path) - - def test_webroot_map_partial_without_perform(self): - # This test acknowledges the fact that webroot_map content will be partial if webroot - # plugin perform method is not invoked (corner case when all auths are already valid). - # To not be a problem, the webroot_path must always been conserved during renew. - # This condition is challenged by: - # certbot.tests.renewal_tests::RenewalTest::test_webroot_params_conservation - # See https://github.com/certbot/certbot/pull/7095 for details. - other_webroot_path = tempfile.mkdtemp() - args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( - self.path, self.achall.domain, other_webroot_path).split()) - self.assertEqual(args.webroot_map, {self.achall.domain: self.path}) - self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) - - def _get_config_after_perform(self, config): - from certbot._internal.plugins.webroot import Authenticator - auth = Authenticator(config, "webroot") - auth.perform([self.achall]) - return auth.config - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot/readthedocs.org.requirements.txt b/certbot/readthedocs.org.requirements.txt new file mode 100644 index 000000000..f3964e8a7 --- /dev/null +++ b/certbot/readthedocs.org.requirements.txt @@ -0,0 +1,11 @@ +# readthedocs.org gives no way to change the install command to "pip +# install -e certbot[docs]" (that would in turn install documentation +# dependencies), but it allows to specify a requirements.txt file at +# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) + +# Although ReadTheDocs certainly doesn't need to install the project +# in --editable mode (-e), just "pip install .[docs]" does not work as +# expected and "pip install -e certbot[docs]" must be used instead + +-e acme +-e certbot[docs] diff --git a/certbot/reverter.py b/certbot/reverter.py deleted file mode 100644 index 9118fef94..000000000 --- a/certbot/reverter.py +++ /dev/null @@ -1,535 +0,0 @@ -"""Reverter class saves configuration checkpoints and allows for recovery.""" -import csv -import glob -import logging -import shutil -import sys -import time -import traceback - -import six - -from certbot._internal import constants -from certbot import errors -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem - -logger = logging.getLogger(__name__) - - -class Reverter(object): - """Reverter Class - save and revert configuration checkpoints. - - This class can be used by the plugins, especially Installers, to - undo changes made to the user's system. Modifications to files and - commands to do undo actions taken by the plugin should be registered - with this class before the action is taken. - - Once a change has been registered with this class, there are three - states the change can be in. First, the change can be a temporary - change. This should be used for changes that will soon be reverted, - such as config changes for the purpose of solving a challenge. - Changes are added to this state through calls to - :func:`~add_to_temp_checkpoint` and reverted when - :func:`~revert_temporary_config` or :func:`~recovery_routine` is - called. - - The second state a change can be in is in progress. These changes - are not temporary, however, they also have not been finalized in a - checkpoint. A change must become in progress before it can be - finalized. Changes are added to this state through calls to - :func:`~add_to_checkpoint` and reverted when - :func:`~recovery_routine` is called. - - The last state a change can be in is finalized in a checkpoint. A - change is put into this state by first becoming an in progress - change and then calling :func:`~finalize_checkpoint`. Changes - in this state can be reverted through calls to - :func:`~rollback_checkpoints`. - - As a final note, creating new files and registering undo commands - are handled specially and use the methods - :func:`~register_file_creation` and :func:`~register_undo_command` - respectively. Both of these methods can be used to create either - temporary or in progress changes. - - .. note:: Consider moving everything over to CSV format. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - """ - def __init__(self, config): - self.config = config - - util.make_or_verify_dir( - config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) - - def revert_temporary_config(self): - """Reload users original configuration files after a temporary save. - - This function should reinstall the users original configuration files - for all saves with temporary=True - - :raises .ReverterError: when unable to revert config - - """ - if os.path.isdir(self.config.temp_checkpoint_dir): - try: - self._recover_checkpoint(self.config.temp_checkpoint_dir) - except errors.ReverterError: - # We have a partial or incomplete recovery - logger.critical( - "Incomplete or failed recovery for %s", - self.config.temp_checkpoint_dir, - ) - raise errors.ReverterError("Unable to revert temporary config") - - def rollback_checkpoints(self, rollback=1): - """Revert 'rollback' number of configuration checkpoints. - - :param int rollback: Number of checkpoints to reverse. A str num will be - cast to an integer. So "2" is also acceptable. - - :raises .ReverterError: - if there is a problem with the input or if the function is - unable to correctly revert the configuration checkpoints - - """ - try: - rollback = int(rollback) - except ValueError: - logger.error("Rollback argument must be a positive integer") - raise errors.ReverterError("Invalid Input") - # Sanity check input - if rollback < 0: - logger.error("Rollback argument must be a positive integer") - raise errors.ReverterError("Invalid Input") - - backups = os.listdir(self.config.backup_dir) - backups.sort() - - if not backups: - logger.warning( - "Certbot hasn't modified your configuration, so rollback " - "isn't available.") - elif len(backups) < rollback: - logger.warning("Unable to rollback %d checkpoints, only %d exist", - rollback, len(backups)) - - while rollback > 0 and backups: - cp_dir = os.path.join(self.config.backup_dir, backups.pop()) - try: - self._recover_checkpoint(cp_dir) - except errors.ReverterError: - logger.critical("Failed to load checkpoint during rollback") - raise errors.ReverterError( - "Unable to load checkpoint during rollback") - rollback -= 1 - - def add_to_temp_checkpoint(self, save_files, save_notes): - """Add files to temporary checkpoint. - - :param set save_files: set of filepaths to save - :param str save_notes: notes about changes during the save - - """ - self._add_to_checkpoint_dir( - self.config.temp_checkpoint_dir, save_files, save_notes) - - def add_to_checkpoint(self, save_files, save_notes): - """Add files to a permanent checkpoint. - - :param set save_files: set of filepaths to save - :param str save_notes: notes about changes during the save - - """ - # Check to make sure we are not overwriting a temp file - self._check_tempfile_saves(save_files) - self._add_to_checkpoint_dir( - self.config.in_progress_dir, save_files, save_notes) - - def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes): - """Add save files to checkpoint directory. - - :param str cp_dir: Checkpoint directory filepath - :param set save_files: set of files to save - :param str save_notes: notes about changes made during the save - - :raises IOError: if unable to open cp_dir + FILEPATHS file - :raises .ReverterError: if unable to add checkpoint - - """ - util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) - - op_fd, existing_filepaths = self._read_and_append( - os.path.join(cp_dir, "FILEPATHS")) - - idx = len(existing_filepaths) - - for filename in save_files: - # No need to copy/index already existing files - # The oldest copy already exists in the directory... - if filename not in existing_filepaths: - # Tag files with index so multiple files can - # have the same filename - logger.debug("Creating backup of %s", filename) - try: - shutil.copy2(filename, os.path.join( - cp_dir, os.path.basename(filename) + "_" + str(idx))) - op_fd.write('{0}\n'.format(filename)) - # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 - except IOError: - op_fd.close() - logger.error( - "Unable to add file %s to checkpoint %s", - filename, cp_dir) - raise errors.ReverterError( - "Unable to add file {0} to checkpoint " - "{1}".format(filename, cp_dir)) - idx += 1 - op_fd.close() - - with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: - notes_fd.write(save_notes) - - def _read_and_append(self, filepath): # pylint: disable=no-self-use - """Reads the file lines and returns a file obj. - - Read the file returning the lines, and a pointer to the end of the file. - - """ - # Open up filepath differently depending on if it already exists - if os.path.isfile(filepath): - op_fd = open(filepath, "r+") - lines = op_fd.read().splitlines() - else: - lines = [] - op_fd = open(filepath, "w") - - return op_fd, lines - - def _recover_checkpoint(self, cp_dir): - """Recover a specific checkpoint. - - Recover a specific checkpoint provided by cp_dir - Note: this function does not reload augeas. - - :param str cp_dir: checkpoint directory file path - - :raises errors.ReverterError: If unable to recover checkpoint - - """ - # Undo all commands - if os.path.isfile(os.path.join(cp_dir, "COMMANDS")): - self._run_undo_commands(os.path.join(cp_dir, "COMMANDS")) - # Revert all changed files - if os.path.isfile(os.path.join(cp_dir, "FILEPATHS")): - try: - with open(os.path.join(cp_dir, "FILEPATHS")) as paths_fd: - filepaths = paths_fd.read().splitlines() - for idx, path in enumerate(filepaths): - shutil.copy2(os.path.join( - cp_dir, - os.path.basename(path) + "_" + str(idx)), path) - except (IOError, OSError): - # This file is required in all checkpoints. - logger.error("Unable to recover files from %s", cp_dir) - raise errors.ReverterError( - "Unable to recover files from %s" % cp_dir) - - # Remove any newly added files if they exist - self._remove_contained_files(os.path.join(cp_dir, "NEW_FILES")) - - try: - shutil.rmtree(cp_dir) - except OSError: - logger.error("Unable to remove directory: %s", cp_dir) - raise errors.ReverterError( - "Unable to remove directory: %s" % cp_dir) - - def _run_undo_commands(self, filepath): # pylint: disable=no-self-use - """Run all commands in a file.""" - # NOTE: csv module uses native strings. That is, bytes on Python 2 and - # unicode on Python 3 - # It is strongly advised to set newline = '' on Python 3 with CSV, - # and it fixes problems on Windows. - kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} - with open(filepath, 'r', **kwargs) as csvfile: # type: ignore - csvreader = csv.reader(csvfile) - for command in reversed(list(csvreader)): - try: - util.run_script(command) - except errors.SubprocessError: - logger.error( - "Unable to run undo command: %s", " ".join(command)) - - def _check_tempfile_saves(self, save_files): - """Verify save isn't overwriting any temporary files. - - :param set save_files: Set of files about to be saved. - - :raises certbot.errors.ReverterError: - when save is attempting to overwrite a temporary file. - - """ - protected_files = [] - - # Get temp modified files - temp_path = os.path.join(self.config.temp_checkpoint_dir, "FILEPATHS") - if os.path.isfile(temp_path): - with open(temp_path, "r") as protected_fd: - protected_files.extend(protected_fd.read().splitlines()) - - # Get temp new files - new_path = os.path.join(self.config.temp_checkpoint_dir, "NEW_FILES") - if os.path.isfile(new_path): - with open(new_path, "r") as protected_fd: - protected_files.extend(protected_fd.read().splitlines()) - - # Verify no save_file is in protected_files - for filename in protected_files: - if filename in save_files: - raise errors.ReverterError( - "Attempting to overwrite challenge " - "file - %s" % filename) - - def register_file_creation(self, temporary, *files): - r"""Register the creation of all files during certbot execution. - - Call this method before writing to the file to make sure that the - file will be cleaned up if the program exits unexpectedly. - (Before a save occurs) - - :param bool temporary: If the file creation registry is for - a temp or permanent save. - :param \*files: file paths (str) to be registered - - :raises certbot.errors.ReverterError: If - call does not contain necessary parameters or if the file creation - is unable to be registered. - - """ - # Make sure some files are provided... as this is an error - # Made this mistake in my initial implementation of apache.dvsni.py - if not files: - raise errors.ReverterError("Forgot to provide files to registration call") - - cp_dir = self._get_cp_dir(temporary) - - # Append all new files (that aren't already registered) - new_fd = None - try: - new_fd, ex_files = self._read_and_append(os.path.join(cp_dir, "NEW_FILES")) - - for path in files: - if path not in ex_files: - new_fd.write("{0}\n".format(path)) - except (IOError, OSError): - logger.error("Unable to register file creation(s) - %s", files) - raise errors.ReverterError( - "Unable to register file creation(s) - {0}".format(files)) - finally: - if new_fd is not None: - new_fd.close() - - def register_undo_command(self, temporary, command): - """Register a command to be run to undo actions taken. - - .. warning:: This function does not enforce order of operations in terms - of file modification vs. command registration. All undo commands - are run first before all normal files are reverted to their previous - state. If you need to maintain strict order, you may create - checkpoints before and after the the command registration. This - function may be improved in the future based on demand. - - :param bool temporary: Whether the command should be saved in the - IN_PROGRESS or TEMPORARY checkpoints. - :param command: Command to be run. - :type command: list of str - - """ - commands_fp = os.path.join(self._get_cp_dir(temporary), "COMMANDS") - command_file = None - # It is strongly advised to set newline = '' on Python 3 with CSV, - # and it fixes problems on Windows. - kwargs = {'newline': ''} if sys.version_info[0] > 2 else {} - try: - if os.path.isfile(commands_fp): - command_file = open(commands_fp, "a", **kwargs) # type: ignore - else: - command_file = open(commands_fp, "w", **kwargs) # type: ignore - - csvwriter = csv.writer(command_file) - csvwriter.writerow(command) - - except (IOError, OSError): - logger.error("Unable to register undo command") - raise errors.ReverterError( - "Unable to register undo command.") - finally: - if command_file is not None: - command_file.close() - - def _get_cp_dir(self, temporary): - """Return the proper reverter directory.""" - if temporary: - cp_dir = self.config.temp_checkpoint_dir - else: - cp_dir = self.config.in_progress_dir - - util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) - - return cp_dir - - def recovery_routine(self): - """Revert configuration to most recent finalized checkpoint. - - Remove all changes (temporary and permanent) that have not been - finalized. This is useful to protect against crashes and other - execution interruptions. - - :raises .errors.ReverterError: If unable to recover the configuration - - """ - # First, any changes found in IConfig.temp_checkpoint_dir are removed, - # then IN_PROGRESS changes are removed The order is important. - # IN_PROGRESS is unable to add files that are already added by a TEMP - # change. Thus TEMP must be rolled back first because that will be the - # 'latest' occurrence of the file. - self.revert_temporary_config() - if os.path.isdir(self.config.in_progress_dir): - try: - self._recover_checkpoint(self.config.in_progress_dir) - except errors.ReverterError: - # We have a partial or incomplete recovery - logger.critical("Incomplete or failed recovery for IN_PROGRESS " - "checkpoint - %s", - self.config.in_progress_dir) - raise errors.ReverterError( - "Incomplete or failed recovery for IN_PROGRESS checkpoint " - "- %s" % self.config.in_progress_dir) - - def _remove_contained_files(self, file_list): # pylint: disable=no-self-use - """Erase all files contained within file_list. - - :param str file_list: file containing list of file paths to be deleted - - :returns: Success - :rtype: bool - - :raises certbot.errors.ReverterError: If - all files within file_list cannot be removed - - """ - # Check to see that file exists to differentiate can't find file_list - # and can't remove filepaths within file_list errors. - if not os.path.isfile(file_list): - return False - try: - with open(file_list, "r") as list_fd: - filepaths = list_fd.read().splitlines() - for path in filepaths: - # Files are registered before they are added... so - # check to see if file exists first - if os.path.lexists(path): - os.remove(path) - else: - logger.warning( - "File: %s - Could not be found to be deleted\n" - " - Certbot probably shut down unexpectedly", - path) - except (IOError, OSError): - logger.critical( - "Unable to remove filepaths contained within %s", file_list) - raise errors.ReverterError( - "Unable to remove filepaths contained within " - "{0}".format(file_list)) - - return True - - def finalize_checkpoint(self, title): - """Finalize the checkpoint. - - Timestamps and permanently saves all changes made through the use - of :func:`~add_to_checkpoint` and :func:`~register_file_creation` - - :param str title: Title describing checkpoint - - :raises certbot.errors.ReverterError: when the - checkpoint is not able to be finalized. - - """ - # Check to make sure an "in progress" directory exists - if not os.path.isdir(self.config.in_progress_dir): - return - - changes_since_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE") - changes_since_tmp_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE.tmp") - - if not os.path.exists(changes_since_path): - logger.info("Rollback checkpoint is empty (no changes made?)") - with open(changes_since_path, 'w') as f: - f.write("No changes\n") - - # Add title to self.config.in_progress_dir CHANGES_SINCE - try: - with open(changes_since_tmp_path, "w") as changes_tmp: - changes_tmp.write("-- %s --\n" % title) - with open(changes_since_path, "r") as changes_orig: - changes_tmp.write(changes_orig.read()) - - # Move self.config.in_progress_dir to Backups directory - shutil.move(changes_since_tmp_path, changes_since_path) - except (IOError, OSError): - logger.error("Unable to finalize checkpoint - adding title") - logger.debug("Exception was:\n%s", traceback.format_exc()) - raise errors.ReverterError("Unable to add title") - - # rename the directory as a timestamp - self._timestamp_progress_dir() - - def _checkpoint_timestamp(self): - "Determine the timestamp of the checkpoint, enforcing monotonicity." - timestamp = str(time.time()) - others = glob.glob(os.path.join(self.config.backup_dir, "[0-9]*")) - others = [os.path.basename(d) for d in others] - others.append(timestamp) - others.sort() - if others[-1] != timestamp: - timetravel = str(float(others[-1]) + 1) - logger.warning("Current timestamp %s does not correspond to newest reverter " - "checkpoint; your clock probably jumped. Time travelling to %s", - timestamp, timetravel) - timestamp = timetravel - elif len(others) > 1 and others[-2] == timestamp: - # It is possible if the checkpoints are made extremely quickly - # that will result in a name collision. - logger.debug("Race condition with timestamp %s, incrementing by 0.01", timestamp) - timetravel = str(float(others[-1]) + 0.01) - timestamp = timetravel - return timestamp - - def _timestamp_progress_dir(self): - """Timestamp the checkpoint.""" - # It is possible save checkpoints faster than 1 per second resulting in - # collisions in the naming convention. - - for _ in six.moves.range(2): - timestamp = self._checkpoint_timestamp() - final_dir = os.path.join(self.config.backup_dir, timestamp) - try: - filesystem.replace(self.config.in_progress_dir, final_dir) - return - except OSError: - logger.warning("Extreme, unexpected race condition, retrying (%s)", timestamp) - - # After 10 attempts... something is probably wrong here... - logger.error( - "Unable to finalize checkpoint, %s -> %s", - self.config.in_progress_dir, final_dir) - raise errors.ReverterError( - "Unable to finalize checkpoint renaming") diff --git a/certbot/setup.cfg b/certbot/setup.cfg new file mode 100644 index 000000000..a21bab793 --- /dev/null +++ b/certbot/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[easy_install] +zip_ok = false diff --git a/certbot/setup.py b/certbot/setup.py new file mode 100644 index 000000000..752b5e39c --- /dev/null +++ b/certbot/setup.py @@ -0,0 +1,175 @@ +import codecs +import os +import re +import sys + +from distutils.version import StrictVersion +from setuptools import find_packages, setup, __version__ as setuptools_version +from setuptools.command.test import test as TestCommand + +# Workaround for http://bugs.python.org/issue8876, see +# http://bugs.python.org/issue8876#msg208792 +# This can be removed when using Python 2.7.9 or later: +# https://hg.python.org/cpython/raw-file/v2.7.9/Misc/NEWS +if os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant': + del os.link + + +def read_file(filename, encoding='utf8'): + """Read unicode from given file.""" + with codecs.open(filename, encoding=encoding) as fd: + return fd.read() + + +here = os.path.abspath(os.path.dirname(__file__)) + +# read version number (and other metadata) from package init +init_fn = os.path.join(here, 'certbot', '__init__.py') +meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", read_file(init_fn))) + +readme = read_file(os.path.join(here, 'README.rst')) +version = meta['version'] + +# This package relies on PyOpenSSL, requests, and six, however, it isn't +# specified here to avoid masking the more specific request requirements in +# acme. See https://github.com/pypa/pip/issues/988 for more info. +install_requires = [ + 'acme>=0.40.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.3', # load_pem_x509_certificate + 'distro>=1.0.1', + # 1.1.0+ is required to avoid the warnings described at + # https://github.com/certbot/josepy/issues/13. + 'josepy>=1.1.0', + 'mock', + 'parsedatetime>=1.3', # Calendar.parseDT + 'pyrfc3339', + 'pytz', + 'setuptools', + 'zope.component', + 'zope.interface', +] + +# Add pywin32 on Windows platforms to handle low-level system calls. +# This dependency needs to be added using environment markers to avoid its installation on Linux. +# However environment markers are supported only with setuptools >= 36.2. +# So this dependency is not added for old Linux distributions with old setuptools, +# in order to allow these systems to build certbot from sources. +pywin32_req = 'pywin32>=225' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py +if StrictVersion(setuptools_version) >= StrictVersion('36.2'): + install_requires.append(pywin32_req + " ; sys_platform == 'win32'") +elif 'bdist_wheel' in sys.argv[1:]: + raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' + 'of setuptools. Version 36.2+ of setuptools is required.') +elif os.name == 'nt': + # This branch exists to improve this package's behavior on Windows. Without + # it, if the sdist is installed on Windows with an old version of + # setuptools, pywin32 will not be specified as a dependency. + install_requires.append(pywin32_req) + +dev_extras = [ + 'astroid==1.6.5', + 'coverage', + 'ipdb', + 'pytest', + 'pytest-cov', + 'pytest-xdist', + 'pylint==1.9.4', + 'tox', + 'twine', + 'wheel', +] + +dev3_extras = [ + 'mypy', + 'typing', # for python3.4 +] + +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>=1.2', # Annotation support + 'sphinx_rtd_theme', +] + + +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + + +setup( + name='certbot', + version=version, + description="ACME client", + long_description=readme, + url='https://github.com/letsencrypt/letsencrypt', + author="Certbot Project", + author_email='client-dev@letsencrypt.org', + license='Apache License 2.0', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Environment :: Console :: Curses', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Security', + 'Topic :: System :: Installation/Setup', + 'Topic :: System :: Networking', + 'Topic :: System :: Systems Administration', + 'Topic :: Utilities', + ], + + packages=find_packages(exclude=['docs', 'examples', 'tests', 'venv']), + include_package_data=True, + + install_requires=install_requires, + extras_require={ + 'dev': dev_extras, + 'dev3': dev3_extras, + 'docs': docs_extras, + }, + + test_suite='certbot', + tests_require=["pytest"], + cmdclass={"test": PyTest}, + + entry_points={ + 'console_scripts': [ + 'certbot = certbot.main:main', + ], + 'certbot.plugins': [ + 'manual = certbot._internal.plugins.manual:Authenticator', + 'null = certbot._internal.plugins.null:Installer', + 'standalone = certbot._internal.plugins.standalone:Authenticator', + 'webroot = certbot._internal.plugins.webroot:Authenticator', + ], + }, +) diff --git a/certbot/ssl-dhparams.pem b/certbot/ssl-dhparams.pem deleted file mode 100644 index 9b182b720..000000000 --- a/certbot/ssl-dhparams.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz -+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a -87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 -YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi -7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD -ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== ------END DH PARAMETERS----- diff --git a/certbot/tests/__init__.py b/certbot/tests/__init__.py deleted file mode 100644 index 2f4d6e07c..000000000 --- a/certbot/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Tests""" diff --git a/certbot/tests/acme_util.py b/certbot/tests/acme_util.py deleted file mode 100644 index c88fcd706..000000000 --- a/certbot/tests/acme_util.py +++ /dev/null @@ -1,98 +0,0 @@ -"""ACME utilities for testing.""" -import datetime - -import josepy as jose -import six - -from acme import challenges -from acme import messages - -from certbot._internal import auth_handler - -from certbot.tests import util - - -JWK = jose.JWK.load(util.load_vector('rsa512_key.pem')) -KEY = util.load_rsa_private_key('rsa512_key.pem') - -# Challenges -HTTP01 = challenges.HTTP01( - token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") -DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a") -DNS01_2 = challenges.DNS01(token=b"cafecafecafecafecafecafe0feedbac") - -CHALLENGES = [HTTP01, DNS01] - - -def gen_combos(challbs): - """Generate natural combinations for challbs.""" - # completing a single DV challenge satisfies the CA - return tuple((i,) for i, _ in enumerate(challbs)) - - -def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name - """Return ChallengeBody from Challenge.""" - kwargs = { - "chall": chall, - "uri": chall.typ + "_uri", - "status": status, - } - - if status == messages.STATUS_VALID: - kwargs.update({"validated": datetime.datetime.now()}) - - return messages.ChallengeBody(**kwargs) - - -# Pending ChallengeBody objects -HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) -DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING) -DNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING) - -CHALLENGES_P = [HTTP01_P, DNS01_P] - - -# AnnotatedChallenge objects -HTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, "example.com") -DNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, "example.org") -DNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, "esimerkki.example.org") - -ACHALLENGES = [HTTP01_A, DNS01_A] - - -def gen_authzr(authz_status, domain, challs, statuses, combos=True): - """Generate an authorization resource. - - :param authz_status: Status object - :type authz_status: :class:`acme.messages.Status` - :param list challs: Challenge objects - :param list statuses: status of each challenge object - :param bool combos: Whether or not to add combinations - - """ - # pylint: disable=redefined-outer-name - challbs = tuple( - chall_to_challb(chall, status) - for chall, status in six.moves.zip(challs, statuses) - ) - authz_kwargs = { - "identifier": messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value=domain), - "challenges": challbs, - } - if combos: - authz_kwargs.update({"combinations": gen_combos(challbs)}) - if authz_status == messages.STATUS_VALID: - authz_kwargs.update({ - "status": authz_status, - "expires": datetime.datetime.now() + datetime.timedelta(days=31), - }) - else: - authz_kwargs.update({ - "status": authz_status, - }) - - return messages.AuthorizationResource( - uri="https://trusted.ca/new-authz-resource", - body=messages.Authorization(**authz_kwargs) - ) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 582cf09b8..4f0837723 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -15,9 +15,10 @@ from certbot.compat import os from certbot.compat import filesystem from certbot.display import util as display_util from certbot._internal.storage import ALL_FOUR -from certbot.tests import storage_test from certbot.tests import util as test_util +import storage_test + class BaseCertManagerTest(test_util.ConfigTestCase): """Base class for setting up Cert Manager tests. diff --git a/certbot/tests/plugins/__init__.py b/certbot/tests/plugins/__init__.py new file mode 100644 index 000000000..3cfcb5008 --- /dev/null +++ b/certbot/tests/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot Plugins Tests""" diff --git a/certbot/tests/plugins/common_test.py b/certbot/tests/plugins/common_test.py new file mode 100644 index 000000000..977500f86 --- /dev/null +++ b/certbot/tests/plugins/common_test.py @@ -0,0 +1,356 @@ +"""Tests for certbot.plugins.common.""" +import functools +import shutil +import unittest + +import josepy as jose +import mock + +from acme import challenges + +from certbot import achallenges +from certbot import crypto_util +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem +from certbot.tests import acme_util +from certbot.tests import util as test_util + +AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) +ACHALL = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb(challenges.HTTP01(token=b'token1'), + "pending"), + domain="encryption-example.demo", account_key=AUTH_KEY) + +class NamespaceFunctionsTest(unittest.TestCase): + """Tests for certbot.plugins.common.*_namespace functions.""" + + def test_option_namespace(self): + from certbot.plugins.common import option_namespace + self.assertEqual("foo-", option_namespace("foo")) + + def test_dest_namespace(self): + from certbot.plugins.common import dest_namespace + self.assertEqual("foo_", dest_namespace("foo")) + + def test_dest_namespace_with_dashes(self): + from certbot.plugins.common import dest_namespace + self.assertEqual("foo_bar_", dest_namespace("foo-bar")) + + +class PluginTest(unittest.TestCase): + """Test for certbot.plugins.common.Plugin.""" + + def setUp(self): + from certbot.plugins.common import Plugin + + class MockPlugin(Plugin): # pylint: disable=missing-docstring + @classmethod + def add_parser_arguments(cls, add): + add("foo-bar", dest="different_to_foo_bar", x=1, y=None) + + self.plugin_cls = MockPlugin + self.config = mock.MagicMock() + self.plugin = MockPlugin(config=self.config, name="mock") + + def test_init(self): + self.assertEqual("mock", self.plugin.name) + self.assertEqual(self.config, self.plugin.config) + + def test_option_namespace(self): + self.assertEqual("mock-", self.plugin.option_namespace) + + def test_option_name(self): + self.assertEqual("mock-foo_bar", self.plugin.option_name("foo_bar")) + + def test_dest_namespace(self): + self.assertEqual("mock_", self.plugin.dest_namespace) + + def test_dest(self): + self.assertEqual("mock_foo_bar", self.plugin.dest("foo-bar")) + self.assertEqual("mock_foo_bar", self.plugin.dest("foo_bar")) + + def test_conf(self): + self.assertEqual(self.config.mock_foo_bar, self.plugin.conf("foo-bar")) + + def test_inject_parser_options(self): + parser = mock.MagicMock() + self.plugin_cls.inject_parser_options(parser, "mock") + # note that inject_parser_options doesn't check if dest has + # correct prefix + parser.add_argument.assert_called_once_with( + "--mock-foo-bar", dest="different_to_foo_bar", x=1, y=None) + + +class InstallerTest(test_util.ConfigTestCase): + """Tests for certbot.plugins.common.Installer.""" + + def setUp(self): + super(InstallerTest, self).setUp() + filesystem.mkdir(self.config.config_dir) + from certbot.plugins.common import Installer + + self.installer = Installer(config=self.config, + name="Installer") + self.reverter = self.installer.reverter + + def test_add_to_real_checkpoint(self): + files = set(("foo.bar", "baz.qux",)) + save_notes = "foo bar baz qux" + self._test_wrapped_method("add_to_checkpoint", files, save_notes) + + def test_add_to_real_checkpoint2(self): + self._test_add_to_checkpoint_common(False) + + def test_add_to_temporary_checkpoint(self): + self._test_add_to_checkpoint_common(True) + + def _test_add_to_checkpoint_common(self, temporary): + files = set(("foo.bar", "baz.qux",)) + save_notes = "foo bar baz qux" + + installer_func = functools.partial(self.installer.add_to_checkpoint, + temporary=temporary) + + if temporary: + reverter_func_name = "add_to_temp_checkpoint" + else: + reverter_func_name = "add_to_checkpoint" + + self._test_adapted_method(installer_func, reverter_func_name, files, save_notes) + + def test_finalize_checkpoint(self): + self._test_wrapped_method("finalize_checkpoint", "foo") + + def test_recovery_routine(self): + self._test_wrapped_method("recovery_routine") + + def test_revert_temporary_config(self): + self._test_wrapped_method("revert_temporary_config") + + def test_rollback_checkpoints(self): + self._test_wrapped_method("rollback_checkpoints", 42) + + def _test_wrapped_method(self, name, *args, **kwargs): + """Test a wrapped reverter method. + + :param str name: name of the method to test + :param tuple args: position arguments to method + :param dict kwargs: keyword arguments to method + + """ + installer_func = getattr(self.installer, name) + self._test_adapted_method(installer_func, name, *args, **kwargs) + + def _test_adapted_method(self, installer_func, + reverter_func_name, *passed_args, **passed_kwargs): + """Test an adapted reverter method + + :param callable installer_func: installer method to test + :param str reverter_func_name: name of the method on the + reverter that should be called + :param tuple passed_args: positional arguments passed from + installer method to the reverter method + :param dict passed_kargs: keyword arguments passed from + installer method to the reverter method + + """ + with mock.patch.object(self.reverter, reverter_func_name) as reverter_func: + installer_func(*passed_args, **passed_kwargs) + reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) + reverter_func.side_effect = errors.ReverterError + self.assertRaises( + errors.PluginError, installer_func, *passed_args, **passed_kwargs) + + def test_install_ssl_dhparams(self): + self.installer.install_ssl_dhparams() + self.assertTrue(os.path.isfile(self.installer.ssl_dhparams)) + + def _current_ssl_dhparams_hash(self): + from certbot._internal.constants import SSL_DHPARAMS_SRC + return crypto_util.sha256sum(SSL_DHPARAMS_SRC) + + def test_current_file_hash_in_all_hashes(self): + from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES + self.assertTrue(self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES, + "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" + " with the sha256 hash of self.config.ssl_dhparams when it is updated.") + + +class AddrTest(unittest.TestCase): + """Tests for certbot._internal.client.plugins.common.Addr.""" + + def setUp(self): + from certbot.plugins.common import Addr + self.addr1 = Addr.fromstring("192.168.1.1") + self.addr2 = Addr.fromstring("192.168.1.1:*") + self.addr3 = Addr.fromstring("192.168.1.1:80") + self.addr4 = Addr.fromstring("[fe00::1]") + self.addr5 = Addr.fromstring("[fe00::1]:*") + self.addr6 = Addr.fromstring("[fe00::1]:80") + self.addr7 = Addr.fromstring("[fe00::1]:5") + self.addr8 = Addr.fromstring("[fe00:1:2:3:4:5:6:7:8:9]:8080") + + def test_fromstring(self): + self.assertEqual(self.addr1.get_addr(), "192.168.1.1") + self.assertEqual(self.addr1.get_port(), "") + self.assertEqual(self.addr2.get_addr(), "192.168.1.1") + self.assertEqual(self.addr2.get_port(), "*") + self.assertEqual(self.addr3.get_addr(), "192.168.1.1") + self.assertEqual(self.addr3.get_port(), "80") + self.assertEqual(self.addr4.get_addr(), "[fe00::1]") + self.assertEqual(self.addr4.get_port(), "") + self.assertEqual(self.addr5.get_addr(), "[fe00::1]") + self.assertEqual(self.addr5.get_port(), "*") + self.assertEqual(self.addr6.get_addr(), "[fe00::1]") + self.assertEqual(self.addr6.get_port(), "80") + self.assertEqual(self.addr6.get_ipv6_exploded(), + "fe00:0:0:0:0:0:0:1") + self.assertEqual(self.addr1.get_ipv6_exploded(), + "") + self.assertEqual(self.addr7.get_port(), "5") + self.assertEqual(self.addr8.get_ipv6_exploded(), + "fe00:1:2:3:4:5:6:7") + + def test_str(self): + self.assertEqual(str(self.addr1), "192.168.1.1") + self.assertEqual(str(self.addr2), "192.168.1.1:*") + self.assertEqual(str(self.addr3), "192.168.1.1:80") + self.assertEqual(str(self.addr4), "[fe00::1]") + self.assertEqual(str(self.addr5), "[fe00::1]:*") + self.assertEqual(str(self.addr6), "[fe00::1]:80") + + def test_get_addr_obj(self): + self.assertEqual(str(self.addr1.get_addr_obj("443")), "192.168.1.1:443") + self.assertEqual(str(self.addr2.get_addr_obj("")), "192.168.1.1") + self.assertEqual(str(self.addr1.get_addr_obj("*")), "192.168.1.1:*") + self.assertEqual(str(self.addr4.get_addr_obj("443")), "[fe00::1]:443") + self.assertEqual(str(self.addr5.get_addr_obj("")), "[fe00::1]") + self.assertEqual(str(self.addr4.get_addr_obj("*")), "[fe00::1]:*") + + def test_eq(self): + self.assertEqual(self.addr1, self.addr2.get_addr_obj("")) + self.assertNotEqual(self.addr1, self.addr2) + self.assertFalse(self.addr1 == 3333) + + self.assertEqual(self.addr4, self.addr4.get_addr_obj("")) + self.assertNotEqual(self.addr4, self.addr5) + self.assertFalse(self.addr4 == 3333) + from certbot.plugins.common import Addr + self.assertEqual(self.addr4, Addr.fromstring("[fe00:0:0::1]")) + self.assertEqual(self.addr4, Addr.fromstring("[fe00:0::0:0:1]")) + + + def test_set_inclusion(self): + from certbot.plugins.common import Addr + set_a = set([self.addr1, self.addr2]) + addr1b = Addr.fromstring("192.168.1.1") + addr2b = Addr.fromstring("192.168.1.1:*") + set_b = set([addr1b, addr2b]) + + self.assertEqual(set_a, set_b) + + set_c = set([self.addr4, self.addr5]) + addr4b = Addr.fromstring("[fe00::1]") + addr5b = Addr.fromstring("[fe00::1]:*") + set_d = set([addr4b, addr5b]) + + self.assertEqual(set_c, set_d) + + +class ChallengePerformerTest(unittest.TestCase): + """Tests for certbot.plugins.common.ChallengePerformer.""" + + def setUp(self): + configurator = mock.MagicMock() + + from certbot.plugins.common import ChallengePerformer + self.performer = ChallengePerformer(configurator) + + def test_add_chall(self): + self.performer.add_chall(ACHALL, 0) + self.assertEqual(1, len(self.performer.achalls)) + self.assertEqual([0], self.performer.indices) + + def test_perform(self): + self.assertRaises(NotImplementedError, self.performer.perform) + + +class InstallVersionControlledFileTest(test_util.TempDirTestCase): + """Tests for certbot.plugins.common.install_version_controlled_file.""" + + def setUp(self): + super(InstallVersionControlledFileTest, self).setUp() + self.hashes = ["someotherhash"] + self.dest_path = os.path.join(self.tempdir, "options-ssl-dest.conf") + self.hash_path = os.path.join(self.tempdir, ".options-ssl-conf.txt") + self.old_path = os.path.join(self.tempdir, "options-ssl-old.conf") + self.source_path = os.path.join(self.tempdir, "options-ssl-src.conf") + for path in (self.source_path, self.old_path,): + with open(path, "w") as f: + f.write(path) + self.hashes.append(crypto_util.sha256sum(path)) + + def _call(self): + from certbot.plugins.common import install_version_controlled_file + install_version_controlled_file(self.dest_path, + self.hash_path, + self.source_path, + self.hashes) + + def _current_file_hash(self): + return crypto_util.sha256sum(self.source_path) + + def _assert_current_file(self): + self.assertTrue(os.path.isfile(self.dest_path)) + self.assertEqual(crypto_util.sha256sum(self.dest_path), + self._current_file_hash()) + + def test_no_file(self): + self.assertFalse(os.path.isfile(self.dest_path)) + self._call() + self._assert_current_file() + + def test_current_file(self): + # 1st iteration installs the file, the 2nd checks if it needs updating + for _ in range(2): + self._call() + self._assert_current_file() + + def test_prev_file_updates_to_current(self): + shutil.copyfile(self.old_path, self.dest_path) + self._call() + self._assert_current_file() + + def test_manually_modified_current_file_does_not_update(self): + self._call() + with open(self.dest_path, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + self.assertTrue(os.path.isfile(self.dest_path)) + self.assertEqual(crypto_util.sha256sum(self.source_path), + self._current_file_hash()) + self.assertNotEqual(crypto_util.sha256sum(self.dest_path), + self._current_file_hash()) + + def test_manually_modified_past_file_warns(self): + with open(self.dest_path, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with open(self.hash_path, "w") as f: + f.write("hashofanoldversion") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertEqual(mock_logger.warning.call_args[0][0], + "%s has been manually modified; updated file " + "saved to %s. We recommend updating %s for security purposes.") + self.assertEqual(crypto_util.sha256sum(self.source_path), + self._current_file_hash()) + # only print warning once + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/disco_test.py b/certbot/tests/plugins/disco_test.py new file mode 100644 index 000000000..f739512f0 --- /dev/null +++ b/certbot/tests/plugins/disco_test.py @@ -0,0 +1,292 @@ +"""Tests for certbot._internal.plugins.disco.""" +import functools +import string +import unittest + +import mock +import pkg_resources +import six +import zope.interface + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from certbot import errors +from certbot import interfaces + +from certbot._internal.plugins import standalone +from certbot._internal.plugins import webroot + +EP_SA = pkg_resources.EntryPoint( + "sa", "certbot._internal.plugins.standalone", + attrs=("Authenticator",), + dist=mock.MagicMock(key="certbot")) +EP_WR = pkg_resources.EntryPoint( + "wr", "certbot._internal.plugins.webroot", + attrs=("Authenticator",), + dist=mock.MagicMock(key="certbot")) + + +class PluginEntryPointTest(unittest.TestCase): + """Tests for certbot._internal.plugins.disco.PluginEntryPoint.""" + + def setUp(self): + self.ep1 = pkg_resources.EntryPoint( + "ep1", "p1.ep1", dist=mock.MagicMock(key="p1")) + self.ep1prim = pkg_resources.EntryPoint( + "ep1", "p2.ep2", dist=mock.MagicMock(key="p2")) + # nested + self.ep2 = pkg_resources.EntryPoint( + "ep2", "p2.foo.ep2", dist=mock.MagicMock(key="p2")) + # project name != top-level package name + self.ep3 = pkg_resources.EntryPoint( + "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) + + from certbot._internal.plugins.disco import PluginEntryPoint + self.plugin_ep = PluginEntryPoint(EP_SA) + + def test_entry_point_to_plugin_name(self): + from certbot._internal.plugins.disco import PluginEntryPoint + + names = { + self.ep1: "p1:ep1", + self.ep1prim: "p2:ep1", + self.ep2: "p2:ep2", + self.ep3: "p3:ep3", + EP_SA: "sa", + } + + for entry_point, name in six.iteritems(names): + self.assertEqual( + name, PluginEntryPoint.entry_point_to_plugin_name(entry_point)) + + def test_description(self): + self.assertTrue("temporary webserver" in self.plugin_ep.description) + + def test_description_with_name(self): + self.plugin_ep.plugin_cls = mock.MagicMock(description="Desc") + self.assertEqual( + "Desc (sa)", self.plugin_ep.description_with_name) + + def test_long_description(self): + self.plugin_ep.plugin_cls = mock.MagicMock( + long_description="Long desc") + self.assertEqual( + "Long desc", self.plugin_ep.long_description) + + def test_long_description_nonexistent(self): + self.plugin_ep.plugin_cls = mock.MagicMock( + description="Long desc not found", spec=["description"]) + self.assertEqual( + "Long desc not found", self.plugin_ep.long_description) + + def test_ifaces(self): + self.assertTrue(self.plugin_ep.ifaces((interfaces.IAuthenticator,))) + self.assertFalse(self.plugin_ep.ifaces((interfaces.IInstaller,))) + self.assertFalse(self.plugin_ep.ifaces(( + interfaces.IInstaller, interfaces.IAuthenticator))) + + def test__init__(self): + self.assertFalse(self.plugin_ep.initialized) + self.assertFalse(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + self.assertTrue(self.plugin_ep.problem is None) + self.assertTrue(self.plugin_ep.entry_point is EP_SA) + self.assertEqual("sa", self.plugin_ep.name) + + self.assertTrue(self.plugin_ep.plugin_cls is standalone.Authenticator) + + def test_init(self): + config = mock.MagicMock() + plugin = self.plugin_ep.init(config=config) + self.assertTrue(self.plugin_ep.initialized) + self.assertTrue(plugin.config is config) + # memoize! + self.assertTrue(self.plugin_ep.init() is plugin) + self.assertTrue(plugin.config is config) + # try to give different config + self.assertTrue(self.plugin_ep.init(123) is plugin) + self.assertTrue(plugin.config is config) + + self.assertFalse(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + + def test_verify(self): + iface1 = mock.MagicMock(__name__="iface1") + iface2 = mock.MagicMock(__name__="iface2") + iface3 = mock.MagicMock(__name__="iface3") + # pylint: disable=protected-access + self.plugin_ep._initialized = plugin = mock.MagicMock() + + exceptions = zope.interface.exceptions + with mock.patch("certbot._internal.plugins." + "disco.zope.interface") as mock_zope: + mock_zope.exceptions = exceptions + + def verify_object(iface, obj): # pylint: disable=missing-docstring + assert obj is plugin + assert iface is iface1 or iface is iface2 or iface is iface3 + if iface is iface3: + raise mock_zope.exceptions.BrokenImplementation(None, None) + mock_zope.verify.verifyObject.side_effect = verify_object + self.assertTrue(self.plugin_ep.verify((iface1,))) + self.assertTrue(self.plugin_ep.verify((iface1, iface2))) + self.assertFalse(self.plugin_ep.verify((iface3,))) + self.assertFalse(self.plugin_ep.verify((iface1, iface3))) + + def test_prepare(self): + config = mock.MagicMock() + self.plugin_ep.init(config=config) + self.plugin_ep.prepare() + self.assertTrue(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + + # output doesn't matter that much, just test if it runs + str(self.plugin_ep) + + def test_prepare_misconfigured(self): + plugin = mock.MagicMock() + plugin.prepare.side_effect = errors.MisconfigurationError + # pylint: disable=protected-access + self.plugin_ep._initialized = plugin + self.assertTrue(isinstance(self.plugin_ep.prepare(), + errors.MisconfigurationError)) + self.assertTrue(self.plugin_ep.prepared) + self.assertTrue(self.plugin_ep.misconfigured) + self.assertTrue(isinstance(self.plugin_ep.problem, + errors.MisconfigurationError)) + self.assertTrue(self.plugin_ep.available) + + def test_prepare_no_installation(self): + plugin = mock.MagicMock() + plugin.prepare.side_effect = errors.NoInstallationError + # pylint: disable=protected-access + self.plugin_ep._initialized = plugin + self.assertTrue(isinstance(self.plugin_ep.prepare(), + errors.NoInstallationError)) + self.assertTrue(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + + def test_prepare_generic_plugin_error(self): + plugin = mock.MagicMock() + plugin.prepare.side_effect = errors.PluginError + # pylint: disable=protected-access + self.plugin_ep._initialized = plugin + self.assertTrue(isinstance(self.plugin_ep.prepare(), errors.PluginError)) + self.assertTrue(self.plugin_ep.prepared) + self.assertFalse(self.plugin_ep.misconfigured) + self.assertFalse(self.plugin_ep.available) + + def test_repr(self): + self.assertEqual("PluginEntryPoint#sa", repr(self.plugin_ep)) + + +class PluginsRegistryTest(unittest.TestCase): + """Tests for certbot._internal.plugins.disco.PluginsRegistry.""" + + @classmethod + def _create_new_registry(cls, plugins): + from certbot._internal.plugins.disco import PluginsRegistry + return PluginsRegistry(plugins) + + def setUp(self): + self.plugin_ep = mock.MagicMock() + self.plugin_ep.name = "mock" + self.plugin_ep.__hash__.side_effect = TypeError + self.plugins = {self.plugin_ep.name: self.plugin_ep} + self.reg = self._create_new_registry(self.plugins) + + def test_find_all(self): + from certbot._internal.plugins.disco import PluginsRegistry + with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: + mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]), + iter([EP_WR])] + plugins = PluginsRegistry.find_all() + self.assertTrue(plugins["sa"].plugin_cls is standalone.Authenticator) + self.assertTrue(plugins["sa"].entry_point is EP_SA) + self.assertTrue(plugins["wr"].plugin_cls is webroot.Authenticator) + self.assertTrue(plugins["wr"].entry_point is EP_WR) + + def test_getitem(self): + self.assertEqual(self.plugin_ep, self.reg["mock"]) + + def test_iter(self): + self.assertEqual(["mock"], list(self.reg)) + + def test_len(self): + self.assertEqual(0, len(self._create_new_registry({}))) + self.assertEqual(1, len(self.reg)) + + def test_init(self): + self.plugin_ep.init.return_value = "baz" + self.assertEqual(["baz"], self.reg.init("bar")) + self.plugin_ep.init.assert_called_once_with("bar") + + def test_filter(self): + self.assertEqual( + self.plugins, + self.reg.filter(lambda p_ep: p_ep.name.startswith("m"))) + self.assertEqual( + {}, self.reg.filter(lambda p_ep: p_ep.name.startswith("b"))) + + def test_ifaces(self): + self.plugin_ep.ifaces.return_value = True + # pylint: disable=protected-access + self.assertEqual(self.plugins, self.reg.ifaces()._plugins) + self.plugin_ep.ifaces.return_value = False + self.assertEqual({}, self.reg.ifaces()._plugins) + + def test_verify(self): + self.plugin_ep.verify.return_value = True + # pylint: disable=protected-access + self.assertEqual( + self.plugins, self.reg.verify(mock.MagicMock())._plugins) + self.plugin_ep.verify.return_value = False + self.assertEqual({}, self.reg.verify(mock.MagicMock())._plugins) + + def test_prepare(self): + self.plugin_ep.prepare.return_value = "baz" + self.assertEqual(["baz"], self.reg.prepare()) + self.plugin_ep.prepare.assert_called_once_with() + + def test_prepare_order(self): + order = [] # type: List[str] + plugins = dict( + (c, mock.MagicMock(prepare=functools.partial(order.append, c))) + for c in string.ascii_letters) + reg = self._create_new_registry(plugins) + reg.prepare() + # order of prepare calls must be sorted to prevent deadlock + # caused by plugins acquiring locks during prepare + self.assertEqual(order, sorted(string.ascii_letters)) + + def test_available(self): + self.plugin_ep.available = True + # pylint: disable=protected-access + self.assertEqual(self.plugins, self.reg.available()._plugins) + self.plugin_ep.available = False + self.assertEqual({}, self.reg.available()._plugins) + + def test_find_init(self): + self.assertTrue(self.reg.find_init(mock.Mock()) is None) + self.plugin_ep.initialized = True + self.assertTrue( + self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep) + + def test_repr(self): + self.plugin_ep.__repr__ = lambda _: "PluginEntryPoint#mock" + self.assertEqual("PluginsRegistry(PluginEntryPoint#mock)", + repr(self.reg)) + + def test_str(self): + self.assertEqual("No plugins", str(self._create_new_registry({}))) + self.plugin_ep.__str__ = lambda _: "Mock" + self.assertEqual("Mock", str(self.reg)) + plugins = {self.plugin_ep.name: self.plugin_ep, "foo": "Bar"} + reg = self._create_new_registry(plugins) + self.assertEqual("Bar\n\nMock", str(reg)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/dns_common_lexicon_test.py b/certbot/tests/plugins/dns_common_lexicon_test.py new file mode 100644 index 000000000..986362ca9 --- /dev/null +++ b/certbot/tests/plugins/dns_common_lexicon_test.py @@ -0,0 +1,27 @@ +"""Tests for certbot.plugins.dns_common_lexicon.""" + +import unittest + +import mock + +from certbot.plugins import dns_common_lexicon +from certbot.plugins import dns_test_common_lexicon + + +class LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + class _FakeLexiconClient(dns_common_lexicon.LexiconClient): + pass + + def setUp(self): + super(LexiconClientTest, self).setUp() + + self.client = LexiconClientTest._FakeLexiconClient() + self.provider_mock = mock.MagicMock() + + self.client.provider = self.provider_mock + + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/dns_common_test.py b/certbot/tests/plugins/dns_common_test.py new file mode 100644 index 000000000..eba3c89d6 --- /dev/null +++ b/certbot/tests/plugins/dns_common_test.py @@ -0,0 +1,230 @@ +"""Tests for certbot.plugins.dns_common.""" + +import collections +import logging +import unittest + +import mock + +from certbot import errors +from certbot import util +from certbot.compat import os +from certbot.display import util as display_util +from certbot.plugins import dns_common +from certbot.plugins import dns_test_common +from certbot.tests import util as test_util + + +class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): + # pylint: disable=protected-access + + class _FakeDNSAuthenticator(dns_common.DNSAuthenticator): + _setup_credentials = mock.MagicMock() + _perform = mock.MagicMock() + _cleanup = mock.MagicMock() + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'A fake authenticator for testing.' + + class _FakeConfig(object): + fake_propagation_seconds = 0 + fake_config_key = 1 + fake_other_key = None + fake_file_path = None + + def setUp(self): + super(DNSAuthenticatorTest, self).setUp() + + self.config = DNSAuthenticatorTest._FakeConfig() + + self.auth = DNSAuthenticatorTest._FakeDNSAuthenticator(self.config, "fake") + + def test_perform(self): + self.auth.perform([self.achall]) + + self.auth._perform.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) + + def test_cleanup(self): + self.auth._attempt_cleanup = True + + self.auth.cleanup([self.achall]) + + self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) + + @test_util.patch_get_utility() + def test_prompt(self, mock_get_utility): + mock_display = mock_get_utility() + mock_display.input.side_effect = ((display_util.OK, "",), + (display_util.OK, "value",)) + + self.auth._configure("other_key", "") + self.assertEqual(self.auth.config.fake_other_key, "value") + + @test_util.patch_get_utility() + def test_prompt_canceled(self, mock_get_utility): + mock_display = mock_get_utility() + mock_display.input.side_effect = ((display_util.CANCEL, "c",),) + + self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "") + + @test_util.patch_get_utility() + def test_prompt_file(self, mock_get_utility): + path = os.path.join(self.tempdir, 'file.ini') + open(path, "wb").close() + + mock_display = mock_get_utility() + mock_display.directory_select.side_effect = ((display_util.OK, "",), + (display_util.OK, "not-a-file.ini",), + (display_util.OK, self.tempdir), + (display_util.OK, path,)) + + self.auth._configure_file("file_path", "") + self.assertEqual(self.auth.config.fake_file_path, path) + + @test_util.patch_get_utility() + def test_prompt_file_canceled(self, mock_get_utility): + mock_display = mock_get_utility() + mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),) + + self.assertRaises(errors.PluginError, self.auth._configure_file, "file_path", "") + + def test_configure_credentials(self): + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"fake_test": "value"}, path) + setattr(self.config, "fake_credentials", path) + + credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) + + self.assertEqual(credentials.conf("test"), "value") + + @test_util.patch_get_utility() + def test_prompt_credentials(self, mock_get_utility): + bad_path = os.path.join(self.tempdir, 'bad-file.ini') + dns_test_common.write({"fake_other": "other_value"}, bad_path) + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"fake_test": "value"}, path) + setattr(self.config, "fake_credentials", "") + + mock_display = mock_get_utility() + mock_display.directory_select.side_effect = ((display_util.OK, "",), + (display_util.OK, "not-a-file.ini",), + (display_util.OK, self.tempdir), + (display_util.OK, bad_path), + (display_util.OK, path,)) + + credentials = self.auth._configure_credentials("credentials", "", {"test": ""}) + self.assertEqual(credentials.conf("test"), "value") + + +class CredentialsConfigurationTest(test_util.TempDirTestCase): + class _MockLoggingHandler(logging.Handler): + messages = None + + def __init__(self, *args, **kwargs): + self.reset() + logging.Handler.__init__(self, *args, **kwargs) + + def emit(self, record): + self.messages[record.levelname.lower()].append(record.getMessage()) + + def reset(self): + """Allows the handler to be reset between tests.""" + self.messages = collections.defaultdict(list) + + def test_valid_file(self): + path = os.path.join(self.tempdir, 'too-permissive-file.ini') + + dns_test_common.write({"test": "value", "other": 1}, path) + + credentials_configuration = dns_common.CredentialsConfiguration(path) + self.assertEqual("value", credentials_configuration.conf("test")) + self.assertEqual("1", credentials_configuration.conf("other")) + + def test_nonexistent_file(self): + path = os.path.join(self.tempdir, 'not-a-file.ini') + + self.assertRaises(errors.PluginError, dns_common.CredentialsConfiguration, path) + + def test_valid_file_with_unsafe_permissions(self): + log = self._MockLoggingHandler() + dns_common.logger.addHandler(log) + + path = os.path.join(self.tempdir, 'too-permissive-file.ini') + util.safe_open(path, "wb", 0o744).close() + + dns_common.CredentialsConfiguration(path) + + self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")])) + + +class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): + + def setUp(self): + super(CredentialsConfigurationRequireTest, self).setUp() + + self.path = os.path.join(self.tempdir, 'file.ini') + + def _write(self, values): + dns_test_common.write(values, self.path) + + def test_valid(self): + self._write({"test": "value", "other": 1}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + credentials_configuration.require({"test": "", "other": ""}) + + def test_valid_but_extra(self): + self._write({"test": "value", "other": 1}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + credentials_configuration.require({"test": ""}) + + def test_valid_empty(self): + self._write({}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + credentials_configuration.require({}) + + def test_missing(self): + self._write({}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + + def test_blank(self): + self._write({"test": ""}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + + def test_typo(self): + self._write({"tets": "typo!"}) + + credentials_configuration = dns_common.CredentialsConfiguration(self.path) + self.assertRaises(errors.PluginError, credentials_configuration.require, {"test": ""}) + + +class DomainNameGuessTest(unittest.TestCase): + + def test_simple_case(self): + self.assertTrue( + 'example.com' in + dns_common.base_domain_name_guesses("example.com") + ) + + def test_sub_domain(self): + self.assertTrue( + 'example.com' in + dns_common.base_domain_name_guesses("foo.bar.baz.example.com") + ) + + def test_second_level_domain(self): + self.assertTrue( + 'example.co.uk' in + dns_common.base_domain_name_guesses("foo.bar.baz.example.co.uk") + ) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py new file mode 100644 index 000000000..86482184e --- /dev/null +++ b/certbot/tests/plugins/enhancements_test.py @@ -0,0 +1,65 @@ +"""Tests for new style enhancements""" +import unittest +import mock + +from certbot.plugins import enhancements +from certbot._internal.plugins import null + +import certbot.tests.util as test_util + + +class EnhancementTest(test_util.ConfigTestCase): + """Tests for new style enhancements in certbot.plugins.enhancements""" + + def setUp(self): + super(EnhancementTest, self).setUp() + self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) + + + @test_util.patch_get_utility() + def test_enhancement_enabled_enhancements(self, _): + FAKEINDEX = [ + { + "name": "autohsts", + "cli_dest": "auto_hsts", + }, + { + "name": "somethingelse", + "cli_dest": "something", + } + ] + with mock.patch("certbot.plugins.enhancements._INDEX", FAKEINDEX): + self.config.auto_hsts = True + self.config.something = True + enabled = list(enhancements.enabled_enhancements(self.config)) + self.assertEqual(len(enabled), 2) + self.assertTrue([i for i in enabled if i["name"] == "autohsts"]) + self.assertTrue([i for i in enabled if i["name"] == "somethingelse"]) + + def test_are_requested(self): + 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.assertEqual( + len([i for i in enhancements.enabled_enhancements(self.config)]), 1) + self.assertTrue(enhancements.are_requested(self.config)) + + def test_are_supported(self): + self.config.auto_hsts = True + unsupported = null.Installer(self.config, "null") + self.assertTrue(enhancements.are_supported(self.config, self.mockinstaller)) + self.assertFalse(enhancements.are_supported(self.config, unsupported)) + + def test_enable(self): + self.config.auto_hsts = True + domains = ["example.com", "www.example.com"] + lineage = "lineage" + enhancements.enable(lineage, domains, self.mockinstaller, self.config) + self.assertTrue(self.mockinstaller.enable_autohsts.called) + self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0], + (lineage, domains)) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py new file mode 100644 index 000000000..8796c30f1 --- /dev/null +++ b/certbot/tests/plugins/manual_test.py @@ -0,0 +1,133 @@ +"""Tests for certbot._internal.plugins.manual""" +import unittest +import sys + +import mock +import six + +from acme import challenges + +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem +from certbot.tests import acme_util +from certbot.tests import util as test_util + + +class AuthenticatorTest(test_util.TempDirTestCase): + """Tests for certbot._internal.plugins.manual.Authenticator.""" + + def setUp(self): + super(AuthenticatorTest, self).setUp() + self.http_achall = acme_util.HTTP01_A + self.dns_achall = acme_util.DNS01_A + self.dns_achall_2 = acme_util.DNS01_A_2 + self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2] + for d in ["config_dir", "work_dir", "in_progress"]: + filesystem.mkdir(os.path.join(self.tempdir, d)) + # "backup_dir" and "temp_checkpoint_dir" get created in + # certbot.util.make_or_verify_dir() during the Reverter + # initialization. + self.config = mock.MagicMock( + http01_port=0, manual_auth_hook=None, manual_cleanup_hook=None, + manual_public_ip_logging_ok=False, noninteractive_mode=False, + validate_hooks=False, + config_dir=os.path.join(self.tempdir, "config_dir"), + work_dir=os.path.join(self.tempdir, "work_dir"), + backup_dir=os.path.join(self.tempdir, "backup_dir"), + temp_checkpoint_dir=os.path.join( + self.tempdir, "temp_checkpoint_dir"), + in_progress_dir=os.path.join(self.tempdir, "in_progess")) + + from certbot._internal.plugins.manual import Authenticator + self.auth = Authenticator(self.config, name='manual') + + def test_prepare_no_hook_noninteractive(self): + self.config.noninteractive_mode = True + self.assertRaises(errors.PluginError, self.auth.prepare) + + def test_prepare_bad_hook(self): + self.config.manual_auth_hook = os.path.abspath(os.sep) # is / on UNIX + self.config.validate_hooks = True + self.assertRaises(errors.HookCommandNotFound, self.auth.prepare) + + def test_more_info(self): + self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) + + def test_get_chall_pref(self): + self.assertEqual(self.auth.get_chall_pref('example.org'), + [challenges.HTTP01, challenges.DNS01]) + + @test_util.patch_get_utility() + def test_ip_logging_not_ok(self, mock_get_utility): + mock_get_utility().yesno.return_value = False + self.assertRaises(errors.PluginError, self.auth.perform, []) + + @test_util.patch_get_utility() + def test_ip_logging_ok(self, mock_get_utility): + mock_get_utility().yesno.return_value = True + self.auth.perform([]) + self.assertTrue(self.config.manual_public_ip_logging_ok) + + def test_script_perform(self): + self.config.manual_public_ip_logging_ok = True + self.config.manual_auth_hook = ( + '{0} -c "from __future__ import print_function;' + 'from certbot.compat import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));' + 'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));' + 'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"' + .format(sys.executable)) + dns_expected = '{0}\n{1}\n{2}'.format( + self.dns_achall.domain, 'notoken', + self.dns_achall.validation(self.dns_achall.account_key)) + http_expected = '{0}\n{1}\n{2}'.format( + self.http_achall.domain, self.http_achall.chall.encode('token'), + self.http_achall.validation(self.http_achall.account_key)) + + self.assertEqual( + self.auth.perform(self.achalls), + [achall.response(achall.account_key) for achall in self.achalls]) + self.assertEqual( + self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'], + dns_expected) + self.assertEqual( + self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'], + http_expected) + + @test_util.patch_get_utility() + def test_manual_perform(self, mock_get_utility): + self.config.manual_public_ip_logging_ok = True + self.assertEqual( + self.auth.perform(self.achalls), + [achall.response(achall.account_key) for achall in self.achalls]) + for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list): + achall = self.achalls[i] + self.assertTrue( + achall.validation(achall.account_key) in args[0]) + self.assertFalse(kwargs['wrap']) + + def test_cleanup(self): + self.config.manual_public_ip_logging_ok = True + self.config.manual_auth_hook = ('{0} -c "import sys; sys.stdout.write(\'foo\')"' + .format(sys.executable)) + self.config.manual_cleanup_hook = '# cleanup' + self.auth.perform(self.achalls) + + for achall in self.achalls: + self.auth.cleanup([achall]) + self.assertEqual(os.environ['CERTBOT_AUTH_OUTPUT'], 'foo') + self.assertEqual(os.environ['CERTBOT_DOMAIN'], achall.domain) + if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)): + self.assertEqual( + os.environ['CERTBOT_VALIDATION'], + achall.validation(achall.account_key)) + if isinstance(achall.chall, challenges.HTTP01): + self.assertEqual( + os.environ['CERTBOT_TOKEN'], + achall.chall.encode('token')) + else: + self.assertFalse('CERTBOT_TOKEN' in os.environ) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/null_test.py b/certbot/tests/plugins/null_test.py new file mode 100644 index 000000000..41cd45a93 --- /dev/null +++ b/certbot/tests/plugins/null_test.py @@ -0,0 +1,22 @@ +"""Tests for certbot._internal.plugins.null.""" +import unittest +import six + +import mock + + +class InstallerTest(unittest.TestCase): + """Tests for certbot._internal.plugins.null.Installer.""" + + def setUp(self): + from certbot._internal.plugins.null import Installer + self.installer = Installer(config=mock.MagicMock(), name="null") + + def test_it(self): + self.assertTrue(isinstance(self.installer.more_info(), six.string_types)) + self.assertEqual([], self.installer.get_all_names()) + self.assertEqual([], self.installer.supported_enhancements()) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py new file mode 100644 index 000000000..9de7f7941 --- /dev/null +++ b/certbot/tests/plugins/selection_test.py @@ -0,0 +1,220 @@ +"""Tests for letsencrypt.plugins.selection""" +import sys +import unittest + +import mock +import zope.component + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot import interfaces +from certbot.compat import os +from certbot.display import util as display_util +from certbot._internal.plugins.disco import PluginsRegistry +from certbot.tests import util as test_util + + +class ConveniencePickPluginTest(unittest.TestCase): + """Tests for certbot._internal.plugins.selection.pick_*.""" + + def _test(self, fun, ifaces): + config = mock.Mock() + default = mock.Mock() + plugins = mock.Mock() + + with mock.patch("certbot._internal.plugins.selection.pick_plugin") as mock_p: + mock_p.return_value = "foo" + self.assertEqual("foo", fun(config, default, plugins, "Question?")) + mock_p.assert_called_once_with( + config, default, plugins, "Question?", ifaces) + + def test_authenticator(self): + from certbot._internal.plugins.selection import pick_authenticator + self._test(pick_authenticator, (interfaces.IAuthenticator,)) + + def test_installer(self): + from certbot._internal.plugins.selection import pick_installer + self._test(pick_installer, (interfaces.IInstaller,)) + + def test_configurator(self): + from certbot._internal.plugins.selection import pick_configurator + self._test(pick_configurator, + (interfaces.IAuthenticator, interfaces.IInstaller)) + + +class PickPluginTest(unittest.TestCase): + """Tests for certbot._internal.plugins.selection.pick_plugin.""" + + def setUp(self): + self.config = mock.Mock(noninteractive_mode=False) + self.default = None + self.reg = mock.MagicMock() + self.question = "Question?" + self.ifaces = [] # type: List[interfaces.IPlugin] + + def _call(self): + from certbot._internal.plugins.selection import pick_plugin + return pick_plugin(self.config, self.default, self.reg, + self.question, self.ifaces) + + def test_default_provided(self): + self.default = "foo" + self._call() + self.assertEqual(1, self.reg.filter.call_count) + + def test_no_default(self): + self._call() + self.assertEqual(1, self.reg.visible().ifaces.call_count) + + def test_no_candidate(self): + self.assertTrue(self._call() is None) + + def test_single(self): + plugin_ep = mock.MagicMock() + plugin_ep.init.return_value = "foo" + plugin_ep.misconfigured = False + + self.reg.visible().ifaces().verify().available.return_value = { + "bar": plugin_ep} + self.assertEqual("foo", self._call()) + + def test_single_misconfigured(self): + plugin_ep = mock.MagicMock() + plugin_ep.init.return_value = "foo" + plugin_ep.misconfigured = True + + self.reg.visible().ifaces().verify().available.return_value = { + "bar": plugin_ep} + self.assertTrue(self._call() is None) + + def test_multiple(self): + plugin_ep = mock.MagicMock() + plugin_ep.init.return_value = "foo" + self.reg.visible().ifaces().verify().available.return_value = { + "bar": plugin_ep, + "baz": plugin_ep, + } + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: + mock_choose.return_value = plugin_ep + self.assertEqual("foo", self._call()) + mock_choose.assert_called_once_with( + [plugin_ep, plugin_ep], self.question) + + def test_choose_plugin_none(self): + self.reg.visible().ifaces().verify().available.return_value = { + "bar": None, + "baz": None, + } + + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: + mock_choose.return_value = None + self.assertTrue(self._call() is None) + + +class ChoosePluginTest(unittest.TestCase): + """Tests for certbot._internal.plugins.selection.choose_plugin.""" + + def setUp(self): + zope.component.provideUtility(display_util.FileDisplay(sys.stdout, + False)) + self.mock_apache = mock.Mock( + description_with_name="a", misconfigured=True) + self.mock_apache.name = "apache" + self.mock_stand = mock.Mock( + description_with_name="s", misconfigured=False) + self.mock_stand.init().more_info.return_value = "standalone" + self.plugins = [ + self.mock_apache, + self.mock_stand, + ] + + def _call(self): + from certbot._internal.plugins.selection import choose_plugin + return choose_plugin(self.plugins, "Question?") + + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") + def test_selection(self, mock_util): + mock_util().menu.side_effect = [(display_util.OK, 0), + (display_util.OK, 1)] + self.assertEqual(self.mock_stand, self._call()) + self.assertEqual(mock_util().notification.call_count, 1) + + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") + def test_more_info(self, mock_util): + mock_util().menu.side_effect = [ + (display_util.OK, 1), + ] + + self.assertEqual(self.mock_stand, self._call()) + + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") + def test_no_choice(self, mock_util): + mock_util().menu.return_value = (display_util.CANCEL, 0) + self.assertTrue(self._call() is None) + + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") + def test_new_interaction_avoidance(self, mock_util): + mock_nginx = mock.Mock( + description_with_name="n", misconfigured=False) + mock_nginx.init().more_info.return_value = "nginx plugin" + mock_nginx.name = "nginx" + self.plugins[1] = mock_nginx + mock_util().menu.return_value = (display_util.CANCEL, 0) + + unset_cb_auto = os.environ.get("CERTBOT_AUTO") is None + if unset_cb_auto: + os.environ["CERTBOT_AUTO"] = "foo" + try: + self._call() + finally: + if unset_cb_auto: + del os.environ["CERTBOT_AUTO"] + + self.assertTrue("default" in mock_util().menu.call_args[1]) + +class GetUnpreparedInstallerTest(test_util.ConfigTestCase): + """Tests for certbot._internal.plugins.selection.get_unprepared_installer.""" + + def setUp(self): + super(GetUnpreparedInstallerTest, self).setUp() + self.mock_apache_fail_ep = mock.Mock( + description_with_name="afail") + self.mock_apache_fail_ep.name = "afail" + self.mock_apache_ep = mock.Mock( + description_with_name="apache") + self.mock_apache_ep.name = "apache" + self.mock_apache_plugin = mock.MagicMock() + self.mock_apache_ep.init.return_value = self.mock_apache_plugin + self.plugins = PluginsRegistry({ + "afail": self.mock_apache_fail_ep, + "apache": self.mock_apache_ep, + }) + + def _call(self): + from certbot._internal.plugins.selection import get_unprepared_installer + return get_unprepared_installer(self.config, self.plugins) + + def test_no_installer_defined(self): + self.config.configurator = None + self.assertEqual(self._call(), None) + + def test_no_available_installers(self): + self.config.configurator = "apache" + self.plugins = PluginsRegistry({}) + self.assertRaises(errors.PluginSelectionError, self._call) + + def test_get_plugin(self): + self.config.configurator = "apache" + installer = self._call() + self.assertTrue(installer is self.mock_apache_plugin) + + def test_multiple_installers_returned(self): + self.config.configurator = "apache" + # Two plugins with the same name + self.mock_apache_fail_ep.name = "apache" + self.assertRaises(errors.PluginSelectionError, self._call) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py new file mode 100644 index 000000000..c9dabb8b4 --- /dev/null +++ b/certbot/tests/plugins/standalone_test.py @@ -0,0 +1,186 @@ +"""Tests for certbot._internal.plugins.standalone.""" +import socket +# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +from socket import errno as socket_errors # type: ignore +import unittest + +import josepy as jose +import mock +import six + +import OpenSSL.crypto # pylint: disable=unused-import + +from acme import challenges +from acme import standalone as acme_standalone # pylint: disable=unused-import +from acme.magic_typing import Dict, Tuple, Set # pylint: disable=unused-import, no-name-in-module + +from certbot import achallenges +from certbot import errors + +from certbot.tests import acme_util +from certbot.tests import util as test_util + + +class ServerManagerTest(unittest.TestCase): + """Tests for certbot._internal.plugins.standalone.ServerManager.""" + + def setUp(self): + from certbot._internal.plugins.standalone import ServerManager + self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] + self.http_01_resources = {} \ + # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] + self.mgr = ServerManager(self.certs, self.http_01_resources) + + def test_init(self): + self.assertTrue(self.mgr.certs is self.certs) + self.assertTrue( + self.mgr.http_01_resources is self.http_01_resources) + + def _test_run_stop(self, challenge_type): + server = self.mgr.run(port=0, challenge_type=challenge_type) + port = server.getsocknames()[0][1] # pylint: disable=no-member + self.assertEqual(self.mgr.running(), {port: server}) + self.mgr.stop(port=port) + self.assertEqual(self.mgr.running(), {}) + + def test_run_stop_http_01(self): + self._test_run_stop(challenges.HTTP01) + + def test_run_idempotent(self): + server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) + port = server.getsocknames()[0][1] # pylint: disable=no-member + server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) + self.assertEqual(self.mgr.running(), {port: server}) + self.assertTrue(server is server2) + self.mgr.stop(port) + self.assertEqual(self.mgr.running(), {}) + + def test_run_bind_error(self): + some_server = socket.socket(socket.AF_INET6) + some_server.bind(("", 0)) + port = some_server.getsockname()[1] + maybe_another_server = socket.socket() + try: + maybe_another_server.bind(("", port)) + except socket.error: + pass + self.assertRaises( + errors.StandaloneBindError, self.mgr.run, port, + challenge_type=challenges.HTTP01) + self.assertEqual(self.mgr.running(), {}) + some_server.close() + maybe_another_server.close() + + +def get_open_port(): + """Gets an open port number from the OS.""" + open_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + open_socket.bind(("", 0)) + port = open_socket.getsockname()[1] + open_socket.close() + return port + + +class AuthenticatorTest(unittest.TestCase): + """Tests for certbot._internal.plugins.standalone.Authenticator.""" + + def setUp(self): + from certbot._internal.plugins.standalone import Authenticator + + self.config = mock.MagicMock(http01_port=get_open_port()) + self.auth = Authenticator(self.config, name="standalone") + self.auth.servers = mock.MagicMock() + + def test_more_info(self): + self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) + + def test_get_chall_pref(self): + self.assertEqual(self.auth.get_chall_pref(domain=None), + [challenges.HTTP01]) + + def test_perform(self): + achalls = self._get_achalls() + response = self.auth.perform(achalls) + + expected = [achall.response(achall.account_key) for achall in achalls] + self.assertEqual(response, expected) + + @test_util.patch_get_utility() + def test_perform_eaddrinuse_retry(self, mock_get_utility): + mock_utility = mock_get_utility() + errno = socket_errors.EADDRINUSE + error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) + self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()] + mock_yesno = mock_utility.yesno + mock_yesno.return_value = True + + self.test_perform() + self._assert_correct_yesno_call(mock_yesno) + + @test_util.patch_get_utility() + def test_perform_eaddrinuse_no_retry(self, mock_get_utility): + mock_utility = mock_get_utility() + mock_yesno = mock_utility.yesno + mock_yesno.return_value = False + + errno = socket_errors.EADDRINUSE + self.assertRaises(errors.PluginError, self._fail_perform, errno) + self._assert_correct_yesno_call(mock_yesno) + + def _assert_correct_yesno_call(self, mock_yesno): + yesno_args, yesno_kwargs = mock_yesno.call_args + self.assertTrue("in use" in yesno_args[0]) + self.assertFalse(yesno_kwargs.get("default", True)) + + def test_perform_eacces(self): + errno = socket_errors.EACCES + self.assertRaises(errors.PluginError, self._fail_perform, errno) + + def test_perform_unexpected_socket_error(self): + errno = socket_errors.ENOTCONN + self.assertRaises( + errors.StandaloneBindError, self._fail_perform, errno) + + def _fail_perform(self, errno): + error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1) + self.auth.servers.run.side_effect = error + self.auth.perform(self._get_achalls()) + + @classmethod + def _get_achalls(cls): + domain = b'localhost' + key = jose.JWK.load(test_util.load_vector('rsa512_key.pem')) + http_01 = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain=domain, account_key=key) + + return [http_01] + + def test_cleanup(self): + self.auth.servers.running.return_value = { + 1: "server1", + 2: "server2", + } + self.auth.served["server1"].add("chall1") + self.auth.served["server2"].update(["chall2", "chall3"]) + + self.auth.cleanup(["chall1"]) + self.assertEqual(self.auth.served, { + "server1": set(), "server2": set(["chall2", "chall3"])}) + self.auth.servers.stop.assert_called_once_with(1) + + self.auth.servers.running.return_value = { + 2: "server2", + } + self.auth.cleanup(["chall2"]) + self.assertEqual(self.auth.served, { + "server1": set(), "server2": set(["chall3"])}) + self.assertEqual(1, self.auth.servers.stop.call_count) + + self.auth.cleanup(["chall3"]) + self.assertEqual(self.auth.served, { + "server1": set(), "server2": set([])}) + self.auth.servers.stop.assert_called_with(2) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/storage_test.py b/certbot/tests/plugins/storage_test.py new file mode 100644 index 000000000..9d08cc7ef --- /dev/null +++ b/certbot/tests/plugins/storage_test.py @@ -0,0 +1,119 @@ +"""Tests for certbot.plugins.storage.PluginStorage""" +import json +import unittest +import mock + +from certbot import errors + +from certbot.compat import os +from certbot.compat import filesystem +from certbot.plugins import common +from certbot.tests import util as test_util + + +class PluginStorageTest(test_util.ConfigTestCase): + """Test for certbot.plugins.storage.PluginStorage""" + + def setUp(self): + super(PluginStorageTest, self).setUp() + self.plugin_cls = common.Installer + filesystem.mkdir(self.config.config_dir) + with mock.patch("certbot.reverter.util"): + self.plugin = self.plugin_cls(config=self.config, name="mockplugin") + + def test_load_errors_cant_read(self): + with open(os.path.join(self.config.config_dir, + ".pluginstorage.json"), "w") as fh: + fh.write("dummy") + # When unable to read file that exists + mock_open = mock.mock_open() + mock_open.side_effect = IOError + self.plugin.storage.storagepath = os.path.join(self.config.config_dir, + ".pluginstorage.json") + with mock.patch("six.moves.builtins.open", mock_open): + with mock.patch('certbot.compat.os.path.isfile', return_value=True): + with mock.patch("certbot.reverter.util"): + self.assertRaises(errors.PluginStorageError, + self.plugin.storage._load) # pylint: disable=protected-access + + def test_load_errors_empty(self): + with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), "w") as fh: + fh.write('') + with mock.patch("certbot.plugins.storage.logger.debug") as mock_log: + # Should not error out but write a debug log line instead + with mock.patch("certbot.reverter.util"): + nocontent = self.plugin_cls(self.config, "mockplugin") + self.assertRaises(KeyError, + nocontent.storage.fetch, "value") + self.assertTrue(mock_log.called) + self.assertTrue("no values loaded" in mock_log.call_args[0][0]) + + def test_load_errors_corrupted(self): + with open(os.path.join(self.config.config_dir, + ".pluginstorage.json"), "w") as fh: + fh.write('invalid json') + with mock.patch("certbot.plugins.storage.logger.error") as mock_log: + with mock.patch("certbot.reverter.util"): + corrupted = self.plugin_cls(self.config, "mockplugin") + self.assertRaises(errors.PluginError, + corrupted.storage.fetch, + "value") + self.assertTrue("is corrupted" in mock_log.call_args[0][0]) + + def test_save_errors_cant_serialize(self): + with mock.patch("certbot.plugins.storage.logger.error") as mock_log: + # Set data as something that can't be serialized + self.plugin.storage._initialized = True # pylint: disable=protected-access + self.plugin.storage.storagepath = "/tmp/whatever" + self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access + self.assertRaises(errors.PluginStorageError, + self.plugin.storage.save) + self.assertTrue("Could not serialize" in mock_log.call_args[0][0]) + + def test_save_errors_unable_to_write_file(self): + mock_open = mock.mock_open() + mock_open.side_effect = IOError + with mock.patch("certbot.compat.filesystem.open", mock_open): + with mock.patch("certbot.plugins.storage.logger.error") as mock_log: + self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access + self.plugin.storage._initialized = True # pylint: disable=protected-access + self.assertRaises(errors.PluginStorageError, + self.plugin.storage.save) + self.assertTrue("Could not write" in mock_log.call_args[0][0]) + + def test_save_uninitialized(self): + with mock.patch("certbot.reverter.util"): + self.assertRaises(errors.PluginStorageError, + self.plugin_cls(self.config, "x").storage.save) + + def test_namespace_isolation(self): + with mock.patch("certbot.reverter.util"): + plugin1 = self.plugin_cls(self.config, "first") + plugin2 = self.plugin_cls(self.config, "second") + plugin1.storage.put("first_key", "first_value") + self.assertRaises(KeyError, + plugin2.storage.fetch, "first_key") + self.assertRaises(KeyError, + plugin2.storage.fetch, "first") + self.assertEqual(plugin1.storage.fetch("first_key"), "first_value") + + + def test_saved_state(self): + self.plugin.storage.put("testkey", "testvalue") + # Write to disk + self.plugin.storage.save() + with mock.patch("certbot.reverter.util"): + another = self.plugin_cls(self.config, "mockplugin") + self.assertEqual(another.storage.fetch("testkey"), "testvalue") + + with open(os.path.join(self.config.config_dir, + ".pluginstorage.json"), 'r') as fh: + psdata = fh.read() + psjson = json.loads(psdata) + self.assertTrue("mockplugin" in psjson.keys()) + self.assertEqual(len(psjson), 1) + self.assertEqual(psjson["mockplugin"]["testkey"], "testvalue") + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/util_test.py b/certbot/tests/plugins/util_test.py new file mode 100644 index 000000000..c41e55222 --- /dev/null +++ b/certbot/tests/plugins/util_test.py @@ -0,0 +1,45 @@ +"""Tests for certbot.plugins.util.""" +import unittest + +import mock + +from certbot.compat import os + + +class GetPrefixTest(unittest.TestCase): + """Tests for certbot.plugins.get_prefixes.""" + def test_get_prefix(self): + from certbot.plugins.util import get_prefixes + self.assertEqual( + get_prefixes('/a/b/c'), + [os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']]) + self.assertEqual(get_prefixes('/'), [os.path.normpath('/')]) + self.assertEqual(get_prefixes('a'), ['a']) + + +class PathSurgeryTest(unittest.TestCase): + """Tests for certbot.plugins.path_surgery.""" + + @mock.patch("certbot.plugins.util.logger.debug") + def test_path_surgery(self, mock_debug): + from certbot.plugins.util import path_surgery + all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"} + with mock.patch.dict('os.environ', all_path): + with mock.patch('certbot.util.exe_exists') as mock_exists: + mock_exists.return_value = True + self.assertEqual(path_surgery("eg"), True) + self.assertEqual(mock_debug.call_count, 0) + self.assertEqual(os.environ["PATH"], all_path["PATH"]) + if os.name != 'nt': + # This part is specific to Linux since on Windows no PATH surgery is ever done. + no_path = {"PATH": "/tmp/"} + with mock.patch.dict('os.environ', no_path): + path_surgery("thingy") + self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1) + self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) + self.assertTrue("/usr/local/bin" in os.environ["PATH"]) + self.assertTrue("/tmp" in os.environ["PATH"]) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/plugins/webroot_test.py b/certbot/tests/plugins/webroot_test.py new file mode 100644 index 000000000..70501f812 --- /dev/null +++ b/certbot/tests/plugins/webroot_test.py @@ -0,0 +1,318 @@ +"""Tests for certbot._internal.plugins.webroot.""" + +from __future__ import print_function + +import argparse +import errno +import json +import shutil +import tempfile +import unittest + +import josepy as jose +import mock +import six + +from acme import challenges + +from certbot import achallenges +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem +from certbot.display import util as display_util +from certbot.tests import acme_util +from certbot.tests import util as test_util + +KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + + +class AuthenticatorTest(unittest.TestCase): + """Tests for certbot._internal.plugins.webroot.Authenticator.""" + + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) + + def setUp(self): + from certbot._internal.plugins.webroot import Authenticator + # On Linux directories created by tempfile.mkdtemp inherit their permissions from their + # parent directory. So the actual permissions are inconsistent over various tests env. + # To circumvent this, a dedicated sub-workspace is created under the workspace, using + # filesystem.mkdir to get consistent permissions. + self.workspace = tempfile.mkdtemp() + self.path = os.path.join(self.workspace, 'webroot') + filesystem.mkdir(self.path) + self.partial_root_challenge_path = os.path.join( + self.path, ".well-known") + self.root_challenge_path = os.path.join( + self.path, ".well-known", "acme-challenge") + self.validation_path = os.path.join( + self.root_challenge_path, + "ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ") + self.config = mock.MagicMock(webroot_path=self.path, + webroot_map={"thing.com": self.path}) + self.auth = Authenticator(self.config, "webroot") + + def tearDown(self): + shutil.rmtree(self.path) + + def test_more_info(self): + more_info = self.auth.more_info() + self.assertTrue(isinstance(more_info, six.string_types)) + self.assertTrue(self.path in more_info) + + def test_add_parser_arguments(self): + add = mock.MagicMock() + self.auth.add_parser_arguments(add) + self.assertEqual(2, add.call_count) + + def test_prepare(self): + self.auth.prepare() # shouldn't raise any exceptions + + @test_util.patch_get_utility() + def test_webroot_from_list(self, mock_get_utility): + self.config.webroot_path = [] + self.config.webroot_map = {"otherthing.com": self.path} + mock_display = mock_get_utility() + mock_display.menu.return_value = (display_util.OK, 1,) + + self.auth.perform([self.achall]) + self.assertTrue(mock_display.menu.called) + for call in mock_display.menu.call_args_list: + self.assertTrue(self.achall.domain in call[0][0]) + self.assertTrue(all( + webroot in call[0][1] + for webroot in six.itervalues(self.config.webroot_map))) + self.assertEqual(self.config.webroot_map[self.achall.domain], + self.path) + + @test_util.patch_get_utility() + def test_webroot_from_list_help_and_cancel(self, mock_get_utility): + self.config.webroot_path = [] + self.config.webroot_map = {"otherthing.com": self.path} + + mock_display = mock_get_utility() + mock_display.menu.side_effect = ((display_util.CANCEL, -1),) + self.assertRaises(errors.PluginError, self.auth.perform, [self.achall]) + self.assertTrue(mock_display.menu.called) + for call in mock_display.menu.call_args_list: + self.assertTrue(self.achall.domain in call[0][0]) + self.assertTrue(all( + webroot in call[0][1] + for webroot in six.itervalues(self.config.webroot_map))) + + @test_util.patch_get_utility() + def test_new_webroot(self, mock_get_utility): + self.config.webroot_path = [] + self.config.webroot_map = {"something.com": self.path} + + mock_display = mock_get_utility() + mock_display.menu.return_value = (display_util.OK, 0,) + with mock.patch('certbot.display.ops.validated_directory') as m: + m.side_effect = ((display_util.CANCEL, -1), + (display_util.OK, self.path,)) + + self.auth.perform([self.achall]) + + self.assertEqual(self.config.webroot_map[self.achall.domain], self.path) + + @test_util.patch_get_utility() + def test_new_webroot_empty_map_cancel(self, mock_get_utility): + self.config.webroot_path = [] + self.config.webroot_map = {} + + mock_display = mock_get_utility() + mock_display.menu.return_value = (display_util.OK, 0,) + with mock.patch('certbot.display.ops.validated_directory') as m: + m.return_value = (display_util.CANCEL, -1) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_perform_missing_root(self): + self.config.webroot_path = None + self.config.webroot_map = {} + self.assertRaises(errors.PluginError, self.auth.perform, []) + + def test_perform_reraises_other_errors(self): + self.auth.full_path = os.path.join(self.path, "null") + permission_canary = os.path.join(self.path, "rnd") + with open(permission_canary, "w") as f: + f.write("thingimy") + filesystem.chmod(self.path, 0o000) + try: + open(permission_canary, "r") + print("Warning, running tests as root skips permissions tests...") + except IOError: + # ok, permissions work, test away... + self.assertRaises(errors.PluginError, self.auth.perform, []) + filesystem.chmod(self.path, 0o700) + + @mock.patch("certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode") + def test_failed_chown(self, mock_ownership): + mock_ownership.side_effect = OSError(errno.EACCES, "msg") + self.auth.perform([self.achall]) # exception caught and logged + + @test_util.patch_get_utility() + def test_perform_new_webroot_not_in_map(self, mock_get_utility): + new_webroot = tempfile.mkdtemp() + self.config.webroot_path = [] + self.config.webroot_map = {"whatever.com": self.path} + mock_display = mock_get_utility() + mock_display.menu.side_effect = ((display_util.OK, 0), + (display_util.OK, new_webroot)) + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain="something.com", account_key=KEY) + with mock.patch('certbot.display.ops.validated_directory') as m: + m.return_value = (display_util.OK, new_webroot,) + self.auth.perform([achall]) + self.assertEqual(self.config.webroot_map[achall.domain], new_webroot) + + def test_perform_permissions(self): + self.auth.prepare() + + # Remove exec bit from permission check, so that it + # matches the file + self.auth.perform([self.achall]) + self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) + + # Check permissions of the directories + for dirpath, dirnames, _ in os.walk(self.path): + for directory in dirnames: + full_path = os.path.join(dirpath, directory) + self.assertTrue(filesystem.check_mode(full_path, 0o755)) + + self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path)) + + def test_perform_cleanup(self): + self.auth.prepare() + responses = self.auth.perform([self.achall]) + self.assertEqual(1, len(responses)) + self.assertTrue(os.path.exists(self.validation_path)) + with open(self.validation_path) as validation_f: + validation = validation_f.read() + self.assertTrue( + challenges.KeyAuthorizationChallengeResponse( + key_authorization=validation).verify( + self.achall.chall, KEY.public_key())) + + self.auth.cleanup([self.achall]) + self.assertFalse(os.path.exists(self.validation_path)) + self.assertFalse(os.path.exists(self.root_challenge_path)) + self.assertFalse(os.path.exists(self.partial_root_challenge_path)) + + def test_perform_cleanup_existing_dirs(self): + filesystem.mkdir(self.partial_root_challenge_path) + self.auth.prepare() + self.auth.perform([self.achall]) + self.auth.cleanup([self.achall]) + + # Ensure we don't "clean up" directories that previously existed + self.assertFalse(os.path.exists(self.validation_path)) + self.assertFalse(os.path.exists(self.root_challenge_path)) + + def test_perform_cleanup_multiple_challenges(self): + bingo_achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=b"bingo"), "pending"), + domain="thing.com", account_key=KEY) + + bingo_validation_path = "YmluZ28" + filesystem.mkdir(self.partial_root_challenge_path) + self.auth.prepare() + self.auth.perform([bingo_achall, self.achall]) + + self.auth.cleanup([self.achall]) + self.assertFalse(os.path.exists(bingo_validation_path)) + self.assertTrue(os.path.exists(self.root_challenge_path)) + self.auth.cleanup([bingo_achall]) + self.assertFalse(os.path.exists(self.validation_path)) + self.assertFalse(os.path.exists(self.root_challenge_path)) + + def test_cleanup_leftovers(self): + self.auth.prepare() + self.auth.perform([self.achall]) + + leftover_path = os.path.join(self.root_challenge_path, 'leftover') + filesystem.mkdir(leftover_path) + + self.auth.cleanup([self.achall]) + self.assertFalse(os.path.exists(self.validation_path)) + self.assertTrue(os.path.exists(self.root_challenge_path)) + + os.rmdir(leftover_path) + + @mock.patch('certbot.compat.os.rmdir') + def test_cleanup_failure(self, mock_rmdir): + self.auth.prepare() + self.auth.perform([self.achall]) + + os_error = OSError() + os_error.errno = errno.EACCES + mock_rmdir.side_effect = os_error + + self.auth.cleanup([self.achall]) + self.assertFalse(os.path.exists(self.validation_path)) + self.assertTrue(os.path.exists(self.root_challenge_path)) + + +class WebrootActionTest(unittest.TestCase): + """Tests for webroot argparse actions.""" + + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) + + def setUp(self): + from certbot._internal.plugins.webroot import Authenticator + self.path = tempfile.mkdtemp() + self.parser = argparse.ArgumentParser() + self.parser.add_argument("-d", "--domains", + action="append", default=[]) + Authenticator.inject_parser_options(self.parser, "webroot") + + def test_webroot_map_action(self): + args = self.parser.parse_args( + ["--webroot-map", json.dumps({'thing.com': self.path})]) + self.assertEqual(args.webroot_map["thing.com"], self.path) + + def test_domain_before_webroot(self): + args = self.parser.parse_args( + "-d {0} -w {1}".format(self.achall.domain, self.path).split()) + config = self._get_config_after_perform(args) + self.assertEqual(config.webroot_map[self.achall.domain], self.path) + + def test_domain_before_webroot_error(self): + self.assertRaises(errors.PluginError, self.parser.parse_args, + "-d foo -w bar -w baz".split()) + self.assertRaises(errors.PluginError, self.parser.parse_args, + "-d foo -w bar -d baz -w qux".split()) + + def test_multiwebroot(self): + args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( + self.path, self.achall.domain, tempfile.mkdtemp()).split()) + self.assertEqual(args.webroot_map[self.achall.domain], self.path) + config = self._get_config_after_perform(args) + self.assertEqual( + config.webroot_map[self.achall.domain], self.path) + + def test_webroot_map_partial_without_perform(self): + # This test acknowledges the fact that webroot_map content will be partial if webroot + # plugin perform method is not invoked (corner case when all auths are already valid). + # To not be a problem, the webroot_path must always been conserved during renew. + # This condition is challenged by: + # certbot.tests.renewal_tests::RenewalTest::test_webroot_params_conservation + # See https://github.com/certbot/certbot/pull/7095 for details. + other_webroot_path = tempfile.mkdtemp() + args = self.parser.parse_args("-w {0} -d {1} -w {2} -d bar".format( + self.path, self.achall.domain, other_webroot_path).split()) + self.assertEqual(args.webroot_map, {self.achall.domain: self.path}) + self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) + + def _get_config_after_perform(self, config): + from certbot._internal.plugins.webroot import Authenticator + auth = Authenticator(config, "webroot") + auth.perform([self.achall]) + return auth.config + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot/tests/testdata/README b/certbot/tests/testdata/README deleted file mode 100644 index 867215916..000000000 --- a/certbot/tests/testdata/README +++ /dev/null @@ -1,11 +0,0 @@ -The following command has been used to generate test keys: - - for x in 256 512 2048; do openssl genrsa -out rsa${k}_key.pem $k; done - -and for the CSR PEM (Certificate Signing Request): - - openssl req -new -out csr-Xsans_X.pem -key rsa512_key.pem [-config csr-Xsans_X.conf | -subj '/CN=example.com'] [-outform DER > csr_X.der] - -and for the certificate: - - openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der] \ No newline at end of file diff --git a/certbot/tests/testdata/cert-5sans_512.pem b/certbot/tests/testdata/cert-5sans_512.pem deleted file mode 100644 index 5de7cc6cb..000000000 --- a/certbot/tests/testdata/cert-5sans_512.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICkTCCAjugAwIBAgIJAJNbfABWQ8bbMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV -BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp -c2NvMScwJQYDVQQKDB5FbGVjdHJvbmljIEZyb250aWVyIEZvdW5kYXRpb24xFDAS -BgNVBAMMC2V4YW1wbGUuY29tMB4XDTE2MDYwOTIzMDEzNloXDTE2MDcwOTIzMDEz -NloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM -DVNhbiBGcmFuY2lzY28xJzAlBgNVBAoMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91 -bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANL -ADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE -30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4GlMIGiMB0GA1UdDgQWBBQmz8jt -S9eUsuQlA1gkjwTAdNWXijAfBgNVHSMEGDAWgBQmz8jtS9eUsuQlA1gkjwTAdNWX -ijAMBgNVHRMEBTADAQH/MFIGA1UdEQRLMEmCDWEuZXhhbXBsZS5jb22CDWIuZXhh -bXBsZS5jb22CDWMuZXhhbXBsZS5jb22CDWQuZXhhbXBsZS5jb22CC2V4YW1wbGUu -Y29tMA0GCSqGSIb3DQEBCwUAA0EAVXmZxB+IJdgFvY2InOYeytTD1QmouDZRtj/T -H/HIpSdsfO7qr4d/ZprI2IhLRxp2S4BiU5Qc5HUkeADcpNd06A== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert-nosans_nistp256.pem b/certbot/tests/testdata/cert-nosans_nistp256.pem deleted file mode 100644 index 4ec3f24ce..000000000 --- a/certbot/tests/testdata/cert-nosans_nistp256.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBoDCCAUYCCQDCnzfUZ7TQdDAKBggqhkjOPQQDAjBYMQswCQYDVQQGEwJVUzER -MA8GA1UECAwITWljaGlnYW4xEjAQBgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwD -RUZGMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xODA1MTUxNzIyMzlaFw0xODA2 -MTQxNzIyMzlaMFgxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjESMBAG -A1UEBwwJQW5uIEFyYm9yMQwwCgYDVQQKDANFRkYxFDASBgNVBAMMC2V4YW1wbGUu -Y29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPl0JauSZukvAUWv4l5VNLAY -QXhuPXYQBf4dVET3s0E5q9ZCbSe+pNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tzAK -BggqhkjOPQQDAgNIADBFAiEAv8S2GXmWJqZ+j3DBfm72E1YK+HkOf+TOUHsbVR+O -Z1oCIFWNt1SPdIgRp4QAyzVk2pcTF8jDNajEMLWETDtxgRvM ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert-san_512.pem b/certbot/tests/testdata/cert-san_512.pem deleted file mode 100644 index dcb835994..000000000 --- a/certbot/tests/testdata/cert-san_512.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt -cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF -nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 -RDjyGMKy5ZgM2w== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert_2048.pem b/certbot/tests/testdata/cert_2048.pem deleted file mode 100644 index e02f18e91..000000000 --- a/certbot/tests/testdata/cert_2048.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV -BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD -RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC -Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G -yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 -apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 -r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 -0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F -OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f -AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME -GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG -SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh -vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv -xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D -HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X -YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ -5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert_512.pem b/certbot/tests/testdata/cert_512.pem deleted file mode 100644 index 96c55cbf4..000000000 --- a/certbot/tests/testdata/cert_512.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll -vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn -B/o= ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert_512_bad.pem b/certbot/tests/testdata/cert_512_bad.pem deleted file mode 100644 index d868dc445..000000000 --- a/certbot/tests/testdata/cert_512_bad.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICYzCCAg2gAwIBAgIJAPvqv4TcAtuFMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -VQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEMMAoG -A1UECgwDRUZGMRYwFAYDVQQLDA1UZWNoIFByb2plY3RzMQ4wDAYDVQQDDAVZb21u -YTEjMCEGCSqGSIb3DQEJARYUeW9tbmEubmFzc2VyQGVmZi5vcmcwHhcNMTcwMzI0 -MjIzMjUxWhcNNDgwMTI0MjIzMjUxWjCBjDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgM -B09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xDDAKBgNVBAoMA0VGRjEWMBQGA1UE -CwwNVGVjaCBQcm9qZWN0czEOMAwGA1UEAwwFWW9tbmExIzAhBgkqhkiG9w0BCQEW -FHlvbW5hLm5hc3NlckBlZmYub3JnMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1 -c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvO -jm0c+pVE6K+EdE/twuUCAwEAAaNQME4wHQYDVR0OBBYEFCbPyO1L15Sy5CUDWCSP -BMB01ZeKMB8GA1UdIwQYMBaAFCbPyO1L15Sy5CUDWCSPBMB01ZeKMAwGA1UdEwQF -MAMBAf8wDQYJKoZIhvcNAQELBQADQQAeWDdcrJOolFHr3m8TrlDJ/Ca4SfJya2jb -K1wahbX83sC42834HbDOQASGBhoLYDhC1cMPbKDDjMbR9rjYuf7T ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cert_fullchain_2048.pem b/certbot/tests/testdata/cert_fullchain_2048.pem deleted file mode 100644 index 422d221fb..000000000 --- a/certbot/tests/testdata/cert_fullchain_2048.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV -BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD -RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC -Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G -yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 -apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 -r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 -0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F -OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f -AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME -GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG -SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh -vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv -xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D -HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X -YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ -5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV -BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD -RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC -Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G -yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 -apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 -r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 -0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F -OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f -AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME -GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG -SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh -vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv -xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D -HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X -YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ -5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/cli.ini b/certbot/tests/testdata/cli.ini deleted file mode 100644 index 8ef506071..000000000 --- a/certbot/tests/testdata/cli.ini +++ /dev/null @@ -1 +0,0 @@ -agree-dev-preview = True diff --git a/certbot/tests/testdata/csr-6sans_512.conf b/certbot/tests/testdata/csr-6sans_512.conf deleted file mode 100644 index fa7b3edc2..000000000 --- a/certbot/tests/testdata/csr-6sans_512.conf +++ /dev/null @@ -1,29 +0,0 @@ -[req] -distinguished_name = req_distinguished_name -req_extensions = v3_req - -[req_distinguished_name] -C=US -C_default = US -ST=Michigan -ST_default=Michigan -L=Ann Arbor -L_default=Ann Arbor -O=EFF -O_default=EFF -OU=University of Michigan -OU_default=University of Michigan -CN=example.com -CN_default=example.com - - -[ v3_req ] -subjectAltName = @alt_names - -[alt_names] -DNS.1 = example.com -DNS.2 = example.org -DNS.3 = example.net -DNS.4 = example.info -DNS.5 = subdomain.example.com -DNS.6 = other.subdomain.example.com \ No newline at end of file diff --git a/certbot/tests/testdata/csr-6sans_512.pem b/certbot/tests/testdata/csr-6sans_512.pem deleted file mode 100644 index f72c0541d..000000000 --- a/certbot/tests/testdata/csr-6sans_512.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoIGGMIGDBgkqhkiG -9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL -ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t -ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQA+ -sU6T30n3SsdnHlj0Va8eECOWK7Lf8nUfxxgjPMQ7BoU8gbAnGfDmOlwDronTRqf1 -Me+nlYJU4TX1OiX10DYu ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/csr-nonames_512.pem b/certbot/tests/testdata/csr-nonames_512.pem deleted file mode 100644 index abe1029ca..000000000 --- a/certbot/tests/testdata/csr-nonames_512.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF -AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+ -6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD -QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH -lKWVQ8+xwYMscGWK0NApHGco ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/csr-nosans_512.conf b/certbot/tests/testdata/csr-nosans_512.conf deleted file mode 100644 index 1026cf9ad..000000000 --- a/certbot/tests/testdata/csr-nosans_512.conf +++ /dev/null @@ -1,16 +0,0 @@ -[req] -distinguished_name = req_distinguished_name - -[req_distinguished_name] -C=US -C_default = US -ST=Michigan -ST_default=Michigan -L=Ann Arbor -L_default=Ann Arbor -O=EFF -O_default=EFF -OU=University of Michigan -OU_default=University of Michigan -CN=example.com -CN_default=example.com \ No newline at end of file diff --git a/certbot/tests/testdata/csr-nosans_512.pem b/certbot/tests/testdata/csr-nosans_512.pem deleted file mode 100644 index 5f02d7e97..000000000 --- a/certbot/tests/testdata/csr-nosans_512.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBMzCB3gIBADB5MQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ -BgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMR8wHQYDVQQLDBZVbml2ZXJz -aXR5IHBmIE1pY2hpZ2FuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3 -DQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db0Cx1GxS4dQEEEOUfA2VF3d+n -nzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLlAgMBAAGgADANBgkqhkiG9w0B -AQsFAANBAG06jIPvSC6wiGLy7sUTaEX4UCE6Cztp3vh/uXN7Q++CGn6KiXNs/BRW -eFlcFPbvxbVG/ZZFR5aPs+Oy6RhqOjg= ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/csr-nosans_nistp256.pem b/certbot/tests/testdata/csr-nosans_nistp256.pem deleted file mode 100644 index 2f0a671ed..000000000 --- a/certbot/tests/testdata/csr-nosans_nistp256.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBFDCBugIBADBYMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ -BgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMRQwEgYDVQQDDAtleGFtcGxl -LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDz5dCWrkmbpLwFFr+JeVTSw -GEF4bj12EAX+HVRE97NBOavWQm0nvqTVG5KPRfkxZLnO11Y0D7H5A24dCPZw9Leg -ADAKBggqhkjOPQQDAgNJADBGAiEAuoZHrYA5sy2DRTdLAxJTBNHKFFKbtaGt+QaJ -A62qa8sCIQCUkSgSAiNaEnJ7r5fKphdjeORHqhpl6flYkLE3lGmGdg== ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/csr-san_512.pem b/certbot/tests/testdata/csr-san_512.pem deleted file mode 100644 index a7128e35c..000000000 --- a/certbot/tests/testdata/csr-san_512.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN -AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t -MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy -tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/csr_512.der b/certbot/tests/testdata/csr_512.der deleted file mode 100644 index 5c03f3a11..000000000 Binary files a/certbot/tests/testdata/csr_512.der and /dev/null differ diff --git a/certbot/tests/testdata/csr_512.pem b/certbot/tests/testdata/csr_512.pem deleted file mode 100644 index c62224ca7..000000000 --- a/certbot/tests/testdata/csr_512.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh -MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtFeGFt -cGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db -0Cx1GxS4dQEEEOUfA2VF3d+nnzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLl -AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAAceUlq4La8qaiK0DeDP3M19BIVzMmz2 -oemG2fOvPiwNCB90ctSWQ6bMpUMV85ShcFi31C5vlntPfztehhq6YuE= ------END CERTIFICATE REQUEST----- diff --git a/certbot/tests/testdata/nistp256_key.pem b/certbot/tests/testdata/nistp256_key.pem deleted file mode 100644 index 4be37e49b..000000000 --- a/certbot/tests/testdata/nistp256_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIOvXH384CyNNv2lfxvjc7hg2f7ScYoLvlk/VpINLJlGBoAoGCCqGSM49 -AwEHoUQDQgAEPPl0JauSZukvAUWv4l5VNLAYQXhuPXYQBf4dVET3s0E5q9ZCbSe+ -pNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tw== ------END EC PRIVATE KEY----- diff --git a/certbot/tests/testdata/ocsp_certificate.pem b/certbot/tests/testdata/ocsp_certificate.pem deleted file mode 100644 index 471844859..000000000 --- a/certbot/tests/testdata/ocsp_certificate.pem +++ /dev/null @@ -1,37 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIGYDCCBEigAwIBAgIKcjrC4hZcebbtODANBgkqhkiG9w0BAQsFADBRMQswCQYD -VQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNVBAMM -GkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDUxMjE1NTgyMVoXDTE5 -MTEwODIyNTkwMFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9P -b+YhJPypm4ui+AZUHPrJ6IsB9R/6Wvgec2G/GuW/UNQFktIhU10HOHAbiJeYLqNZ -1Cia8JD6NXXGbprOjIbZWvjulYTaLSlClcK0H7HZrcgrK60OeIGEtur27ga68RML -hs1FG7TNyWVysifOtwW9Oo1mZQQtxViiE2Yb+Q4QqIxitnbrnFmKrVJSUHVXi8/I -BK1yLrJiRBZMIw0wvAWcWEG2Gpp9PAbemlb11Zx8sm/RSGh7u60rmETbB2Pu941s -XJCSQRtq5yKdtjIJTIgbe12SPkknqTqa3aUh7hgho0IymlDSeeocL60SUiUAsPEr -QRWleodOR1ChXz5mFokCAwEAAaOCAokwggKFMAkGA1UdEwQCMAAwHwYDVR0jBBgw -FoAUd9nQBpFm2N0ZJo1JrNowL2p7YrEwHQYDVR0OBBYEFExS23I6sLCeO6KIxzoc -tr9s+HmiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB -BQUHAwIwIAYDVR0gBBkwFzALBglghEIBGgEAAgcwCAYGZ4EMAQIBMEIGA1UdHwQ7 -MDkwN6A1oDOGMWh0dHA6Ly9jcmwudGVzdDQuYnV5cGFzcy5uby9jcmwvQlBDbGFz -czJUNENBNS5jcmwwIQYDVR0RAQH/BBcwFYITYnV5cGFzcy5wYWNhbGlzLm5ldDB4 -BggrBgEFBQcBAQRsMGowKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1 -eXBhc3MuY29tMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnRlc3Q0LmJ1eXBhc3Mu -bm8vY3J0L0JQQ2xhc3MyVDRDQTUuY2VyMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDw -AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsTLGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMA -RzBFAiEA1oWB4c6q7+tqGA4HhLNACOemr9c2aIUuWxeQE7/PlSYCIEolZ7pWVs1J -VyQW/AqeuXGB7qScwUgLh9C1uOJoeRe6AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsT -LGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMARzBFAiAoLaNvIwMDifsDAXJBsAKHlYx7 -QPLXL8onYKm8f+Sf1wIhAMepo2GX84UR7WtooqzkBZLG+PaBy1zMuUAG6mwnroF9 -MA0GCSqGSIb3DQEBCwUAA4ICAQAPWLdjNS5lLL5SEtghYebtDmNj2968NYSDvb1L -1/uFwg3LCVRR1Xb3z1Hc/sc1W0IFXU0zOqEQiuP8jkVP7UqkaWuK5Eu0eP0zPI83 -WBZM0+eBwxwzIMK/Q7fYKTu1+vg/FlH0WhtV43DQSik66366zvPi2Tfag9IPvRei -DOjbSOBF0o4er2oCrtI0lK5YrHOdWtD7xwQIuA606P9ucuufMf+JcmduRJsVZ2Zu -3K32SMDdAnyjvQWZNbt1ex3G8vuFQEi690UBhPcha/SO8QvLS89wcaLJnyMIWdv7 -54cbw+fa1nLKM7qph6Mk1yb0qpomPqLmKw4T6WX36c0vDlFSpexJLGgWDFqLUxPN -qV7cJz4mi1qaYfdWXRrnyU4bl55pHTTgEzbohV7apsmytkCe1uFNrpcTh8jzAhGN -PQqarX9UoESR56B/ufbBGlBWi0pkV49BFks6Ue0GVKo7djoxuV6+SsmYSE+6MNPv -IUsm54TSnwxjA8WyG7pl14g1hkGFQ4NRYJMiVqK3DMABaPxVmT7NRxUQQiM0mmM7 -EKNzLBeWHJF5ecdDR1MiIF3ayn+RiZb0r8aSQBMLwN1YwUZw+hSYz1eCd7bHN1gC -1ksxP61f8LBz0SwDoyOTr8wY++wqF26KfoYuKQ3LjLeHvuUtL3EMnAhiyuej8ZOZ -22spng== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/ocsp_issuer_certificate.pem b/certbot/tests/testdata/ocsp_issuer_certificate.pem deleted file mode 100644 index 4f894ae4b..000000000 --- a/certbot/tests/testdata/ocsp_issuer_certificate.pem +++ /dev/null @@ -1,38 +0,0 @@ -22spng== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIGMzCCBBugAwIBAgIJMvsa+ZFQCj8nMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV -BAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEmMCQGA1UEAwwd -QnV5cGFzcyBDbGFzcyAyIFRlc3Q0IFJvb3QgQ0EwHhcNMTcwMjEzMTY1MjQ2WhcN -MjcwMjEzMTY1MjQ2WjBRMQswCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBB -Uy05ODMxNjMzMjcxIzAhBgNVBAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi/vpgO2sbUQZsoxWd6us -QvT/59kvw5ehoJABBXFs1J1AV1/K2hjhDXit/sNGKjzDvkfE9PJqXMnhKpPFkUzC -z/NmDK++d6aRflnDvJrxlPVpp0QGbe3qOErByFjWiHoobuVItlpRO/BaBdlgGvmQ -LeZFBXs/ZrLNFUKBcE+DZIyJH7vy2EB5dNNVn2mx0n+371InpKsYUaHNlxPpp+uj -TOL+e4OjWTBwDaI7rVzpavozb8SPzFxjpxLLVH/j+8VPwoe3lmxr8ATyI178iRdA -uxYfaKURSfu7PWjnDNTnq26E3pwW3E5zUbsADgUMh/PzoJAcszL1eHGUQaAGBP85 -PlLmHr+nsPMHXOUyl7Ts6KGkZlvjnVshKwUxYAqjAC7/BY0iI0xc406NK9heeVDk -NiFA8/To6mQ09vO/TBxQtkfNk2yuxiixa101peSg4/+E4VhwYv6MJxS/oVqBd2d3 -wemYW/JUVeJg9wXGq1e/c09/UjGwUGwU9s5LNFEgj4v1tcvWnONzWNXkyMrs5g4e -U8L/DQ3XgNrcA9zrfFq0cQhSJonj/VI/jbBYyB2yEuQAIjAN6eDIOoLmHGIIvZtE -0LL5jaZC3W518jB1OF7QSvaFtaFl0VqDy6LMXL50elMVC+hr9KpDnN0t8gaSiPyZ -wEC9SMdQ7SLVOUK1Xdh3dh0CAwEAAaOCAQkwggEFMA8GA1UdEwEB/wQFMAMBAf8w -HwYDVR0jBBgwFoAU0aT+MaGsc75ZynH0up0oH+tVHh4wHQYDVR0OBBYEFHfZ0AaR -ZtjdGSaNSazaMC9qe2KxMA4GA1UdDwEB/wQEAwIBBjAgBgNVHSAEGTAXMAsGCWCE -QgEaAQACBzAIBgZngQwBAgEwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC50 -ZXN0NC5idXlwYXNzLm5vL2NybC9CUENsYXNzMlQ0Um9vdENBLmNybDA5BggrBgEF -BQcBAQQtMCswKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1eXBhc3Mu -Y29tMA0GCSqGSIb3DQEBCwUAA4ICAQBOgxedV31NCpZQRc8yFxoqQNgBnY1UeH/h -/s/9fGQzyGnTWZldEi5MGJKF6ulcYnklitlg/jic9au3xSoqP/i2smUHByX2wMrC -mDpLCwio2x2p/0Wscj5asqzJE2cCWqob2iHxo36nsr3Jdd2GIlzhZ0wm8rMZxsQG -FgbgHYIer79S+PIdHoZuUnCJhsJ+1PRUmm2t7vcmZpu8l4CeL0XJX98l2L8kbBds -MGo1EazGAEirZnSfQKCARhUcEdavsKl067+irsGGcK4+L78Vl9S1/QPfKG30L5fv -nM1X1qAdhsbjwVdrhLkjpzabT0icsW6W17HLh8UBYdA7k4GclA6h+mNrXAt7JAeZ -PzMFq0I7vVJNEdolZHTVCqT0sdJiTj+phS1ztK86Wb1R/5d5B1VSb789zSdJfrwV -ppXgPtZq5x3GQi6ooteWyuWj3cBcNu9TU1D8u1F0XI5gw4Y0VpxlDxysUgFQJlo4 -VYmMpgr442o/35UgwzkIC7x/6dkvMZvM4jYB5JZJXjynR35XawXB/hzybermJ8BB -DsY0MCOwxhpsTbyEC4wfxZ08B4JtORkToOt4OWuejovsr68Ht6ytOPj7dquoPPNM -9eGNSp94nEIiZ2n75ZMg0gIQArXU9OCV6B2TXxB7w2YB0y0teDgVhoM3IY/ltqJ/ -PJrUUjM8OQ== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/ocsp_responder_certificate.pem b/certbot/tests/testdata/ocsp_responder_certificate.pem deleted file mode 100644 index 53bc4a92a..000000000 --- a/certbot/tests/testdata/ocsp_responder_certificate.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEpjCCAo6gAwIBAgINARMIGYlEsD1LTt6D7zANBgkqhkiG9w0BAQsFADBRMQsw -CQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNV -BAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDQwNTEwMDAwMFoX -DTE5MDcwNDEwMDAwMFowSTELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3Mg -QVMtOTgzMTYzMzI3MRswGQYDVQQDDBJCdXlwYXNzIFRlc3Q0IE9DU1AwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKGF+kYNd1fbhYT7Vf9xouZlx+4w45 -Y5EowPoaSKFo4uUDDxkj4PwmMiH4w9Q2bGrCbZRrDrvlNVY/kwzLu4CIk6Ip0dgm -VZGNFB3Xo9nai7rI5pn/YVvVnDIQXh1LRbekzLVyHvhRgMpRb19xN/iYsxaOJDph -8eAgbTKf6eitvfbvn/zXHj4KGKycuULI4+mwlfV3uioT4ulbT7PTVJetgi/XXFDO -xMjbqx6I1ZMmzKJ6LNaFlfx6GdZsaLRDCidHzGp8Fm4ZdV+UPvMZcVDQO6rvQ3wU -iGyCqgfE5e0aFvfeLoBPBtaoT0Ht1CvGdTfVet6PXrF6gh40fdEH5Ob5AgMBAAGj -gYQwgYEwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBR32dAGkWbY3RkmjUms2jAvanti -sTAdBgNVHQ4EFgQU3VlR+sSIVpmXklieP7IlpVUcXIowDgYDVR0PAQH/BAQDAgeA -MBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GCSsGAQUFBzABBQQCBQAwDQYJKoZIhvcN -AQELBQADggIBAFBRLVsBadNFAoFi0HOrfxYsiqggZGJLlgxGyi/0NBIgduG4kcpM -THvplwBwMQEqyp5511pSEbLPAFj8EqC5c46hXZXmT49xlfRvr2Bo+qtTPV9szuWr -8muEIejwRrkATpqWPZWR2zVTXfB90mU2oGuRvxUVmnW4v+FrCChJo7+9yTocZJKx -p4vxYfPMeggomdGAAUz94+0ppSjOLDzs3MA8uOcR0zJ2Y7UHb7PBf/HiM3GO2uKB -sRgdDaGIf/PNpav0xJ/abGNNNwvXzHiMgqqImsuv/JoncPQWbClNurhXpdN7xt9C -HcLX2AdggabcogjWm4guBFuFTsL1i0l8Bsu/6iPJ7ddCeANfYzf7h6AcQq12uFl3 -070F29DtPh8D3FPWgRZZsxoANFjXErxfj4a4+DR+jhhkb9YM/wI0vCOM7W6PKxVn -ZK5kHGOQTcQMj7RCX52gEf27M33zC7HVam+kKhGvwq7D9Bs5hZclzcbjpR4eIxT7 -tzuiy5VpPh1DRLPrphPUB4xsA1dy6zbkg8OqddG6NxD++ja/iZyzSB3SeWyO02qA -QoK2FzDasxpZ9rT3ioAcms3wVNe4lcd4OP8gHZONuat/gvxk6OZvAld6cnIrQZYB -Tbu89ZWvhsyI3p4YC/15pUvA95j9Y0te+G+CF22Eoyb+rtz6mMletnUB ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/os-release b/certbot/tests/testdata/os-release deleted file mode 100644 index 15bc5fb3c..000000000 --- a/certbot/tests/testdata/os-release +++ /dev/null @@ -1,7 +0,0 @@ -NAME="SystemdOS" -VERSION="42.42.42 LTS, Unreal" -ID=systemdos -ID_LIKE="something nonexistent debian" -VERSION_ID="42" -HOME_URL="http://www.example.com/" -SUPPORT_URL="http://help.example.com/" diff --git a/certbot/tests/testdata/rsa2048_key.pem b/certbot/tests/testdata/rsa2048_key.pem deleted file mode 100644 index 33a68c74d..000000000 --- a/certbot/tests/testdata/rsa2048_key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm1WIecnHjL4Fs -JvxDP27GyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop -+D7s+oh0apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3 -DaNokGn7r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf -5QU5pFx60a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDV -MJ3mIB8FOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf -3nCJFk1fAgMBAAECggEAJkhbVntagfgd+cbZbXm2sIdKQGlwXk92/Zxd3tZMcuNY -rU+/C2bJ5uTEm+0R/V9f3FXlsCagGde2t7ExFnJScSRAGCuFRxudMMI/wNvUvnpR -O9vN3HxrRo2rZqBkqHIZCR0d2Bxs/0cvGqTLZgsVWKV4xM07TThcE7DtvsNGegRn -WFxfsRcRypkIvZoba1HagvCituRBEa07R7mQp8kRhP9ZeRq3bZws9qBmqzj1cylG -q8QA4Foq7sK8P78bpIhrcOFBDAr+Vr1ZGY6u01J0w13MUtl6iIx4VCjQKt4NkzsK -dj2q+GAMwhReR2ZS42o8LiyGpwusj+dKIFfFekgK2QKBgQD4wwmRDgvt85brQTNF -Tkhui0eToz5oXt8mVDb58nwkpojFQOv87ZyNsEqm7S0t/3RtEViVio2aymTMsrz4 -21vRq46dvhINQ3DoMok6xIchEOEgMeonOilkURWtrMjD/Kn297Asv7zOqI5BCNiP -3FFcRqf+CaqbhnOgMkcI5z6b7QKBgQDtjM1otFFHyS7ctyLRuMeFyxWUSbWHvi8U -xjUW256c6wpQ2DBLSVB61VQjfrSjkZ5DJVFGnbw42HxSDafL11mzTbY1vDbgtgLK -YiuVHG7OYZJTLaZoM68BseX4xHN8FztnvvP1ttuk5oFb+vD8q6ODZSEawRd3PvtX -D7RtNouc+wKBgQDiwBWGTUF+gt18T5BGilbnvLlf0Btg06mgrH74UpnqZoqhEs6J -XKWpWZqSkfruxL4BdSBEH2l4QSiklgA+7uTBOBnlm42k3WaboQUJtn5eG5651AXV -/+Qe9vJFvwu56iObZKcIAzY9QdN5YHDWoULgU99pZrJG1cWrrmilqvOc+QKBgQCB -iOslslY0N+926eJxzDn4qkJtJzh2+e1AfcjLWx0F4mEwroK/Ow5IvPVxmZE1NJ3B -baMBR9gwg1RfhhS+4gKG9NRsPuMJ7BZfd+LeH7AImEorU1RPtAc1fGW0HqP+wchi -DU2I6pqhNBTMLG2myo2Sg93mce6y1sRFuEmh2EGPawKBgQC3uUEdjQekXaxXfYHi -1Dk3Ht1a9t8XxwoCVRqicE7lqlwDtS2y9lHAeUP7JNy8ZGNjx8srRZpkYVMztugo -Ecw26UA7FbNqJP5OPkGjfiFqtOq70h9vlfLdiAPmoqyOx//RkgiNXt9m5xcDzzdB -7EtBK59KSiQkB8fHtooy7Ipiiw== ------END PRIVATE KEY----- diff --git a/certbot/tests/testdata/rsa256_key.pem b/certbot/tests/testdata/rsa256_key.pem deleted file mode 100644 index 659274d1d..000000000 --- a/certbot/tests/testdata/rsa256_key.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh -AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N -E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 -rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt ------END RSA PRIVATE KEY----- diff --git a/certbot/tests/testdata/rsa512_key.pem b/certbot/tests/testdata/rsa512_key.pem deleted file mode 100644 index 610c8d315..000000000 --- a/certbot/tests/testdata/rsa512_key.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 -vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn -elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc -mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp -Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj -8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq -6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ ------END RSA PRIVATE KEY----- diff --git a/certbot/tests/testdata/sample-archive/cert1.pem b/certbot/tests/testdata/sample-archive/cert1.pem deleted file mode 100644 index 4010000ef..000000000 --- a/certbot/tests/testdata/sample-archive/cert1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF -ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5 -MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+ -slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN -NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h -A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx -UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP -r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC -AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB -BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8 -L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE -bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy -eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0 -c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB -8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw -Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl -cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy -dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl -IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0 -b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy -KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve -jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2 -Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU -+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf -rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ== ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/sample-archive/chain1.pem b/certbot/tests/testdata/sample-archive/chain1.pem deleted file mode 100644 index 760417fe9..000000000 --- a/certbot/tests/testdata/sample-archive/chain1.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV -BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw -NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i -8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8 -tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj -7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8 -BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD -HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj -UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7 -eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA -A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB -vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl -zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo -vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L -oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW -rFo4Uv1EnkKJm3vJFe50eJGhEKlx ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/sample-archive/fullchain1.pem b/certbot/tests/testdata/sample-archive/fullchain1.pem deleted file mode 100644 index 6e24d6038..000000000 --- a/certbot/tests/testdata/sample-archive/fullchain1.pem +++ /dev/null @@ -1,47 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF -ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5 -MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+ -slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN -NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h -A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx -UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP -r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC -AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB -BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8 -L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE -bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy -eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0 -c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB -8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw -Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl -cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy -dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl -IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0 -b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy -KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve -jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2 -Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU -+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf -rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV -BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw -NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i -8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8 -tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj -7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8 -BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD -HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj -UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7 -eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA -A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB -vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl -zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo -vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L -oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW -rFo4Uv1EnkKJm3vJFe50eJGhEKlx ------END CERTIFICATE----- diff --git a/certbot/tests/testdata/sample-archive/privkey1.pem b/certbot/tests/testdata/sample-archive/privkey1.pem deleted file mode 100644 index f03fdd0a3..000000000 --- a/certbot/tests/testdata/sample-archive/privkey1.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8rnaiynCHVmeV -WitX7oZRA22bb7NhPrJZuHZkktZxb3uzHZWevStIHbZ4ZIj0NMBkE5YoHg2fH2PI -i/OxmTtLaeX+0vkCTTWAomMOo+KqeSQcv8xttpCc9ULru/71zGC8y7yusemIsYlg -Fsqqr5VFQS0ta+Ft4QOWfhdRCLF3ss+0yXt5QMyFgAMaXx/Tr4iM9qxA6LbgnVO+ -9VEiyzUsOKiaIzZU8VKWRupRhbPChm/LakTl5J2jChOD7zd0iE4sUlExW5LbdVs+ -tJqFmzXSzCr4JM7lD6+L4jtj+EjubJTQGNGXhLLD6VQHpTdxL6wHBGbaCphuX50A -1aPLlWoNAgMBAAECggEAfKKWFWS6PnwSAnNErFoQeZVVItb/XB5JO8EA2+CvLNFi -mefR/MCixYlzDkYCvaXW7ISPrMJlZxYaGNBx0oAQzfkPB2wfNqj/zY/29SXGxast -8puzk0mEb1oHsaZGfeFaiXvfkFpPlI8J2uJTT7qaVNv/1sArciSv9QonpsyiRhlB -yqT49juNVoR1tJHyXzkkRfHKTG8OlJd4kuFOl3fM9dTFPQ/ft0kTNAQ/B4SFvSwF -RJsbLbsbFGsUdV9ekE6UX6oWD/Ah707rvgtCyS0Bc+0O3t2EKwmm3RXPRUMHCVxE -bKdTxRB4etbjMVXMuVhB8Y4GbfrtMCy+qxZQ6znCAQKBgQDr7bcYAZVZp/nBMVB+ -lBO9w73J6lnEWm6bZ9728KlGAKETaRhxZQSi6TN6MWwNwnk6rinyz4uVwVr9ZRCs -WkB1TbvW0JNcWdr3YClwsKXAt8X22bjGe0LagDJHG6r1TPS+MdovOS2M6IMaxlbT -rzFhSJ8ojLX3tqnOsmc7YAFLjQKBgQDMu8E9hoJt82lQzOGrjHmGzGEu2GLx9WKO -e4nkj335kX6fIhMMqSXBFbTJZwXoYvk5J8ZnaARbYG0m5nxDCwRjX5HWa8q0B2Po -ta53w01sKKznzlPjUhsdhEthun7MCFfLZpgvcZ9xVzOXo3/Zfn2+RrsPSjrVDqBy -hj+k5mW4gQKBgHFWKf3LTO7cBdvsD8ou4mjn7nVgMi1kb/wR4wdnxzmMtdR4STi4 -GYkVVBhgQ5M8mDY7UoWFdH3FfCt8cI0Lcimn5ROl8RSNSeZKeL3c7lNtNRmHr/8R -WaVTrlOAlBjxFiWEF1dWNW6ah9jF7RIV+DfOxj6ZkhTk2CAmjfb1AMpFAoGABf96 -KdNG/vGipDtcYSo8ZTaXoke0nmISARqdb5TEnAsnKoJVDInoEUARi9T411YO9x2z -MlRZzFOG3xzhhxVLi53BKAcAaUXOJ4MrGVcfbYvDhQcGbiJ5qOO3UaWlEVUtPUhE -LR+nDCsB1+9yT2zlQi3QTSJflt5W1QQZ2TrmwAECgYEAvQ7+sTcHs1K9yKj7koEu -A19FbMA0IwvrVRcV/VqmlsoW6e6wW2YND+GtaDbKdD0aBPivqLJwpNFrsRA+W0iB -vzmML6sKhhL+j7tjSgq+iQdBkKz0j9PyReuhe9CRnljMmyun+4qKEk0KUvxBrjPY -Skn+ML18qyUoEPnmbpfHxCs= ------END PRIVATE KEY----- diff --git a/certbot/tests/testdata/sample-renewal-ancient.conf b/certbot/tests/testdata/sample-renewal-ancient.conf deleted file mode 100644 index 9586d5492..000000000 --- a/certbot/tests/testdata/sample-renewal-ancient.conf +++ /dev/null @@ -1,73 +0,0 @@ -cert = MAGICDIR/live/sample-renewal-ancient/cert.pem -privkey = MAGICDIR/live/sample-renewal-ancient/privkey.pem -chain = MAGICDIR/live/sample-renewal-ancient/chain.pem -fullchain = MAGICDIR/live/sample-renewal-ancient/fullchain.pem -renew_before_expiry = 1 year - -# Options and defaults used in the renewal process -[renewalparams] -no_self_upgrade = False -apache_enmod = a2enmod -no_verify_ssl = False -ifaces = None -apache_dismod = a2dismod -register_unsafely_without_email = False -apache_handle_modules = True -uir = None -installer = None -nginx_ctl = nginx -config_dir = MAGICDIR -text_mode = False -func = -staging = True -prepare = False -work_dir = /var/lib/letsencrypt -tos = False -init = False -http01_port = 80 -duplicate = False -noninteractive_mode = True -key_path = None -nginx = False -nginx_server_root = /etc/nginx -fullchain_path = /home/ubuntu/letsencrypt/chain.pem -email = None -csr = None -agree_dev_preview = None -redirect = None -verb = certonly -verbose_count = -3 -config_file = None -renew_by_default = False -hsts = False -apache_handle_sites = True -authenticator = webroot -domains = isnot.org, -rsa_key_size = 2048 -apache_challenge_location = /etc/apache2 -checkpoints = 1 -manual_test_mode = False -apache = False -cert_path = /home/ubuntu/letsencrypt/cert.pem -webroot_path = /var/www/ -reinstall = False -expand = False -strict_permissions = False -apache_server_root = /etc/apache2 -account = None -dry_run = False -manual_public_ip_logging_ok = False -chain_path = /home/ubuntu/letsencrypt/chain.pem -break_my_certs = False -standalone = True -manual = False -server = https://acme-staging.api.letsencrypt.org/directory -webroot = True -os_packages_only = False -apache_init_script = None -user_agent = None -apache_le_vhost_ext = -le-ssl.conf -debug = False -logs_dir = /var/log/letsencrypt -apache_vhost_root = /etc/apache2/sites-available -configurator = None diff --git a/certbot/tests/testdata/sample-renewal.conf b/certbot/tests/testdata/sample-renewal.conf deleted file mode 100644 index 936c5c0e0..000000000 --- a/certbot/tests/testdata/sample-renewal.conf +++ /dev/null @@ -1,75 +0,0 @@ -cert = MAGICDIR/live/sample-renewal/cert.pem -privkey = MAGICDIR/live/sample-renewal/privkey.pem -chain = MAGICDIR/live/sample-renewal/chain.pem -fullchain = MAGICDIR/live/sample-renewal/fullchain.pem -renew_before_expiry = 4 years - -# Options and defaults used in the renewal process -[renewalparams] -no_self_upgrade = False -apache_enmod = a2enmod -no_verify_ssl = False -ifaces = None -apache_dismod = a2dismod -register_unsafely_without_email = False -apache_handle_modules = True -uir = None -installer = None -nginx_ctl = nginx -config_dir = MAGICDIR -text_mode = False -func = -staging = True -prepare = False -work_dir = /var/lib/letsencrypt -tos = False -init = False -http01_port = 80 -duplicate = False -noninteractive_mode = True -key_path = None -nginx = False -nginx_server_root = /etc/nginx -fullchain_path = /home/ubuntu/letsencrypt/chain.pem -email = None -csr = None -agree_dev_preview = None -redirect = None -verb = certonly -verbose_count = -3 -config_file = None -renew_by_default = False -hsts = False -apache_handle_sites = True -authenticator = standalone -domains = isnot.org, -rsa_key_size = 2048 -apache_challenge_location = /etc/apache2 -checkpoints = 1 -manual_test_mode = False -apache = False -cert_path = /home/ubuntu/letsencrypt/cert.pem -webroot_path = None -reinstall = False -expand = False -strict_permissions = False -apache_server_root = /etc/apache2 -account = None -dry_run = False -manual_public_ip_logging_ok = False -chain_path = /home/ubuntu/letsencrypt/chain.pem -break_my_certs = False -standalone = True -manual = False -server = https://acme-staging-v02.api.letsencrypt.org/directory -webroot = False -os_packages_only = False -apache_init_script = None -user_agent = None -apache_le_vhost_ext = -le-ssl.conf -debug = False -logs_dir = /var/log/letsencrypt -apache_vhost_root = /etc/apache2/sites-available -configurator = None -must_staple = True -[[webroot_map]] diff --git a/certbot/tests/testdata/webrootconftest.ini b/certbot/tests/testdata/webrootconftest.ini deleted file mode 100644 index de3bd98a6..000000000 --- a/certbot/tests/testdata/webrootconftest.ini +++ /dev/null @@ -1,3 +0,0 @@ -webroot -webroot-path = /tmp -domains = eg.com, eg2.com diff --git a/certbot/tests/util.py b/certbot/tests/util.py deleted file mode 100644 index d9ff18f1c..000000000 --- a/certbot/tests/util.py +++ /dev/null @@ -1,399 +0,0 @@ -"""Test utilities. - -.. warning:: This module is not part of the public API. - -""" -import logging -import shutil -import sys -import tempfile -import unittest -from multiprocessing import Process, Event - -import OpenSSL -import josepy as jose -import mock -import pkg_resources -import six -from six.moves import reload_module # pylint: disable=import-error -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization - -from certbot._internal import configuration -from certbot._internal import constants -from certbot import interfaces -from certbot._internal import lock -from certbot._internal import storage -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem -from certbot.display import util as display_util - - -def vector_path(*names): - """Path to a test vector.""" - return pkg_resources.resource_filename( - __name__, os.path.join('testdata', *names)) - - -def load_vector(*names): - """Load contents of a test vector.""" - # luckily, resource_string opens file in binary mode - data = pkg_resources.resource_string( - __name__, os.path.join('testdata', *names)) - # Try at most to convert CRLF to LF when data is text - try: - return data.decode().replace('\r\n', '\n').encode() - except ValueError: - # Failed to process the file with standard encoding. - # Most likely not a text file, return its bytes untouched. - return data - - -def _guess_loader(filename, loader_pem, loader_der): - _, ext = os.path.splitext(filename) - if ext.lower() == '.pem': - return loader_pem - elif ext.lower() == '.der': - return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") - - -def load_cert(*names): - """Load certificate.""" - loader = _guess_loader( - names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return OpenSSL.crypto.load_certificate(loader, load_vector(*names)) - - -def load_csr(*names): - """Load certificate request.""" - loader = _guess_loader( - names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names)) - - -def load_comparable_csr(*names): - """Load ComparableX509 certificate request.""" - return jose.ComparableX509(load_csr(*names)) - - -def load_rsa_private_key(*names): - """Load RSA private key.""" - loader = _guess_loader(names[-1], serialization.load_pem_private_key, - serialization.load_der_private_key) - return jose.ComparableRSAKey(loader( - load_vector(*names), password=None, backend=default_backend())) - - -def load_pyopenssl_private_key(*names): - """Load pyOpenSSL private key.""" - loader = _guess_loader( - names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return OpenSSL.crypto.load_privatekey(loader, load_vector(*names)) - - -def make_lineage(config_dir, testfile): - """Creates a lineage defined by testfile. - - This creates the archive, live, and renewal directories if - necessary and creates a simple lineage. - - :param str config_dir: path to the configuration directory - :param str testfile: configuration file to base the lineage on - - :returns: path to the renewal conf file for the created lineage - :rtype: str - - """ - lineage_name = testfile[:-len('.conf')] - - conf_dir = os.path.join( - config_dir, constants.RENEWAL_CONFIGS_DIR) - archive_dir = os.path.join( - config_dir, constants.ARCHIVE_DIR, lineage_name) - live_dir = os.path.join( - config_dir, constants.LIVE_DIR, lineage_name) - - for directory in (archive_dir, conf_dir, live_dir,): - if not os.path.exists(directory): - filesystem.makedirs(directory) - - sample_archive = vector_path('sample-archive') - for kind in os.listdir(sample_archive): - shutil.copyfile(os.path.join(sample_archive, kind), - os.path.join(archive_dir, kind)) - - for kind in storage.ALL_FOUR: - os.symlink(os.path.join(archive_dir, '{0}1.pem'.format(kind)), - os.path.join(live_dir, '{0}.pem'.format(kind))) - - conf_path = os.path.join(config_dir, conf_dir, testfile) - with open(vector_path(testfile)) as src: - with open(conf_path, 'w') as dst: - dst.writelines( - line.replace('MAGICDIR', config_dir) for line in src) - - return conf_path - - -def patch_get_utility(target='zope.component.getUtility'): - """Patch zope.component.getUtility to use a special mock IDisplay. - - The mock IDisplay works like a regular mock object, except it also - also asserts that methods are called with valid arguments. - - :param str target: path to patch - - :returns: mock zope.component.getUtility - :rtype: mock.MagicMock - - """ - return mock.patch(target, new_callable=_create_get_utility_mock) - - -def patch_get_utility_with_stdout(target='zope.component.getUtility', - stdout=None): - """Patch zope.component.getUtility to use a special mock IDisplay. - - The mock IDisplay works like a regular mock object, except it also - also asserts that methods are called with valid arguments. - - The `message` argument passed to the IDisplay methods is passed to - stdout's write method. - - :param str target: path to patch - :param object stdout: object to write standard output to; it is - expected to have a `write` method - - :returns: mock zope.component.getUtility - :rtype: mock.MagicMock - - """ - stdout = stdout if stdout else six.StringIO() - - freezable_mock = _create_get_utility_mock_with_stdout(stdout) - return mock.patch(target, new=freezable_mock) - - -class FreezableMock(object): - """Mock object with the ability to freeze attributes. - - This class works like a regular mock.MagicMock object, except - attributes and behavior set before the object is frozen cannot - be changed during tests. - - If a func argument is provided to the constructor, this function - is called first when an instance of FreezableMock is called, - followed by the usual behavior defined by MagicMock. The return - value of func is ignored. - - """ - def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT): - self._frozen_set = set() if frozen else {'freeze', } - self._func = func - self._mock = mock.MagicMock() - if return_value != mock.sentinel.DEFAULT: - self.return_value = return_value - self._frozen = frozen - - def freeze(self): - """Freeze object preventing further changes.""" - self._frozen = True - - def __call__(self, *args, **kwargs): - if self._func is not None: - self._func(*args, **kwargs) - return self._mock(*args, **kwargs) - - def __getattribute__(self, name): - if name == '_frozen': - try: - return object.__getattribute__(self, name) - except AttributeError: - return False - elif name in ('return_value', 'side_effect',): - return getattr(object.__getattribute__(self, '_mock'), name) - elif name == '_frozen_set' or name in self._frozen_set: - return object.__getattribute__(self, name) - else: - return getattr(object.__getattribute__(self, '_mock'), name) - - def __setattr__(self, name, value): - """ Before it is frozen, attributes are set on the FreezableMock - instance and added to the _frozen_set. Attributes in the _frozen_set - cannot be changed after the FreezableMock is frozen. In this case, - they are set on the underlying _mock. - - In cases of return_value and side_effect, these attributes are always - passed through to the instance's _mock and added to the _frozen_set - before the object is frozen. - - """ - if self._frozen: - if name in self._frozen_set: - raise AttributeError('Cannot change frozen attribute ' + name) - else: - return setattr(self._mock, name, value) - - if name != '_frozen_set': - self._frozen_set.add(name) - - if name in ('return_value', 'side_effect'): - return setattr(self._mock, name, value) - - return object.__setattr__(self, name, value) - - -def _create_get_utility_mock(): - display = FreezableMock() - # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 - if name != 'notification': - frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call) - setattr(display, name, frozen_mock) - display.freeze() - return FreezableMock(frozen=True, return_value=display) - - -def _create_get_utility_mock_with_stdout(stdout): - def _write_msg(message, *unused_args, **unused_kwargs): - """Write to message to stdout. - """ - if message: - stdout.write(message) - - def mock_method(*args, **kwargs): - """ - Mock function for IDisplay methods. - """ - _assert_valid_call(args, kwargs) - _write_msg(*args, **kwargs) - - - display = FreezableMock() - # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 - if name == 'notification': - frozen_mock = FreezableMock(frozen=True, - func=_write_msg) - setattr(display, name, frozen_mock) - else: - frozen_mock = FreezableMock(frozen=True, - func=mock_method) - setattr(display, name, frozen_mock) - display.freeze() - - return FreezableMock(frozen=True, return_value=display) - - -def _assert_valid_call(*args, **kwargs): - assert_args = [args[0] if args else kwargs['message']] - - assert_kwargs = {} - assert_kwargs['default'] = kwargs.get('default', None) - assert_kwargs['cli_flag'] = kwargs.get('cli_flag', None) - assert_kwargs['force_interactive'] = kwargs.get('force_interactive', False) - - display_util.assert_valid_call(*assert_args, **assert_kwargs) - - -class TempDirTestCase(unittest.TestCase): - """Base test class which sets up and tears down a temporary directory""" - - def setUp(self): - """Execute before test""" - self.tempdir = tempfile.mkdtemp() - - def tearDown(self): - """Execute after test""" - # Cleanup opened resources after a test. This is usually done through atexit handlers in - # Certbot, but during tests, atexit will not run registered functions before tearDown is - # called and instead will run them right before the entire test process exits. - # It is a problem on Windows, that does not accept to clean resources before closing them. - logging.shutdown() - # Remove logging handlers that have been closed so they won't be - # accidentally used in future tests. - logging.getLogger().handlers = [] - util._release_locks() # pylint: disable=protected-access - - shutil.rmtree(self.tempdir) - - -class ConfigTestCase(TempDirTestCase): - """Test class which sets up a NamespaceConfig object.""" - def setUp(self): - super(ConfigTestCase, self).setUp() - self.config = configuration.NamespaceConfig( - mock.MagicMock(**constants.CLI_DEFAULTS) - ) - self.config.verb = "certonly" - self.config.config_dir = os.path.join(self.tempdir, 'config') - self.config.work_dir = os.path.join(self.tempdir, 'work') - self.config.logs_dir = os.path.join(self.tempdir, 'logs') - self.config.cert_path = constants.CLI_DEFAULTS['auth_cert_path'] - self.config.fullchain_path = constants.CLI_DEFAULTS['auth_chain_path'] - self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path'] - self.config.server = "https://example.com" - - -def _handle_lock(event_in, event_out, path): - """ - Acquire a file lock on given path, then wait to release it. This worker is coordinated - using events to signal when the lock should be acquired and released. - :param multiprocessing.Event event_in: event object to signal when to release the lock - :param multiprocessing.Event event_out: event object to signal when the lock is acquired - :param path: the path to lock - """ - if os.path.isdir(path): - my_lock = lock.lock_dir(path) - else: - my_lock = lock.LockFile(path) - try: - event_out.set() - assert event_in.wait(timeout=20), 'Timeout while waiting to release the lock.' - finally: - my_lock.release() - - -def lock_and_call(callback, path_to_lock): - """ - Grab a lock on path_to_lock from a foreign process then execute the callback. - :param callable callback: object to call after acquiring the lock - :param str path_to_lock: path to file or directory to lock - """ - # Reload certbot.util module to reset internal _LOCKS dictionary. - reload_module(util) - - emit_event = Event() - receive_event = Event() - process = Process(target=_handle_lock, args=(emit_event, receive_event, path_to_lock)) - process.start() - - # Wait confirmation that lock is acquired - assert receive_event.wait(timeout=10), 'Timeout while waiting to acquire the lock.' - # Execute the callback - callback() - # Trigger unlock from foreign process - emit_event.set() - - # Wait for process termination - process.join(timeout=10) - assert process.exitcode == 0 - - -def skip_on_windows(reason): - """Decorator to skip permanently a test on Windows. A reason is required.""" - def wrapper(function): - """Wrapped version""" - return unittest.skipIf(sys.platform == 'win32', reason)(function) - return wrapper - - -def temp_join(path): - """ - Return the given path joined to the tempdir path for the current platform - Eg.: 'cert' => /tmp/cert (Linux) or 'C:\\Users\\currentuser\\AppData\\Temp\\cert' (Windows) - """ - return os.path.join(tempfile.gettempdir(), path) diff --git a/certbot/util.py b/certbot/util.py deleted file mode 100644 index 5d8aa8f22..000000000 --- a/certbot/util.py +++ /dev/null @@ -1,603 +0,0 @@ -"""Utilities for all Certbot.""" -import argparse -import atexit -import collections -from collections import OrderedDict -# distutils.version under virtualenv confuses pylint -# For more info, see: https://github.com/PyCQA/pylint/issues/73 -import distutils.version # pylint: disable=import-error,no-name-in-module -import errno -import logging -import platform -import re -import socket -import subprocess -import sys - -import configargparse -import six - -from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module - -from certbot._internal import constants -from certbot import errors -from certbot._internal import lock -from certbot.compat import os -from certbot.compat import filesystem - -if sys.platform.startswith('linux'): - import distro - _USE_DISTRO = True -else: - _USE_DISTRO = False - -logger = logging.getLogger(__name__) - - -Key = collections.namedtuple("Key", "file pem") -# Note: form is the type of data, "pem" or "der" -CSR = collections.namedtuple("CSR", "file data form") - - -# ANSI SGR escape codes -# Formats text as bold or with increased intensity -ANSI_SGR_BOLD = '\033[1m' -# Colors text red -ANSI_SGR_RED = "\033[31m" -# Resets output format -ANSI_SGR_RESET = "\033[0m" - - -PERM_ERR_FMT = os.linesep.join(( - "The following error was encountered:", "{0}", - "Either run as root, or set --config-dir, " - "--work-dir, and --logs-dir to writeable paths.")) - - -# Stores importing process ID to be used by atexit_register() -_INITIAL_PID = os.getpid() -# Maps paths to locked directories to their lock object. All locks in -# the dict are attempted to be cleaned up at program exit. If the -# program exits before the lock is cleaned up, it is automatically -# released, but the file isn't deleted. -_LOCKS = OrderedDict() # type: OrderedDict[str, lock.LockFile] - - -def run_script(params, log=logger.error): - """Run the script with the given params. - - :param list params: List of parameters to pass to Popen - :param callable log: Logger method to use for errors - - """ - try: - proc = subprocess.Popen(params, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - - except (OSError, ValueError): - msg = "Unable to run the command: %s" % " ".join(params) - log(msg) - raise errors.SubprocessError(msg) - - stdout, stderr = proc.communicate() - - if proc.returncode != 0: - msg = "Error while running %s.\n%s\n%s" % ( - " ".join(params), stdout, stderr) - # Enter recovery routine... - log(msg) - raise errors.SubprocessError(msg) - - return stdout, stderr - - -def exe_exists(exe): - """Determine whether path/name refers to an executable. - - :param str exe: Executable path or name - - :returns: If exe is a valid executable - :rtype: bool - - """ - path, _ = os.path.split(exe) - if path: - return filesystem.is_executable(exe) - else: - for path in os.environ["PATH"].split(os.pathsep): - if filesystem.is_executable(os.path.join(path, exe)): - return True - - return False - - -def lock_dir_until_exit(dir_path): - """Lock the directory at dir_path until program exit. - - :param str dir_path: path to directory - - :raises errors.LockError: if the lock is held by another process - - """ - if not _LOCKS: # this is the first lock to be released at exit - atexit_register(_release_locks) - - if dir_path not in _LOCKS: - _LOCKS[dir_path] = lock.lock_dir(dir_path) - - -def _release_locks(): - for dir_lock in six.itervalues(_LOCKS): - try: - dir_lock.release() - except: # pylint: disable=bare-except - msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock) - logger.debug(msg, exc_info=True) - _LOCKS.clear() - - -def set_up_core_dir(directory, mode, strict): - """Ensure directory exists with proper permissions and is locked. - - :param str directory: Path to a directory. - :param int mode: Directory mode. - :param bool strict: require directory to be owned by current user - - :raises .errors.LockError: if the directory cannot be locked - :raises .errors.Error: if the directory cannot be made or verified - - """ - try: - make_or_verify_dir(directory, mode, strict) - lock_dir_until_exit(directory) - except OSError as error: - logger.debug("Exception was:", exc_info=True) - raise errors.Error(PERM_ERR_FMT.format(error)) - - -def make_or_verify_dir(directory, mode=0o755, strict=False): - """Make sure directory exists with proper permissions. - - :param str directory: Path to a directory. - :param int mode: Directory mode. - :param bool strict: require directory to be owned by current user - - :raises .errors.Error: if a directory already exists, - but has wrong permissions or owner - - :raises OSError: if invalid or inaccessible file names and - paths, or other arguments that have the correct type, - but are not accepted by the operating system. - - """ - try: - filesystem.makedirs(directory, mode) - except OSError as exception: - if exception.errno == errno.EEXIST: - if strict and not filesystem.check_permissions(directory, mode): - raise errors.Error( - "%s exists, but it should be owned by current user with" - " permissions %s" % (directory, oct(mode))) - else: - raise - - -def safe_open(path, mode="w", chmod=None): - """Safely open a file. - - :param str path: Path to a file. - :param str mode: Same os `mode` for `open`. - :param int chmod: Same as `mode` for `filesystem.open`, uses Python defaults - if ``None``. - - """ - open_args = () # type: Union[Tuple[()], Tuple[int]] - if chmod is not None: - open_args = (chmod,) - fdopen_args = () # type: Union[Tuple[()], Tuple[int]] - fd = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) - return os.fdopen(fd, mode, *fdopen_args) - - -def _unique_file(path, filename_pat, count, chmod, mode): - while True: - current_path = os.path.join(path, filename_pat(count)) - try: - return safe_open(current_path, chmod=chmod, mode=mode),\ - os.path.abspath(current_path) - except OSError as err: - # "File exists," is okay, try a different name. - if err.errno != errno.EEXIST: - raise - count += 1 - - -def unique_file(path, chmod=0o777, mode="w"): - """Safely finds a unique file. - - :param str path: path/filename.ext - :param int chmod: File mode - :param str mode: Open mode - - :returns: tuple of file object and file name - - """ - path, tail = os.path.split(path) - return _unique_file( - path, filename_pat=(lambda count: "%04d_%s" % (count, tail)), - count=0, chmod=chmod, mode=mode) - - -def unique_lineage_name(path, filename, chmod=0o644, mode="w"): - """Safely finds a unique file using lineage convention. - - :param str path: directory path - :param str filename: proposed filename - :param int chmod: file mode - :param str mode: open mode - - :returns: tuple of file object and file name (which may be modified - from the requested one by appending digits to ensure uniqueness) - - :raises OSError: if writing files fails for an unanticipated reason, - such as a full disk or a lack of permission to write to - specified location. - - """ - preferred_path = os.path.join(path, "%s.conf" % (filename)) - try: - return safe_open(preferred_path, chmod=chmod), preferred_path - except OSError as err: - if err.errno != errno.EEXIST: - raise - return _unique_file( - path, filename_pat=(lambda count: "%s-%04d.conf" % (filename, count)), - count=1, chmod=chmod, mode=mode) - - -def safely_remove(path): - """Remove a file that may not exist.""" - try: - os.remove(path) - except OSError as err: - if err.errno != errno.ENOENT: - raise - - -def get_filtered_names(all_names): - """Removes names that aren't considered valid by Let's Encrypt. - - :param set all_names: all names found in the configuration - - :returns: all found names that are considered valid by LE - :rtype: set - - """ - filtered_names = set() - for name in all_names: - try: - filtered_names.add(enforce_le_validity(name)) - except errors.ConfigurationError: - logger.debug('Not suggesting name "%s"', name, exc_info=True) - return filtered_names - -def get_os_info(): - """ - Get OS name and version - - :returns: (os_name, os_version) - :rtype: `tuple` of `str` - """ - - return get_python_os_info(pretty=False) - -def get_os_info_ua(): - """ - Get OS name and version string for User Agent - - :returns: os_ua - :rtype: `str` - """ - if _USE_DISTRO: - os_info = distro.name(pretty=True) - - if not _USE_DISTRO or not os_info: - return " ".join(get_python_os_info(pretty=True)) - return os_info - -def get_systemd_os_like(): - """ - Get a list of strings that indicate the distribution likeness to - other distributions. - - :returns: List of distribution acronyms - :rtype: `list` of `str` - """ - - if _USE_DISTRO: - return distro.like().split(" ") - return [] - -def get_var_from_file(varname, filepath="/etc/os-release"): - """ - Get single value from a file formatted like systemd /etc/os-release - - :param str varname: Name of variable to fetch - :param str filepath: File path of os-release file - :returns: requested value - :rtype: `str` - """ - - var_string = varname+"=" - if not os.path.isfile(filepath): - return "" - with open(filepath, 'r') as fh: - contents = fh.readlines() - - for line in contents: - if line.strip().startswith(var_string): - # Return the value of var, normalized - return _normalize_string(line.strip()[len(var_string):]) - return "" - -def _normalize_string(orig): - """ - Helper function for get_var_from_file() to remove quotes - and whitespaces - """ - return orig.replace('"', '').replace("'", "").strip() - -def get_python_os_info(pretty=False): - """ - Get Operating System type/distribution and major version - using python platform module - - :param bool pretty: If the returned OS name should be in longer (pretty) form - - :returns: (os_name, os_version) - :rtype: `tuple` of `str` - """ - info = platform.system_alias( - platform.system(), - platform.release(), - platform.version() - ) - os_type, os_ver, _ = info - os_type = os_type.lower() - if os_type.startswith('linux') and _USE_DISTRO: - info = distro.linux_distribution(pretty) - # On arch, distro.linux_distribution() is reportedly ('','',''), - # so handle it defensively - if info[0]: - os_type = info[0] - if info[1]: - os_ver = info[1] - elif os_type.startswith('darwin'): - try: - proc = subprocess.Popen( - ["/usr/bin/sw_vers", "-productVersion"], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - except OSError: - proc = subprocess.Popen( - ["sw_vers", "-productVersion"], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - os_ver = proc.communicate()[0].rstrip('\n') - elif os_type.startswith('freebsd'): - # eg "9.3-RC3-p1" - os_ver = os_ver.partition("-")[0] - os_ver = os_ver.partition(".")[0] - elif platform.win32_ver()[1]: - os_ver = platform.win32_ver()[1] - else: - # Cases known to fall here: Cygwin python - os_ver = '' - return os_type, os_ver - -# Just make sure we don't get pwned... Make sure that it also doesn't -# start with a period or have two consecutive periods <- this needs to -# be done in addition to the regex -EMAIL_REGEX = re.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+$") - - -def safe_email(email): - """Scrub email address before using it.""" - if EMAIL_REGEX.match(email) is not None: - return not email.startswith(".") and ".." not in email - logger.warning("Invalid email address: %s.", email) - return False - - -class _ShowWarning(argparse.Action): - """Action to log a warning when an argument is used.""" - def __call__(self, unused1, unused2, unused3, option_string=None): - logger.warning("Use of %s is deprecated.", option_string) - - -def add_deprecated_argument(add_argument, argument_name, nargs): - """Adds a deprecated argument with the name argument_name. - - Deprecated arguments are not shown in the help. If they are used on - the command line, a warning is shown stating that the argument is - deprecated and no other action is taken. - - :param callable add_argument: Function that adds arguments to an - argument parser/group. - :param str argument_name: Name of deprecated argument. - :param nargs: Value for nargs when adding the argument to argparse. - - """ - if _ShowWarning not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE: - # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was - # changed from a set to a tuple. - if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set): - # pylint: disable=no-member - configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add( - _ShowWarning) - else: - configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += ( - _ShowWarning,) - add_argument(argument_name, action=_ShowWarning, - help=argparse.SUPPRESS, nargs=nargs) - - -def enforce_le_validity(domain): - """Checks that Let's Encrypt will consider domain to be valid. - - :param str domain: FQDN to check - :type domain: `str` or `unicode` - :returns: The domain cast to `str`, with ASCII-only contents - :rtype: str - :raises ConfigurationError: for invalid domains and cases where Let's - Encrypt currently will not issue certificates - - """ - domain = enforce_domain_sanity(domain) - if not re.match("^[A-Za-z0-9.-]*$", domain): - raise errors.ConfigurationError( - "{0} contains an invalid character. " - "Valid characters are A-Z, a-z, 0-9, ., and -.".format(domain)) - - labels = domain.split(".") - if len(labels) < 2: - raise errors.ConfigurationError( - "{0} needs at least two labels".format(domain)) - for label in labels: - if label.startswith("-"): - raise errors.ConfigurationError( - 'label "{0}" in domain "{1}" cannot start with "-"'.format( - label, domain)) - if label.endswith("-"): - raise errors.ConfigurationError( - 'label "{0}" in domain "{1}" cannot end with "-"'.format( - label, domain)) - return domain - -def enforce_domain_sanity(domain): - """Method which validates domain value and errors out if - the requirements are not met. - - :param domain: Domain to check - :type domain: `str` or `unicode` - :raises ConfigurationError: for invalid domains and cases where Let's - Encrypt currently will not issue certificates - - :returns: The domain cast to `str`, with ASCII-only contents - :rtype: str - """ - # Unicode - try: - if isinstance(domain, six.binary_type): - domain = domain.decode('utf-8') - domain.encode('ascii') - except UnicodeError: - raise errors.ConfigurationError("Non-ASCII domain names not supported. " - "To issue for an Internationalized Domain Name, use Punycode.") - - domain = domain.lower() - - # Remove trailing dot - domain = domain[:-1] if domain.endswith(u'.') else domain - - # Separately check for odd "domains" like "http://example.com" to fail - # fast and provide a clear error message - for scheme in ["http", "https"]: # Other schemes seem unlikely - if domain.startswith("{0}://".format(scheme)): - raise errors.ConfigurationError( - "Requested name {0} appears to be a URL, not a FQDN. " - "Try again without the leading \"{1}://\".".format( - domain, scheme - ) - ) - - # Explain separately that IP addresses aren't allowed (apart from not - # being FQDNs) because hope springs eternal concerning this point - try: - socket.inet_aton(domain) - raise errors.ConfigurationError( - "Requested name {0} is an IP address. The Let's Encrypt " - "certificate authority will not issue certificates for a " - "bare IP address.".format(domain)) - except socket.error: - # It wasn't an IP address, so that's good - pass - - # FQDN checks according to RFC 2181: domain name should be less than 255 - # octets (inclusive). And each label is 1 - 63 octets (inclusive). - # https://tools.ietf.org/html/rfc2181#section-11 - msg = "Requested domain {0} is not a FQDN because".format(domain) - if len(domain) > 255: - raise errors.ConfigurationError("{0} it is too long.".format(msg)) - labels = domain.split('.') - for l in labels: - if not l: - raise errors.ConfigurationError("{0} it contains an empty label.".format(msg)) - elif len(l) > 63: - raise errors.ConfigurationError("{0} label {1} is too long.".format(msg, l)) - - return domain - - -def is_wildcard_domain(domain): - """"Is domain a wildcard domain? - - :param domain: domain to check - :type domain: `bytes` or `str` or `unicode` - - :returns: True if domain is a wildcard, otherwise, False - :rtype: bool - - """ - if isinstance(domain, six.text_type): - wildcard_marker = u"*." - else: - wildcard_marker = b"*." - - return domain.startswith(wildcard_marker) - - -def get_strict_version(normalized): - """Converts a normalized version to a strict version. - - :param str normalized: normalized version string - - :returns: An equivalent strict version - :rtype: distutils.version.StrictVersion - - """ - # strict version ending with "a" and a number designates a pre-release - # pylint: disable=no-member - return distutils.version.StrictVersion(normalized.replace(".dev", "a")) - - -def is_staging(srv): - """ - Determine whether a given ACME server is a known test / staging server. - - :param str srv: the URI for the ACME server - :returns: True iff srv is a known test / staging server - :rtype bool: - """ - return srv == constants.STAGING_URI or "staging" in srv - - -def atexit_register(func, *args, **kwargs): - """Sets func to be called before the program exits. - - Special care is taken to ensure func is only called when the process - that first imports this module exits rather than any child processes. - - :param function func: function to be called in case of an error - - """ - atexit.register(_atexit_call, func, *args, **kwargs) - - -def _atexit_call(func, *args, **kwargs): - if _INITIAL_PID == os.getpid(): - func(*args, **kwargs) diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 415eebca1..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,183 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LetsEncrypt.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LetsEncrypt.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/LetsEncrypt" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LetsEncrypt" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_templates/footer.html b/docs/_templates/footer.html deleted file mode 100644 index 8fd0f127d..000000000 --- a/docs/_templates/footer.html +++ /dev/null @@ -1,52 +0,0 @@ -
- {% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %} - - {% endif %} - -
- -
-

- - © Copyright 2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license. - -
-
- - Let's Encrypt Status - - - {%- if build_id and build_url %} - {% trans build_url=build_url, build_id=build_id %} - - Build - {{ build_id }}. - - {% endtrans %} - {%- elif commit %} - {% trans commit=commit %} - - Revision {{ commit }}. - - {% endtrans %} - {%- elif last_updated %} - {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} - {%- endif %} - -

-
- - {%- if show_sphinx %} - {% trans %}Built with Sphinx using a theme provided by Read the Docs{% endtrans %}. - {%- endif %} - - {%- block extrafooter %} {% endblock %} - -
diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/docs/api/achallenges.rst b/docs/api/achallenges.rst deleted file mode 100644 index 90dda3f06..000000000 --- a/docs/api/achallenges.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.achallenges` ------------------------------- - -.. automodule:: certbot.achallenges - :members: diff --git a/docs/api/crypto_util.rst b/docs/api/crypto_util.rst deleted file mode 100644 index 2f473944c..000000000 --- a/docs/api/crypto_util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.crypto_util` ------------------------------- - -.. automodule:: certbot.crypto_util - :members: diff --git a/docs/api/display.rst b/docs/api/display.rst deleted file mode 100644 index 70038786c..000000000 --- a/docs/api/display.rst +++ /dev/null @@ -1,17 +0,0 @@ -:mod:`certbot.display` --------------------------- - -.. automodule:: certbot.display - :members: - -:mod:`certbot.display.util` -=============================== - -.. automodule:: certbot.display.util - :members: - -:mod:`certbot.display.ops` -============================== - -.. automodule:: certbot.display.ops - :members: diff --git a/docs/api/errors.rst b/docs/api/errors.rst deleted file mode 100644 index a9324765b..000000000 --- a/docs/api/errors.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.errors` -------------------------- - -.. automodule:: certbot.errors - :members: diff --git a/docs/api/index.rst b/docs/api/index.rst deleted file mode 100644 index be94214c9..000000000 --- a/docs/api/index.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot` ------------------- - -.. automodule:: certbot - :members: diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst deleted file mode 100644 index 2988b3b87..000000000 --- a/docs/api/interfaces.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.interfaces` ------------------------------ - -.. automodule:: certbot.interfaces - :members: diff --git a/docs/api/plugins/common.rst b/docs/api/plugins/common.rst deleted file mode 100644 index 7cfaf8d70..000000000 --- a/docs/api/plugins/common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.common` ---------------------------------- - -.. automodule:: certbot.plugins.common - :members: diff --git a/docs/api/plugins/dns_common.rst b/docs/api/plugins/dns_common.rst deleted file mode 100644 index ee3945e74..000000000 --- a/docs/api/plugins/dns_common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common` ---------------------------------- - -.. automodule:: certbot.plugins.dns_common - :members: diff --git a/docs/api/plugins/dns_common_lexicon.rst b/docs/api/plugins/dns_common_lexicon.rst deleted file mode 100644 index a48166828..000000000 --- a/docs/api/plugins/dns_common_lexicon.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common_lexicon` ------------------------------------------ - -.. automodule:: certbot.plugins.dns_common_lexicon - :members: diff --git a/docs/api/plugins/util.rst b/docs/api/plugins/util.rst deleted file mode 100644 index 30ab3d49f..000000000 --- a/docs/api/plugins/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.util` -------------------------------- - -.. automodule:: certbot.plugins.util - :members: diff --git a/docs/api/reverter.rst b/docs/api/reverter.rst deleted file mode 100644 index 3e0ac750b..000000000 --- a/docs/api/reverter.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.reverter` ---------------------------- - -.. automodule:: certbot.reverter - :members: diff --git a/docs/api/util.rst b/docs/api/util.rst deleted file mode 100644 index 7d0e33501..000000000 --- a/docs/api/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.util` --------------------------- - -.. automodule:: certbot.util - :members: diff --git a/docs/challenges.rst b/docs/challenges.rst deleted file mode 100644 index ee8bb8e61..000000000 --- a/docs/challenges.rst +++ /dev/null @@ -1,70 +0,0 @@ -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 -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) -* 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 -changes itself, or runs another program that does so), or *manually* (Certbot tells you to make a -certain change, and you edit a configuration file of some kind in order to accomplish it). Certbot's -design favors performing challenges automatically, and this is the normal case for most users of Certbot. - -Some plugins offer an *authenticator*, meaning that they can satisfy challenges: - -* 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: (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, - and are prepared to repeat these steps every time the certificate needs to be renewed. - -Tips for Challenges -------------------- -General tips: - -* Run Certbot on your web server, not on your laptop or another server. It’s usually the easiest way to get a certificate. -* Use a tool like the DNSchecker at dnsstuff.com to check your DNS records to make sure - there are no serious errors. A DNS error can prevent a certificate authority from - issuing a certificate, even if it does not prevent your site from loading in a browser. -* If you are using Apache or NGINX plugins, make sure the configuration of your Apache or NGINX server is correct. - -HTTP-01 Challenge -~~~~~~~~~~~~~~~~~ - -* Make sure the domain name exists and is already pointed to the public IP address of the server where - you’re requesting the certificate. -* Make sure port 80 is open, publicly reachable from the Internet, and not blocked by a router or firewall. -* When using the Webroot plugin or the manual plugin, make sure the the webroot directory exists and that you - specify it properly. If you set the webroot directory for example.com to `/var/www/example.com` - then a file placed in `/var/www/example.com/.well-known/acme-challenge/testfile` should appear on - your web site at `http://example.com/.well-known/acme-challenge/testfile` (A redirection to HTTPS - is OK here and should not stop the challenge from working.) -* In some web server configurations, all pages are dynamically generated by some kind of framework, - usually using a database backend. In this case, there might not be a particular directory - from which the web server can serve filesdirectly. Using the Webroot plugin in this case - requires making a change to your web server configuration first. -* Make sure your web server serves files properly from the directory where the challenge - file is placed (e. g. `/.well-known/acme-challenge`) to the expected location on the - website without adding a header or footer. -* 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. - -DNS-01 Challenge -~~~~~~~~~~~~~~~~ - -* When using the manual plugin, make sure your DNS records are correctly updated; - you must be able to make appropriate changes to your DNS zone in order to pass the challenge. - diff --git a/docs/ciphers.rst b/docs/ciphers.rst deleted file mode 100644 index 04b24b526..000000000 --- a/docs/ciphers.rst +++ /dev/null @@ -1,339 +0,0 @@ -============ -Ciphersuites -============ - -.. contents:: Table of Contents - :local: - - -.. _ciphersuites: - -Introduction -============ - -Autoupdates ------------ - -Within certain limits, TLS server software can choose what kind of -cryptography to use when a client connects. These choices can affect -security, compatibility, and performance in complex ways. Most of -these options are independent of a particular certificate. Certbot -tries to provide defaults that we think are most useful to our users. - -As described below, Certbot will default to modifying -server software's cryptographic settings to keep these up-to-date with -what we think are appropriate defaults when new versions of the Certbot -are installed (for example, by an operating system package manager). - -When this feature is implemented, this document will be updated -to describe how to disable these automatic changes. - - -Cryptographic choices ---------------------- - -Software that uses cryptography must inevitably make choices about what -kind of cryptography to use and how. These choices entail assumptions -about how well particular cryptographic mechanisms resist attack, and what -trade-offs are available and appropriate. The choices are constrained -by compatibility issues (in order to interoperate with other software, -an implementation must agree to use cryptographic mechanisms that the -other side also supports) and protocol issues (cryptographic mechanisms -must be specified in protocols and there must be a way to agree to use -them in a particular context). - -The best choices for a particular application may change over time in -response to new research, new standardization events, changes in computer -hardware, and changes in the prevalence of legacy software. Much important -research on cryptanalysis and cryptographic vulnerabilities is unpublished -because many researchers have been working in the interest of improving -some entities' communications security while weakening, or failing to -improve, others' security. But important information that improves our -understanding of the state of the art is published regularly. - -When enabling TLS support in a compatible web server (which is a separate -step from obtaining a certificate), Certbot has the ability to -update that web server's TLS configuration. Again, this is *different -from the cryptographic particulars of the certificate itself*; the -certificate as of the initial release will be RSA-signed using one of -Let's Encrypt's 2048-bit RSA keys, and will describe the subscriber's -RSA public key ("subject public key") of at least 2048 bits, which is -used for key establishment. - -Note that the subscriber's RSA public key can be used in a wide variety -of key establishment methods, most of which do not use RSA directly -for key exchange, but only for authenticating the server! For example, -in DHE and ECDHE key exchanges, the subject public key is just used to -sign other parameters for authentication. You do not have to "use RSA" -for other purposes just because you're using an RSA key for authentication. - -The certificate doesn't specify other cryptographic or ciphersuite -particulars; for example, it doesn't say whether or not parties should -use a particular symmetric algorithm like 3DES, or what cipher modes -they should use. All of these details are negotiated between client -and server independent of the content of the ciphersuite. The -Let's Encrypt project hopes to provide useful defaults that reflect -good security choices with respect to the publicly-known state of the -art. However, the Let's Encrypt certificate authority does *not* -dictate end-users' security policy, and any site is welcome to change -its preferences in accordance with its own policy or its administrators' -preferences, and use different cryptographic mechanisms or parameters, -or a different priority order, than the defaults provided by Certbot. - -If you don't use Certbot to configure your server directly, because the -client doesn't integrate with your server software or because you chose -not to use this integration, then the cryptographic defaults haven't been -modified, and the cryptography chosen by the server will still be whatever -the default for your software was. For example, if you obtain a -certificate using *standalone* mode and then manually install it in an IMAP -or LDAP server, your cryptographic settings will not be modified by the -client in any way. - - -Sources of defaults -------------------- - -Initially, Certbot will configure users' servers to use the cryptographic -defaults recommended by the Mozilla project. These settings are well-reasoned -recommendations that carefully consider client software compatibility. They -are described at - -https://wiki.mozilla.org/Security/Server_Side_TLS - -and the version implemented by Certbot will be the -version that was most current as of the release date of each client -version. Mozilla offers three separate sets of cryptographic options, -which trade off security and compatibility differently. These are -referred to as the "Modern", "Intermediate", and "Old" configurations -(in order from most secure to least secure, and least-backwards compatible -to most-backwards compatible). The client will follow the Mozilla defaults -for the *Intermediate* configuration by default, at least with regards to -ciphersuites and TLS versions. Mozilla's web site describes which client -software will be compatible with each configuration. You can also use -the Qualys SSL Labs site, which Certbot will suggest -when installing a certificate, to test your server and see whether it -will be compatible with particular software versions. - -It will be possible to ask Certbot to instead apply (and track) Modern -or Old configurations. - -The Let's Encrypt project expects to follow the Mozilla recommendations -in the future as those recommendations are updated. (For example, some -users have proposed prioritizing a new ciphersuite known as ``0xcc13`` -which uses the ChaCha and Poly1305 algorithms, and which is already -implemented by the Chrome browser. Mozilla has delayed recommending -``0xcc13`` over compatibility and standardization concerns, but is likely -to recommend it in the future once these concerns have been addressed. At -that point, Certbot would likely follow the Mozilla recommendations and favor -the use of this ciphersuite as well.) - -The Let's Encrypt project may deviate from the Mozilla recommendations -in the future if good cause is shown and we believe our users' -priorities would be well-served by doing so. In general, please address -relevant proposals for changing priorities to the Mozilla security -team first, before asking the Certbot developers to change -Certbot's priorities. The Mozilla security team is likely to have more -resources and expertise to bring to bear on evaluating reasons why its -recommendations should be updated. - -The Let's Encrypt project will entertain proposals to create a *very* -small number of alternative configurations (apart from Modern, -Intermediate, and Old) that there's reason to believe would be widely -used by sysadmins; this would usually be a preferable course to modifying -an existing configuration. For example, if many sysadmins want their -servers configured to track a different expert recommendation, Certbot -could add an option to do so. - - -Resources for recommendations ------------------------------ - -In the course of considering how to handle this issue, we received -recommendations with sources of expert guidance on ciphersuites and other -cryptographic parameters. We're grateful to everyone who contributed -suggestions. The recommendations we received are available under Feedback_. - -Certbot users are welcome to review these authorities to -better inform their own cryptographic parameter choices. We also -welcome suggestions of other resources to add to this list. Please keep -in mind that different recommendations may reflect different priorities -or evaluations of trade-offs, especially related to compatibility! - - -Changing your settings ----------------------- - -This will probably look something like - -.. code-block:: shell - - certbot --cipher-recommendations mozilla-secure - certbot --cipher-recommendations mozilla-intermediate - certbot --cipher-recommendations mozilla-old - -to track Mozilla's *Secure*, *Intermediate*, or *Old* recommendations, -and - -.. code-block:: shell - - certbot --update-ciphers on - -to enable updating ciphers with each new Certbot release, or - -.. code-block:: shell - - certbot --update-ciphers off - -to disable automatic configuration updates. These features have not yet -been implemented and this syntax may change when they are implemented. - - -TODO ----- - -The status of this feature is tracked as part of issue #1123 in our -bug tracker. - -https://github.com/certbot/certbot/issues/1123 - -Prior to implementation of #1123, the client does not actually modify -ciphersuites (this is intended to be implemented as a "configuration -enhancement", but the only configuration enhancement implemented -so far is redirecting HTTP requests to HTTPS in web servers, the -"redirect" enhancement). The changes here would probably be either a new -"ciphersuite" enhancement in each plugin that provides an installer, -or a family of enhancements, one per selectable ciphersuite configuration. - -Feedback -======== -We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers. - -Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions. - -Some people provided rationale information mostly having to do with compatibility of particular user-agents (especially UAs that don't support ECC, or that don't support DH groups > 1024 bits). Some ciphersuite configurations have been chosen to try to increase compatibility with older UAs while allowing newer UAs to negotiate stronger crypto. For example, some configurations forego forward secrecy entirely for connections from old UAs, like by offering ECDHE and RSA key exchange, but no DHE at all. (There are UAs that can fail the negotiation completely if a DHE ciphersuite with prime > 1024 bits is offered.) - -References ----------- - -RFC 7575 -~~~~~~~~ - -IETF has published a BCP document, RFC 7525, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)" - -https://datatracker.ietf.org/doc/rfc7525/ - -BetterCrypto.org -~~~~~~~~~~~~~~~~ - -BetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, "Applied Crypto Hardening" - -https://bettercrypto.org/ - -FF-DHE Internet-Draft -~~~~~~~~~~~~~~~~~~~~~ - -Gillmor's Internet-Draft "Negotiated Discrete Log Diffie-Hellman Ephemeral Parameters for TLS" is being developed at the IETF TLS WG. It advocates using *standardized* DH groups in all cases, not individually-chosen ones (mostly because of the Triple Handshake attack which can involve maliciously choosing invalid DH groups). The draft provides a list of recommended groups, with primes beginning at 2048 bits and going up from there. It also has a new protocol mechanism for agreeing to use these groups, with the possibility of backwards compatibility (and use of weaker DH groups) for older clients and servers that don't know about this mechanism. - -https://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10 - -Mozilla -~~~~~~~ - -Mozilla's general server configuration guidance is available at https://wiki.mozilla.org/Security/Server_Side_TLS - -Mozilla has also produced a configuration generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/ - -Dutch National Cyber Security Centre -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Dutch National Cyber Security Centre has published guidance on "ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)" ("IT Security Guidelines for Transport Layer Security (TLS)"). These are available only in Dutch at - -https://web.archive.org/web/20190516085116/https://www.ncsc.nl/actueel/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html - -I have access to an English-language summary of the recommendations. - -Keylength.com -~~~~~~~~~~~~~ - -Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2017. - -http://www.keylength.com/ - -NIST -~~~~ -NISA published its "NIST Special Publication 800-52 Revision 1: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations" - -http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r1.pdf - -and its "NIST Special Publication 800-57: Recommendation for Key Management – Part 1: General (Revision 3)" - -http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf - -ENISA -~~~~~ - -ENISA published its "Algorithms, Key Sizes and Parameters Report - 2013" - -https://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/algorithms-key-sizes-and-parameters-report - -WeakDH/Logjam -------------- - -The WeakDH/Logjam research has thrown into question the safety of some existing practice using DH ciphersuites, especially the use of standardized groups with a prime ≤ 1024 bits. The authors provided detailed guidance, including ciphersuite lists, at - -https://weakdh.org/sysadmin.html - -These lists may have been derived from Mozilla's recommendations. -One of the authors clarified his view of the priorities for various changes as a result of the research at - -https://web.archive.org/web/20150526022820/https://www.ietf.org/mail-archive/web/tls/current/msg16496.html - -In particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn't clear from the group's original recommendations). - -Particular sites' opinions or configurations --------------------------------------------- - -Amazon ELB -~~~~~~~~~~ - -Amazon ELB explains its current ciphersuite choices at - -https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html - -U.S. Government 18F -~~~~~~~~~~~~~~~~~~~ - -The 18F site (https://18f.gsa.gov/) is using - -:: - - ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED'; - -Duraconf -~~~~~~~~ - -The Duraconf project collects particular configuration files, with an apparent focus on avoiding the use of obsolete symmetric ciphers and hash functions, and favoring forward secrecy while not requiring it. - -https://github.com/ioerror/duraconf - -Site scanning or rating tools ------------------------------ - -Qualys SSL Labs -~~~~~~~~~~~~~~~ - -Qualys offers the best-known TLS security scanner, maintained by Ivan Ristić. - -https://www.ssllabs.com/ - -Dutch NCSC -~~~~~~~~~~ - -The Dutch NCSC, mentioned above, has also made available its own site security scanner which indicates how well sites comply with the recommendations. - -https://en.internet.nl/ - -Java compatibility issue ------------------------- - -A lot of backward-compatibility concerns have to do with Java hard-coding DHE primes to a 1024-bit limit, accepting DHE ciphersuites in negotiation, and then aborting the connection entirely if a prime > 1024 bits is presented. The simple summary is that servers offering a Java-compatible DHE ciphersuite in preference to other Java-compatible ciphersuites, and then presenting a DH group with a prime > 1024 bits, will be completely incompatible with clients running some versions of Java. (This may also be the case with very old MSIE versions...?) There are various strategies for dealing with this, and maybe we can document the options here. diff --git a/docs/cli-help.txt b/docs/cli-help.txt deleted file mode 100644 index efade498c..000000000 --- a/docs/cli-help.txt +++ /dev/null @@ -1,718 +0,0 @@ -usage: - certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ... - -Certbot can obtain and install HTTPS/TLS/SSL certificates. By default, -it will attempt to use a webserver both for obtaining and installing the -certificate. The most common SUBCOMMANDS and flags are: - -obtain, install, and renew certificates: - (default) run Obtain & install a certificate in your current webserver - certonly Obtain or renew a certificate, but do not install it - renew Renew all previously obtained certificates that are near expiry - enhance Add security enhancements to your existing configuration - -d DOMAINS Comma-separated list of domains to obtain a certificate for - - --apache Use the Apache plugin for authentication & installation - --standalone Run a standalone webserver for authentication - --nginx Use the Nginx plugin for authentication & installation - --webroot Place files in a server's webroot folder for authentication - --manual Obtain certificates interactively, or using shell script hooks - - -n Run non-interactively - --test-cert Obtain a test certificate from a staging server - --dry-run Test "renew" or "certonly" without saving any certificates to disk - -manage certificates: - certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate - -manage your account: - register Create an ACME account - unregister Deactivate an ACME account - update_account Update an ACME account - --agree-tos Agree to the ACME server's Subscriber Agreement - -m EMAIL Email address for important account notifications - -optional arguments: - -h, --help show this help message and exit - -c CONFIG_FILE, --config CONFIG_FILE - path to config file (default: /etc/letsencrypt/cli.ini - and ~/.config/letsencrypt/cli.ini) - -v, --verbose This flag can be used multiple times to incrementally - increase the verbosity of output, e.g. -vvv. (default: - -2) - --max-log-backups MAX_LOG_BACKUPS - Specifies the maximum number of backup logs that - should be kept by Certbot's built in log rotation. - Setting this flag to 0 disables log rotation entirely, - causing Certbot to always append to the same log file. - (default: 1000) - -n, --non-interactive, --noninteractive - Run without ever asking for user input. This may - require additional command line flags; the client will - try to explain which ones are required if it finds one - missing (default: False) - --force-interactive Force Certbot to be interactive even if it detects - it's not being run in a terminal. This flag cannot be - used with the renew subcommand. (default: False) - -d DOMAIN, --domains DOMAIN, --domain DOMAIN - Domain names to apply. For multiple domains you can - use multiple -d flags or enter a comma separated list - of domains as a parameter. The first domain provided - will be the subject CN of the certificate, and all - domains will be Subject Alternative Names on the - certificate. The first domain will also be used in - some software user interfaces and as the file paths - for the certificate and related material unless - otherwise 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) - --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 - certificate names, run 'certbot certificates'. When - creating a new certificate, specifies the new - certificate's name. (default: the first provided - domain or the name of an existing certificate on your - system for the same domains) - --dry-run Perform a test run of the client, obtaining test - (invalid) certificates but not saving them to disk. - This can currently only be used with the 'certonly' - and 'renew' subcommands. Note: Although --dry-run - tries to avoid making any persistent changes on a - system, it is not completely side-effect free: if used - with webserver authenticator plugins like apache and - nginx, it makes and then reverts temporary config - changes in order to obtain test certificates, and - reloads webservers to deploy and then roll back those - changes. It also calls --pre-hook and --post-hook - commands if they are defined because they may be - necessary to accurately simulate renewal. --deploy- - hook commands are not called. (default: False) - --debug-challenges After setting up challenges, wait for user input - before submitting to CA (default: False) - --preferred-challenges PREF_CHALLS - A sorted, comma delimited list of the preferred - challenge to use during authorization with the most - preferred challenge listed first (Eg, "dns" or - "http,dns"). Not all plugins support all challenges. - See https://certbot.eff.org/docs/using.html#plugins - for details. ACME Challenges are versioned, but if you - pick "http" rather than "http-01", Certbot will select - the latest version automatically. (default: []) - --user-agent USER_AGENT - Set a custom user agent string for the client. User - agent strings allow the CA to collect high level - statistics about success rates by OS, plugin and use - 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.40.1 - (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX - Installer/YYY (SUBCOMMAND; flags: FLAGS) - Py/major.minor.patchlevel). The flags encoded in the - user agent are: --duplicate, --force-renew, --allow- - subset-of-names, -n, and whether any hooks are set. - --user-agent-comment USER_AGENT_COMMENT - Add a comment to the default user agent string. May be - used when repackaging Certbot or calling it from - another tool to allow additional statistical data to - be collected. Ignored if --user-agent is set. - (Example: Foo-Wrapper/1.0) (default: None) - -automation: - Flags for automating execution & other tweaks - - --keep-until-expiring, --keep, --reinstall - If the requested certificate matches an existing - certificate, always keep the existing one until it is - due for renewal (for the 'run' subcommand this means - reinstall the existing certificate). (default: Ask) - --expand If an existing certificate is a strict subset of the - requested names, always expand and replace it with the - additional names. (default: Ask) - --version show program's version number and exit - --force-renewal, --renew-by-default - If a certificate already exists for the requested - domains, renew it now, regardless of whether it is - near expiry. (Often --keep-until-expiring is more - appropriate). Also implies --expand. (default: False) - --renew-with-new-domains - If a certificate already exists for the requested - certificate name but does not match the requested - domains, renew it now, regardless of whether it is - near expiry. (default: False) - --reuse-key When renewing, use the same private key as the - existing certificate. (default: False) - --allow-subset-of-names - When performing domain validation, do not consider it - a failure if authorizations can not be obtained for a - strict subset of the requested domains. This may be - useful for allowing renewals for multiple domains to - succeed even if some domains no longer point at this - system. This option cannot be used with --csr. - (default: False) - --agree-tos Agree to the ACME Subscriber Agreement (default: Ask) - --duplicate Allow making a certificate lineage that duplicates an - existing one (both can be renewed in parallel) - (default: False) - --os-packages-only (certbot-auto only) install OS package dependencies - and then stop (default: False) - --no-self-upgrade (certbot-auto only) prevent the certbot-auto script - from upgrading itself to newer released versions - (default: Upgrade automatically) - --no-bootstrap (certbot-auto only) prevent the certbot-auto script - from installing OS-level dependencies (default: Prompt - to install OS-wide dependencies, but exit if the user - says 'No') - --no-permissions-check - (certbot-auto only) skip the check on the file system - permissions of the certbot-auto script (default: - False) - -q, --quiet Silence all output except errors. Useful for - automation via cron. Implies --non-interactive. - (default: False) - -security: - Security parameters & server settings - - --rsa-key-size N Size of the RSA key. (default: 2048) - --must-staple Adds the OCSP Must Staple extension to the - certificate. Autoconfigures OCSP Stapling for - supported setups (Apache version >= 2.3.3 ). (default: - False) - --redirect Automatically redirect all HTTP traffic to HTTPS for - the newly authenticated vhost. (default: Ask) - --no-redirect Do not automatically redirect all HTTP traffic to - HTTPS for the newly authenticated vhost. (default: - Ask) - --hsts Add the Strict-Transport-Security header to every HTTP - response. Forcing browser to always use SSL for the - domain. Defends against SSL Stripping. (default: None) - --uir Add the "Content-Security-Policy: upgrade-insecure- - requests" header to every HTTP response. Forcing the - browser to use https:// for every http:// resource. - (default: None) - --staple-ocsp Enables OCSP Stapling. A valid OCSP response is - stapled to the certificate that the server offers - during TLS. (default: None) - --strict-permissions Require that all configuration files are owned by the - current user; only needed if your config is somewhere - unsafe like /tmp/ (default: False) - --auto-hsts Gradually increasing max-age value for HTTP Strict - Transport Security security header (default: False) - -testing: - The following flags are meant for testing and integration purposes only. - - --test-cert, --staging - Use the staging server to obtain or revoke test - (invalid) certificates; equivalent to --server https - ://acme-staging-v02.api.letsencrypt.org/directory - (default: False) - --debug Show tracebacks in case of errors, and allow certbot- - auto execution on experimental platforms (default: - False) - --no-verify-ssl Disable verification of the ACME server's certificate. - (default: False) - --http-01-port HTTP01_PORT - Port used in the http-01 challenge. This only affects - the port Certbot listens on. A conforming ACME server - will still attempt to connect on port 80. (default: - 80) - --http-01-address HTTP01_ADDRESS - The address the server listens to during http-01 - challenge. (default: ) - --https-port HTTPS_PORT - Port used to serve HTTPS. This affects which port - Nginx will listen on after a LE certificate is - installed. (default: 443) - --break-my-certs Be willing to replace or renew valid certificates with - invalid (testing/staging) certificates (default: - False) - -paths: - Flags for changing execution paths & servers - - --cert-path CERT_PATH - Path to where certificate is saved (with auth --csr), - installed from, or revoked. (default: None) - --key-path KEY_PATH Path to private key for certificate installation or - revocation (if account key is missing) (default: None) - --fullchain-path FULLCHAIN_PATH - Accompanying path to a full certificate chain - (certificate plus chain). (default: None) - --chain-path CHAIN_PATH - Accompanying path to a certificate chain. (default: - None) - --config-dir CONFIG_DIR - Configuration directory. (default: /etc/letsencrypt) - --work-dir WORK_DIR Working directory. (default: /var/lib/letsencrypt) - --logs-dir LOGS_DIR Logs directory. (default: /var/log/letsencrypt) - --server SERVER ACME Directory Resource URI. (default: - https://acme-v02.api.letsencrypt.org/directory) - -manage: - Various subcommands and flags are available for managing your - certificates: - - certificates List certificates managed by Certbot - delete Clean up all files related to a certificate - renew Renew all certificates (or one specified with --cert- - name) - revoke Revoke a certificate specified with --cert-path or - --cert-name - update_symlinks Recreate symlinks in your /etc/letsencrypt/live/ - directory - -run: - Options for obtaining & installing certificates - -certonly: - Options for modifying how a certificate is obtained - - --csr CSR Path to a Certificate Signing Request (CSR) in DER or - PEM format. Currently --csr only works with the - 'certonly' subcommand. (default: None) - -renew: - The 'renew' subcommand will attempt to renew all certificates (or more - precisely, certificate lineages) you have previously obtained if they are - close to expiry, and print a summary of the results. By default, 'renew' - will reuse the options used to create obtain or most recently successfully - renew each certificate lineage. You can try it with `--dry-run` first. For - more fine-grained control, you can renew individual lineages with the - `certonly` subcommand. Hooks are available to run commands before and - after renewal; see https://certbot.eff.org/docs/using.html#renewal for - more information on these. - - --pre-hook PRE_HOOK Command to be run in a shell before obtaining any - certificates. Intended primarily for renewal, where it - can be used to temporarily shut down a webserver that - might conflict with the standalone plugin. This will - only be called if a certificate is actually to be - obtained/renewed. When renewing several certificates - that have identical pre-hooks, only the first will be - executed. (default: None) - --post-hook POST_HOOK - Command to be run in a shell after attempting to - obtain/renew certificates. Can be used to deploy - renewed certificates, or to restart any servers that - were stopped by --pre-hook. This is only run if an - attempt was made to obtain/renew a certificate. If - multiple renewed certificates have identical post- - hooks, only one will be run. (default: None) - --deploy-hook DEPLOY_HOOK - Command to be run in a shell once for each - successfully issued certificate. For this command, the - shell variable $RENEWED_LINEAGE will point to the - config live subdirectory (for example, - "/etc/letsencrypt/live/example.com") containing the - new certificates and keys; the shell variable - $RENEWED_DOMAINS will contain a space-delimited list - of renewed certificate domains (for example, - "example.com www.example.com" (default: None) - --disable-hook-validation - Ordinarily the commands specified for --pre-hook - /--post-hook/--deploy-hook will be checked for - validity, to see if the programs being run are in the - $PATH, so that mistakes can be caught early, even when - the hooks aren't being run just yet. The validation is - rather simplistic and fails if you use more advanced - shell constructs, so you can use this switch to - disable it. (default: False) - --no-directory-hooks Disable running executables found in Certbot's hook - directories during renewal. (default: False) - --disable-renew-updates - Disable automatic updates to your server configuration - that would otherwise be done by the selected installer - plugin, and triggered when the user executes "certbot - renew", regardless of if the certificate is renewed. - This setting does not apply to important TLS - configuration updates. (default: False) - --no-autorenew Disable auto renewal of certificates. (default: True) - -certificates: - List certificates managed by Certbot - -delete: - Options for deleting a certificate - -revoke: - Options for revocation of certificates - - --reason {unspecified,keycompromise,affiliationchanged,superseded,cessationofoperation} - Specify reason for revoking certificate. (default: - unspecified) - --delete-after-revoke - Delete certificates after revoking them, along with - all previous and later versions of those certificates. - (default: None) - --no-delete-after-revoke - Do not delete certificates after revoking them. This - option should be used with caution because the 'renew' - subcommand will attempt to renew undeleted revoked - certificates. (default: None) - -register: - Options for account registration - - --register-unsafely-without-email - Specifying this flag enables registering an account - with no email address. This is strongly discouraged, - because in the event of key loss or account compromise - you will irrevocably lose access to your account. You - will also be unable to receive notice about impending - expiration or revocation of your certificates. Updates - to the Subscriber Agreement will still affect you, and - will be effective 14 days after posting an update to - the web site. (default: False) - -m EMAIL, --email EMAIL - Email used for registration and recovery contact. Use - comma to register multiple emails, ex: - u1@example.com,u2@example.com. (default: Ask). - --eff-email Share your e-mail address with EFF (default: None) - --no-eff-email Don't share your e-mail address with EFF (default: - None) - -update_account: - Options for account modification - -unregister: - Options for account deactivation. - - --account ACCOUNT_ID Account ID to use (default: None) - -install: - Options for modifying how a certificate is deployed - -rollback: - Options for rolling back server configuration changes - - --checkpoints N Revert configuration N number of checkpoints. - (default: 1) - -plugins: - Options for the "plugins" subcommand - - --init Initialize plugins. (default: False) - --prepare Initialize and prepare plugins. (default: False) - --authenticators Limit to authenticator plugins only. (default: None) - --installers Limit to installer plugins only. (default: None) - -update_symlinks: - Recreates certificate and key symlinks in /etc/letsencrypt/live, if you - changed them by hand or edited a renewal configuration file - -enhance: - Helps to harden the TLS configuration by adding security enhancements to - already existing configuration. - -plugins: - Plugin Selection: Certbot client supports an extensible plugins - architecture. See 'certbot plugins' for a list of all installed plugins - and their names. You can force a particular plugin by setting options - provided below. Running --help will list flags specific to - that plugin. - - --configurator CONFIGURATOR - Name of the plugin that is both an authenticator and - an installer. Should not be used together with - --authenticator or --installer. (default: Ask) - -a AUTHENTICATOR, --authenticator AUTHENTICATOR - Authenticator plugin name. (default: None) - -i INSTALLER, --installer INSTALLER - Installer plugin name (also used to find domains). - (default: None) - --apache Obtain and install certificates using Apache (default: - False) - --nginx Obtain and install certificates using Nginx (default: - False) - --standalone Obtain certificates using a "standalone" webserver. - (default: False) - --manual Provide laborious manual instructions for obtaining a - certificate (default: False) - --webroot Obtain certificates by placing files in a webroot - directory. (default: False) - --dns-cloudflare Obtain certificates using a DNS TXT record (if you are - using Cloudflare for DNS). (default: False) - --dns-cloudxns Obtain certificates using a DNS TXT record (if you are - using CloudXNS for DNS). (default: False) - --dns-digitalocean Obtain certificates using a DNS TXT record (if you are - using DigitalOcean for DNS). (default: False) - --dns-dnsimple Obtain certificates using a DNS TXT record (if you are - using DNSimple for DNS). (default: False) - --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you are - using DNS Made Easy for DNS). (default: False) - --dns-gehirn Obtain certificates using a DNS TXT record (if you are - using Gehirn Infrastracture Service for DNS). - (default: False) - --dns-google Obtain certificates using a DNS TXT record (if you are - using Google Cloud DNS). (default: False) - --dns-linode Obtain certificates using a DNS TXT record (if you are - using Linode for DNS). (default: False) - --dns-luadns Obtain certificates using a DNS TXT record (if you are - using LuaDNS for DNS). (default: False) - --dns-nsone Obtain certificates using a DNS TXT record (if you are - using NS1 for DNS). (default: False) - --dns-ovh Obtain certificates using a DNS TXT record (if you are - using OVH for DNS). (default: False) - --dns-rfc2136 Obtain certificates using a DNS TXT record (if you are - using BIND for DNS). (default: False) - --dns-route53 Obtain certificates using a DNS TXT record (if you are - using Route53 for DNS). (default: False) - --dns-sakuracloud Obtain certificates using a DNS TXT record (if you are - using Sakura Cloud for DNS). (default: False) - -apache: - Apache Web Server plugin (Please note that the default values of the - Apache plugin options change depending on the operating system Certbot is - run on.) - - --apache-enmod APACHE_ENMOD - Path to the Apache 'a2enmod' binary (default: None) - --apache-dismod APACHE_DISMOD - Path to the Apache 'a2dismod' binary (default: None) - --apache-le-vhost-ext APACHE_LE_VHOST_EXT - SSL vhost configuration extension (default: -le- - ssl.conf) - --apache-server-root APACHE_SERVER_ROOT - Apache server root directory (default: /etc/apache2) - --apache-vhost-root APACHE_VHOST_ROOT - Apache server VirtualHost configuration root (default: - None) - --apache-logs-root APACHE_LOGS_ROOT - Apache server logs directory (default: - /var/log/apache2) - --apache-challenge-location APACHE_CHALLENGE_LOCATION - Directory path for challenge configuration (default: - /etc/apache2) - --apache-handle-modules APACHE_HANDLE_MODULES - Let installer handle enabling required modules for you - (Only Ubuntu/Debian currently) (default: False) - --apache-handle-sites APACHE_HANDLE_SITES - Let installer handle enabling sites for you (Only - Ubuntu/Debian currently) (default: False) - --apache-ctl APACHE_CTL - Full path to Apache control script (default: - apache2ctl) - -dns-cloudflare: - Obtain certificates using a DNS TXT record (if you are using Cloudflare - for DNS). - - --dns-cloudflare-propagation-seconds DNS_CLOUDFLARE_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 10) - --dns-cloudflare-credentials DNS_CLOUDFLARE_CREDENTIALS - Cloudflare credentials INI file. (default: None) - -dns-cloudxns: - Obtain certificates using a DNS TXT record (if you are using CloudXNS for - DNS). - - --dns-cloudxns-propagation-seconds DNS_CLOUDXNS_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-cloudxns-credentials DNS_CLOUDXNS_CREDENTIALS - CloudXNS credentials INI file. (default: None) - -dns-digitalocean: - Obtain certs using a DNS TXT record (if you are using DigitalOcean for - DNS). - - --dns-digitalocean-propagation-seconds DNS_DIGITALOCEAN_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 10) - --dns-digitalocean-credentials DNS_DIGITALOCEAN_CREDENTIALS - DigitalOcean credentials INI file. (default: None) - -dns-dnsimple: - Obtain certificates using a DNS TXT record (if you are using DNSimple for - DNS). - - --dns-dnsimple-propagation-seconds DNS_DNSIMPLE_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-dnsimple-credentials DNS_DNSIMPLE_CREDENTIALS - DNSimple credentials INI file. (default: None) - -dns-dnsmadeeasy: - Obtain certificates using a DNS TXT record (if you are using DNS Made Easy - for DNS). - - --dns-dnsmadeeasy-propagation-seconds DNS_DNSMADEEASY_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 60) - --dns-dnsmadeeasy-credentials DNS_DNSMADEEASY_CREDENTIALS - DNS Made Easy credentials INI file. (default: None) - -dns-gehirn: - Obtain certificates using a DNS TXT record (if you are using Gehirn - Infrastracture Service for DNS). - - --dns-gehirn-propagation-seconds DNS_GEHIRN_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-gehirn-credentials DNS_GEHIRN_CREDENTIALS - Gehirn Infrastracture Service credentials file. - (default: None) - -dns-google: - Obtain certificates using a DNS TXT record (if you are using Google Cloud - DNS for DNS). - - --dns-google-propagation-seconds DNS_GOOGLE_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 60) - --dns-google-credentials DNS_GOOGLE_CREDENTIALS - Path to Google Cloud DNS service account JSON file. - (See https://developers.google.com/identity/protocols/ - OAuth2ServiceAccount#creatinganaccount forinformation - about creating a service account and - https://cloud.google.com/dns/access- - control#permissions_and_roles for information about - therequired permissions.) (default: None) - -dns-linode: - Obtain certs using a DNS TXT record (if you are using Linode for DNS). - - --dns-linode-propagation-seconds DNS_LINODE_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 1200) - --dns-linode-credentials DNS_LINODE_CREDENTIALS - Linode credentials INI file. (default: None) - -dns-luadns: - Obtain certificates using a DNS TXT record (if you are using LuaDNS for - DNS). - - --dns-luadns-propagation-seconds DNS_LUADNS_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-luadns-credentials DNS_LUADNS_CREDENTIALS - LuaDNS credentials INI file. (default: None) - -dns-nsone: - Obtain certificates using a DNS TXT record (if you are using NS1 for DNS). - - --dns-nsone-propagation-seconds DNS_NSONE_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-nsone-credentials DNS_NSONE_CREDENTIALS - NS1 credentials file. (default: None) - -dns-ovh: - Obtain certificates using a DNS TXT record (if you are using OVH for DNS). - - --dns-ovh-propagation-seconds DNS_OVH_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 30) - --dns-ovh-credentials DNS_OVH_CREDENTIALS - OVH credentials INI file. (default: None) - -dns-rfc2136: - Obtain certificates using a DNS TXT record (if you are using BIND for - DNS). - - --dns-rfc2136-propagation-seconds DNS_RFC2136_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 60) - --dns-rfc2136-credentials DNS_RFC2136_CREDENTIALS - RFC 2136 credentials INI file. (default: None) - -dns-route53: - Obtain certificates using a DNS TXT record (if you are using AWS Route53 - for DNS). - - --dns-route53-propagation-seconds DNS_ROUTE53_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 10) - -dns-sakuracloud: - Obtain certificates using a DNS TXT record (if you are using Sakura Cloud - for DNS). - - --dns-sakuracloud-propagation-seconds DNS_SAKURACLOUD_PROPAGATION_SECONDS - The number of seconds to wait for DNS to propagate - before asking the ACME server to verify the DNS - record. (default: 90) - --dns-sakuracloud-credentials DNS_SAKURACLOUD_CREDENTIALS - Sakura Cloud credentials file. (default: None) - -manual: - Authenticate through manual configuration or custom shell scripts. When - using shell scripts, an authenticator script must be provided. The - environment variables available to this script depend on the type of - challenge. $CERTBOT_DOMAIN will always contain the domain being - authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION is the - validation string, and $CERTBOT_TOKEN is the filename of the resource - requested when performing an HTTP-01 challenge. An additional cleanup - script can also be provided and can use the additional variable - $CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth - script. - - --manual-auth-hook MANUAL_AUTH_HOOK - Path or command to execute for the authentication - script (default: None) - --manual-cleanup-hook MANUAL_CLEANUP_HOOK - Path or command to execute for the cleanup script - (default: None) - --manual-public-ip-logging-ok - Automatically allows public IP logging (default: Ask) - -nginx: - Nginx Web Server plugin - - --nginx-server-root NGINX_SERVER_ROOT - Nginx server root directory. (default: /etc/nginx or - /usr/local/etc/nginx) - --nginx-ctl NGINX_CTL - Path to the 'nginx' binary, used for 'configtest' and - retrieving nginx version number. (default: nginx) - -null: - Null Installer - -standalone: - Spin up a temporary webserver - -webroot: - Place files in webroot directory - - --webroot-path WEBROOT_PATH, -w WEBROOT_PATH - public_html / webroot path. This can be specified - multiple times to handle different domains; each - domain will have the webroot path that preceded it. - For instance: `-w /var/www/example -d example.com -d - www.example.com -w /var/www/thing -d thing.net -d - m.thing.net` (default: Ask) - --webroot-map WEBROOT_MAP - JSON dictionary mapping domains to webroot paths; this - implies -d for each entry. You may need to escape this - from your shell. E.g.: --webroot-map - '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' - This option is merged with, but takes precedence over, - -w / -d entries. At present, if you put webroot-map in - a config file, it needs to be on a single line, like: - webroot-map = {"example.com":"/var/www"}. (default: - {}) diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index c72d1c1cf..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,323 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Certbot documentation build configuration file, created by -# sphinx-quickstart on Sun Nov 23 20:35:21 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import codecs -import os -import re -import sys - -import sphinx - - -here = os.path.abspath(os.path.dirname(__file__)) - -# read version number (and other metadata) from package init -init_fn = os.path.join(here, '..', 'certbot', '__init__.py') -with codecs.open(init_fn, encoding='utf8') as fd: - meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", fd.read())) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -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.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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - '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'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Certbot' -# this is now overridden by the footer.html template -#copyright = u'2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license.' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '.'.join(meta['version'].split('.')[:2]) -# The full version, including alpha/beta/rc tags. -release = meta['version'] - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'py:obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - -suppress_warnings = ['image.nonlocal_uri'] - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Certbotdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - #'preamble': '', - - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'Certbot.tex', u'Certbot Documentation', - u'Certbot Project', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'certbot', u'Certbot Documentation', - [project], 7), - ('man/certbot', 'certbot', u'certbot script documentation', - [project], 1), -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Certbot', u'Certbot Documentation', - u'Certbot Project', 'Certbot', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), -} diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index 2a98658e4..000000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,596 +0,0 @@ -=============== -Developer Guide -=============== - -.. contents:: Table of Contents - :local: - - -.. _getting_started: - -Getting Started -=============== - -Certbot has the same :ref:`system requirements ` when set -up for development. While the section below will help you install Certbot and -its dependencies, Certbot needs to be run on a UNIX-like OS so if you're using -Windows, you'll need to set up a (virtual) machine running an OS such as Linux -and continue with these instructions on that UNIX-like OS. - -.. _local copy: - -Running a local copy of the client ----------------------------------- - -Running the client in developer mode from your local tree is a little different -than running Certbot as a user. To get set up, clone our git repository by -running: - -.. code-block:: shell - - git clone https://github.com/certbot/certbot - -If you're on macOS, we recommend you skip the rest of this section and instead -run Certbot in Docker. You can find instructions for how to do this :ref:`here -`. If you're running on Linux, you can run the following commands to -install dependencies and set up a virtual environment where you can run -Certbot. - -Install the OS system dependencies required to run Certbot. - -.. code-block:: shell - - # For APT-based distributions (e.g. Debian, Ubuntu ...) - sudo apt update - sudo apt install python3-dev python3-venv gcc libaugeas0 libssl-dev \ - libffi-dev ca-certificates openssl - # For RPM-based distributions (e.g. Fedora, CentOS ...) - # NB1: old distributions will use yum instead of dnf - # NB2: RHEL-based distributions use python3X-devel instead of python3-devel (e.g. python36-devel) - sudo dnf install python3-devel gcc augeas-libs openssl-devel libffi-devel \ - redhat-rpm-config ca-certificates openssl - -Set up the Python virtual environment that will host your Certbot local instance. - -.. code-block:: shell - - cd certbot - python tools/venv3.py - -.. note:: You may need to repeat this when - Certbot's dependencies change or when a new plugin is introduced. - -You can now run the copy of Certbot from git either by executing -``venv3/bin/certbot``, or by activating the virtual environment. You can do the -latter by running: - -.. code-block:: shell - - source venv3/bin/activate - -After running this command, ``certbot`` and development tools like ``ipdb``, -``ipython``, ``pytest``, and ``tox`` are available in the shell where you ran -the command. These tools are installed in the virtual environment and are kept -separate from your global Python installation. This works by setting -environment variables so the right executables are found and Python can pull in -the versions of various packages needed by Certbot. More information can be -found in the `virtualenv docs`_. - -.. _`virtualenv docs`: https://virtualenv.pypa.io - -Find issues to work on ----------------------- - -You can find the open issues in the `github issue tracker`_. Comparatively -easy ones are marked `good first issue`_. If you're starting work on -something, post a comment to let others know and seek feedback on your plan -where appropriate. - -Once you've got a working branch, you can open a pull request. All changes in -your pull request must have thorough unit test coverage, pass our -tests, and be compliant with the :ref:`coding style `. - -.. _github issue tracker: https://github.com/certbot/certbot/issues -.. _good first issue: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 - -.. _testing: - -Testing -------- - -You can test your code in several ways: - -- running the `automated unit`_ tests, -- running the `automated integration`_ tests -- running an *ad hoc* `manual integration`_ test - -.. _automated unit: - -Running automated unit tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you are working in a file ``foo.py``, there should also be a file ``foo_test.py`` -either in the same directory as ``foo.py`` or in the ``tests`` subdirectory -(if there isn't, make one). While you are working on your code and tests, run -``python foo_test.py`` to run the relevant tests. - -For debugging, we recommend putting -``import ipdb; ipdb.set_trace()`` statements inside the source code. - -Once you are done with your code changes, and the tests in ``foo_test.py`` pass, -run all of the unittests for Certbot with ``tox -e py27`` (this uses Python -2.7). - -Once all the unittests pass, check for sufficient test coverage using ``tox -e -py27-cover``, and then check for code style with ``tox -e lint`` (all files) or -``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). - -Once all of the above is successful, you may run the full test suite using -``tox --skip-missing-interpreters``. We recommend running the commands above -first, because running all tests like this is very slow, and the large amount -of output can make it hard to find specific failures when they happen. - -.. warning:: The full test suite may attempt to modify your system's Apache - config if your user has sudo permissions, so it should not be run on a - production Apache server. - -.. _automated integration: - -Running automated integration tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Generally it is sufficient to open a pull request and let Github and Travis run -integration tests for you. However, you may want to run them locally before submitting -your pull request. You need Docker and docker-compose installed and working. - -The tox environment `integration` will setup `Pebble`_, the Let's Encrypt ACME CA server -for integration testing, then launch the Certbot integration tests. - -With a user allowed to access your local Docker daemon, run: - -.. code-block:: shell - - tox -e integration - -Tests will be run using pytest. A test report and a code coverage report will be -displayed at the end of the integration tests execution. - -.. _Pebble: https://github.com/letsencrypt/pebble - -.. _manual integration: - -Running manual integration tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also manually execute Certbot against a local instance of the `Pebble`_ ACME server. -This is useful to verify that the modifications done to the code makes Certbot behave as expected. - -To do so you need: - -- Docker installed, and a user with access to the Docker client, -- an available `local copy`_ of Certbot. - -The virtual environment set up with `python tools/venv.py` contains two commands -that can be used once the virtual environment is activated: - -.. code-block:: shell - - run_acme_server - -- Starts a local instance of Pebble and runs in the foreground printing its logs. -- Press CTRL+C to stop this instance. -- This instance is configured to validate challenges against certbot executed locally. - -.. code-block:: shell - - certbot_test [ARGS...] - -- Execute certbot with the provided arguments and other arguments useful for testing purposes, - such as: verbose output, full tracebacks in case Certbot crashes, *etc.* -- Execution is preconfigured to interact with the Pebble CA started with ``run_acme_server``. -- Any arguments can be passed as they would be to Certbot (eg. ``certbot_test certonly -d test.example.com``). - -Here is a typical workflow to verify that Certbot successfully issued a certificate -using an HTTP-01 challenge on a machine with Python 3: - -.. code-block:: shell - - python tools/venv3.py - source venv3/bin/activate - run_acme_server & - certbot_test certonly --standalone -d test.example.com - # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C - -Code components and layout -========================== - -The following components of the Certbot repository are distributed to users: - -acme - contains all protocol specific code -certbot - main client code -certbot-apache and certbot-nginx - client code to configure specific web servers -certbot-dns-* - client code to configure DNS providers -certbot-auto and letsencrypt-auto - shell scripts to install Certbot and its dependencies on UNIX systems -windows installer - Installs Certbot on Windows and is built using the files in windows-installer/ - -Plugin-architecture -------------------- - -Certbot has a plugin architecture to facilitate support for -different webservers, other TLS servers, and operating systems. -The interfaces available for plugins to implement are defined in -`interfaces.py`_ and `plugins/common.py`_. - -The main two plugin interfaces are `~certbot.interfaces.IAuthenticator`, which -implements various ways of proving domain control to a certificate authority, -and `~certbot.interfaces.IInstaller`, which configures a server to use a -certificate once it is issued. Some plugins, like the built-in Apache and Nginx -plugins, implement both interfaces and perform both tasks. Others, like the -built-in Standalone authenticator, implement just one interface. - -There are also `~certbot.interfaces.IDisplay` plugins, -which can change how prompts are displayed to a user. - -.. _interfaces.py: https://github.com/certbot/certbot/blob/master/certbot/interfaces.py -.. _plugins/common.py: https://github.com/certbot/certbot/blob/master/certbot/plugins/common.py#L34 - - -Authenticators --------------- - -Authenticators are plugins that prove control of a domain name by solving a -challenge provided by the ACME server. ACME currently defines several types of -challenges: HTTP, TLS-ALPN, 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 -`get_chall_pref(domain)` to return a sorted list of challenge types in -preference order. - -An Authenticator must also implement `perform(achalls)`, which "performs" a list -of challenges by, for instance, provisioning a file on an HTTP server, or -setting a TXT record in DNS. Once all challenges have succeeded or failed, -Certbot will call the plugin's `cleanup(achalls)` method to remove any files or -DNS records that were needed only during authentication. - -Installer ---------- - -Installers plugins exist to actually setup the certificate in a server, -possibly tweak the security configuration to make it more correct and secure -(Fix some mixed content problems, turn on HSTS, redirect to HTTPS, etc). -Installer plugins tell the main client about their abilities to do the latter -via the :meth:`~.IInstaller.supported_enhancements` call. We currently -have two Installers in the tree, the `~.ApacheConfigurator`. and the -`~.NginxConfigurator`. External projects have made some progress toward -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 80, but it cannot install certs; a postfix plugin would -be an installer but not an authenticator). - -Installers and Authenticators are kept separate because -it should be possible to use the `~.StandaloneAuthenticator` (it sets -up its own Python server to perform challenges) with a program that -cannot solve challenges itself (Such as MTA installers). - - -Installer Development ---------------------- - -There are a few existing classes that may be beneficial while -developing a new `~certbot.interfaces.IInstaller`. -Installers aimed to reconfigure UNIX servers may use Augeas for -configuration parsing and can inherit from `~.AugeasConfigurator` class -to handle much of the interface. Installers that are unable to use -Augeas may still find the `~.Reverter` class helpful in handling -configuration checkpoints and rollback. - - -.. _dev-plugin: - -Writing your own plugin -~~~~~~~~~~~~~~~~~~~~~~~ - -Certbot client supports dynamic discovery of plugins through the -`setuptools entry points`_ using the `certbot.plugins` group. This -way you can, for example, create a custom implementation of -`~certbot.interfaces.IAuthenticator` or the -`~certbot.interfaces.IInstaller` without having to merge it -with the core upstream source code. An example is provided in -``examples/plugins/`` directory. - -While developing, you can install your plugin into a Certbot development -virtualenv like this: - -.. code-block:: shell - - . venv/bin/activate - pip install -e examples/plugins/ - certbot_test plugins - -Your plugin should show up in the output of the last command. If not, -it was not installed properly. - -Once you've finished your plugin and published it, you can have your -users install it system-wide with `pip install`. Note that this will -only work for users who have Certbot installed from OS packages or via -pip. Users who run `certbot-auto` are currently unable to use third-party -plugins. It's technically possible to install third-party plugins into -the virtualenv used by `certbot-auto`, but they will be wiped away when -`certbot-auto` upgrades. - -.. warning:: Please be aware though that as this client is still in a - developer-preview stage, the API may undergo a few changes. If you - believe the plugin will be beneficial to the community, please - consider submitting a pull request to the repo and we will update - it with any necessary API changes. - -.. _`setuptools entry points`: - http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points - -.. _coding-style: - -Coding style -============ - -Please: - -1. **Be consistent with the rest of the code**. - -2. Read `PEP 8 - Style Guide for Python Code`_. - -3. Follow the `Google Python Style Guide`_, with the exception that we - use `Sphinx-style`_ documentation:: - - def foo(arg): - """Short description. - - :param int arg: Some number. - - :returns: Argument - :rtype: int - - """ - return arg - -4. Remember to use ``pylint``. - -.. _Google Python Style Guide: - https://google.github.io/styleguide/pyguide.html -.. _Sphinx-style: http://sphinx-doc.org/ -.. _PEP 8 - Style Guide for Python Code: - https://www.python.org/dev/peps/pep-0008 - -Use ``certbot.compat.os`` instead of ``os`` -=========================================== - - -Python's standard library ``os`` module lacks full support for several Windows -security features about file permissions (eg. DACLs). However several files -handled by Certbot (eg. private keys) need strongly restricted access -on both Linux and Windows. - -To help with this, the ``certbot.compat.os`` module wraps the standard -``os`` module, and forbids usage of methods that lack support for these Windows -security features. - -As a developer, when working on Certbot or its plugins, you must use ``certbot.compat.os`` -in every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of -``import os``). Otherwise the tests will fail when your PR is submitted. - -.. _type annotations: - -Mypy type annotations -===================== - -Certbot uses the `mypy`_ static type checker. Python 3 natively supports official type annotations, -which can then be tested for consistency using mypy. Python 2 doesn’t, but type annotations can -be `added in comments`_. Mypy does some type checks even without type annotations; we can find -bugs in Certbot even without a fully annotated codebase. - -Certbot supports both Python 2 and 3, so we’re using Python 2-style annotations. - -Zulip wrote a `great guide`_ to using mypy. It’s useful, but you don’t have to read the whole thing -to start contributing to Certbot. - -To run mypy on Certbot, use ``tox -e mypy`` on a machine that has Python 3 installed. - -Note that instead of just importing ``typing``, due to packaging issues, in Certbot we import from -``acme.magic_typing`` and have to add some comments for pylint like this: - -.. code-block:: python - - from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - -Also note that OpenSSL, which we rely on, has type definitions for crypto but not SSL. We use both. -Those imports should look like this: - -.. code-block:: python - - from OpenSSL import crypto - from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 - -.. _mypy: https://mypy.readthedocs.io -.. _added in comments: https://mypy.readthedocs.io/en/latest/cheat_sheet.html -.. _great guide: https://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/ - -Submitting a pull request -========================= - -Steps: - -1. Write your code! When doing this, you should add :ref:`mypy type annotations - ` for any functions you add or modify. You can check that - you've done this correctly by running ``tox -e mypy`` on a machine that has - Python 3 installed. -2. Make sure your environment is set up properly and that you're in your - virtualenv. You can do this by following the instructions in the - :ref:`Getting Started ` section. -3. Run ``tox -e lint`` to check for pylint errors. Fix any errors. -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. 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 `_ 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. - -.. _ask for help: - -Asking for help -=============== - -If you have any questions while working on a Certbot issue, don't hesitate to -ask for help! You can do this in the Certbot channel in EFF's Mattermost -instance for its open source projects as described below. - -You can get involved with several of EFF's software projects such as Certbot at -the `EFF Open Source Contributor Chat Platform -`_. -By signing up for the EFF Open Source Contributor Chat Platform, you consent to -share your personal information with the Electronic Frontier Foundation, which -is the operator and data controller for this platform. The channels will be -available both to EFF, and to other users of EFFOSCCP, who may use or disclose -information in these channels outside of EFFOSCCP. EFF will use your -information, according to the `Privacy Policy `_, -to further the mission of EFF, including hosting and moderating the discussions -on this platform. - -Use of EFFOSCCP is subject to the `EFF Code of Conduct -`_. When investigating an alleged Code of -Conduct violation, EFF may review discussion channels or direct messages. - -Updating certbot-auto and letsencrypt-auto -========================================== - -.. note:: We are currently only accepting changes to certbot-auto that fix - regressions on platforms where certbot-auto is the recommended installation - method at https://certbot.eff.org/instructions. If you are unsure if a change - you want to make qualifies, don't hesitate to `ask for help`_! - -Updating the scripts --------------------- -Developers should *not* modify the ``certbot-auto`` and ``letsencrypt-auto`` files -in the root directory of the repository. Rather, modify the -``letsencrypt-auto.template`` and associated platform-specific shell scripts in -the ``letsencrypt-auto-source`` and -``letsencrypt-auto-source/pieces/bootstrappers`` directory, respectively. - -Building letsencrypt-auto-source/letsencrypt-auto -------------------------------------------------- -Once changes to any of the aforementioned files have been made, the -``letsencrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of -manually updating this script, run the build script, which lives at -``letsencrypt-auto-source/build.py``: - -.. code-block:: shell - - python letsencrypt-auto-source/build.py - -Running ``build.py`` will update the ``letsencrypt-auto-source/letsencrypt-auto`` -script. Note that the ``certbot-auto`` and ``letsencrypt-auto`` scripts in the root -directory of the repository will remain **unchanged** after this script is run. -Your changes will be propagated to these files during the next release of -Certbot. - -Opening a PR ------------- -When opening a PR, ensure that the following files are committed: - -1. ``letsencrypt-auto-source/letsencrypt-auto.template`` and - ``letsencrypt-auto-source/pieces/bootstrappers/*`` -2. ``letsencrypt-auto-source/letsencrypt-auto`` (generated by ``build.py``) - -It might also be a good idea to double check that **no** changes were -inadvertently made to the ``certbot-auto`` or ``letsencrypt-auto`` scripts in the -root of the repository. These scripts will be updated by the core developers -during the next release. - - -Updating the documentation -========================== - -In order to generate the Sphinx documentation, run the following -commands: - -.. code-block:: shell - - make -C docs clean html man - -This should generate documentation in the ``docs/_build/html`` -directory. - -.. note:: If you skipped the "Getting Started" instructions above, - run ``pip install -e ".[docs]"`` to install Certbot's docs extras modules. - - -.. _docker-dev: - -Running the client with Docker -============================== - -You can use Docker Compose to quickly set up an environment for running and -testing Certbot. To install Docker Compose, follow the instructions at -https://docs.docker.com/compose/install/. - -.. note:: Linux users can simply run ``pip install docker-compose`` to get - Docker Compose after installing Docker Engine and activating your shell as - described in the :ref:`Getting Started ` section. - -Now you can develop on your host machine, but run Certbot and test your changes -in Docker. When using ``docker-compose`` make sure you are inside your clone of -the Certbot repository. As an example, you can run the following command to -check for linting errors:: - - docker-compose run --rm --service-ports development bash -c 'tox -e lint' - -You can also leave a terminal open running a shell in the Docker container and -modify Certbot code in another window. The Certbot repo on your host machine is -mounted inside of the container so any changes you make immediately take -effect. To do this, run:: - - docker-compose run --rm --service-ports development bash - -Now running the check for linting errors described above is as easy as:: - - tox -e lint - -.. _prerequisites: - -Notes on OS dependencies -======================== - -OS-level dependencies can be installed like so: - -.. code-block:: shell - - ./certbot-auto --debug --os-packages-only - -In general... - -* ``sudo`` is required as a suggested way of running privileged process -* `Python`_ 2.7 or 3.4+ is required -* `Augeas`_ is required for the Python bindings -* ``virtualenv`` is used for managing other Python library dependencies - -.. _Python: https://wiki.python.org/moin/BeginnersGuide/Download -.. _Augeas: http://augeas.net/ -.. _Virtualenv: https://virtualenv.pypa.io - - -FreeBSD -------- - -FreeBSD by default uses ``tcsh``. In order to activate virtualenv (see -above), you will need a compatible shell, e.g. ``pkg install bash && -bash``. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 17cde1adf..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,26 +0,0 @@ -Welcome to the Certbot documentation! -================================================== - -.. toctree:: - :maxdepth: 2 - - intro - what - install - using - contributing - packaging - resources - -.. toctree:: - :maxdepth: 1 - - api - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index 1cadc9453..000000000 --- a/docs/install.rst +++ /dev/null @@ -1,336 +0,0 @@ -===================== -Get Certbot -===================== - -.. contents:: Table of Contents - :local: - - -About Certbot -============= - -*Certbot is meant to be run directly on a web server*, normally by a system administrator. In most cases, running Certbot on your personal computer is not a useful option. The instructions below relate to installing and running Certbot on a server. - -System administrators can use Certbot directly to request certificates; they should *not* allow unprivileged users to run arbitrary Certbot commands as ``root``, because Certbot allows its user to specify arbitrary file locations and run arbitrary scripts. - -Certbot is packaged for many common operating systems and web servers. Check whether -``certbot`` (or ``letsencrypt``) is packaged for your web server's OS by visiting -certbot.eff.org_, where you will also find the correct installation instructions for -your system. - -.. Note:: Unless you have very specific requirements, we kindly suggest that you use the Certbot packages provided by your package manager (see certbot.eff.org_). If such packages are not available, we recommend using ``certbot-auto``, which automates the process of installing Certbot on your system. - -.. _certbot.eff.org: https://certbot.eff.org - - -.. _system_requirements: - -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 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 -client as root, either `letsencrypt-nosudo -`_ or `simp_le -`_ are more appropriate choices. - -The Apache plugin currently requires an OS with augeas version 1.0; currently `it -supports -`_ -modern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin. - - -Additional integrity verification of certbot-auto script can be done by verifying its digital signature. -This requires a local installation of gpg2, which comes packaged in many Linux distributions under name gnupg or gnupg2. - - -Installing with ``certbot-auto`` requires 512MB of RAM in order to build some -of the dependencies. Installing from pre-built OS packages avoids this -requirement. You can also temporarily set a swap file. See "Problems with -Python virtual environment" below for details. - - -Alternate installation methods -================================ - -If you are offline or your operating system doesn't provide a package, you can use -an alternate method for installing ``certbot``. - -.. _certbot-auto: - -Certbot-Auto ------------- - -The ``certbot-auto`` wrapper script installs Certbot, obtaining some dependencies -from your web server OS and putting others in a python virtual environment. You can -download and run it as follows:: - - user@webserver:~$ wget https://dl.eff.org/certbot-auto - user@webserver:~$ sudo mv certbot-auto /usr/local/bin/certbot-auto - user@webserver:~$ sudo chown root /usr/local/bin/certbot-auto - user@webserver:~$ chmod 0755 /usr/local/bin/certbot-auto - user@webserver:~$ /usr/local/bin/certbot-auto --help - -To check the integrity of the ``certbot-auto`` script, -you can use these steps:: - - - user@webserver:~$ wget -N https://dl.eff.org/certbot-auto.asc - user@webserver:~$ gpg2 --keyserver pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2 - user@webserver:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto - - - -The output of the last command should look something like:: - - - gpg: Signature made Wed 02 May 2018 05:29:12 AM IST - gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2 - gpg: key 4D17C995CD9775F2 marked as ultimately trusted - gpg: checking the trustdb - gpg: marginals needed: 3 completes needed: 1 trust model: pgp - gpg: depth: 0 valid: 2 signed: 2 trust: 0-, 0q, 0n, 0m, 0f, 2u - gpg: depth: 1 valid: 2 signed: 0 trust: 2-, 0q, 0n, 0m, 0f, 0u - gpg: next trustdb check due at 2027-11-22 - gpg: Good signature from "Let's Encrypt Client Team " [ultimate] - - - -The ``certbot-auto`` command updates to the latest client release automatically. -Since ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly -the same command line flags and arguments. For more information, see -`Certbot command-line options `_. - -For full command line help, you can type:: - - /usr/local/bin/certbot-auto --help all - -Problems with Python virtual environment ----------------------------------------- - -On a low memory system such as VPS with less than 512MB of RAM, the required dependencies of Certbot will fail to build. -This can be identified if the pip outputs contains something like ``internal compiler error: Killed (program cc1)``. -You can workaround this restriction by creating a temporary swapfile:: - - user@webserver:~$ sudo fallocate -l 1G /tmp/swapfile - user@webserver:~$ sudo chmod 600 /tmp/swapfile - user@webserver:~$ sudo mkswap /tmp/swapfile - user@webserver:~$ sudo swapon /tmp/swapfile - -Disable and remove the swapfile once the virtual environment is constructed:: - - user@webserver:~$ sudo swapoff /tmp/swapfile - user@webserver:~$ sudo rm /tmp/swapfile - -.. _docker-user: - -Running with Docker -------------------- - -Docker_ is an amazingly simple and quick way to obtain a -certificate. However, this mode of operation is unable to install -certificates or configure your webserver, because our installer -plugins cannot reach your webserver from inside the Docker container. - -Most users should use the operating system packages (see instructions at -certbot.eff.org_) or, as a fallback, ``certbot-auto``. You should only -use Docker if you are sure you know what you are doing and have a -good reason to do so. - -You should definitely read the :ref:`where-certs` section, in order to -know how to manage the certs -manually. `Our ciphersuites page `__ -provides some information about recommended ciphersuites. If none of -these make much sense to you, you should definitely use the -certbot-auto_ method, which enables you to use installer plugins -that cover both of those hard topics. - -If you're still not convinced and have decided to use this method, from -the server that the domain you're requesting a certficate for resolves -to, `install Docker`_, then issue a command like the one found below. If -you are using Certbot with the :ref:`Standalone` plugin, you will need -to make the port it uses accessible from outside of the container by -including something like ``-p 80:80`` or ``-p 443:443`` on the command -line before ``certbot/certbot``. - -.. code-block:: shell - - sudo docker run -it --rm --name certbot \ - -v "/etc/letsencrypt:/etc/letsencrypt" \ - -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ - certbot/certbot certonly - -Running Certbot with the ``certonly`` command will obtain a certificate and place it in the directory -``/etc/letsencrypt/live`` on your system. Because Certonly cannot install the certificate from -within Docker, you must install the certificate manually according to the procedure -recommended by the provider of your webserver. - -There are also Docker images for each of Certbot's DNS plugins available -at https://hub.docker.com/u/certbot which automate doing domain -validation over DNS for popular providers. To use one, just replace -``certbot/certbot`` in the command above with the name of the image you -want to use. For example, to use Certbot's plugin for Amazon Route 53, -you'd use ``certbot/dns-route53``. You may also need to add flags to -Certbot and/or mount additional directories to provide access to your -DNS API credentials as specified in the :ref:`DNS plugin documentation -`. If you would like to obtain a wildcard certificate from -Let's Encrypt's ACMEv2 server, you'll need to include ``--server -https://acme-v02.api.letsencrypt.org/directory`` on the command line as -well. - -For more information about the layout -of the ``/etc/letsencrypt`` directory, see :ref:`where-certs`. - -.. _Docker: https://docker.com -.. _`install Docker`: https://docs.docker.com/engine/installation/ - -Operating System Packages -------------------------- - -**Arch Linux** - -.. code-block:: shell - - sudo pacman -S certbot - -**Debian** - -If you run Debian Buster or Debian testing/Sid, you can easily install certbot -packages through commands like: - -.. code-block:: shell - - sudo apt-get update - sudo apt-get install certbot - -If you run Debian Stretch, we recommend you use the packages in Debian -backports repository. First you'll have to follow the instructions at -https://backports.debian.org/Instructions/ to enable the Stretch backports repo, -if you have not already done so. Then run: - -.. code-block:: shell - - sudo apt-get install certbot -t stretch-backports - -In all of these cases, there also packages available to help Certbot integrate -with Apache, nginx, or various DNS services. If you are using Apache or nginx, -we strongly recommend that you install the ``python-certbot-apache`` or -``python-certbot-nginx`` package so that Certbot can fully automate HTTPS -configuration for your server. A full list of these packages can be found -through a command like: - -.. code-block:: shell - - apt search 'python-certbot*' - -They can be installed by running the same installation command above but -replacing ``certbot`` with the name of the desired package. - -There are no Certbot packages available for Debian Jessie and Jessie users -should instead use certbot-auto_. - -**Ubuntu** - -If you run Ubuntu Trusty, Xenial, or Bionic, certbot is available through the official PPA, -that can be installed as followed: - -.. code-block:: shell - - sudo apt-get update - sudo apt-get install software-properties-common - sudo add-apt-repository universe - sudo add-apt-repository ppa:certbot/certbot - sudo apt-get update - -Then, certbot can be installed using: - -.. code-block:: shell - - sudo apt-get install certbot - -Optionally to install the Certbot Apache plugin, you can use: - -.. code-block:: shell - - sudo apt-get install python-certbot-apache - -**Fedora** - -.. code-block:: shell - - sudo dnf install certbot python2-certbot-apache - -**FreeBSD** - - * Port: ``cd /usr/ports/security/py-certbot && make install clean`` - * Package: ``pkg install py27-certbot`` - -**Gentoo** - -The official Certbot client is available in Gentoo Portage. If you -want to use the Apache plugin, it has to be installed separately: - -.. code-block:: shell - - emerge -av app-crypt/certbot - emerge -av app-crypt/certbot-apache - -When using the Apache plugin, you will run into a "cannot find an -SSLCertificateFile directive" or "cannot find an SSLCertificateKeyFile -directive for certificate" error if you're sporting the default Gentoo -``httpd.conf``. You can fix this by commenting out two lines in -``/etc/apache2/httpd.conf`` as follows: - -Change - -.. code-block:: shell - - - LoadModule ssl_module modules/mod_ssl.so - - -to - -.. code-block:: shell - - # - LoadModule ssl_module modules/mod_ssl.so - # - -For the time being, this is the only way for the Apache plugin to recognise -the appropriate directives when installing the certificate. -Note: this change is not required for the other plugins. - -**NetBSD** - - * Build from source: ``cd /usr/pkgsrc/security/py-certbot && make install clean`` - * Install pre-compiled package: ``pkg_add py27-certbot`` - -**OpenBSD** - - * Port: ``cd /usr/ports/security/letsencrypt/client && make install clean`` - * Package: ``pkg_add letsencrypt`` - -**Other Operating Systems** - -OS packaging is an ongoing effort. If you'd like to package -Certbot for your distribution of choice please have a -look at the :doc:`packaging`. - -Installing from source ----------------------- - -Installation from source is only supported for developers and the -whole process is described in the :doc:`contributing`. - -.. warning:: Please do **not** use ``python setup.py install``, ``python pip - install .``, or ``easy_install .``. Please do **not** attempt the - installation commands as superuser/root and/or without virtual environment, - e.g. ``sudo python setup.py install``, ``sudo pip install``, ``sudo - ./venv/bin/...``. These modes of operation might corrupt your operating - system and are **not supported** by the Certbot team! diff --git a/docs/intro.rst b/docs/intro.rst deleted file mode 100644 index 2d4abdc2d..000000000 --- a/docs/intro.rst +++ /dev/null @@ -1,10 +0,0 @@ -===================== -Introduction -===================== - -.. note:: - To get started quickly, use the `interactive installation guide `_. - -.. include:: ../README.rst - :start-after: tag:intro-begin - :end-before: tag:intro-end diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 198e864c3..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LetsEncrypt.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LetsEncrypt.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/man/certbot.rst b/docs/man/certbot.rst deleted file mode 100644 index d03f3eed4..000000000 --- a/docs/man/certbot.rst +++ /dev/null @@ -1 +0,0 @@ -.. literalinclude:: ../cli-help.txt diff --git a/docs/packaging.rst b/docs/packaging.rst deleted file mode 100644 index 7b0b1d41a..000000000 --- a/docs/packaging.rst +++ /dev/null @@ -1,49 +0,0 @@ -=============== -Packaging Guide -=============== - -Releases -======== - -We release packages and upload them to PyPI (wheels and source tarballs). - -- https://pypi.python.org/pypi/acme -- https://pypi.python.org/pypi/certbot -- https://pypi.python.org/pypi/certbot-apache -- https://pypi.python.org/pypi/certbot-nginx -- https://pypi.python.org/pypi/certbot-dns-cloudflare -- https://pypi.python.org/pypi/certbot-dns-cloudxns -- https://pypi.python.org/pypi/certbot-dns-digitalocean -- https://pypi.python.org/pypi/certbot-dns-dnsimple -- https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy -- https://pypi.python.org/pypi/certbot-dns-google -- https://pypi.python.org/pypi/certbot-dns-linode -- https://pypi.python.org/pypi/certbot-dns-luadns -- https://pypi.python.org/pypi/certbot-dns-nsone -- https://pypi.python.org/pypi/certbot-dns-ovh -- https://pypi.python.org/pypi/certbot-dns-rfc2136 -- https://pypi.python.org/pypi/certbot-dns-route53 - -The following scripts are used in the process: - -- https://github.com/certbot/certbot/blob/master/tools/release.sh - -We use git tags to identify releases, using `Semantic Versioning`_. For -example: `v0.11.1`. - -.. _`Semantic Versioning`: http://semver.org/ - -Notes for package maintainers -============================= - -0. Please use our tagged releases, not ``master``! - -1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally. - -2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins. - -3. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. - -4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. - -5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. diff --git a/docs/resources.rst b/docs/resources.rst deleted file mode 100644 index 459d8a829..000000000 --- a/docs/resources.rst +++ /dev/null @@ -1,7 +0,0 @@ -===================== -Resources -===================== - -.. include:: ../README.rst - :start-after: tag:links-begin - :end-before: tag:links-end diff --git a/docs/using.rst b/docs/using.rst deleted file mode 100644 index a67d28c89..000000000 --- a/docs/using.rst +++ /dev/null @@ -1,990 +0,0 @@ -========== -User Guide -========== - -.. contents:: Table of Contents - :local: - -Certbot Commands -================ - -Certbot uses a number of different commands (also referred -to as "subcommands") to request specific actions such as -obtaining, renewing, or revoking certificates. The most important -and commonly-used commands will be discussed throughout this -document; an exhaustive list also appears near the end of the document. - -The ``certbot`` script on your web server might be named ``letsencrypt`` if your system uses an older package, or ``certbot-auto`` if you used an alternate installation method. Throughout the docs, whenever you see ``certbot``, swap in the correct name as needed. - -.. _plugins: - -Getting certificates (and choosing plugins) -=========================================== - -The Certbot client supports two types of plugins for -obtaining and installing certificates: authenticators and installers. - -Authenticators are plugins used with the ``certonly`` command to obtain a certificate. -The authenticator validates that you -control the domain(s) you are requesting a certificate for, obtains a certificate for the specified -domain(s), and places the certificate in the ``/etc/letsencrypt`` directory on your -machine. The authenticator does not install the certificate (it does not edit any of your server's configuration files to serve the -obtained certificate). If you specify multiple domains to authenticate, they will -all be listed in a single certificate. To obtain multiple separate certificates -you will need to run Certbot multiple times. - -Installers are Plugins used with the ``install`` command to install a certificate. -These plugins can modify your webserver's configuration to -serve your website over HTTPS using certificates obtained by certbot. - -Plugins that do both can be used with the ``certbot run`` command, which is the default -when no command is specified. The ``run`` subcommand can also be used to specify -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. http-01_ (80) -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. -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) - | modifying DNS records to prove you have control over a - | 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) 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 ` - -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) -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``. - -There are also many third-party-plugins_ available. Below we describe in more detail -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 -.. _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 - -Apache ------- - -The Apache plugin currently `supports -`_ -modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. -This automates both obtaining *and* installing certificates on an Apache -webserver. To specify this plugin on the command line, simply include -``--apache``. - -Webroot -------- - -If you're running a local webserver for which you have the ability -to modify the content being served, and you'd prefer not to stop the -webserver during the certificate issuance process, you can use the webroot -plugin to obtain a certificate by including ``certonly`` and ``--webroot`` on -the command line. In addition, you'll need to specify ``--webroot-path`` -or ``-w`` with the top-level directory ("web root") containing the files -served by your webserver. For example, ``--webroot-path /var/www/html`` -or ``--webroot-path /usr/share/nginx/html`` are two common webroot paths. - -If you're getting a certificate for many domains at once, the plugin -needs to know where each domain's files are served from, which could -potentially be a separate directory for each domain. When requesting a -certificate for multiple domains, each domain will use the most recently -specified ``--webroot-path``. So, for instance, - -:: - - certbot certonly --webroot -w /var/www/example -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net - -would obtain a single certificate for all of those names, using the -``/var/www/example`` webroot directory for the first two, and -``/var/www/other`` for the second two. - -The webroot plugin works by creating a temporary file for each of your requested -domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's Encrypt -validation server makes HTTP requests to validate that the DNS for each -requested domain resolves to the server running certbot. An example request -made to your web server would look like: - -:: - - 66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" - -Note that to use the webroot plugin, your server must be configured to serve -files from hidden directories. If ``/.well-known`` is treated specially by -your webserver configuration, you might need to modify the configuration -to ensure that files inside ``/.well-known/acme-challenge`` are served by -the webserver. - -Nginx ------ - -The Nginx plugin should work for most configurations. We recommend backing up -Nginx configurations before using it (though you can also revert changes to -configurations with ``certbot --nginx rollback``). You can use it by providing -the ``--nginx`` flag on the commandline. - -:: - - certbot --nginx - -.. _standalone: - -Standalone ----------- - -Use standalone mode to obtain a certificate if you don't want to use (or don't currently have) -existing server software. The standalone plugin does not rely on any other server -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 in -order to perform domain validation, so you may need to stop your -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. - -By default, Certbot first attempts to bind to the port for all interfaces using -IPv6 and then bind to that port using IPv4; Certbot continues so long as at -least one bind succeeds. On most Linux systems, IPv4 traffic will be routed to -the bound IPv6 port and the failure during the second bind is expected. - -Use ``---address`` to explicitly tell Certbot which interface -(and protocol) to bind. - -.. _dns_plugins: - -DNS Plugins ------------ - -If you'd like to obtain a wildcard certificate from Let's Encrypt or run -``certbot`` on a machine other than your target webserver, you can use one of -Certbot's DNS plugins. - -These plugins are not included in a default Certbot installation and must be -installed separately. While the DNS plugins cannot currently be used with -``certbot-auto``, they are available in many OS package managers and as Docker -images. Visit https://certbot.eff.org to learn the best way to use the DNS -plugins on your system. - -Once installed, you can find documentation on how to use each plugin at: - -* `certbot-dns-cloudflare `_ -* `certbot-dns-cloudxns `_ -* `certbot-dns-digitalocean `_ -* `certbot-dns-dnsimple `_ -* `certbot-dns-dnsmadeeasy `_ -* `certbot-dns-google `_ -* `certbot-dns-linode `_ -* `certbot-dns-luadns `_ -* `certbot-dns-nsone `_ -* `certbot-dns-ovh `_ -* `certbot-dns-rfc2136 `_ -* `certbot-dns-route53 `_ - -Manual ------- - -If you'd like to obtain a certificate running ``certbot`` on a machine -other than your target webserver or perform the steps for domain -validation yourself, you can use the manual plugin. While hidden from -the UI, you can use the plugin to obtain a certificate by specifying -``certonly`` and ``--manual`` on the command line. This requires you -to copy and paste commands into another terminal session, which may -be on a different computer. - -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 -specific content in the ``/.well-known/acme-challenge/`` directory directly -in the top-level directory (“web rootâ€) containing the files served by your -webserver. In essence it's the same as the webroot_ plugin, but not automated. - -When using the ``dns`` challenge, ``certbot`` will ask you to place a TXT DNS -record with specific contents under the domain name consisting of the hostname -for which you want a certificate issued, prepended by ``_acme-challenge``. - -For example, for the domain ``example.com``, a zone file entry would look like: - -:: - - _acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM" - - -Additionally you can specify scripts to prepare for validation and -perform the authentication procedure and/or clean up after it by using -the ``--manual-auth-hook`` and ``--manual-cleanup-hook`` flags. This is -described in more depth in the hooks_ section. - -.. _combination: - -Combining plugins ------------------ - -Sometimes you may want to specify a combination of distinct authenticator and -installer plugins. To do so, specify the authenticator plugin with -``--authenticator`` or ``-a`` and the installer plugin with ``--installer`` or -``-i``. - -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 -------------------- - -There are also a number of third-party plugins for the client, provided by -other developers. Many are beta/experimental, but some are already in -widespread use: - -================== ==== ==== =============================================================== -Plugin Auth Inst Notes -================== ==== ==== =============================================================== -haproxy_ Y Y Integration with the HAProxy load balancer -s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets -gandi_ Y N Obtain certificates via the Gandi LiveDNS API -varnish_ Y N Obtain certificates via a Varnish server -external_ Y N A plugin for convenient scripting (See also ticket 2782_) -pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers -proxmox_ N Y Install certificates in Proxmox Virtualization servers -dns-standalone_ Y N Obtain certificates via an integrated DNS server -dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server -================== ==== ==== =============================================================== - -.. _haproxy: https://github.com/greenhost/certbot-haproxy -.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front -.. _gandi: https://github.com/obynio/certbot-plugin-gandi -.. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin -.. _2782: https://github.com/certbot/certbot/issues/2782 -.. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl -.. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox -.. _external: https://github.com/marcan/letsencrypt-external -.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone -.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig - -If you're interested, you can also :ref:`write your own plugin `. - -.. _managing-certs: - -Managing certificates -===================== - -To view a list of the certificates Certbot knows about, run -the ``certificates`` subcommand: - -``certbot certificates`` - -This returns information in the following format:: - - Found the following certs: - Certificate Name: example.com - Domains: example.com, www.example.com - Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days) - Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem - Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem - -``Certificate Name`` shows the name of the certificate. Pass this name -using the ``--cert-name`` flag to specify a particular certificate for the ``run``, -``certonly``, ``certificates``, ``renew``, and ``delete`` commands. Example:: - - certbot certonly --cert-name example.com - -.. _updating_certs: - -Re-creating and Updating Existing Certificates ----------------------------------------------- - -You can use ``certonly`` or ``run`` subcommands to request -the creation of a single new certificate even if you already have an -existing certificate with some of the same domain names. - -If a certificate is requested with ``run`` or ``certonly`` specifying a -certificate name that already exists, Certbot updates -the existing certificate. Otherwise a new certificate -is created and assigned the specified name. - -The ``--force-renewal``, ``--duplicate``, and ``--expand`` options -control Certbot's behavior when re-creating -a certificate with the same name as an existing certificate. -If you don't specify a requested behavior, Certbot may ask you what you intended. - - -``--force-renewal`` tells Certbot to request a new certificate -with the same domains as an existing certificate. Each domain -must be explicitly specified via ``-d``. If successful, this certificate -is saved alongside the earlier one and symbolic links (the "``live``" -reference) will be updated to point to the new certificate. This is a -valid method of renewing a specific individual -certificate. - -``--duplicate`` tells Certbot to create a separate, unrelated certificate -with the same domains as an existing certificate. This certificate is -saved completely separately from the prior one. Most users will not -need to issue this command in normal circumstances. - -``--expand`` tells Certbot to update an existing certificate with a new -certificate that contains all of the old domains and one or more additional -new domains. With the ``--expand`` option, use the ``-d`` option to specify -all existing domains and one or more new domains. - -Example: - -.. code-block:: none - - certbot --expand -d existing.com,example.com,newdomain.com - -If you prefer, you can specify the domains individually like this: - -.. code-block:: none - - certbot --expand -d existing.com -d example.com -d newdomain.com - -Consider using ``--cert-name`` instead of ``--expand``, as it gives more control -over which certificate is modified and it lets you remove domains as well as adding them. - - -``--allow-subset-of-names`` tells Certbot to continue with certificate generation if -only some of the specified domain authorizations can be obtained. This may -be useful if some domains specified in a certificate no longer point at this -system. - -Whenever you obtain a new certificate in any of these ways, the new -certificate exists alongside any previously obtained certificates, whether -or not the previous certificates have expired. The generation of a new -certificate counts against several rate limits that are intended to prevent -abuse of the ACME protocol, as described -`here `__. - -.. _changing: - -Changing a Certificate's Domains -================================ - -The ``--cert-name`` flag can also be used to modify the domains a certificate contains, -by specifying new domains using the ``-d`` or ``--domains`` flag. If certificate ``example.com`` -previously contained ``example.com`` and ``www.example.com``, it can be modified to only -contain ``example.com`` by specifying only ``example.com`` with the ``-d`` or ``--domains`` flag. Example:: - - certbot certonly --cert-name example.com -d example.com - -The same format can be used to expand the set of domains a certificate contains, or to -replace that set entirely:: - - certbot certonly --cert-name example.com -d example.org,www.example.org - - -Revoking certificates ---------------------- - -If your account key has been compromised or you otherwise need to revoke a certificate, -use the ``revoke`` command to do so. Note that the ``revoke`` command takes the certificate path -(ending in ``cert.pem``), not a certificate name or domain. Example:: - - certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem - -You can also specify the reason for revoking your certificate by using the ``reason`` flag. -Reasons include ``unspecified`` which is the default, as well as ``keycompromise``, -``affiliationchanged``, ``superseded``, and ``cessationofoperation``:: - - certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem --reason keycompromise - -Additionally, if a certificate -is a test certificate obtained via the ``--staging`` or ``--test-cert`` flag, that flag must be passed to the -``revoke`` subcommand. -Once a certificate is revoked (or for other certificate management tasks), all of a certificate's -relevant files can be removed from the system with the ``delete`` subcommand:: - - certbot delete --cert-name example.com - -.. note:: If you don't use ``delete`` to remove the certificate completely, it will be renewed automatically at the next renewal event. - -.. note:: Revoking a certificate will have no effect on the rate limit imposed by the Let's Encrypt server. - -.. _renewal: - -Renewing certificates ---------------------- - -.. note:: Let's Encrypt CA issues short-lived certificates (90 - days). Make sure you renew the certificates at least once in 3 - months. - -.. seealso:: Many of the certbot clients obtained through a - distribution come with automatic renewal out of the box, - such as Debian and Ubuntu versions installed through `apt`, - CentOS/RHEL 7 through EPEL, etc. See `Automated Renewals`_ - for more details. - -As of version 0.10.0, Certbot supports a ``renew`` action to check -all installed certificates for impending expiry and attempt to renew -them. The simplest form is simply - -``certbot renew`` - -This command attempts to renew any previously-obtained certificates that -expire in less than 30 days. The same plugin and options that were used -at the time the certificate was originally issued will be used for the -renewal attempt, unless you specify other plugins or options. Unlike ``certonly``, ``renew`` acts on -multiple certificates and always takes into account whether each one is near -expiry. Because of this, ``renew`` is suitable (and designed) for automated use, -to allow your system to automatically renew each certificate when appropriate. -Since ``renew`` only renews certificates that are near expiry it can be -run as frequently as you want - since it will usually take no action. - -The ``renew`` command includes hooks for running commands or scripts before or after a certificate is -renewed. For example, if you have a single certificate obtained using -the standalone_ plugin, you might need to stop the webserver -before renewing so standalone can bind to the necessary ports, and -then restart it after the plugin is finished. Example:: - - certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start" - -If a hook exits with a non-zero exit code, the error will be printed -to ``stderr`` but renewal will be attempted anyway. A failing hook -doesn't directly cause Certbot to exit with a non-zero exit code, but -since Certbot exits with a non-zero exit code when renewals fail, a -failed hook causing renewal failures will indirectly result in a -non-zero exit code. Hooks will only be run if a certificate is due for -renewal, so you can run the above command frequently without -unnecessarily stopping your webserver. - -When Certbot detects that a certificate is due for renewal, ``--pre-hook`` -and ``--post-hook`` hooks run before and after each attempt to renew it. -If you want your hook to run only after a successful renewal, use -``--deploy-hook`` in a command like this. - -``certbot renew --deploy-hook /path/to/deploy-hook-script`` - -For example, if you have a daemon that does not read its certificates as the -root user, a deploy hook like this can copy them to the correct location and -apply appropriate file permissions. - -/path/to/deploy-hook-script - -.. code-block:: none - - #!/bin/sh - - set -e - - for domain in $RENEWED_DOMAINS; do - case $domain in - example.com) - daemon_cert_root=/etc/some-daemon/certs - - # Make sure the certificate and private key files are - # never world readable, even just for an instant while - # we're copying them into daemon_cert_root. - umask 077 - - cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert" - cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key" - - # Apply the proper file ownership and permissions for - # the daemon to read its certificate and key. - chown some-daemon "$daemon_cert_root/$domain.cert" \ - "$daemon_cert_root/$domain.key" - chmod 400 "$daemon_cert_root/$domain.cert" \ - "$daemon_cert_root/$domain.key" - - service some-daemon restart >/dev/null - ;; - esac - done - -You can also specify hooks by placing files in subdirectories of Certbot's -configuration directory. Assuming your configuration directory is -``/etc/letsencrypt``, any executable files found in -``/etc/letsencrypt/renewal-hooks/pre``, -``/etc/letsencrypt/renewal-hooks/deploy``, and -``/etc/letsencrypt/renewal-hooks/post`` will be run as pre, deploy, and post -hooks respectively when any certificate is renewed with the ``renew`` -subcommand. These hooks are run in alphabetical order and are not run for other -subcommands. (The order the hooks are run is determined by the byte value of -the characters in their filenames and is not dependent on your locale.) - -Hooks specified in the command line, :ref:`configuration file -`, or :ref:`renewal configuration files ` are -run as usual after running all hooks in these directories. One minor exception -to this is if a hook specified elsewhere is simply the path to an executable -file in the hook directory of the same type (e.g. your pre-hook is the path to -an executable in ``/etc/letsencrypt/renewal-hooks/pre``), the file is not run a -second time. You can stop Certbot from automatically running executables found -in these directories by including ``--no-directory-hooks`` on the command line. - -More information about hooks can be found by running -``certbot --help renew``. - -If you're sure that this command executes successfully without human -intervention, you can add the command to ``crontab`` (since certificates -are only renewed when they're determined to be near expiry, the command -can run on a regular basis, like every week or every day). In that case, -you are likely to want to use the ``-q`` or ``--quiet`` quiet flag to -silence all output except errors. - -If you are manually renewing all of your certificates, the -``--force-renewal`` flag may be helpful; it causes the expiration time of -the certificate(s) to be ignored when considering renewal, and attempts to -renew each and every installed certificate regardless of its age. (This -form is not appropriate to run daily because each certificate will be -renewed every day, which will quickly run into the certificate authority -rate limit.) - -Note that options provided to ``certbot renew`` will apply to -*every* certificate for which renewal is attempted; for example, -``certbot renew --rsa-key-size 4096`` would try to replace every -near-expiry certificate with an equivalent certificate using a 4096-bit -RSA public key. If a certificate is successfully renewed using -specified options, those options will be saved and used for future -renewals of that certificate. - -An alternative form that provides for more fine-grained control over the -renewal process (while renewing specified certificates one at a time), -is ``certbot certonly`` with the complete set of subject domains of -a specific certificate specified via `-d` flags. You may also want to -include the ``-n`` or ``--noninteractive`` flag to prevent blocking on -user input (which is useful when running the command from cron). - -``certbot certonly -n -d example.com -d www.example.com`` - -All of the domains covered by the certificate must be specified in -this case in order to renew and replace the old certificate rather -than obtaining a new one; don't forget any `www.` domains! Specifying -a subset of the domains creates a new, separate certificate containing -only those domains, rather than replacing the original certificate. -When run with a set of domains corresponding to an existing certificate, -the ``certonly`` command attempts to renew that specific certificate. - -Please note that the CA will send notification emails to the address -you provide if you do not renew certificates that are about to expire. - -Certbot is working hard to improve the renewal process, and we -apologize for any inconvenience you encounter in integrating these -commands into your individual environment. - -.. note:: ``certbot renew`` exit status will only be 1 if a renewal attempt failed. - This means ``certbot renew`` exit status will be 0 if no certificate needs to be updated. - If you write a custom script and expect to run a command only after a certificate was actually renewed - you will need to use the ``--deploy-hook`` since the exit status will be 0 both on successful renewal - and when renewal is not necessary. - -.. _renewal-config-file: - - -Modifying the Renewal Configuration File ----------------------------------------- - -When a certificate is issued, by default Certbot creates a renewal configuration file that -tracks the options that were selected when Certbot was run. This allows Certbot -to use those same options again when it comes time for renewal. These renewal -configuration files are located at ``/etc/letsencrypt/renewal/CERTNAME``. - -For advanced certificate management tasks, it is possible to manually modify the certificate's -renewal configuration file, but this is discouraged since it can easily break Certbot's -ability to renew your certificates. If you choose to modify the renewal configuration file -we advise you to test its validity with the ``certbot renew --dry-run`` command. - -.. warning:: Modifying any files in ``/etc/letsencrypt`` can damage them so Certbot can no longer properly manage its certificates, and we do not recommend doing so. - -For most tasks, it is safest to limit yourself to pointing symlinks at the files there, or using -``--deploy-hook`` to copy / make new files based upon those files, if your operational situation requires it -(for instance, combining certificates and keys in different way, or having copies of things with different -specific permissions that are demanded by other programs). - -If the contents of ``/etc/letsencrypt/archive/CERTNAME`` are moved to a new folder, first specify -the new folder's name in the renewal configuration file, then run ``certbot update_symlinks`` to -point the symlinks in ``/etc/letsencrypt/live/CERTNAME`` to the new folder. - -If you would like the live certificate files whose symlink location Certbot updates on each run to -reside in a different location, first move them to that location, then specify the full path of -each of the four files in the renewal configuration file. Since the symlinks are relative links, -you must follow this with an invocation of ``certbot update_symlinks``. - -For example, say that a certificate's renewal configuration file previously contained the following -directives:: - - archive_dir = /etc/letsencrypt/archive/example.com - cert = /etc/letsencrypt/live/example.com/cert.pem - privkey = /etc/letsencrypt/live/example.com/privkey.pem - chain = /etc/letsencrypt/live/example.com/chain.pem - fullchain = /etc/letsencrypt/live/example.com/fullchain.pem - -The following commands could be used to specify where these files are located:: - - mv /etc/letsencrypt/archive/example.com /home/user/me/certbot/example_archive - sed -i 's,/etc/letsencrypt/archive/example.com,/home/user/me/certbot/example_archive,' /etc/letsencrypt/renewal/example.com.conf - mv /etc/letsencrypt/live/example.com/*.pem /home/user/me/certbot/ - sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf - certbot update_symlinks - -Automated Renewals ------------------- - -Many Linux distributions provide automated renewal when you use the -packages installed through their system package manager. The -following table is an *incomplete* list of distributions which do so, -as well as their methods for doing so. - -If you are not sure whether or not your system has this already -automated, refer to your distribution's documentation, or check your -system's crontab (typically in `/etc/crontab/` and `/etc/cron.*/*` and -systemd timers (`systemctl list-timers`). - -.. csv-table:: Distributions with Automated Renewal - :header: "Distribution Name", "Distribution Version", "Automation Method" - - "CentOS", "EPEL 7", "systemd" - "Debian", "jessie", "cron, systemd" - "Debian", "stretch", "cron, systemd" - "Debian", "testing/sid", "cron, systemd" - "Fedora", "26", "systemd" - "Fedora", "27", "systemd" - "RHEL", "EPEL 7", "systemd" - "Ubuntu", "17.10", "cron, systemd" - "Ubuntu", "certbot PPA", "cron, systemd" - -.. _where-certs: - -Where are my certificates? -========================== - -All generated keys and issued certificates can be found in -``/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. - -.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys`` - contain all previous keys and certificates, while - ``/etc/letsencrypt/live`` symlinks to the latest versions. - -The following files are available: - -``privkey.pem`` - Private key for the certificate. - - .. warning:: This **must be kept secret at all times**! Never share - it with anyone, including Certbot developers. You cannot - 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 - `_, - and Nginx for `ssl_certificate_key - `_. - -``fullchain.pem`` - All certificates, **including** server certificate (aka leaf certificate or - end-entity certificate). The server certificate is the first one in this file, - followed by any intermediates. - - This is what Apache >= 2.4.8 needs for `SSLCertificateFile - `_, - and what Nginx needs for `ssl_certificate - `_. - -``cert.pem`` and ``chain.pem`` (less common) - ``cert.pem`` contains the server certificate by itself, and - ``chain.pem`` contains the additional intermediate certificate or - certificates that web browsers will need in order to validate the - server certificate. If you provide one of these files to your web - server, you **must** provide both of them, or some browsers will show - "This Connection is Untrusted" errors for your site, `some of the time - `_. - - Apache < 2.4.8 needs these for `SSLCertificateFile - `_. - and `SSLCertificateChainFile - `_, - respectively. - - If you're using OCSP stapling with Nginx >= 1.3.7, ``chain.pem`` should be - provided as the `ssl_trusted_certificate - `_ - to validate OCSP responses. - -.. note:: All files are PEM-encoded. - If you need other format, such as DER or PFX, then you - could convert using ``openssl``. You can automate that with - ``--deploy-hook`` if you're using automatic renewal_. - -.. _hooks: - -Pre and Post Validation Hooks -============================= - -Certbot allows for the specification of pre and post validation hooks when run -in manual mode. The flags to specify these scripts are ``--manual-auth-hook`` -and ``--manual-cleanup-hook`` respectively and can be used as follows: - -:: - - certbot certonly --manual --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com - -This will run the ``authenticator.sh`` script, attempt the validation, and then run -the ``cleanup.sh`` script. Additionally certbot will pass relevant environment -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) - -Additionally for cleanup: - -- ``CERTBOT_AUTH_OUTPUT``: Whatever the auth script wrote to stdout - -Example usage for HTTP-01: - -:: - - certbot certonly --manual --preferred-challenges=http --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com - -/path/to/http/authenticator.sh - -.. code-block:: none - - #!/bin/bash - echo $CERTBOT_VALIDATION > /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN - -/path/to/http/cleanup.sh - -.. code-block:: none - - #!/bin/bash - rm -f /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN - -Example usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not use as-is) - -:: - - certbot certonly --manual --preferred-challenges=dns --manual-auth-hook /path/to/dns/authenticator.sh --manual-cleanup-hook /path/to/dns/cleanup.sh -d secure.example.com - -/path/to/dns/authenticator.sh - -.. code-block:: none - - #!/bin/bash - - # Get your API key from https://www.cloudflare.com/a/account/my-account - API_KEY="your-api-key" - EMAIL="your.email@example.com" - - # Strip only the top domain to get the zone id - DOMAIN=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)') - - # Get the Cloudflare zone id - ZONE_EXTRA_PARAMS="status=active&page=1&per_page=20&order=status&direction=desc&match=all" - ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN&$ZONE_EXTRA_PARAMS" \ - -H "X-Auth-Email: $EMAIL" \ - -H "X-Auth-Key: $API_KEY" \ - -H "Content-Type: application/json" | python -c "import sys,json;print(json.load(sys.stdin)['result'][0]['id'])") - - # Create TXT record - CREATE_DOMAIN="_acme-challenge.$CERTBOT_DOMAIN" - RECORD_ID=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ - -H "X-Auth-Email: $EMAIL" \ - -H "X-Auth-Key: $API_KEY" \ - -H "Content-Type: application/json" \ - --data '{"type":"TXT","name":"'"$CREATE_DOMAIN"'","content":"'"$CERTBOT_VALIDATION"'","ttl":120}' \ - | python -c "import sys,json;print(json.load(sys.stdin)['result']['id'])") - # Save info for cleanup - if [ ! -d /tmp/CERTBOT_$CERTBOT_DOMAIN ];then - mkdir -m 0700 /tmp/CERTBOT_$CERTBOT_DOMAIN - fi - echo $ZONE_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID - echo $RECORD_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID - - # Sleep to make sure the change has time to propagate over to DNS - sleep 25 - -/path/to/dns/cleanup.sh - -.. code-block:: none - - #!/bin/bash - - # Get your API key from https://www.cloudflare.com/a/account/my-account - API_KEY="your-api-key" - EMAIL="your.email@example.com" - - if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID ]; then - ZONE_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID) - rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID - fi - - if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID ]; then - RECORD_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID) - rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID - fi - - # Remove the challenge TXT record from the zone - if [ -n "${ZONE_ID}" ]; then - if [ -n "${RECORD_ID}" ]; then - curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ - -H "X-Auth-Email: $EMAIL" \ - -H "X-Auth-Key: $API_KEY" \ - -H "Content-Type: application/json" - fi - fi - -.. _lock-files: - -Changing the ACME Server -======================== - -By default, Certbot uses Let's Encrypt's initial production server at -https://acme-v01.api.letsencrypt.org/. You can tell Certbot to use a -different CA by providing ``--server`` on the command line or in a -:ref:`configuration file ` with the URL of the server's -ACME directory. For example, if you would like to use Let's Encrypt's -new ACMEv2 server, you would add ``--server -https://acme-v02.api.letsencrypt.org/directory`` to the command line. -Certbot will automatically select which version of the ACME protocol to -use based on the contents served at the provided URL. - -If you use ``--server`` to specify an ACME CA that implements a newer -version of the spec, you may be able to obtain a certificate for a -wildcard domain. Some CAs (such as Let's Encrypt) require that domain -validation for wildcard domains must be done through modifications to -DNS records which means that the dns-01_ challenge type must be used. To -see a list of Certbot plugins that support this challenge type and how -to use them, see plugins_. - -Lock Files -========== - -When processing a validation Certbot writes a number of lock files on your system -to prevent multiple instances from overwriting each other's changes. This means -that by default two instances of Certbot will not be able to run in parallel. - -Since the directories used by Certbot are configurable, Certbot -will write a lock file for all of the directories it uses. This include Certbot's -``--work-dir``, ``--logs-dir``, and ``--config-dir``. By default these are -``/var/lib/letsencrypt``, ``/var/log/letsencrypt``, and ``/etc/letsencrypt`` -respectively. Additionally if you are using Certbot with Apache or nginx it will -lock the configuration folder for that program, which are typically also in the -``/etc`` directory. - -Note that these lock files will only prevent other instances of Certbot from -using those directories, not other processes. If you'd like to run multiple -instances of Certbot simultaneously you should specify different directories -as the ``--work-dir``, ``--logs-dir``, and ``--config-dir`` for each instance -of Certbot that you would like to run. - -.. _config-file: - -Configuration file -================== - -Certbot accepts a global configuration file that applies its options to all invocations -of Certbot. Certificate specific configuration choices should be set in the ``.conf`` -files that can be found in ``/etc/letsencrypt/renewal``. - -By default no cli.ini file is created (though it may exist already if you installed Certbot -via a package manager, for instance). -After creating one it is possible to specify the location of this configuration file with -``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An -example configuration file is shown below: - -.. include:: ../examples/cli.ini - :code: ini - -By default, the following locations are searched: - -- ``/etc/letsencrypt/cli.ini`` -- ``$XDG_CONFIG_HOME/letsencrypt/cli.ini`` (or - ``~/.config/letsencrypt/cli.ini`` if ``$XDG_CONFIG_HOME`` is not - set). - -Since this configuration file applies to all invocations of certbot it is incorrect -to list domains in it. Listing domains in cli.ini may prevent renewal from working. -Additionally due to how arguments in cli.ini are parsed, options which wish to -not be set should not be listed. Options set to false will instead be read -as being set to true by older versions of Certbot, since they have been listed -in the config file. - -.. keep it up to date with constants.py - -.. _log-rotation: - -Log Rotation -============ - -By default certbot stores status logs in ``/var/log/letsencrypt``. By default -certbot will begin rotating logs once there are 1000 logs in the log directory. -Meaning that once 1000 files are in ``/var/log/letsencrypt`` Certbot will delete -the oldest one to make room for new logs. The number of subsequent logs can be -changed by passing the desired number to the command line flag -``--max-log-backups``. - -.. note:: Some distributions, including Debian and Ubuntu, disable - certbot's internal log rotation in favor of a more traditional - logrotate script. If you are using a distribution's packages and - want to alter the log rotation, check `/etc/logrotate.d/` for a - certbot rotation script. - -.. _command-line: - -Certbot command-line options -============================ - -Certbot supports a lot of command line options. Here's the full list, from -``certbot --help all``: - -.. literalinclude:: cli-help.txt - -Getting help -============ - -If you're having problems, we recommend posting on the Let's Encrypt -`Community Forum `_. - -If you find a bug in the software, please do report it in our `issue -tracker `_. Remember to -give us as much information as possible: - -- copy and paste exact command line used and the output (though mind - that the latter might include some personally identifiable - information, including your email and domains) -- copy and paste logs from ``/var/log/letsencrypt`` (though mind they - also might contain personally identifiable information) -- copy and paste ``certbot --version`` output -- your operating system, including specific version -- specify which installation method you've chosen diff --git a/docs/what.rst b/docs/what.rst deleted file mode 100644 index 3d33346c2..000000000 --- a/docs/what.rst +++ /dev/null @@ -1,31 +0,0 @@ -====================== -What is a Certificate? -====================== - -A public key or digital *certificate* (formerly called an SSL certificate) uses a public key -and a private key to enable secure communication between a client program (web browser, email client, -etc.) and a server over an encrypted SSL (secure socket layer) or TLS (transport layer security) connection. -The certificate is used both to encrypt the initial stage of communication (secure key exchange) -and to identify the server. The certificate -includes information about the key, information about the server identity, and the digital signature -of the certificate issuer. If the issuer is trusted by the software that initiates the communication, -and the signature is valid, then the key can be used to communicate securely with the server identified by -the certificate. Using a certificate is a good way to prevent "man-in-the-middle" attacks, in which -someone in between you and the server you think you are talking to is able to insert their own (harmful) -content. - -You can use Certbot to easily obtain and configure a free certificate from Let's Encrypt, a -joint project of EFF, Mozilla, and many other sponsors. - -Certificates and Lineages -========================= - -Certbot introduces the concept of a *lineage,* which is a collection of all the versions of a certificate -plus Certbot configuration information maintained for that certificate from -renewal to renewal. Whenever you renew a certificate, Certbot keeps the same configuration unless -you explicitly change it, for example by adding or removing domains. If you add domains, you can -either add them to an existing lineage or create -a new one. - -See also: -:ref:`updating_certs` diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index abaf425d1..000000000 --- a/examples/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# generate-csr.sh: -/key.pem -/csr.der \ No newline at end of file diff --git a/examples/cli.ini b/examples/cli.ini deleted file mode 100644 index 4215fda5b..000000000 --- a/examples/cli.ini +++ /dev/null @@ -1,22 +0,0 @@ -# This is an example of the kind of things you can do in a configuration file. -# All flags used by the client can be configured here. Run Certbot with -# "--help" to learn more about the available options. -# -# Note that these options apply automatically to all use of Certbot for -# obtaining or renewing certificates, so options specific to a single -# certificate on a system with several certificates should not be placed -# here. - -# Use a 4096 bit RSA key instead of 2048 -rsa-key-size = 4096 - -# Uncomment and update to register with the specified e-mail address -# email = foo@example.com - -# Uncomment to use the standalone authenticator on port 443 -# authenticator = standalone - -# Uncomment to use the webroot authenticator. Replace webroot-path with the -# path to the public_html / webroot folder being served by your web server. -# authenticator = webroot -# webroot-path = /usr/share/nginx/html diff --git a/examples/dev-cli.ini b/examples/dev-cli.ini deleted file mode 100644 index a405a0aef..000000000 --- a/examples/dev-cli.ini +++ /dev/null @@ -1,20 +0,0 @@ -# Always use the staging/testing server - avoids rate limiting -server = https://acme-staging-v02.api.letsencrypt.org/directory - -# This is an example configuration file for developers -config-dir = /tmp/le/conf -work-dir = /tmp/le/conf -logs-dir = /tmp/le/logs - -# make sure to use a valid email and domains! -email = foo@example.com -domains = example.com - -text = True -agree-tos = True -debug = True -# Unfortunately, it's not possible to specify "verbose" multiple times -# (correspondingly to -vvvvvv) -verbose = True - -authenticator = standalone diff --git a/examples/generate-csr.sh b/examples/generate-csr.sh deleted file mode 100755 index 55f6c7b9f..000000000 --- a/examples/generate-csr.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# This script generates a simple SAN CSR to be used with Let's Encrypt -# CA. Mostly intended for "auth --csr" testing, but, since it's easily -# auditable, feel free to adjust it and use it on your production web -# server. - -if [ "$#" -lt 1 ] -then - echo "Usage: $0 domain [domain...]" >&2 - exit 1 -fi - -domains="DNS:$1" -shift -for x in "$@" -do - domains="$domains,DNS:$x" -done - -SAN="$domains" openssl req -config "${OPENSSL_CNF:-openssl.cnf}" \ - -new -nodes -subj '/' -reqexts san \ - -out "${CSR_PATH:-csr.der}" \ - -keyout "${KEY_PATH:-key.pem}" \ - -newkey rsa:2048 \ - -outform DER -# 512 or 1024 too low for Boulder, 2048 is smallest for tests - -echo "You can now run: certbot auth --csr ${CSR_PATH:-csr.der}" diff --git a/examples/openssl.cnf b/examples/openssl.cnf deleted file mode 100644 index a3e6f3895..000000000 --- a/examples/openssl.cnf +++ /dev/null @@ -1,5 +0,0 @@ -[ req ] -distinguished_name = req_distinguished_name -[ req_distinguished_name ] -[ san ] -subjectAltName=${ENV::SAN} diff --git a/examples/plugins/certbot_example_plugins.py b/examples/plugins/certbot_example_plugins.py deleted file mode 100644 index 9dec2e108..000000000 --- a/examples/plugins/certbot_example_plugins.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Example Certbot plugins. - -For full examples, see `certbot.plugins`. - -""" -import zope.interface - -from certbot import interfaces -from certbot.plugins import common - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(common.Plugin): - """Example Authenticator.""" - - description = "Example Authenticator plugin" - - # Implement all methods from IAuthenticator, remembering to add - # "self" as first argument, e.g. def prepare(self)... - - -@zope.interface.implementer(interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class Installer(common.Plugin): - """Example Installer.""" - - description = "Example Installer plugin" - - # Implement all methods from IInstaller, remembering to add - # "self" as first argument, e.g. def get_all_names(self)... diff --git a/examples/plugins/setup.py b/examples/plugins/setup.py deleted file mode 100644 index 4538e83b8..000000000 --- a/examples/plugins/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup - - -setup( - name='certbot-example-plugins', - package='certbot_example_plugins.py', - install_requires=[ - 'certbot', - 'zope.interface', - ], - entry_points={ - 'certbot.plugins': [ - 'example_authenticator = certbot_example_plugins:Authenticator', - 'example_installer = certbot_example_plugins:Installer', - ], - }, -) diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index e660568c3..a79bdd8aa 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -75,7 +75,7 @@ PYVER=`/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cu /opt/eff.org/certbot/venv/bin/python letsencrypt-auto-source/pieces/create_venv.py /tmp/venv "$PYVER" 1 /tmp/venv/bin/python letsencrypt-auto-source/pieces/pipstrap.py -/tmp/venv/bin/pip install -e acme -e . -e certbot-apache -e certbot-nginx -c /tmp/constraints.txt +/tmp/venv/bin/pip install -e acme -e certbot -e certbot-apache -e certbot-nginx -c /tmp/constraints.txt /tmp/venv/bin/certbot plugins /tmp/venv/bin/pip freeze >> /tmp/workspace/requirements.txt """ diff --git a/letsencrypt-auto-source/version.py b/letsencrypt-auto-source/version.py index c49d96654..d70ffefac 100755 --- a/letsencrypt-auto-source/version.py +++ b/letsencrypt-auto-source/version.py @@ -14,6 +14,7 @@ def certbot_version(build_script_dir): """Return the version number stamped in certbot/__init__.py.""" return re.search('''^__version__ = ['"](.+)['"].*''', file_contents(join(dirname(build_script_dir), + 'certbot', 'certbot', '__init__.py')), re.M).group(1) diff --git a/letshelp-certbot/readthedocs.org.requirements.txt b/letshelp-certbot/readthedocs.org.requirements.txt index 7858b312f..b24681caa 100644 --- a/letshelp-certbot/readthedocs.org.requirements.txt +++ b/letshelp-certbot/readthedocs.org.requirements.txt @@ -1,10 +1,10 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot[docs]" (that would in turn install documentation # dependencies), but it allows to specify a requirements.txt file at # https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) # Although ReadTheDocs certainly doesn't need to install the project # in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead +# expected and "pip install -e certbot[docs]" must be used instead -e letshelp-certbot[docs] diff --git a/local-oldest-requirements.txt b/local-oldest-requirements.txt deleted file mode 100644 index f6d158890..000000000 --- a/local-oldest-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Remember to update setup.py to match the package versions below. -acme[dev]==0.40.0 diff --git a/readthedocs.org.requirements.txt b/readthedocs.org.requirements.txt deleted file mode 100644 index 94a81e788..000000000 --- a/readthedocs.org.requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation -# dependencies), but it allows to specify a requirements.txt file at -# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259) - -# Although ReadTheDocs certainly doesn't need to install the project -# in --editable mode (-e), just "pip install .[docs]" does not work as -# expected and "pip install -e .[docs]" must be used instead - --e acme --e .[docs] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a21bab793..000000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -universal = 1 - -[easy_install] -zip_ok = false diff --git a/setup.py b/setup.py deleted file mode 100644 index b230f3ba0..000000000 --- a/setup.py +++ /dev/null @@ -1,177 +0,0 @@ -import codecs -import os -import re -import sys - -from distutils.version import StrictVersion -from setuptools import find_packages, setup, __version__ as setuptools_version -from setuptools.command.test import test as TestCommand - -# Workaround for http://bugs.python.org/issue8876, see -# http://bugs.python.org/issue8876#msg208792 -# This can be removed when using Python 2.7.9 or later: -# https://hg.python.org/cpython/raw-file/v2.7.9/Misc/NEWS -if os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant': - del os.link - - -def read_file(filename, encoding='utf8'): - """Read unicode from given file.""" - with codecs.open(filename, encoding=encoding) as fd: - return fd.read() - - -here = os.path.abspath(os.path.dirname(__file__)) - -# read version number (and other metadata) from package init -init_fn = os.path.join(here, 'certbot', '__init__.py') -meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", read_file(init_fn))) - -readme = read_file(os.path.join(here, 'README.rst')) -version = meta['version'] - -# This package relies on PyOpenSSL, requests, and six, however, it isn't -# specified here to avoid masking the more specific request requirements in -# acme. See https://github.com/pypa/pip/issues/988 for more info. -install_requires = [ - 'acme>=0.40.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.3', # load_pem_x509_certificate - 'distro>=1.0.1', - # 1.1.0+ is required to avoid the warnings described at - # https://github.com/certbot/josepy/issues/13. - 'josepy>=1.1.0', - 'mock', - 'parsedatetime>=1.3', # Calendar.parseDT - 'pyrfc3339', - 'pytz', - 'setuptools', - 'zope.component', - 'zope.interface', -] - -# Add pywin32 on Windows platforms to handle low-level system calls. -# This dependency needs to be added using environment markers to avoid its installation on Linux. -# However environment markers are supported only with setuptools >= 36.2. -# So this dependency is not added for old Linux distributions with old setuptools, -# in order to allow these systems to build certbot from sources. -pywin32_req = 'pywin32>=225' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py -if StrictVersion(setuptools_version) >= StrictVersion('36.2'): - install_requires.append(pywin32_req + " ; sys_platform == 'win32'") -elif 'bdist_wheel' in sys.argv[1:]: - raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' - 'of setuptools. Version 36.2+ of setuptools is required.') -elif os.name == 'nt': - # This branch exists to improve this package's behavior on Windows. Without - # it, if the sdist is installed on Windows with an old version of - # setuptools, pywin32 will not be specified as a dependency. - install_requires.append(pywin32_req) - -dev_extras = [ - 'astroid==1.6.5', - 'coverage', - 'ipdb', - 'pytest', - 'pytest-cov', - 'pytest-xdist', - 'pylint==1.9.4', - 'tox', - 'twine', - 'wheel', -] - -dev3_extras = [ - 'mypy', - 'typing', # for python3.4 -] - -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>=1.2', # Annotation support - 'sphinx_rtd_theme', -] - - -class PyTest(TestCommand): - user_options = [] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = '' - - def run_tests(self): - import shlex - # import here, cause outside the eggs aren't loaded - import pytest - errno = pytest.main(shlex.split(self.pytest_args)) - sys.exit(errno) - - -setup( - name='certbot', - version=version, - description="ACME client", - long_description=readme, - url='https://github.com/letsencrypt/letsencrypt', - author="Certbot Project", - author_email='client-dev@letsencrypt.org', - license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Environment :: Console :: Curses', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Security', - 'Topic :: System :: Installation/Setup', - 'Topic :: System :: Networking', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ], - - packages=find_packages(exclude=['docs', 'examples', 'tests', 'venv']), - include_package_data=True, - - install_requires=install_requires, - extras_require={ - 'dev': dev_extras, - 'dev3': dev3_extras, - 'docs': docs_extras, - }, - - # to test all packages run "python setup.py test -s - # {acme,certbot_apache,certbot_nginx}" - test_suite='certbot', - tests_require=["pytest"], - cmdclass={"test": PyTest}, - - entry_points={ - 'console_scripts': [ - 'certbot = certbot._internal.main:main', - ], - 'certbot.plugins': [ - 'manual = certbot._internal.plugins.manual:Authenticator', - 'null = certbot._internal.plugins.null:Installer', - 'standalone = certbot._internal.plugins.standalone:Authenticator', - 'webroot = certbot._internal.plugins.webroot:Authenticator', - ], - }, -) diff --git a/tests/letstest/scripts/test_apache2.sh b/tests/letstest/scripts/test_apache2.sh index 7ebaaa5fd..9af39e8bb 100755 --- a/tests/letstest/scripts/test_apache2.sh +++ b/tests/letstest/scripts/test_apache2.sh @@ -50,7 +50,7 @@ fi # instance, Fedora uses Python 3 and Python 2 is not installed. . tests/letstest/scripts/set_python_envvars.sh -"$VENV_SCRIPT" -e acme[dev] -e .[dev,docs] -e certbot-apache +"$VENV_SCRIPT" -e acme[dev] -e certbot[dev,docs] -e certbot-apache sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-tos \ --renew-by-default --redirect --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index 347589e04..dc024c567 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -27,7 +27,7 @@ VERSION=$("$PYTHON_NAME" letsencrypt-auto-source/version.py) tools/pip_install.py pytest # build sdists -for pkg_dir in acme . $PLUGINS; do +for pkg_dir in acme certbot $PLUGINS; do cd $pkg_dir python setup.py clean rm -rf build dist diff --git a/tests/letstest/scripts/test_tests.sh b/tests/letstest/scripts/test_tests.sh index 77ef44270..fb86ce4cd 100755 --- a/tests/letstest/scripts/test_tests.sh +++ b/tests/letstest/scripts/test_tests.sh @@ -7,7 +7,7 @@ REPO_ROOT="letsencrypt" LE_AUTO="$REPO_ROOT/letsencrypt-auto-source/letsencrypt-auto" LE_AUTO="$LE_AUTO --debug --no-self-upgrade --non-interactive" -MODULES="acme certbot certbot_apache certbot_nginx" +MODULES="acme certbot certbot-apache certbot-nginx" PIP_INSTALL="$REPO_ROOT/tools/pip_install.py" VENV_NAME=venv @@ -17,10 +17,13 @@ LE_AUTO_SUDO="" VENV_PATH="$VENV_NAME" $LE_AUTO --no-bootstrap --version . $VENV_NAME/bin/activate "$PIP_INSTALL" pytest -# change to an empty directory to ensure CWD doesn't affect tests -cd $(mktemp -d) +# To run tests that aren't packaged in modules, run pytest +# from the repo root. The directory structure should still +# cause the installed packages to be tested while using +# the tests available in the subdirectories. +cd $REPO_ROOT for module in $MODULES ; do echo testing $module - pytest -v --pyargs $module + pytest -v $module done diff --git a/tools/_release.sh b/tools/_release.sh index e228bae99..224c69d94 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -30,7 +30,6 @@ SUBPKGS_NOT_IN_AUTO="certbot-dns-cloudflare certbot-dns-cloudxns certbot-dns-dig SUBPKGS_IN_AUTO="certbot $SUBPKGS_IN_AUTO_NO_CERTBOT" SUBPKGS_NO_CERTBOT="$SUBPKGS_IN_AUTO_NO_CERTBOT $SUBPKGS_NOT_IN_AUTO" SUBPKGS="$SUBPKGS_IN_AUTO $SUBPKGS_NOT_IN_AUTO" -subpkgs_modules="$(echo $SUBPKGS | sed s/-/_/g)" # certbot_compatibility_test is not packaged because: # - it is not meant to be used by anyone else than Certbot devs # - it causes problems when running pytest - the latter tries to @@ -71,7 +70,7 @@ git add CHANGELOG.md git diff --cached git commit -m "Update changelog for $version release" -for pkg_dir in $SUBPKGS_NO_CERTBOT certbot-compatibility-test . +for pkg_dir in $SUBPKGS certbot-compatibility-test do sed -i 's/\.dev0//' "$pkg_dir/setup.py" git add "$pkg_dir/setup.py" @@ -79,8 +78,8 @@ do if [ -f "$pkg_dir/local-oldest-requirements.txt" ]; then sed -i "s/-e acme\[dev\]/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" sed -i "s/-e acme/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \.\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \./certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e certbot\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e certbot/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" git add "$pkg_dir/local-oldest-requirements.txt" fi done @@ -97,7 +96,7 @@ SetVersion() { fi sed -i "s/^version.*/version = '$ver'/" $pkg_dir/setup.py done - init_file="certbot/__init__.py" + init_file="certbot/certbot/__init__.py" if [ $(grep -c '^__version' "$init_file") != 1 ]; then echo "Unexpected count of __version variables in $init_file" exit 1 @@ -113,7 +112,7 @@ SetVersion "$version" # conditionals like the one found in certbot-dns-dnsimple's setup.py file. unset CERTBOT_OLDEST echo "Preparing sdists and wheels" -for pkg_dir in . $SUBPKGS_NO_CERTBOT +for pkg_dir in $SUBPKGS do cd $pkg_dir @@ -133,8 +132,7 @@ done mkdir "dist.$version" -mv dist "dist.$version/certbot" -for pkg_dir in $SUBPKGS_NO_CERTBOT +for pkg_dir in $SUBPKGS do mv $pkg_dir/dist "dist.$version/$pkg_dir/" done @@ -163,7 +161,7 @@ cd ~- # get a snapshot of the CLI help for the docs # We set CERTBOT_DOCS to use dummy values in example user-agent string. -CERTBOT_DOCS=1 certbot --help all > docs/cli-help.txt +CERTBOT_DOCS=1 certbot --help all > certbot/docs/cli-help.txt jws --help > acme/docs/jws-help.txt cd .. @@ -177,12 +175,12 @@ mkdir kgs kgs="kgs/$version" pip freeze | tee $kgs python ../tools/pip_install.py pytest -for module in $subpkgs_modules ; do +cd ~- +for module in $SUBPKGS ; do echo testing $module # use an empty configuration file rather than the one in the repo root - pytest -c <(echo '') --pyargs $module + pytest -c <(echo '') $module done -cd ~- # pin pip hashes of the things we just built for pkg in $SUBPKGS_IN_AUTO ; do @@ -231,7 +229,7 @@ mv letsencrypt-auto-source/letsencrypt-auto.asc letsencrypt-auto-source/certbot- cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto -git add certbot-auto letsencrypt-auto letsencrypt-auto-source docs/cli-help.txt +git add certbot-auto letsencrypt-auto letsencrypt-auto-source certbot/docs/cli-help.txt git diff --cached while ! git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"; do echo "Unable to sign the release commit using git." diff --git a/tools/_venv_common.py b/tools/_venv_common.py index 0898f4f50..ec6a0ef7a 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -22,7 +22,7 @@ import re REQUIREMENTS = [ '-e acme[dev]', - '-e .[dev,docs]', + '-e certbot[dev,docs]', '-e certbot-apache', '-e certbot-dns-cloudflare', '-e certbot-dns-cloudxns', diff --git a/tools/deps.sh b/tools/deps.sh deleted file mode 100755 index e12f201a5..000000000 --- a/tools/deps.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# Find all Python imports. -# -# ./tools/deps.sh certbot -# ./tools/deps.sh acme -# ./tools/deps.sh certbot-apache -# ... -# -# Manually compare the output with deps in setup.py. - -git grep -h -E '^(import|from.*import)' $1/ | \ - awk '{print $2}' | \ - grep -vE "^$1" | \ - sort -u diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 6987cf2b1..f8d7a2e3d 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -9,17 +9,15 @@ from __future__ import print_function import os import sys -import tempfile -import shutil import subprocess import re SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] -def call_with_print(command, cwd=None): +def call_with_print(command): print(command) - subprocess.check_call(command, shell=True, cwd=cwd or os.getcwd()) + subprocess.check_call(command, shell=True) def main(args): @@ -41,16 +39,8 @@ def main(args): call_with_print(' '.join(current_command)) pkg = re.sub(r'\[\w+\]', '', requirement) - if pkg == '.': - pkg = 'certbot' - - temp_cwd = tempfile.mkdtemp() - shutil.copy2("pytest.ini", temp_cwd) - try: - call_with_print(' '.join([ - sys.executable, '-m', 'pytest', '--pyargs', pkg.replace('-', '_')]), cwd=temp_cwd) - finally: - shutil.rmtree(temp_cwd) + call_with_print(' '.join([ + sys.executable, '-m', 'pytest', pkg])) if __name__ == '__main__': main(sys.argv[1:]) diff --git a/tools/pip_install.py b/tools/pip_install.py index cf0a7aee5..76355a1e6 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -69,9 +69,9 @@ def merge_requirements(tools_path, requirements, test_constraints, all_constrain fd.write(merged_requirements) -def call_with_print(command, cwd=None): +def call_with_print(command): print(command) - subprocess.check_call(command, shell=True, cwd=cwd or os.getcwd()) + subprocess.check_call(command, shell=True) def pip_install_with_print(args_str): diff --git a/tools/venv3.py b/tools/venv3.py index 77a30763d..7ead82bd5 100755 --- a/tools/venv3.py +++ b/tools/venv3.py @@ -21,7 +21,7 @@ def main(pip_args=None): create_venv(venv_path) if not pip_args: - pip_args = _venv_common.REQUIREMENTS + ['-e .[dev3]'] + pip_args = _venv_common.REQUIREMENTS + ['-e certbot[dev3]'] _venv_common.install_packages(venv_path, pip_args) diff --git a/tox.cover.py b/tox.cover.py index 6981bbb41..85e929567 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -47,8 +47,8 @@ def cover(package): .format(pkg_dir))) return - subprocess.check_call([sys.executable, '-m', 'pytest', '--pyargs', - '--cov', pkg_dir, '--cov-append', '--cov-report=', package]) + subprocess.check_call([sys.executable, '-m', 'pytest', + '--cov', pkg_dir, '--cov-append', '--cov-report=', pkg_dir]) subprocess.check_call([ sys.executable, '-m', 'coverage', 'report', '--fail-under', str(threshold), '--include', '{0}/*'.format(pkg_dir), '--show-missing']) diff --git a/tox.ini b/tox.ini index 04715cc2f..f83f40891 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ dns_packages = certbot-dns-sakuracloud all_packages = acme[dev] \ - .[dev] \ + certbot[dev] \ certbot-apache \ {[base]dns_packages} \ certbot-nginx \ @@ -40,7 +40,7 @@ install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages} source_paths = acme/acme - certbot + certbot/certbot certbot-apache/certbot_apache certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare @@ -92,7 +92,7 @@ setenv = [testenv:py27-certbot-oldest] commands = - {[base]install_and_test} .[dev] + {[base]install_and_test} certbot[dev] setenv = {[testenv:py27-oldest]setenv} @@ -134,24 +134,24 @@ commands = basepython = python3 commands = {[base]install_packages} - {[base]pip_install} .[dev3] + {[base]pip_install} certbot[dev3] mypy {[base]source_paths} [testenv:apacheconftest] commands = - {[base]pip_install} acme . certbot-apache certbot-compatibility-test + {[base]pip_install} acme certbot certbot-apache certbot-compatibility-test {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules passenv = SERVER [testenv:apacheconftest-with-pebble] commands = - {[base]pip_install} acme . certbot-apache certbot-ci certbot-compatibility-test + {[base]pip_install} acme certbot certbot-apache certbot-ci certbot-compatibility-test {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules [testenv:nginxroundtrip] commands = - {[base]pip_install} acme . certbot-apache certbot-nginx + {[base]pip_install} acme certbot certbot-apache certbot-nginx python certbot-compatibility-test/nginx/roundtrip.py certbot-compatibility-test/nginx/nginx-roundtrip-testdata # This is a duplication of the command line in testenv:le_auto to @@ -223,7 +223,7 @@ passenv = DOCKER_* [testenv:integration] commands = - {[base]pip_install} acme . certbot-nginx certbot-ci + {[base]pip_install} acme certbot certbot-nginx certbot-ci pytest certbot-ci/certbot_integration_tests \ --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ @@ -234,7 +234,7 @@ passenv = DOCKER_* [testenv:integration-certbot] commands = - {[base]pip_install} acme . certbot-ci + {[base]pip_install} acme certbot certbot-ci pytest certbot-ci/certbot_integration_tests/certbot_tests \ --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov-report= \ @@ -243,7 +243,7 @@ commands = [testenv:integration-certbot-oldest] commands = - {[base]pip_install} . + {[base]pip_install} certbot {[base]pip_install} certbot-ci pytest certbot-ci/certbot_integration_tests/certbot_tests \ --acme-server={env:ACME_SERVER:pebble} diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 089296439..cdf309f13 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -147,7 +147,7 @@ files=run.bat renew-down.ps1 [Command certbot] -entry_point=certbot._internal.main:main +entry_point=certbot.main:main extra_preamble=pywin32_paths.py '''.format(certbot_version=certbot_version, installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', -- cgit v1.2.3 From e023f889ff2bdb7815c2d23234812cd672bfa3be Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 14:30:24 -0800 Subject: Make the contents of the nginx plugin private (#7589) Part of #5775. * Create _internal folder certbot-nginx * Move configurator.py to _internal * Move constants.py to _internal * Move display_ops.py to _internal * Move http_01.py to _internal * Move nginxparser.py to _internal * Move obj.py to _internal * Move parser_obj.py to _internal * Move parser.py to _internal * Update location and references for tls_configs * exclude parser_obj from coverage --- certbot-ci/certbot_integration_tests/.coveragerc | 4 +- .../configurators/nginx/common.py | 4 +- certbot-compatibility-test/nginx/roundtrip.py | 2 +- certbot-nginx/MANIFEST.in | 2 +- certbot-nginx/certbot_nginx/_internal/__init__.py | 1 + .../certbot_nginx/_internal/configurator.py | 1202 ++++++++++++++++++++ certbot-nginx/certbot_nginx/_internal/constants.py | 63 + .../certbot_nginx/_internal/display_ops.py | 44 + certbot-nginx/certbot_nginx/_internal/http_01.py | 207 ++++ .../certbot_nginx/_internal/nginxparser.py | 268 +++++ certbot-nginx/certbot_nginx/_internal/obj.py | 263 +++++ certbot-nginx/certbot_nginx/_internal/parser.py | 762 +++++++++++++ .../certbot_nginx/_internal/parser_obj.py | 396 +++++++ .../tls_configs/options-ssl-nginx-old.conf | 13 + .../tls_configs/options-ssl-nginx-tls12-only.conf | 14 + .../options-ssl-nginx-tls13-session-tix-on.conf | 13 + .../_internal/tls_configs/options-ssl-nginx.conf | 14 + certbot-nginx/certbot_nginx/configurator.py | 1202 -------------------- certbot-nginx/certbot_nginx/constants.py | 63 - certbot-nginx/certbot_nginx/display_ops.py | 44 - certbot-nginx/certbot_nginx/http_01.py | 207 ---- certbot-nginx/certbot_nginx/nginxparser.py | 268 ----- certbot-nginx/certbot_nginx/obj.py | 263 ----- certbot-nginx/certbot_nginx/parser.py | 762 ------------- certbot-nginx/certbot_nginx/parser_obj.py | 396 ------- .../certbot_nginx/tests/configurator_test.py | 77 +- .../certbot_nginx/tests/display_ops_test.py | 6 +- certbot-nginx/certbot_nginx/tests/http_01_test.py | 16 +- .../certbot_nginx/tests/nginxparser_test.py | 4 +- certbot-nginx/certbot_nginx/tests/obj_test.py | 24 +- .../certbot_nginx/tests/parser_obj_test.py | 24 +- certbot-nginx/certbot_nginx/tests/parser_test.py | 18 +- certbot-nginx/certbot_nginx/tests/util.py | 8 +- .../tls_configs/options-ssl-nginx-old.conf | 13 - .../tls_configs/options-ssl-nginx-tls12-only.conf | 14 - .../options-ssl-nginx-tls13-session-tix-on.conf | 13 - .../tls_configs/options-ssl-nginx.conf | 14 - certbot-nginx/setup.py | 2 +- 38 files changed, 3356 insertions(+), 3354 deletions(-) create mode 100644 certbot-nginx/certbot_nginx/_internal/__init__.py create mode 100644 certbot-nginx/certbot_nginx/_internal/configurator.py create mode 100644 certbot-nginx/certbot_nginx/_internal/constants.py create mode 100644 certbot-nginx/certbot_nginx/_internal/display_ops.py create mode 100644 certbot-nginx/certbot_nginx/_internal/http_01.py create mode 100644 certbot-nginx/certbot_nginx/_internal/nginxparser.py create mode 100644 certbot-nginx/certbot_nginx/_internal/obj.py create mode 100644 certbot-nginx/certbot_nginx/_internal/parser.py create mode 100644 certbot-nginx/certbot_nginx/_internal/parser_obj.py create mode 100644 certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf create mode 100644 certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf create mode 100644 certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf create mode 100644 certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf delete mode 100644 certbot-nginx/certbot_nginx/configurator.py delete mode 100644 certbot-nginx/certbot_nginx/constants.py delete mode 100644 certbot-nginx/certbot_nginx/display_ops.py delete mode 100644 certbot-nginx/certbot_nginx/http_01.py delete mode 100644 certbot-nginx/certbot_nginx/nginxparser.py delete mode 100644 certbot-nginx/certbot_nginx/obj.py delete mode 100644 certbot-nginx/certbot_nginx/parser.py delete mode 100644 certbot-nginx/certbot_nginx/parser_obj.py delete mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf delete mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf delete mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf delete mode 100644 certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf diff --git a/certbot-ci/certbot_integration_tests/.coveragerc b/certbot-ci/certbot_integration_tests/.coveragerc index c83880b64..72f7c6adf 100644 --- a/certbot-ci/certbot_integration_tests/.coveragerc +++ b/certbot-ci/certbot_integration_tests/.coveragerc @@ -2,8 +2,8 @@ # Avoid false warnings because certbot packages are not installed in the thread that executes # the coverage: indeed, certbot is launched as a CLI from a subprocess. disable_warnings = module-not-imported,no-data-collected -omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/parser_obj.py +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/_internal/parser_obj.py [report] # Exclude unit tests in coverage during integration tests. -omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/parser_obj.py +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/_internal/parser_obj.py diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 3dcf834c0..5185b8a5d 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -8,8 +8,8 @@ import zope.interface from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot._internal import configuration -from certbot_nginx import configurator -from certbot_nginx import constants +from certbot_nginx._internal import configurator +from certbot_nginx._internal import constants from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces from certbot_compatibility_test import util diff --git a/certbot-compatibility-test/nginx/roundtrip.py b/certbot-compatibility-test/nginx/roundtrip.py index 85d283c78..e2d518e00 100644 --- a/certbot-compatibility-test/nginx/roundtrip.py +++ b/certbot-compatibility-test/nginx/roundtrip.py @@ -3,7 +3,7 @@ import os import sys -from certbot_nginx import nginxparser +from certbot_nginx._internal import nginxparser def roundtrip(stuff): success = True diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index fff59467b..39e956ced 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -1,4 +1,4 @@ include LICENSE.txt include README.rst recursive-include certbot_nginx/tests/testdata * -recursive-include certbot_nginx/tls_configs *.conf +recursive-include certbot_nginx/_internal/tls_configs *.conf diff --git a/certbot-nginx/certbot_nginx/_internal/__init__.py b/certbot-nginx/certbot_nginx/_internal/__init__.py new file mode 100644 index 000000000..71d79b0c2 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/__init__.py @@ -0,0 +1 @@ +"""Certbot nginx plugin internal implementation.""" diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py new file mode 100644 index 000000000..e212ba40d --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -0,0 +1,1202 @@ +"""Nginx Configuration""" +# https://github.com/PyCQA/pylint/issues/73 +from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error +import logging +import re +import socket +import subprocess +import tempfile +import time + +import pkg_resources + +import OpenSSL +import zope.interface + +from acme import challenges +from acme import crypto_util as acme_crypto_util +from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module + +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util +from certbot.compat import os +from certbot.plugins import common + +from certbot_nginx._internal import constants +from certbot_nginx._internal import display_ops +from certbot_nginx._internal import http_01 +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj # pylint: disable=unused-import +from certbot_nginx._internal import parser + +NAME_RANK = 0 +START_WILDCARD_RANK = 1 +END_WILDCARD_RANK = 2 +REGEX_RANK = 3 +NO_SSL_MODIFIER = 4 + + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class NginxConfigurator(common.Installer): + """Nginx configurator. + + .. todo:: Add proper support for comments in the config. Currently, + config files modified by the configurator will lose all their comments. + + :ivar config: Configuration. + :type config: :class:`~certbot.interfaces.IConfig` + + :ivar parser: Handles low level parsing + :type parser: :class:`~certbot_nginx._internal.parser` + + :ivar str save_notes: Human-readable config change notes + + :ivar reverter: saves and reverts checkpoints + :type reverter: :class:`certbot.reverter.Reverter` + + :ivar tup version: version of Nginx + + """ + + description = "Nginx Web Server plugin" + + DEFAULT_LISTEN_PORT = '80' + + # SSL directives that Certbot can add when installing a new certificate. + SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam'] + + @classmethod + def add_parser_arguments(cls, add): + default_server_root = _determine_default_server_root() + add("server-root", default=constants.CLI_DEFAULTS["server_root"], + help="Nginx server root directory. (default: %s)" % default_server_root) + add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the " + "'nginx' binary, used for 'configtest' and retrieving nginx " + "version number.") + + @property + def nginx_conf(self): + """Nginx config file path.""" + return os.path.join(self.conf("server_root"), "nginx.conf") + + def __init__(self, *args, **kwargs): + """Initialize an Nginx Configurator. + + :param tup version: version of Nginx as a tuple (1, 4, 7) + (used mostly for unittesting) + + :param tup openssl_version: version of OpenSSL linked to Nginx as a tuple (1, 4, 7) + (used mostly for unittesting) + + """ + version = kwargs.pop("version", None) + openssl_version = kwargs.pop("openssl_version", None) + super(NginxConfigurator, self).__init__(*args, **kwargs) + + # Files to save + self.save_notes = "" + + # For creating new vhosts if no names match + self.new_vhost = None + + # List of vhosts configured per wildcard domain on this run. + # used by deploy_cert() and enhance() + self._wildcard_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] + self._wildcard_redirect_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] + + # Add number of outstanding challenges + self._chall_out = 0 + + # These will be set in the prepare function + self.parser = None + self.version = version + self.openssl_version = openssl_version + self._enhance_func = {"redirect": self._enable_redirect, + "ensure-http-header": self._set_http_header, + "staple-ocsp": self._enable_ocsp_stapling} + + self.reverter.recovery_routine() + + @property + def mod_ssl_conf_src(self): + """Full absolute path to SSL configuration file source.""" + + # Why all this complexity? Well, we want to support Mozilla's intermediate + # recommendations. But TLS1.3 is only supported by newer versions of Nginx. + # And as for session tickets, our ideal is to turn them off across the board. + # But! Turning them off at all is only supported with new enough versions of + # Nginx. And older versions of OpenSSL have a bug that leads to browser errors + # given certain configurations. While we'd prefer to have forward secrecy, we'd + # rather fail open than error out. Unfortunately, Nginx can be compiled against + # many versions of OpenSSL. So we have to check both for the two different features, + # leading to four different combinations of options. + # For a complete history, check out https://github.com/certbot/certbot/issues/7322 + + use_tls13 = self.version >= (1, 13, 0) + session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ + LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') + + if use_tls13: + if session_tix_off: + config_filename = "options-ssl-nginx.conf" + else: + config_filename = "options-ssl-nginx-tls13-session-tix-on.conf" + else: + if session_tix_off: + config_filename = "options-ssl-nginx-tls12-only.conf" + else: + config_filename = "options-ssl-nginx-old.conf" + + return pkg_resources.resource_filename( + "certbot_nginx", os.path.join("_internal", "tls_configs", config_filename)) + + @property + def mod_ssl_conf(self): + """Full absolute path to SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) + + @property + def updated_mod_ssl_conf_digest(self): + """Full absolute path to digest of updated SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) + + def install_ssl_options_conf(self, options_ssl, options_ssl_digest): + """Copy Certbot's SSL options file into the system's config dir if required.""" + return common.install_version_controlled_file(options_ssl, options_ssl_digest, + self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES) + + # This is called in determine_authenticator and determine_installer + def prepare(self): + """Prepare the authenticator/installer. + + :raises .errors.NoInstallationError: If Nginx ctl cannot be found + :raises .errors.MisconfigurationError: If Nginx is misconfigured + """ + # Verify Nginx is installed + if not util.exe_exists(self.conf('ctl')): + raise errors.NoInstallationError( + "Could not find a usable 'nginx' binary. Ensure nginx exists, " + "the binary is executable, and your PATH is set correctly.") + + # Make sure configuration is valid + self.config_test() + + self.parser = parser.NginxParser(self.conf('server-root')) + + # Set Version + if self.version is None: + self.version = self.get_version() + + if self.openssl_version is None: + self.openssl_version = self._get_openssl_version() + + self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) + + self.install_ssl_dhparams() + + # Prevent two Nginx plugins from modifying a config at once + try: + util.lock_dir_until_exit(self.conf('server-root')) + except (OSError, errors.LockError): + logger.debug('Encountered error:', exc_info=True) + raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root'))) + + # Entry point in main.py for installing cert + def deploy_cert(self, domain, cert_path, key_path, + chain_path=None, fullchain_path=None): + """Deploys certificate to specified virtual host. + + .. note:: Aborts if the vhost is missing ssl_certificate or + ssl_certificate_key. + + .. note:: This doesn't save the config files! + + :raises errors.PluginError: When unable to deploy certificate due to + a lack of directives or configuration + + """ + if not fullchain_path: + raise errors.PluginError( + "The nginx plugin currently requires --fullchain-path to " + "install a cert.") + + vhosts = self.choose_vhosts(domain, create_if_no_match=True) + for vhost in vhosts: + self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) + + def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): # pylint: disable=unused-argument + """ + Helper function for deploy_cert() that handles the actual deployment + this exists because we might want to do multiple deployments per + domain originally passed for deploy_cert(). This is especially true + with wildcard certificates + """ + cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path], + ['\n ', 'ssl_certificate_key', ' ', key_path]] + + self.parser.update_or_add_server_directives(vhost, + cert_directives) + logger.info("Deploying Certificate to VirtualHost %s", vhost.filep) + + self.save_notes += ("Changed vhost at %s with addresses of %s\n" % + (vhost.filep, + ", ".join(str(addr) for addr in vhost.addrs))) + self.save_notes += "\tssl_certificate %s\n" % fullchain_path + self.save_notes += "\tssl_certificate_key %s\n" % key_path + + def _choose_vhosts_wildcard(self, domain, prefer_ssl, no_ssl_filter_port=None): + """Prompts user to choose vhosts to install a wildcard certificate for""" + if prefer_ssl: + vhosts_cache = self._wildcard_vhosts + preference_test = lambda x: x.ssl + else: + vhosts_cache = self._wildcard_redirect_vhosts + preference_test = lambda x: not x.ssl + + # Caching! + if domain in vhosts_cache: + # Vhosts for a wildcard domain were already selected + return vhosts_cache[domain] + + # Get all vhosts whether or not they are covered by the wildcard domain + vhosts = self.parser.get_vhosts() + + # Go through the vhosts, making sure that we cover all the names + # present, but preferring the SSL or non-SSL vhosts + filtered_vhosts = {} + for vhost in vhosts: + # Ensure we're listening non-sslishly on no_ssl_filter_port + if no_ssl_filter_port is not None: + if not self._vhost_listening_on_port_no_ssl(vhost, no_ssl_filter_port): + continue + for name in vhost.names: + if preference_test(vhost): + # Prefer either SSL or non-SSL vhosts + filtered_vhosts[name] = vhost + elif name not in filtered_vhosts: + # Add if not in list previously + filtered_vhosts[name] = vhost + + # Only unique VHost objects + dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + + # Ask the user which of names to enable, expect list of names back + return_vhosts = display_ops.select_vhost_multiple(list(dialog_input)) + + for vhost in return_vhosts: + if domain not in vhosts_cache: + vhosts_cache[domain] = [] + vhosts_cache[domain].append(vhost) + + return return_vhosts + + ####################### + # Vhost parsing methods + ####################### + def _choose_vhost_single(self, target_name): + matches = self._get_ranked_matches(target_name) + vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None] + return vhosts + + def choose_vhosts(self, target_name, create_if_no_match=False): + """Chooses a virtual host based on the given domain name. + + .. note:: This makes the vhost SSL-enabled if it isn't already. Follows + Nginx's server block selection rules preferring blocks that are + already SSL. + + .. todo:: This should maybe return list if no obvious answer + is presented. + + .. todo:: The special name "$hostname" corresponds to the machine's + hostname. Currently we just ignore this. + + :param str target_name: domain name + :param bool create_if_no_match: If we should create a new vhost from default + when there is no match found. If we can't choose a default, raise a + MisconfigurationError. + + :returns: ssl vhosts associated with name + :rtype: list of :class:`~certbot_nginx._internal.obj.VirtualHost` + + """ + if util.is_wildcard_domain(target_name): + # Ask user which VHosts to support. + vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=True) + else: + vhosts = self._choose_vhost_single(target_name) + if not vhosts: + if create_if_no_match: + # result will not be [None] because it errors on failure + vhosts = [self._vhost_from_duplicated_default(target_name, True, + str(self.config.https_port))] + else: + # No matches. Raise a misconfiguration error. + raise errors.MisconfigurationError( + ("Cannot find a VirtualHost matching domain %s. " + "In order for Certbot to correctly perform the challenge " + "please add a corresponding server_name directive to your " + "nginx configuration for every domain on your certificate: " + "https://nginx.org/en/docs/http/server_names.html") % (target_name)) + # Note: if we are enhancing with ocsp, vhost should already be ssl. + for vhost in vhosts: + if not vhost.ssl: + self._make_server_ssl(vhost) + + return vhosts + + def ipv6_info(self, port): + """Returns tuple of booleans (ipv6_active, ipv6only_present) + ipv6_active is true if any server block listens ipv6 address in any port + + ipv6only_present is true if ipv6only=on option exists in any server + block ipv6 listen directive for the specified port. + + :param str port: Port to check ipv6only=on directive for + + :returns: Tuple containing information if IPv6 is enabled in the global + configuration, and existence of ipv6only directive for specified port + :rtype: tuple of type (bool, bool) + """ + # port should be a string, but it's easy to mess up, so let's + # make sure it is one + port = str(port) + vhosts = self.parser.get_vhosts() + ipv6_active = False + ipv6only_present = False + for vh in vhosts: + for addr in vh.addrs: + if addr.ipv6: + ipv6_active = True + if addr.ipv6only and addr.get_port() == port: + ipv6only_present = True + return (ipv6_active, ipv6only_present) + + def _vhost_from_duplicated_default(self, domain, allow_port_mismatch, port): + """if allow_port_mismatch is False, only server blocks with matching ports will be + used as a default server block template. + """ + if self.new_vhost is None: + default_vhost = self._get_default_vhost(domain, allow_port_mismatch, port) + self.new_vhost = self.parser.duplicate_vhost(default_vhost, + remove_singleton_listen_params=True) + self.new_vhost.names = set() + + self._add_server_name_to_vhost(self.new_vhost, domain) + return self.new_vhost + + def _add_server_name_to_vhost(self, vhost, domain): + vhost.names.add(domain) + name_block = [['\n ', 'server_name']] + for name in vhost.names: + name_block[0].append(' ') + name_block[0].append(name) + self.parser.update_or_add_server_directives(vhost, name_block) + + def _get_default_vhost(self, domain, allow_port_mismatch, port): + """Helper method for _vhost_from_duplicated_default; see argument documentation there""" + vhost_list = self.parser.get_vhosts() + # if one has default_server set, return that one + all_default_vhosts = [] + port_matching_vhosts = [] + for vhost in vhost_list: + for addr in vhost.addrs: + if addr.default: + all_default_vhosts.append(vhost) + if self._port_matches(port, addr.get_port()): + port_matching_vhosts.append(vhost) + break + + if len(port_matching_vhosts) == 1: + return port_matching_vhosts[0] + elif len(all_default_vhosts) == 1 and allow_port_mismatch: + return all_default_vhosts[0] + + # TODO: present a list of vhosts for user to choose from + + raise errors.MisconfigurationError("Could not automatically find a matching server" + " block for %s. Set the `server_name` directive to use the Nginx installer." % domain) + + def _get_ranked_matches(self, target_name): + """Returns a ranked list of vhosts that match target_name. + The ranking gives preference to SSL vhosts. + + :param str target_name: The name to match + :returns: list of dicts containing the vhost, the matching name, and + the numerical rank + :rtype: list + + """ + vhost_list = self.parser.get_vhosts() + return self._rank_matches_by_name_and_ssl(vhost_list, target_name) + + def _select_best_name_match(self, matches): + """Returns the best name match of a ranked list of vhosts. + + :param list matches: list of dicts containing the vhost, the matching name, + and the numerical rank + :returns: the most matching vhost + :rtype: :class:`~certbot_nginx._internal.obj.VirtualHost` + + """ + if not matches: + return None + elif matches[0]['rank'] in [START_WILDCARD_RANK, END_WILDCARD_RANK, + START_WILDCARD_RANK + NO_SSL_MODIFIER, END_WILDCARD_RANK + NO_SSL_MODIFIER]: + # Wildcard match - need to find the longest one + rank = matches[0]['rank'] + wildcards = [x for x in matches if x['rank'] == rank] + return max(wildcards, key=lambda x: len(x['name']))['vhost'] + # Exact or regex match + return matches[0]['vhost'] + + def _rank_matches_by_name(self, vhost_list, target_name): + """Returns a ranked list of vhosts from vhost_list that match target_name. + This method should always be followed by a call to _select_best_name_match. + + :param list vhost_list: list of vhosts to filter and rank + :param str target_name: The name to match + :returns: list of dicts containing the vhost, the matching name, and + the numerical rank + :rtype: list + + """ + # Nginx chooses a matching server name for a request with precedence: + # 1. exact name match + # 2. longest wildcard name starting with * + # 3. longest wildcard name ending with * + # 4. first matching regex in order of appearance in the file + matches = [] + for vhost in vhost_list: + name_type, name = parser.get_best_match(target_name, vhost.names) + if name_type == 'exact': + matches.append({'vhost': vhost, + 'name': name, + 'rank': NAME_RANK}) + elif name_type == 'wildcard_start': + matches.append({'vhost': vhost, + 'name': name, + 'rank': START_WILDCARD_RANK}) + elif name_type == 'wildcard_end': + matches.append({'vhost': vhost, + 'name': name, + 'rank': END_WILDCARD_RANK}) + elif name_type == 'regex': + matches.append({'vhost': vhost, + 'name': name, + 'rank': REGEX_RANK}) + return sorted(matches, key=lambda x: x['rank']) + + def _rank_matches_by_name_and_ssl(self, vhost_list, target_name): + """Returns a ranked list of vhosts from vhost_list that match target_name. + The ranking gives preference to SSLishness before name match level. + + :param list vhost_list: list of vhosts to filter and rank + :param str target_name: The name to match + :returns: list of dicts containing the vhost, the matching name, and + the numerical rank + :rtype: list + + """ + matches = self._rank_matches_by_name(vhost_list, target_name) + for match in matches: + if not match['vhost'].ssl: + match['rank'] += NO_SSL_MODIFIER + return sorted(matches, key=lambda x: x['rank']) + + def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False): + """Chooses a single virtual host for redirect enhancement. + + Chooses the vhost most closely matching target_name that is + listening to port without using ssl. + + .. todo:: This should maybe return list if no obvious answer + is presented. + + .. todo:: The special name "$hostname" corresponds to the machine's + hostname. Currently we just ignore this. + + :param str target_name: domain name + :param str port: port number + :param bool create_if_no_match: If we should create a new vhost from default + when there is no match found. If we can't choose a default, raise a + MisconfigurationError. + + :returns: vhosts associated with name + :rtype: list of :class:`~certbot_nginx._internal.obj.VirtualHost` + + """ + if util.is_wildcard_domain(target_name): + # Ask user which VHosts to enhance. + vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=False, + no_ssl_filter_port=port) + else: + matches = self._get_redirect_ranked_matches(target_name, port) + vhosts = [x for x in [self._select_best_name_match(matches)]if x is not None] + if not vhosts and create_if_no_match: + vhosts = [self._vhost_from_duplicated_default(target_name, False, port)] + return vhosts + + def _port_matches(self, test_port, matching_port): + # test_port is a number, matching is a number or "" or None + if matching_port == "" or matching_port is None: + # if no port is specified, Nginx defaults to listening on port 80. + return test_port == self.DEFAULT_LISTEN_PORT + return test_port == matching_port + + def _vhost_listening_on_port_no_ssl(self, vhost, port): + found_matching_port = False + if not vhost.addrs: + # if there are no listen directives at all, Nginx defaults to + # listening on port 80. + found_matching_port = (port == self.DEFAULT_LISTEN_PORT) + else: + for addr in vhost.addrs: + if self._port_matches(port, addr.get_port()) and not addr.ssl: + found_matching_port = True + + if found_matching_port: + # make sure we don't have an 'ssl on' directive + return not self.parser.has_ssl_on_directive(vhost) + return False + + def _get_redirect_ranked_matches(self, target_name, port): + """Gets a ranked list of plaintextish port-listening vhosts matching target_name + + Filter all hosts for those listening on port without using ssl. + Rank by how well these match target_name. + + :param str target_name: The name to match + :param str port: port number as a string + :returns: list of dicts containing the vhost, the matching name, and + the numerical rank + :rtype: list + + """ + all_vhosts = self.parser.get_vhosts() + + def _vhost_matches(vhost, port): + return self._vhost_listening_on_port_no_ssl(vhost, port) + + matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)] + + return self._rank_matches_by_name(matching_vhosts, target_name) + + def get_all_names(self): + """Returns all names found in the Nginx Configuration. + + :returns: All ServerNames, ServerAliases, and reverse DNS entries for + virtual host addresses + :rtype: set + + """ + all_names = set() # type: Set[str] + + for vhost in self.parser.get_vhosts(): + all_names.update(vhost.names) + + for addr in vhost.addrs: + host = addr.get_addr() + if common.hostname_regex.match(host): + # If it's a hostname, add it to the names. + all_names.add(host) + elif not common.private_ips_regex.match(host): + # If it isn't a private IP, do a reverse DNS lookup + try: + if addr.ipv6: + host = addr.get_ipv6_exploded() + socket.inet_pton(socket.AF_INET6, host) + else: + socket.inet_pton(socket.AF_INET, host) + all_names.add(socket.gethostbyaddr(host)[0]) + except (socket.error, socket.herror, socket.timeout): + continue + + return util.get_filtered_names(all_names) + + def _get_snakeoil_paths(self): + """Generate invalid certs that let us create ssl directives for Nginx""" + # TODO: generate only once + tmp_dir = os.path.join(self.config.work_dir, "snakeoil") + le_key = crypto_util.init_save_key( + key_size=1024, key_dir=tmp_dir, keyname="key.pem") + key = OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, le_key.pem) + cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()]) + cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + cert_file, cert_path = util.unique_file( + os.path.join(tmp_dir, "cert.pem"), mode="wb") + with cert_file: + cert_file.write(cert_pem) + return cert_path, le_key.file + + def _make_server_ssl(self, vhost): + """Make a server SSL. + + Make a server SSL by adding new listen and SSL directives. + + :param vhost: The vhost to add SSL to. + :type vhost: :class:`~certbot_nginx._internal.obj.VirtualHost` + + """ + https_port = self.config.https_port + ipv6info = self.ipv6_info(https_port) + ipv6_block = [''] + ipv4_block = [''] + + # If the vhost was implicitly listening on the default Nginx port, + # have it continue to do so. + if not vhost.addrs: + listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]] + self.parser.add_server_directives(vhost, listen_block) + + if vhost.ipv6_enabled(): + ipv6_block = ['\n ', + 'listen', + ' ', + '[::]:{0}'.format(https_port), + ' ', + 'ssl'] + if not ipv6info[1]: + # ipv6only=on is absent in global config + ipv6_block.append(' ') + ipv6_block.append('ipv6only=on') + + if vhost.ipv4_enabled(): + ipv4_block = ['\n ', + 'listen', + ' ', + '{0}'.format(https_port), + ' ', + 'ssl'] + + snakeoil_cert, snakeoil_key = self._get_snakeoil_paths() + + ssl_block = ([ + ipv6_block, + ipv4_block, + ['\n ', 'ssl_certificate', ' ', snakeoil_cert], + ['\n ', 'ssl_certificate_key', ' ', snakeoil_key], + ['\n ', 'include', ' ', self.mod_ssl_conf], + ['\n ', 'ssl_dhparam', ' ', self.ssl_dhparams], + ]) + + self.parser.add_server_directives( + vhost, ssl_block) + + ################################## + # enhancement methods (IInstaller) + ################################## + def supported_enhancements(self): # pylint: disable=no-self-use + """Returns currently supported enhancements.""" + return ['redirect', 'ensure-http-header', 'staple-ocsp'] + + def enhance(self, domain, enhancement, options=None): + """Enhance configuration. + + :param str domain: domain to enhance + :param str enhancement: enhancement type defined in + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + :param options: options for the enhancement + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` + documentation for appropriate parameter. + + """ + try: + return self._enhance_func[enhancement](domain, options) + except (KeyError, ValueError): + raise errors.PluginError( + "Unsupported enhancement: {0}".format(enhancement)) + except errors.PluginError: + logger.warning("Failed %s for %s", enhancement, domain) + raise + + def _has_certbot_redirect(self, vhost, domain): + test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain)) + return vhost.contains_list(test_redirect_block) + + def _set_http_header(self, domain, header_substring): + """Enables header identified by header_substring on domain. + + If the vhost is listening plaintextishly, separates out the relevant + directives into a new server block, and only add header directive to + HTTPS block. + + :param str domain: the domain to enable header for. + :param str header_substring: String to uniquely identify a header. + e.g. Strict-Transport-Security, Upgrade-Insecure-Requests + :returns: Success + :raises .errors.PluginError: If no viable HTTPS host can be created or + set with header header_substring. + """ + vhosts = self.choose_vhosts(domain) + if not vhosts: + raise errors.PluginError( + "Unable to find corresponding HTTPS host for enhancement.") + for vhost in vhosts: + if vhost.has_header(header_substring): + raise errors.PluginEnhancementAlreadyPresent( + "Existing %s header" % (header_substring)) + + # if there is no separate SSL block, break the block into two and + # choose the SSL block. + if vhost.ssl and any([not addr.ssl for addr in vhost.addrs]): + _, vhost = self._split_block(vhost) + + header_directives = [ + ['\n ', 'add_header', ' ', header_substring, ' '] + + constants.HEADER_ARGS[header_substring], + ['\n']] + self.parser.add_server_directives(vhost, header_directives) + + def _add_redirect_block(self, vhost, domain): + """Add redirect directive to vhost + """ + redirect_block = _redirect_block_for_domain(domain) + + self.parser.add_server_directives( + vhost, redirect_block, insert_at_top=True) + + def _split_block(self, vhost, only_directives=None): + """Splits this "virtual host" (i.e. this nginx server block) into + separate HTTP and HTTPS blocks. + + :param vhost: The server block to break up into two. + :param list only_directives: If this exists, only duplicate these directives + when splitting the block. + :type vhost: :class:`~certbot_nginx._internal.obj.VirtualHost` + :returns: tuple (http_vhost, https_vhost) + :rtype: tuple of type :class:`~certbot_nginx._internal.obj.VirtualHost` + """ + http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives) + + def _ssl_match_func(directive): + return 'ssl' in directive + + def _ssl_config_match_func(directive): + return self.mod_ssl_conf in directive + + def _no_ssl_match_func(directive): + return 'ssl' not in directive + + # remove all ssl addresses and related directives from the new block + for directive in self.SSL_DIRECTIVES: + self.parser.remove_server_directives(http_vhost, directive) + self.parser.remove_server_directives(http_vhost, 'listen', match_func=_ssl_match_func) + self.parser.remove_server_directives(http_vhost, 'include', + match_func=_ssl_config_match_func) + + # remove all non-ssl addresses from the existing block + self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func) + return http_vhost, vhost + + def _enable_redirect(self, domain, unused_options): + """Redirect all equivalent HTTP traffic to ssl_vhost. + + If the vhost is listening plaintextishly, separate out the + relevant directives into a new server block and add a rewrite directive. + + .. note:: This function saves the configuration + + :param str domain: domain to enable redirect for + :param unused_options: Not currently used + :type unused_options: Not Available + """ + + port = self.DEFAULT_LISTEN_PORT + # If there are blocks listening plaintextishly on self.DEFAULT_LISTEN_PORT, + # choose the most name-matching one. + + vhosts = self.choose_redirect_vhosts(domain, port) + + if not vhosts: + logger.info("No matching insecure server blocks listening on port %s found.", + self.DEFAULT_LISTEN_PORT) + return + + for vhost in vhosts: + self._enable_redirect_single(domain, vhost) + + def _enable_redirect_single(self, domain, vhost): + """Redirect all equivalent HTTP traffic to ssl_vhost. + + If the vhost is listening plaintextishly, separate out the + relevant directives into a new server block and add a rewrite directive. + + .. note:: This function saves the configuration + + :param str domain: domain to enable redirect for + :param `~obj.Vhost` vhost: vhost to enable redirect for + """ + if vhost.ssl: + http_vhost, _ = self._split_block(vhost, ['listen', 'server_name']) + + # Add this at the bottom to get the right order of directives + return_404_directive = [['\n ', 'return', ' ', '404']] + self.parser.add_server_directives(http_vhost, return_404_directive) + + vhost = http_vhost + + if self._has_certbot_redirect(vhost, domain): + logger.info("Traffic on port %s already redirecting to ssl in %s", + self.DEFAULT_LISTEN_PORT, vhost.filep) + else: + # Redirect plaintextish host to https + self._add_redirect_block(vhost, domain) + logger.info("Redirecting all traffic on port %s to ssl in %s", + self.DEFAULT_LISTEN_PORT, vhost.filep) + + def _enable_ocsp_stapling(self, domain, chain_path): + """Include OCSP response in TLS handshake + + :param str domain: domain to enable OCSP response for + :param chain_path: chain file path + :type chain_path: `str` or `None` + + """ + vhosts = self.choose_vhosts(domain) + for vhost in vhosts: + self._enable_ocsp_stapling_single(vhost, chain_path) + + def _enable_ocsp_stapling_single(self, vhost, chain_path): + """Include OCSP response in TLS handshake + + :param str vhost: vhost to enable OCSP response for + :param chain_path: chain file path + :type chain_path: `str` or `None` + + """ + if self.version < (1, 3, 7): + raise errors.PluginError("Version 1.3.7 or greater of nginx " + "is needed to enable OCSP stapling") + + if chain_path is None: + raise errors.PluginError( + "--chain-path is required to enable " + "Online Certificate Status Protocol (OCSP) stapling " + "on nginx >= 1.3.7.") + + stapling_directives = [ + ['\n ', 'ssl_trusted_certificate', ' ', chain_path], + ['\n ', 'ssl_stapling', ' ', 'on'], + ['\n ', 'ssl_stapling_verify', ' ', 'on'], ['\n']] + + try: + self.parser.add_server_directives(vhost, + stapling_directives) + except errors.MisconfigurationError as error: + logger.debug(str(error)) + raise errors.PluginError("An error occurred while enabling OCSP " + "stapling for {0}.".format(vhost.names)) + + self.save_notes += ("OCSP Stapling was enabled " + "on SSL Vhost: {0}.\n".format(vhost.filep)) + self.save_notes += "\tssl_trusted_certificate {0}\n".format(chain_path) + self.save_notes += "\tssl_stapling on\n" + self.save_notes += "\tssl_stapling_verify on\n" + + ###################################### + # Nginx server management (IInstaller) + ###################################### + def restart(self): + """Restarts nginx server. + + :raises .errors.MisconfigurationError: If either the reload fails. + + """ + nginx_restart(self.conf('ctl'), self.nginx_conf) + + def config_test(self): # pylint: disable=no-self-use + """Check the configuration of Nginx for errors. + + :raises .errors.MisconfigurationError: If config_test fails + + """ + try: + util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"]) + except errors.SubprocessError as err: + raise errors.MisconfigurationError(str(err)) + + def _nginx_version(self): + """Return results of nginx -V + + :returns: version text + :rtype: str + + :raises .PluginError: + Unable to run Nginx version command + """ + try: + proc = subprocess.Popen( + [self.conf('ctl'), "-c", self.nginx_conf, "-V"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + text = proc.communicate()[1] # nginx prints output to stderr + except (OSError, ValueError) as error: + logger.debug(str(error), exc_info=True) + raise errors.PluginError( + "Unable to run %s -V" % self.conf('ctl')) + return text + + def get_version(self): + """Return version of Nginx Server. + + Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) + + :returns: version + :rtype: tuple + + :raises .PluginError: + Unable to find Nginx version or version is unsupported + + """ + text = self._nginx_version() + + version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE) + version_matches = version_regex.findall(text) + + sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE) + sni_matches = sni_regex.findall(text) + + ssl_regex = re.compile(r" --with-http_ssl_module") + ssl_matches = ssl_regex.findall(text) + + if not version_matches: + raise errors.PluginError("Unable to find Nginx version") + if not ssl_matches: + raise errors.PluginError( + "Nginx build is missing SSL module (--with-http_ssl_module).") + if not sni_matches: + raise errors.PluginError("Nginx build doesn't support SNI") + + product_name, product_version = version_matches[0] + if product_name != 'nginx': + logger.warning("NGINX derivative %s is not officially supported by" + " certbot", product_name) + + nginx_version = tuple([int(i) for i in product_version.split(".")]) + + # nginx < 0.8.48 uses machine hostname as default server_name instead of + # the empty string + if nginx_version < (0, 8, 48): + raise errors.NotSupportedError("Nginx version must be 0.8.48+") + + return nginx_version + + def _get_openssl_version(self): + """Return version of OpenSSL linked to Nginx. + + Version is returned as string. If no version can be found, empty string is returned. + + :returns: openssl_version + :rtype: str + + :raises .PluginError: + Unable to run Nginx version command + """ + text = self._nginx_version() + + matches = re.findall(r"running with OpenSSL ([^ ]+) ", text) + if not matches: + matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) + if not matches: + logger.warning("NGINX configured with OpenSSL alternatives is not officially" + "supported by Certbot.") + return "" + return matches[0] + + def more_info(self): + """Human-readable string to help understand the module""" + return ( + "Configures Nginx to authenticate and install HTTPS.{0}" + "Server root: {root}{0}" + "Version: {version}".format( + os.linesep, root=self.parser.config_root, + version=".".join(str(i) for i in self.version)) + ) + + ################################################### + # Wrapper functions for Reverter class (IInstaller) + ################################################### + def save(self, title=None, temporary=False): + """Saves all changes to the configuration files. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (ie. challenges) + + :raises .errors.PluginError: If there was an error in + an attempt to save the configuration, or an error creating a + checkpoint + + """ + save_files = set(self.parser.parsed.keys()) + self.add_to_checkpoint(save_files, self.save_notes, temporary) + self.save_notes = "" + + # Change 'ext' to something else to not override existing conf files + self.parser.filedump(ext='') + if title and not temporary: + self.finalize_checkpoint(title) + + def recovery_routine(self): + """Revert all previously modified files. + + Reverts all modified files that have not been saved as a checkpoint + + :raises .errors.PluginError: If unable to recover the configuration + + """ + super(NginxConfigurator, self).recovery_routine() + self.new_vhost = None + self.parser.load() + + def revert_challenge_config(self): + """Used to cleanup challenge configurations. + + :raises .errors.PluginError: If unable to revert the challenge config. + + """ + self.revert_temporary_config() + self.new_vhost = None + self.parser.load() + + def rollback_checkpoints(self, rollback=1): + """Rollback saved checkpoints. + + :param int rollback: Number of checkpoints to revert + + :raises .errors.PluginError: If there is a problem with the input or + the function is unable to correctly revert the configuration + + """ + super(NginxConfigurator, self).rollback_checkpoints(rollback) + self.new_vhost = None + self.parser.load() + + ########################################################################### + # Challenges Section for IAuthenticator + ########################################################################### + def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use + """Return list of challenge preferences.""" + return [challenges.HTTP01] + + # Entry point in main.py for performing challenges + def perform(self, achalls): + """Perform the configuration related challenge. + + This function currently assumes all challenges will be fulfilled. + If this turns out not to be the case in the future. Cleanup and + outstanding challenges will have to be designed better. + + """ + self._chall_out += len(achalls) + responses = [None] * len(achalls) + http_doer = http_01.NginxHttp01(self) + + for i, achall in enumerate(achalls): + # Currently also have chall_doer hold associated index of the + # challenge. This helps to put all of the responses back together + # when they are all complete. + http_doer.add_chall(achall, i) + + http_response = http_doer.perform() + # Must restart in order to activate the challenges. + # Handled here because we may be able to load up other challenge types + self.restart() + + # Go through all of the challenges and assign them to the proper place + # in the responses return value. All responses must be in the same order + # as the original challenges. + for i, resp in enumerate(http_response): + responses[http_doer.indices[i]] = resp + + return responses + + # called after challenges are performed + def cleanup(self, achalls): + """Revert all challenges.""" + self._chall_out -= len(achalls) + + # If all of the challenges have been finished, clean up everything + if self._chall_out <= 0: + self.revert_challenge_config() + self.restart() + + +def _test_block_from_block(block): + test_block = nginxparser.UnspacedList(block) + parser.comment_directive(test_block, 0) + return test_block[:-1] + + +def _redirect_block_for_domain(domain): + updated_domain = domain + match_symbol = '=' + if util.is_wildcard_domain(domain): + match_symbol = '~' + updated_domain = updated_domain.replace('.', r'\.') + updated_domain = updated_domain.replace('*', '[^.]+') + updated_domain = '^' + updated_domain + '$' + redirect_block = [[ + ['\n ', 'if', ' ', '($host', ' ', match_symbol, ' ', '%s)' % updated_domain, ' '], + [['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], + '\n ']], + ['\n']] + return redirect_block + + +def nginx_restart(nginx_ctl, nginx_conf): + """Restarts the Nginx Server. + + .. todo:: Nginx restart is fatal if the configuration references + non-existent SSL cert/key files. Remove references to /etc/letsencrypt + before restart. + + :param str nginx_ctl: Path to the Nginx binary. + + """ + try: + proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"]) + proc.communicate() + + if proc.returncode != 0: + # Maybe Nginx isn't running + # Write to temporary files instead of piping because of communication issues on Arch + # https://github.com/certbot/certbot/issues/4324 + with tempfile.TemporaryFile() as out: + with tempfile.TemporaryFile() as err: + nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf], + stdout=out, stderr=err) + nginx_proc.communicate() + if nginx_proc.returncode != 0: + # Enter recovery routine... + raise errors.MisconfigurationError( + "nginx restart failed:\n%s\n%s" % (out.read(), err.read())) + + except (OSError, ValueError): + raise errors.MisconfigurationError("nginx restart failed") + # Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep + # for a second. TODO: Check for expected servername and loop until it + # appears or return an error if looping too long. + time.sleep(1) + + +def _determine_default_server_root(): + if os.environ.get("CERTBOT_DOCS") == "1": + default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT, + constants.FREEBSD_DARWIN_SERVER_ROOT) + else: + default_server_root = constants.CLI_DEFAULTS["server_root"] + return default_server_root diff --git a/certbot-nginx/certbot_nginx/_internal/constants.py b/certbot-nginx/certbot_nginx/_internal/constants.py new file mode 100644 index 000000000..fbf6ed424 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/constants.py @@ -0,0 +1,63 @@ +"""nginx plugin constants.""" +import platform + +FREEBSD_DARWIN_SERVER_ROOT = "/usr/local/etc/nginx" +LINUX_SERVER_ROOT = "/etc/nginx" + +if platform.system() in ('FreeBSD', 'Darwin'): + server_root_tmp = FREEBSD_DARWIN_SERVER_ROOT +else: + server_root_tmp = LINUX_SERVER_ROOT + +CLI_DEFAULTS = dict( + server_root=server_root_tmp, + ctl="nginx", +) +"""CLI defaults.""" + + +MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" +"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" + +UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" +"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" + +ALL_SSL_OPTIONS_HASHES = [ + '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', + '9a7b32c49001fed4cff8ad24353329472a50e86ade1ef9b2b9e43566a619612e', + 'a6d9f1c7d6b36749b52ba061fff1421f9a0a3d2cfdafbd63c05d06f65b990937', + '7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff', + '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', + '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', + 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', + '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', + '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', + '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', + '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', + 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', + 'ef11e3fb17213e74d3e1816cde0ec37b8b95b4167cf21e7b8ff1eaa9c6f918ee', + 'af85f6193808a44789a1d293e6cffa249cad9a21135940800958b8e3c72dbc69', + 'a2a612fd21b02abaa32d9d11ac63d987d6e3054dbfa356de5800eea0d7ce17f3', + '2d9648302e3588a172c318e46bff88ade46fc7a16d6afc85322776a04800d473', +] +"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" + +def os_constant(key): + # XXX TODO: In the future, this could return different constants + # based on what OS we are running under. To see an + # approach to how to handle different OSes, see the + # apache version of this file. Currently, we do not + # actually have any OS-specific constants on Nginx. + """ + Get a constant value for operating system + + :param key: name of cli constant + :return: value of constant for active os + """ + return CLI_DEFAULTS[key] + +HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always'] + +HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS} diff --git a/certbot-nginx/certbot_nginx/_internal/display_ops.py b/certbot-nginx/certbot_nginx/_internal/display_ops.py new file mode 100644 index 000000000..9b973d8d3 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/display_ops.py @@ -0,0 +1,44 @@ +"""Contains UI methods for Nginx operations.""" +import logging + +import zope.component + +from certbot import interfaces + +import certbot.display.util as display_util + + +logger = logging.getLogger(__name__) + + +def select_vhost_multiple(vhosts): + """Select multiple Vhosts to install the certificate for + :param vhosts: Available Nginx VirtualHosts + :type vhosts: :class:`list` of type `~obj.Vhost` + :returns: List of VirtualHosts + :rtype: :class:`list`of type `~obj.Vhost` + """ + if not vhosts: + return list() + tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] + # Remove the extra newline from the last entry + if tags_list: + tags_list[-1] = tags_list[-1][:-1] + code, names = zope.component.getUtility(interfaces.IDisplay).checklist( + "Which server blocks would you like to modify?", + tags=tags_list, force_interactive=True) + if code == display_util.OK: + return_vhosts = _reversemap_vhosts(names, vhosts) + return return_vhosts + return [] + +def _reversemap_vhosts(names, vhosts): + """Helper function for select_vhost_multiple for mapping string + representations back to actual vhost objects""" + return_vhosts = list() + + for selection in names: + for vhost in vhosts: + if vhost.display_repr().strip() == selection.strip(): + return_vhosts.append(vhost) + return return_vhosts diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py new file mode 100644 index 000000000..7223548d9 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -0,0 +1,207 @@ +"""A class that performs HTTP-01 challenges for Nginx""" + +import logging + +from acme import challenges +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import errors +from certbot.compat import os +from certbot.plugins import common + +from certbot_nginx._internal import obj +from certbot_nginx._internal import nginxparser + + +logger = logging.getLogger(__name__) + + +class NginxHttp01(common.ChallengePerformer): + """HTTP-01 authenticator for Nginx + + :ivar configurator: NginxConfigurator object + :type configurator: :class:`~nginx.configurator.NginxConfigurator` + + :ivar list achalls: Annotated + class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge` + challenges + + :param list indices: Meant to hold indices of challenges in a + larger array. NginxHttp01 is capable of solving many challenges + at once which causes an indexing issue within NginxConfigurator + who must return all responses in order. Imagine + NginxConfigurator maintaining state about where all of the + challenges, possibly of different types, belong in the response + array. This is an optional utility. + + """ + + def __init__(self, configurator): + super(NginxHttp01, self).__init__(configurator) + self.challenge_conf = os.path.join( + configurator.config.config_dir, "le_http_01_cert_challenge.conf") + + def perform(self): + """Perform a challenge on Nginx. + + :returns: list of :class:`certbot.acme.challenges.HTTP01Response` + :rtype: list + + """ + if not self.achalls: + return [] + + responses = [x.response(x.account_key) for x in self.achalls] + + # Set up the configuration + self._mod_config() + + # Save reversible changes + self.configurator.save("HTTP Challenge", True) + + return responses + + def _mod_config(self): + """Modifies Nginx config to include server_names_hash_bucket_size directive + and server challenge blocks. + + :raises .MisconfigurationError: + Unable to find a suitable HTTP block in which to include + authenticator hosts. + """ + included = False + include_directive = ['\n', 'include', ' ', self.challenge_conf] + root = self.configurator.parser.config_root + + bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128'] + + main = self.configurator.parser.parsed[root] + for line in main: + if line[0] == ['http']: + body = line[1] + found_bucket = False + posn = 0 + for inner_line in body: + if inner_line[0] == bucket_directive[1]: + if int(inner_line[1]) < int(bucket_directive[3]): + body[posn] = bucket_directive + found_bucket = True + posn += 1 + if not found_bucket: + body.insert(0, bucket_directive) + if include_directive not in body: + body.insert(0, include_directive) + included = True + break + if not included: + raise errors.MisconfigurationError( + 'Certbot could not find a block to include ' + 'challenges in %s.' % root) + config = [self._make_or_mod_server_block(achall) for achall in self.achalls] + config = [x for x in config if x is not None] + config = nginxparser.UnspacedList(config) + logger.debug("Generated server block:\n%s", str(config)) + + self.configurator.reverter.register_file_creation( + True, self.challenge_conf) + + with open(self.challenge_conf, "w") as new_conf: + nginxparser.dump(config, new_conf) + + def _default_listen_addresses(self): + """Finds addresses for a challenge block to listen on. + :returns: list of :class:`certbot_nginx._internal.obj.Addr` to apply + :rtype: list + """ + addresses = [] # type: List[obj.Addr] + default_addr = "%s" % self.configurator.config.http01_port + ipv6_addr = "[::]:{0}".format( + self.configurator.config.http01_port) + port = self.configurator.config.http01_port + + ipv6, ipv6only = self.configurator.ipv6_info(port) + + if ipv6: + # If IPv6 is active in Nginx configuration + if not ipv6only: + # If ipv6only=on is not already present in the config + ipv6_addr = ipv6_addr + " ipv6only=on" + addresses = [obj.Addr.fromstring(default_addr), + obj.Addr.fromstring(ipv6_addr)] + logger.info(("Using default addresses %s and %s for authentication."), + default_addr, + ipv6_addr) + else: + addresses = [obj.Addr.fromstring(default_addr)] + logger.info("Using default address %s for authentication.", + default_addr) + return addresses + + def _get_validation_path(self, achall): + return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token")) + + def _make_server_block(self, achall): + """Creates a server block for a challenge. + :param achall: Annotated HTTP-01 challenge + :type achall: + :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` + :param list addrs: addresses of challenged domain + :class:`list` of type :class:`~nginx.obj.Addr` + :returns: server block for the challenge host + :rtype: list + """ + addrs = self._default_listen_addresses() + block = [['listen', ' ', addr.to_string(include_default=False)] for addr in addrs] + + # Ensure we 404 on any other request by setting a root + document_root = os.path.join( + self.configurator.config.work_dir, "http_01_nonexistent") + + block.extend([['server_name', ' ', achall.domain], + ['root', ' ', document_root], + self._location_directive_for_achall(achall) + ]) + # TODO: do we want to return something else if they otherwise access this block? + return [['server'], block] + + def _location_directive_for_achall(self, achall): + validation = achall.validation(achall.account_key) + validation_path = self._get_validation_path(achall) + + location_directive = [['location', ' ', '=', ' ', validation_path], + [['default_type', ' ', 'text/plain'], + ['return', ' ', '200', ' ', validation]]] + return location_directive + + + def _make_or_mod_server_block(self, achall): + """Modifies a server block to respond to a challenge. + + :param achall: Annotated HTTP-01 challenge + :type achall: + :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` + + """ + try: + vhosts = self.configurator.choose_redirect_vhosts(achall.domain, + '%i' % self.configurator.config.http01_port, create_if_no_match=True) + except errors.MisconfigurationError: + # Couldn't find either a matching name+port server block + # or a port+default_server block, so create a dummy block + return self._make_server_block(achall) + + # len is max 1 because Nginx doesn't authenticate wildcards + # if len were or vhosts None, we would have errored + vhost = vhosts[0] + + # Modify existing server block + location_directive = [self._location_directive_for_achall(achall)] + + self.configurator.parser.add_server_directives(vhost, + location_directive) + + rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)', + ' ', '$1', ' ', 'break']] + self.configurator.parser.add_server_directives(vhost, + rewrite_directive, insert_at_top=True) + return None diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py new file mode 100644 index 000000000..f4603dcde --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -0,0 +1,268 @@ +"""Very low-level nginx config parser based on pyparsing.""" +# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed) +import copy +import logging + +from pyparsing import ( + Literal, White, Forward, Group, Optional, OneOrMore, QuotedString, Regex, ZeroOrMore, Combine) +from pyparsing import stringEnd +from pyparsing import restOfLine +import six + +logger = logging.getLogger(__name__) + +class RawNginxParser(object): + # pylint: disable=expression-not-assigned + # pylint: disable=pointless-statement + """A class that parses nginx configuration with pyparsing.""" + + # constants + space = Optional(White()).leaveWhitespace() + required_space = White().leaveWhitespace() + + left_bracket = Literal("{").suppress() + right_bracket = space + Literal("}").suppress() + semicolon = Literal(";").suppress() + dquoted = QuotedString('"', multiline=True, unquoteResults=False, escChar='\\') + squoted = QuotedString("'", multiline=True, unquoteResults=False, escChar='\\') + quoted = dquoted | squoted + head_tokenchars = Regex(r"(\$\{)|[^{};\s'\"]") # if (last_space) + tail_tokenchars = Regex(r"(\$\{)|[^{;\s]") # else + tokenchars = Combine(head_tokenchars + ZeroOrMore(tail_tokenchars)) + paren_quote_extend = Combine(quoted + Literal(')') + ZeroOrMore(tail_tokenchars)) + # note: ')' allows extension, but then we fall into else, not last_space. + + token = paren_quote_extend | tokenchars | quoted + + whitespace_token_group = space + token + ZeroOrMore(required_space + token) + space + assignment = whitespace_token_group + semicolon + + comment = space + Literal('#') + restOfLine + + block = Forward() + + # order matters! see issue 518, and also http { # server { \n} + contents = Group(comment) | Group(block) | Group(assignment) + + block_begin = Group(whitespace_token_group) + block_innards = Group(ZeroOrMore(contents) + space).leaveWhitespace() + block << block_begin + left_bracket + block_innards + right_bracket + + script = OneOrMore(contents) + space + stringEnd + script.parseWithTabs().leaveWhitespace() + + def __init__(self, source): + self.source = source + + def parse(self): + """Returns the parsed tree.""" + return self.script.parseString(self.source) + + def as_list(self): + """Returns the parsed tree as a list.""" + return self.parse().asList() + +class RawNginxDumper(object): + """A class that dumps nginx configuration from the provided tree.""" + def __init__(self, blocks): + self.blocks = blocks + + def __iter__(self, blocks=None): + """Iterates the dumped nginx content.""" + blocks = blocks or self.blocks + for b0 in blocks: + if isinstance(b0, six.string_types): + yield b0 + continue + item = copy.deepcopy(b0) + if spacey(item[0]): + yield item.pop(0) # indentation + if not item: + continue + + if isinstance(item[0], list): # block + yield "".join(item.pop(0)) + '{' + for parameter in item.pop(0): + for line in self.__iter__([parameter]): # negate "for b0 in blocks" + yield line + yield '}' + else: # not a block - list of strings + semicolon = ";" + if isinstance(item[0], six.string_types) and item[0].strip() == '#': # comment + semicolon = "" + yield "".join(item) + semicolon + + def __str__(self): + """Return the parsed block as a string.""" + return ''.join(self) + + +# Shortcut functions to respect Python's serialization interface +# (like pyyaml, picker or json) + +def loads(source): + """Parses from a string. + + :param str source: The string to parse + :returns: The parsed tree + :rtype: list + + """ + return UnspacedList(RawNginxParser(source).as_list()) + + +def load(_file): + """Parses from a file. + + :param file _file: The file to parse + :returns: The parsed tree + :rtype: list + + """ + return loads(_file.read()) + + +def dumps(blocks): + """Dump to a string. + + :param UnspacedList block: The parsed tree + :param int indentation: The number of spaces to indent + :rtype: str + + """ + return str(RawNginxDumper(blocks.spaced)) + + +def dump(blocks, _file): + """Dump to a file. + + :param UnspacedList block: The parsed tree + :param file _file: The file to dump to + :param int indentation: The number of spaces to indent + :rtype: NoneType + + """ + return _file.write(dumps(blocks)) + + +spacey = lambda x: (isinstance(x, six.string_types) and x.isspace()) or x == '' + +class UnspacedList(list): + """Wrap a list [of lists], making any whitespace entries magically invisible""" + + def __init__(self, list_source): + # ensure our argument is not a generator, and duplicate any sublists + self.spaced = copy.deepcopy(list(list_source)) + self.dirty = False + + # Turn self into a version of the source list that has spaces removed + # and all sub-lists also UnspacedList()ed + list.__init__(self, list_source) + for i, entry in reversed(list(enumerate(self))): + if isinstance(entry, list): + sublist = UnspacedList(entry) + list.__setitem__(self, i, sublist) + self.spaced[i] = sublist.spaced + elif spacey(entry): + # don't delete comments + if "#" not in self[:i]: + list.__delitem__(self, i) + + def _coerce(self, inbound): + """ + Coerce some inbound object to be appropriately usable in this object + + :param inbound: string or None or list or UnspacedList + :returns: (coerced UnspacedList or string or None, spaced equivalent) + :rtype: tuple + + """ + if not isinstance(inbound, list): # str or None + return (inbound, inbound) + else: + if not hasattr(inbound, "spaced"): + inbound = UnspacedList(inbound) + return (inbound, inbound.spaced) + + + def insert(self, i, x): + item, spaced_item = self._coerce(x) + slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced) + self.spaced.insert(slicepos, spaced_item) + if not spacey(item): + list.insert(self, i, item) + self.dirty = True + + def append(self, x): + item, spaced_item = self._coerce(x) + self.spaced.append(spaced_item) + if not spacey(item): + list.append(self, item) + self.dirty = True + + def extend(self, x): + item, spaced_item = self._coerce(x) + self.spaced.extend(spaced_item) + list.extend(self, item) + self.dirty = True + + def __add__(self, other): + l = copy.deepcopy(self) + l.extend(other) + l.dirty = True + return l + + def pop(self, _i=None): + raise NotImplementedError("UnspacedList.pop() not yet implemented") + def remove(self, _): + raise NotImplementedError("UnspacedList.remove() not yet implemented") + def reverse(self): + raise NotImplementedError("UnspacedList.reverse() not yet implemented") + def sort(self, _cmp=None, _key=None, _Rev=None): + raise NotImplementedError("UnspacedList.sort() not yet implemented") + def __setslice__(self, _i, _j, _newslice): + raise NotImplementedError("Slice operations on UnspacedLists not yet implemented") + + def __setitem__(self, i, value): + if isinstance(i, slice): + raise NotImplementedError("Slice operations on UnspacedLists not yet implemented") + item, spaced_item = self._coerce(value) + self.spaced.__setitem__(self._spaced_position(i), spaced_item) + if not spacey(item): + list.__setitem__(self, i, item) + self.dirty = True + + def __delitem__(self, i): + self.spaced.__delitem__(self._spaced_position(i)) + list.__delitem__(self, i) + self.dirty = True + + def __deepcopy__(self, memo): + new_spaced = copy.deepcopy(self.spaced, memo=memo) + l = UnspacedList(new_spaced) + l.dirty = self.dirty + return l + + def is_dirty(self): + """Recurse through the parse tree to figure out if any sublists are dirty""" + if self.dirty: + return True + return any((isinstance(x, UnspacedList) and x.is_dirty() for x in self)) + + def _spaced_position(self, idx): + "Convert from indexes in the unspaced list to positions in the spaced one" + pos = spaces = 0 + # Normalize indexes like list[-1] etc, and save the result + if idx < 0: + idx = len(self) + idx + if not 0 <= idx < len(self): + raise IndexError("list index out of range") + idx0 = idx + # Count the number of spaces in the spaced list before idx in the unspaced one + while idx != -1: + if spacey(self.spaced[pos]): + spaces += 1 + else: + idx -= 1 + pos += 1 + return idx0 + spaces diff --git a/certbot-nginx/certbot_nginx/_internal/obj.py b/certbot-nginx/certbot_nginx/_internal/obj.py new file mode 100644 index 000000000..1a92c8b37 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/obj.py @@ -0,0 +1,263 @@ +"""Module contains classes used by the Nginx Configurator.""" +import re + +import six + +from certbot.plugins import common + +ADD_HEADER_DIRECTIVE = 'add_header' + +class Addr(common.Addr): + r"""Represents an Nginx address, i.e. what comes after the 'listen' + directive. + + According to the `documentation`_, this may be address[:port], port, + or unix:path. The latter is ignored here. + + The default value if no directive is specified is \*:80 (superuser) + or \*:8000 (otherwise). If no port is specified, the default is + 80. If no address is specified, listen on all addresses. + + .. _documentation: + http://nginx.org/en/docs/http/ngx_http_core_module.html#listen + + .. todo:: Old-style nginx configs define SSL vhosts in a separate + block instead of using 'ssl' in the listen directive. + + :param str addr: addr part of vhost address, may be hostname, IPv4, IPv6, + "", or "\*" + :param str port: port number or "\*" or "" + :param bool ssl: Whether the directive includes 'ssl' + :param bool default: Whether the directive includes 'default_server' + :param bool default: Whether this is an IPv6 address + :param bool ipv6only: Whether the directive includes 'ipv6only=on' + + """ + UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0') + CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] + + def __init__(self, host, port, ssl, default, ipv6, ipv6only): + super(Addr, self).__init__((host, port)) + self.ssl = ssl + self.default = default + self.ipv6 = ipv6 + self.ipv6only = ipv6only + self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES + + @classmethod + def fromstring(cls, str_addr): + """Initialize Addr from string.""" + parts = str_addr.split(' ') + ssl = False + default = False + ipv6 = False + ipv6only = False + host = '' + port = '' + + # The first part must be the address + addr = parts.pop(0) + + # Ignore UNIX-domain sockets + if addr.startswith('unix:'): + return None + + # IPv6 check + ipv6_match = re.match(r'\[.*\]', addr) + if ipv6_match: + ipv6 = True + # IPv6 handling + host = ipv6_match.group() + # The rest of the addr string will be the port, if any + port = addr[ipv6_match.end()+1:] + else: + # IPv4 handling + tup = addr.partition(':') + if re.match(r'^\d+$', tup[0]): + # This is a bare port, not a hostname. E.g. listen 80 + host = '' + port = tup[0] + else: + # This is a host-port tuple. E.g. listen 127.0.0.1:* + host = tup[0] + port = tup[2] + + # The rest of the parts are options; we only care about ssl and default + while parts: + nextpart = parts.pop() + if nextpart == 'ssl': + ssl = True + elif nextpart == 'default_server': + default = True + elif nextpart == 'default': + default = True + elif nextpart == "ipv6only=on": + ipv6only = True + + return cls(host, port, ssl, default, ipv6, ipv6only) + + def to_string(self, include_default=True): + """Return string representation of Addr""" + parts = '' + if self.tup[0] and self.tup[1]: + parts = "%s:%s" % self.tup + elif self.tup[0]: + parts = self.tup[0] + else: + parts = self.tup[1] + + if self.default and include_default: + parts += ' default_server' + if self.ssl: + parts += ' ssl' + + return parts + + def __str__(self): + return self.to_string() + + def __repr__(self): + return "Addr(" + self.__str__() + ")" + + def __hash__(self): # pylint: disable=useless-super-delegation + # Python 3 requires explicit overridden for __hash__ + # See certbot-apache/certbot_apache/_internal/obj.py for more information + return super(Addr, self).__hash__() + + def super_eq(self, other): + """Check ip/port equality, with IPv6 support. + """ + # If both addresses got an unspecified address, then make sure the + # host representation in each match when doing the comparison. + if self.unspecified_address and other.unspecified_address: + return common.Addr((self.CANONICAL_UNSPECIFIED_ADDRESS, + self.tup[1]), self.ipv6) == \ + common.Addr((other.CANONICAL_UNSPECIFIED_ADDRESS, + other.tup[1]), other.ipv6) + return super(Addr, self).__eq__(other) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.super_eq(other) and + self.ssl == other.ssl and + self.default == other.default) + return False + + +class VirtualHost(object): + """Represents an Nginx Virtualhost. + + :ivar str filep: file path of VH + :ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`) + :ivar set names: Server names/aliases of vhost + (:class:`list` of :class:`str`) + :ivar list raw: The raw form of the parsed server block + + :ivar bool ssl: SSLEngine on in vhost + :ivar bool enabled: Virtual host is enabled + :ivar list path: The indices into the parsed file used to access + the server block defining the vhost + + """ + + def __init__(self, filep, addrs, ssl, enabled, names, raw, path): + """Initialize a VH.""" + self.filep = filep + self.addrs = addrs + self.names = names + self.ssl = ssl + self.enabled = enabled + self.raw = raw + self.path = path + + def __str__(self): + addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str)) + # names might be a set, and it has different representations in Python + # 2 and 3. Force it to be a list here for consistent outputs + return ("file: %s\n" + "addrs: %s\n" + "names: %s\n" + "ssl: %s\n" + "enabled: %s" % (self.filep, addr_str, + list(self.names), self.ssl, self.enabled)) + + def __repr__(self): + return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n" + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.filep == other.filep and + sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and + self.names == other.names and + self.ssl == other.ssl and + self.enabled == other.enabled and + self.path == other.path) + + return False + + def __hash__(self): + return hash((self.filep, tuple(self.path), + tuple(self.addrs), tuple(self.names), + self.ssl, self.enabled)) + + def has_header(self, header_name): + """Determine if this server block has a particular header set. + :param str header_name: The name of the header to check for, e.g. + 'Strict-Transport-Security' + """ + found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name) + return found is not None + + def contains_list(self, test): + """Determine if raw server block contains test list at top level + """ + for i in six.moves.range(0, len(self.raw) - len(test) + 1): + if self.raw[i:i + len(test)] == test: + return True + return False + + def ipv6_enabled(self): + """Return true if one or more of the listen directives in vhost supports + IPv6""" + for a in self.addrs: + if a.ipv6: + return True + return False + + def ipv4_enabled(self): + """Return true if one or more of the listen directives in vhost are IPv4 + only""" + if not self.addrs: + return True + for a in self.addrs: + if not a.ipv6: + return True + return False + + def display_repr(self): + """Return a representation of VHost to be used in dialog""" + return ( + "File: {filename}\n" + "Addresses: {addrs}\n" + "Names: {names}\n" + "HTTPS: {https}\n".format( + filename=self.filep, + addrs=", ".join(str(addr) for addr in self.addrs), + names=", ".join(self.names), + https="Yes" if self.ssl else "No")) + +def _find_directive(directives, directive_name, match_content=None): + """Find a directive of type directive_name in directives. If match_content is given, + Searches for `match_content` in the directive arguments. + """ + if not directives or isinstance(directives, six.string_types): + return None + + # If match_content is None, just match on directive type. Otherwise, match on + # both directive type -and- the content! + if directives[0] == directive_name and \ + (match_content is None or match_content in directives): + return directives + + matches = (_find_directive(line, directive_name, match_content) for line in directives) + return next((m for m in matches if m is not None), None) diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py new file mode 100644 index 000000000..a0d375437 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -0,0 +1,762 @@ +"""NginxParser is a member object of the NginxConfigurator class.""" +import copy +import functools +import glob +import logging +import re +import pyparsing + +import six + +from certbot import errors +from certbot.compat import os + +from certbot_nginx._internal import obj +from certbot_nginx._internal import nginxparser +from acme.magic_typing import Union, Dict, Set, Any, List, Tuple # pylint: disable=unused-import, no-name-in-module + +logger = logging.getLogger(__name__) + + +class NginxParser(object): + """Class handles the fine details of parsing the Nginx Configuration. + + :ivar str root: Normalized absolute path to the server root + directory. Without trailing slash. + :ivar dict parsed: Mapping of file paths to parsed trees + + """ + + def __init__(self, root): + self.parsed = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] + self.root = os.path.abspath(root) + self.config_root = self._find_config_root() + + # Parse nginx.conf and included files. + # TODO: Check sites-available/ as well. For now, the configurator does + # not enable sites from there. + self.load() + + def load(self): + """Loads Nginx files into a parsed tree. + + """ + self.parsed = {} + self._parse_recursively(self.config_root) + + def _parse_recursively(self, filepath): + """Parses nginx config files recursively by looking at 'include' + directives inside 'http' and 'server' blocks. Note that this only + reads Nginx files that potentially declare a virtual host. + + :param str filepath: The path to the files to parse, as a glob + + """ + # pylint: disable=too-many-nested-blocks + filepath = self.abs_path(filepath) + trees = self._parse_files(filepath) + for tree in trees: + for entry in tree: + if _is_include_directive(entry): + # Parse the top-level included file + self._parse_recursively(entry[1]) + elif entry[0] == ['http'] or entry[0] == ['server']: + # Look for includes in the top-level 'http'/'server' context + for subentry in entry[1]: + if _is_include_directive(subentry): + self._parse_recursively(subentry[1]) + elif entry[0] == ['http'] and subentry[0] == ['server']: + # Look for includes in a 'server' context within + # an 'http' context + for server_entry in subentry[1]: + if _is_include_directive(server_entry): + self._parse_recursively(server_entry[1]) + + def abs_path(self, path): + """Converts a relative path to an absolute path relative to the root. + Does nothing for paths that are already absolute. + + :param str path: The path + :returns: The absolute path + :rtype: str + + """ + if not os.path.isabs(path): + return os.path.normpath(os.path.join(self.root, path)) + return os.path.normpath(path) + + def _build_addr_to_ssl(self): + """Builds a map from address to whether it listens on ssl in any server block + """ + servers = self._get_raw_servers() + + addr_to_ssl = {} # type: Dict[Tuple[str, str], bool] + for filename in servers: + for server, _ in servers[filename]: + # Parse the server block to save addr info + parsed_server = _parse_server_raw(server) + for addr in parsed_server['addrs']: + addr_tuple = addr.normalized_tuple() + if addr_tuple not in addr_to_ssl: + addr_to_ssl[addr_tuple] = addr.ssl + addr_to_ssl[addr_tuple] = addr.ssl or addr_to_ssl[addr_tuple] + return addr_to_ssl + + def _get_raw_servers(self): + # pylint: disable=cell-var-from-loop + # type: () -> Dict + """Get a map of unparsed all server blocks + """ + servers = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] + for filename in self.parsed: + tree = self.parsed[filename] + servers[filename] = [] + srv = servers[filename] # workaround undefined loop var in lambdas + + # Find all the server blocks + _do_for_subarray(tree, lambda x: len(x) >= 2 and x[0] == ['server'], + lambda x, y: srv.append((x[1], y))) + + # Find 'include' statements in server blocks and append their trees + for i, (server, path) in enumerate(servers[filename]): + new_server = self._get_included_directives(server) + servers[filename][i] = (new_server, path) + return servers + + def get_vhosts(self): + # pylint: disable=cell-var-from-loop + """Gets list of all 'virtual hosts' found in Nginx configuration. + Technically this is a misnomer because Nginx does not have virtual + hosts, it has 'server blocks'. + + :returns: List of :class:`~certbot_nginx._internal.obj.VirtualHost` + objects found in configuration + :rtype: list + + """ + enabled = True # We only look at enabled vhosts for now + servers = self._get_raw_servers() + + vhosts = [] + for filename in servers: + for server, path in servers[filename]: + # Parse the server block into a VirtualHost object + + parsed_server = _parse_server_raw(server) + vhost = obj.VirtualHost(filename, + parsed_server['addrs'], + parsed_server['ssl'], + enabled, + parsed_server['names'], + server, + path) + vhosts.append(vhost) + + self._update_vhosts_addrs_ssl(vhosts) + + return vhosts + + def _update_vhosts_addrs_ssl(self, vhosts): + """Update a list of raw parsed vhosts to include global address sslishness + """ + addr_to_ssl = self._build_addr_to_ssl() + for vhost in vhosts: + for addr in vhost.addrs: + addr.ssl = addr_to_ssl[addr.normalized_tuple()] + if addr.ssl: + vhost.ssl = True + + def _get_included_directives(self, block): + """Returns array with the "include" directives expanded out by + concatenating the contents of the included file to the block. + + :param list block: + :rtype: list + + """ + result = copy.deepcopy(block) # Copy the list to keep self.parsed idempotent + for directive in block: + if _is_include_directive(directive): + included_files = glob.glob( + self.abs_path(directive[1])) + for incl in included_files: + try: + result.extend(self.parsed[incl]) + except KeyError: + pass + return result + + def _parse_files(self, filepath, override=False): + """Parse files from a glob + + :param str filepath: Nginx config file path + :param bool override: Whether to parse a file that has been parsed + :returns: list of parsed tree structures + :rtype: list + + """ + files = glob.glob(filepath) # nginx on unix calls glob(3) for this + # XXX Windows nginx uses FindFirstFile, and + # should have a narrower call here + trees = [] + for item in files: + if item in self.parsed and not override: + continue + try: + with open(item) as _file: + parsed = nginxparser.load(_file) + self.parsed[item] = parsed + trees.append(parsed) + except IOError: + logger.warning("Could not open file: %s", item) + except pyparsing.ParseException as err: + logger.debug("Could not parse file: %s due to %s", item, err) + return trees + + def _find_config_root(self): + """Return the Nginx Configuration Root file.""" + location = ['nginx.conf'] + + for name in location: + if os.path.isfile(os.path.join(self.root, name)): + return os.path.join(self.root, name) + + raise errors.NoInstallationError( + "Could not find Nginx root configuration file (nginx.conf)") + + def filedump(self, ext='tmp', lazy=True): + """Dumps parsed configurations into files. + + :param str ext: The file extension to use for the dumped files. If + empty, this overrides the existing conf files. + :param bool lazy: Only write files that have been modified + + """ + # Best-effort atomicity is enforced above us by reverter.py + for filename in self.parsed: + tree = self.parsed[filename] + if ext: + filename = filename + os.path.extsep + ext + try: + if lazy and not tree.is_dirty(): + continue + out = nginxparser.dumps(tree) + logger.debug('Writing nginx conf tree to %s:\n%s', filename, out) + with open(filename, 'w') as _file: + _file.write(out) + + except IOError: + logger.error("Could not open file for writing: %s", filename) + + def parse_server(self, server): + """Parses a list of server directives, accounting for global address sslishness. + + :param list server: list of directives in a server block + :rtype: dict + """ + addr_to_ssl = self._build_addr_to_ssl() + parsed_server = _parse_server_raw(server) + _apply_global_addr_ssl(addr_to_ssl, parsed_server) + return parsed_server + + def has_ssl_on_directive(self, vhost): + """Does vhost have ssl on for all ports? + + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost in question + + :returns: True if 'ssl on' directive is included + :rtype: bool + + """ + server = vhost.raw + for directive in server: + if not directive: + continue + elif _is_ssl_on_directive(directive): + return True + + return False + + def add_server_directives(self, vhost, directives, insert_at_top=False): + """Add directives to the server block identified by vhost. + + This method modifies vhost to be fully consistent with the new directives. + + ..note :: It's an error to try and add a nonrepeatable directive that already + exists in the config block with a conflicting value. + + ..todo :: Doesn't match server blocks whose server_name directives are + split across multiple conf files. + + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost + whose information we use to match on + :param list directives: The directives to add + :param bool insert_at_top: True if the directives need to be inserted at the top + of the server block instead of the bottom + + """ + self._modify_server_directives(vhost, + functools.partial(_add_directives, directives, insert_at_top)) + + def update_or_add_server_directives(self, vhost, directives, insert_at_top=False): + """Add or replace directives in the server block identified by vhost. + + This method modifies vhost to be fully consistent with the new directives. + + ..note :: When a directive with the same name already exists in the + config block, the first instance will be replaced. Otherwise, the directive + will be appended/prepended to the config block as in add_server_directives. + + ..todo :: Doesn't match server blocks whose server_name directives are + split across multiple conf files. + + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost + whose information we use to match on + :param list directives: The directives to add + :param bool insert_at_top: True if the directives need to be inserted at the top + of the server block instead of the bottom + + """ + self._modify_server_directives(vhost, + functools.partial(_update_or_add_directives, directives, insert_at_top)) + + def remove_server_directives(self, vhost, directive_name, match_func=None): + """Remove all directives of type directive_name. + + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost + to remove directives from + :param string directive_name: The directive type to remove + :param callable match_func: Function of the directive that returns true for directives + to be deleted. + """ + self._modify_server_directives(vhost, + functools.partial(_remove_directives, directive_name, match_func)) + + def _update_vhost_based_on_new_directives(self, vhost, directives_list): + new_server = self._get_included_directives(directives_list) + parsed_server = self.parse_server(new_server) + vhost.addrs = parsed_server['addrs'] + vhost.ssl = parsed_server['ssl'] + vhost.names = parsed_server['names'] + vhost.raw = new_server + + def _modify_server_directives(self, vhost, block_func): + filename = vhost.filep + try: + result = self.parsed[filename] + for index in vhost.path: + result = result[index] + if not isinstance(result, list) or len(result) != 2: + raise errors.MisconfigurationError("Not a server block.") + result = result[1] + block_func(result) + + self._update_vhost_based_on_new_directives(vhost, result) + except errors.MisconfigurationError as err: + raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err))) + + def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False, + only_directives=None): + """Duplicate the vhost in the configuration files. + + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost_template: The vhost + whose information we copy + :param bool remove_singleton_listen_params: If we should remove parameters + from listen directives in the block that can only be used once per address + :param list only_directives: If it exists, only duplicate the named directives. Only + looks at first level of depth; does not expand includes. + + :returns: A vhost object for the newly created vhost + :rtype: :class:`~certbot_nginx._internal.obj.VirtualHost` + """ + # TODO: https://github.com/certbot/certbot/issues/5185 + # put it in the same file as the template, at the same level + new_vhost = copy.deepcopy(vhost_template) + + enclosing_block = self.parsed[vhost_template.filep] + for index in vhost_template.path[:-1]: + enclosing_block = enclosing_block[index] + raw_in_parsed = copy.deepcopy(enclosing_block[vhost_template.path[-1]]) + + if only_directives is not None: + new_directives = nginxparser.UnspacedList([]) + for directive in raw_in_parsed[1]: + if directive and directive[0] in only_directives: + new_directives.append(directive) + raw_in_parsed[1] = new_directives + + self._update_vhost_based_on_new_directives(new_vhost, new_directives) + + enclosing_block.append(raw_in_parsed) + new_vhost.path[-1] = len(enclosing_block) - 1 + if remove_singleton_listen_params: + for addr in new_vhost.addrs: + addr.default = False + addr.ipv6only = False + for directive in enclosing_block[new_vhost.path[-1]][1]: + if directive and directive[0] == 'listen': + # Exclude one-time use parameters which will cause an error if repeated. + # https://nginx.org/en/docs/http/ngx_http_core_module.html#listen + exclude = set(('default_server', 'default', 'setfib', 'fastopen', 'backlog', + 'rcvbuf', 'sndbuf', 'accept_filter', 'deferred', 'bind', + 'ipv6only', 'reuseport', 'so_keepalive')) + + for param in exclude: + # See: github.com/certbot/certbot/pull/6223#pullrequestreview-143019225 + keys = [x.split('=')[0] for x in directive] + if param in keys: + del directive[keys.index(param)] + return new_vhost + + +def _parse_ssl_options(ssl_options): + if ssl_options is not None: + try: + with open(ssl_options) as _file: + return nginxparser.load(_file) + except IOError: + 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 [] + +def _do_for_subarray(entry, condition, func, path=None): + """Executes a function for a subarray of a nested array if it matches + the given condition. + + :param list entry: The list to iterate over + :param function condition: Returns true iff func should be executed on item + :param function func: The function to call for each matching item + + """ + if path is None: + path = [] + if isinstance(entry, list): + if condition(entry): + func(entry, path) + else: + for index, item in enumerate(entry): + _do_for_subarray(item, condition, func, path + [index]) + + +def get_best_match(target_name, names): + """Finds the best match for target_name out of names using the Nginx + name-matching rules (exact > longest wildcard starting with * > + longest wildcard ending with * > regex). + + :param str target_name: The name to match + :param set names: The candidate server names + :returns: Tuple of (type of match, the name that matched) + :rtype: tuple + + """ + exact = [] + wildcard_start = [] + wildcard_end = [] + regex = [] + + for name in names: + if _exact_match(target_name, name): + exact.append(name) + elif _wildcard_match(target_name, name, True): + wildcard_start.append(name) + elif _wildcard_match(target_name, name, False): + wildcard_end.append(name) + elif _regex_match(target_name, name): + regex.append(name) + + if exact: + # There can be more than one exact match; e.g. eff.org, .eff.org + match = min(exact, key=len) + return ('exact', match) + if wildcard_start: + # Return the longest wildcard + match = max(wildcard_start, key=len) + return ('wildcard_start', match) + if wildcard_end: + # Return the longest wildcard + match = max(wildcard_end, key=len) + return ('wildcard_end', match) + if regex: + # Just return the first one for now + match = regex[0] + return ('regex', match) + + return (None, None) + + +def _exact_match(target_name, name): + return target_name == name or '.' + target_name == name + + +def _wildcard_match(target_name, name, start): + # Degenerate case + if name == '*': + return True + + parts = target_name.split('.') + match_parts = name.split('.') + + # If the domain ends in a wildcard, do the match procedure in reverse + if not start: + parts.reverse() + match_parts.reverse() + + # The first part must be a wildcard or blank, e.g. '.eff.org' + first = match_parts.pop(0) + if first != '*' and first != '': + return False + + target_name = '.'.join(parts) + name = '.'.join(match_parts) + + # Ex: www.eff.org matches *.eff.org, eff.org does not match *.eff.org + return target_name.endswith('.' + name) + + +def _regex_match(target_name, name): + # Must start with a tilde + if len(name) < 2 or name[0] != '~': + return False + + # After tilde is a perl-compatible regex + try: + regex = re.compile(name[1:]) + return re.match(regex, target_name) + except re.error: # pragma: no cover + # perl-compatible regexes are sometimes not recognized by python + return False + + +def _is_include_directive(entry): + """Checks if an nginx parsed entry is an 'include' directive. + + :param list entry: the parsed entry + :returns: Whether it's an 'include' directive + :rtype: bool + + """ + return (isinstance(entry, list) and + len(entry) == 2 and entry[0] == 'include' and + isinstance(entry[1], six.string_types)) + +def _is_ssl_on_directive(entry): + """Checks if an nginx parsed entry is an 'ssl on' directive. + + :param list entry: the parsed entry + :returns: Whether it's an 'ssl on' directive + :rtype: bool + + """ + return (isinstance(entry, list) and + len(entry) == 2 and entry[0] == 'ssl' and + entry[1] == 'on') + +def _add_directives(directives, insert_at_top, block): + """Adds directives to a config block.""" + for directive in directives: + _add_directive(block, directive, insert_at_top) + if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! + block.append(nginxparser.UnspacedList('\n')) + +def _update_or_add_directives(directives, insert_at_top, block): + """Adds or replaces directives in a config block.""" + for directive in directives: + _update_or_add_directive(block, directive, insert_at_top) + if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! + block.append(nginxparser.UnspacedList('\n')) + + +INCLUDE = 'include' +REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'rewrite', 'add_header']) +COMMENT = ' managed by Certbot' +COMMENT_BLOCK = [' ', '#', COMMENT] + +def comment_directive(block, location): + """Add a ``#managed by Certbot`` comment to the end of the line at location. + + :param list block: The block containing the directive to be commented + :param int location: The location within ``block`` of the directive to be commented + """ + next_entry = block[location + 1] if location + 1 < len(block) else None + if isinstance(next_entry, list) and next_entry: + if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: + return + elif isinstance(next_entry, nginxparser.UnspacedList): + next_entry = next_entry.spaced[0] + else: + next_entry = next_entry[0] + + block.insert(location + 1, COMMENT_BLOCK[:]) + if next_entry is not None and "\n" not in next_entry: + block.insert(location + 2, '\n') + +def _comment_out_directive(block, location, include_location): + """Comment out the line at location, with a note of explanation.""" + comment_message = ' duplicated in {0}'.format(include_location) + # add the end comment + # create a dumpable object out of block[location] (so it includes the ;) + directive = block[location] + new_dir_block = nginxparser.UnspacedList([]) # just a wrapper + new_dir_block.append(directive) + dumped = nginxparser.dumps(new_dir_block) + commented = dumped + ' #' + comment_message # add the comment directly to the one-line string + new_dir = nginxparser.loads(commented) # reload into UnspacedList + + # add the beginning comment + insert_location = 0 + if new_dir[0].spaced[0] != new_dir[0][0]: # if there's whitespace at the beginning + insert_location = 1 + new_dir[0].spaced.insert(insert_location, "# ") # comment out the line + new_dir[0].spaced.append(";") # directly add in the ;, because now dumping won't work properly + dumped = nginxparser.dumps(new_dir) + new_dir = nginxparser.loads(dumped) # reload into an UnspacedList + + block[location] = new_dir[0] # set the now-single-line-comment directive back in place + +def _find_location(block, directive_name, match_func=None): + """Finds the index of the first instance of directive_name in block. + If no line exists, use None.""" + return next((index for index, line in enumerate(block) \ + if line and line[0] == directive_name and (match_func is None or match_func(line))), None) + +def _is_whitespace_or_comment(directive): + """Is this directive either a whitespace or comment directive?""" + return len(directive) == 0 or directive[0] == '#' + +def _add_directive(block, directive, insert_at_top): + if not isinstance(directive, nginxparser.UnspacedList): + directive = nginxparser.UnspacedList(directive) + if _is_whitespace_or_comment(directive): + # whitespace or comment + block.append(directive) + return + + location = _find_location(block, directive[0]) + + # Append or prepend directive. Fail if the name is not a repeatable directive name, + # and there is already a copy of that directive with a different value + # in the config file. + + # handle flat include files + + directive_name = directive[0] + def can_append(loc, dir_name): + """ Can we append this directive to the block? """ + return loc is None or (isinstance(dir_name, six.string_types) + and dir_name in REPEATABLE_DIRECTIVES) + + err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".' + + # Give a better error message about the specific directive than Nginx's "fail to restart" + if directive_name == INCLUDE: + # in theory, we might want to do this recursively, but in practice, that's really not + # necessary because we know what file we're talking about (and if we don't recurse, we + # just give a worse error message) + included_directives = _parse_ssl_options(directive[1]) + + for included_directive in included_directives: + included_dir_loc = _find_location(block, included_directive[0]) + included_dir_name = included_directive[0] + if not _is_whitespace_or_comment(included_directive) \ + and not can_append(included_dir_loc, included_dir_name): + if block[included_dir_loc] != included_directive: + raise errors.MisconfigurationError(err_fmt.format(included_directive, + block[included_dir_loc])) + else: + _comment_out_directive(block, included_dir_loc, directive[1]) + + if can_append(location, directive_name): + if insert_at_top: + # Add a newline so the comment doesn't comment + # out existing directives + block.insert(0, nginxparser.UnspacedList('\n')) + block.insert(0, directive) + comment_directive(block, 0) + else: + block.append(directive) + comment_directive(block, len(block) - 1) + elif block[location] != directive: + raise errors.MisconfigurationError(err_fmt.format(directive, block[location])) + +def _update_directive(block, directive, location): + block[location] = directive + comment_directive(block, location) + +def _update_or_add_directive(block, directive, insert_at_top): + if not isinstance(directive, nginxparser.UnspacedList): + directive = nginxparser.UnspacedList(directive) + if _is_whitespace_or_comment(directive): + # whitespace or comment + block.append(directive) + return + + location = _find_location(block, directive[0]) + + # we can update directive + if location is not None: + _update_directive(block, directive, location) + return + + _add_directive(block, directive, insert_at_top) + +def _is_certbot_comment(directive): + return '#' in directive and COMMENT in directive + +def _remove_directives(directive_name, match_func, block): + """Removes directives of name directive_name from a config block if match_func matches. + """ + while True: + location = _find_location(block, directive_name, match_func=match_func) + if location is None: + return + # if the directive was made by us, remove the comment following + if location + 1 < len(block) and _is_certbot_comment(block[location + 1]): + del block[location + 1] + del block[location] + +def _apply_global_addr_ssl(addr_to_ssl, parsed_server): + """Apply global sslishness information to the parsed server block + """ + for addr in parsed_server['addrs']: + addr.ssl = addr_to_ssl[addr.normalized_tuple()] + if addr.ssl: + parsed_server['ssl'] = True + +def _parse_server_raw(server): + """Parses a list of server directives. + + :param list server: list of directives in a server block + :rtype: dict + + """ + addrs = set() # type: Set[obj.Addr] + ssl = False # type: bool + names = set() # type: Set[str] + + apply_ssl_to_all_addrs = False + + for directive in server: + if not directive: + continue + if directive[0] == 'listen': + addr = obj.Addr.fromstring(" ".join(directive[1:])) + if addr: + addrs.add(addr) + if addr.ssl: + ssl = True + elif directive[0] == 'server_name': + names.update(x.strip('"\'') for x in directive[1:]) + elif _is_ssl_on_directive(directive): + ssl = True + apply_ssl_to_all_addrs = True + + if apply_ssl_to_all_addrs: + for addr in addrs: + addr.ssl = True + + return { + 'addrs': addrs, + 'ssl': ssl, + 'names': names + } diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py new file mode 100644 index 000000000..71e8c6088 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -0,0 +1,396 @@ +""" This file contains parsing routines and object classes to help derive meaning from +raw lists of tokens from pyparsing. """ + +import abc +import logging +import six + +from certbot import errors + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +logger = logging.getLogger(__name__) +COMMENT = " managed by Certbot" +COMMENT_BLOCK = ["#", COMMENT] + + +class Parsable(object): + """ Abstract base class for "Parsable" objects whose underlying representation + is a tree of lists. + + :param .Parsable parent: This object's parsed parent in the tree + """ + + __metaclass__ = abc.ABCMeta + + def __init__(self, parent=None): + self._data = [] # type: List[object] + self._tabs = None + self.parent = parent + + @classmethod + def parsing_hooks(cls): + """Returns object types that this class should be able to `parse` recusrively. + The order of the objects indicates the order in which the parser should + try to parse each subitem. + :returns: A list of Parsable classes. + :rtype list: + """ + return (Block, Sentence, Statements) + + @staticmethod + @abc.abstractmethod + def should_parse(lists): + """ Returns whether the contents of `lists` can be parsed into this object. + + :returns: Whether `lists` can be parsed as this object. + :rtype bool: + """ + raise NotImplementedError() + + @abc.abstractmethod + def parse(self, raw_list, add_spaces=False): + """ Loads information into this object from underlying raw_list structure. + Each Parsable object might make different assumptions about the structure of + raw_list. + + :param list raw_list: A list or sublist of tokens from pyparsing, containing whitespace + as separate tokens. + :param bool add_spaces: If set, the method can and should manipulate and insert spacing + between non-whitespace tokens and lists to delimit them. + :raises .errors.MisconfigurationError: when the assumptions about the structure of + raw_list are not met. + """ + raise NotImplementedError() + + @abc.abstractmethod + def iterate(self, expanded=False, match=None): + """ Iterates across this object. If this object is a leaf object, only yields + itself. If it contains references other parsing objects, and `expanded` is set, + this function should first yield itself, then recursively iterate across all of them. + :param bool expanded: Whether to recursively iterate on possible children. + :param callable match: If provided, an object is only iterated if this callable + returns True when called on that object. + + :returns: Iterator over desired objects. + """ + raise NotImplementedError() + + @abc.abstractmethod + def get_tabs(self): + """ Guess at the tabbing style of this parsed object, based on whitespace. + + If this object is a leaf, it deducts the tabbing based on its own contents. + Other objects may guess by calling `get_tabs` recursively on child objects. + + :returns: Guess at tabbing for this object. Should only return whitespace strings + that does not contain newlines. + :rtype str: + """ + raise NotImplementedError() + + @abc.abstractmethod + def set_tabs(self, tabs=" "): + """This tries to set and alter the tabbing of the current object to a desired + whitespace string. Primarily meant for objects that were constructed, so they + can conform to surrounding whitespace. + + :param str tabs: A whitespace string (not containing newlines). + """ + raise NotImplementedError() + + def dump(self, include_spaces=False): + """ Dumps back to pyparsing-like list tree. The opposite of `parse`. + + Note: if this object has not been modified, `dump` with `include_spaces=True` + should always return the original input of `parse`. + + :param bool include_spaces: If set to False, magically hides whitespace tokens from + dumped output. + + :returns: Pyparsing-like list tree. + :rtype list: + """ + return [elem.dump(include_spaces) for elem in self._data] + + +class Statements(Parsable): + """ A group or list of "Statements". A Statement is either a Block or a Sentence. + + The underlying representation is simply a list of these Statement objects, with + an extra `_trailing_whitespace` string to keep track of the whitespace that does not + precede any more statements. + """ + def __init__(self, parent=None): + super(Statements, self).__init__(parent) + self._trailing_whitespace = None + + # ======== Begin overridden functions + + @staticmethod + def should_parse(lists): + return isinstance(lists, list) + + def set_tabs(self, tabs=" "): + """ Sets the tabbing for this set of statements. Does this by calling `set_tabs` + on each of the child statements. + + Then, if a parent is present, sets trailing whitespace to parent tabbing. This + is so that the trailing } of any Block that contains Statements lines up + with parent tabbing. + """ + for statement in self._data: + statement.set_tabs(tabs) + if self.parent is not None: + self._trailing_whitespace = "\n" + self.parent.get_tabs() + + def parse(self, raw_list, add_spaces=False): + """ Parses a list of statements. + Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`, + with an optional whitespace string at the last index of `raw_list`. + """ + if not isinstance(raw_list, list): + raise errors.MisconfigurationError("Statements parsing expects a list!") + # If there's a trailing whitespace in the list of statements, keep track of it. + if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace(): + self._trailing_whitespace = raw_list[-1] + raw_list = raw_list[:-1] + self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list] + + def get_tabs(self): + """ Takes a guess at the tabbing of all contained Statements by retrieving the + tabbing of the first Statement.""" + if self._data: + return self._data[0].get_tabs() + return "" + + def dump(self, include_spaces=False): + """ Dumps this object by first dumping each statement, then appending its + trailing whitespace (if `include_spaces` is set) """ + data = super(Statements, self).dump(include_spaces) + if include_spaces and self._trailing_whitespace is not None: + return data + [self._trailing_whitespace] + return data + + def iterate(self, expanded=False, match=None): + """ Combines each statement's iterator. """ + for elem in self._data: + for sub_elem in elem.iterate(expanded, match): + yield sub_elem + + # ======== End overridden functions + + +def _space_list(list_): + """ Inserts whitespace between adjacent non-whitespace tokens. """ + spaced_statement = [] # type: List[str] + for i in reversed(six.moves.xrange(len(list_))): + spaced_statement.insert(0, list_[i]) + if i > 0 and not list_[i].isspace() and not list_[i-1].isspace(): + spaced_statement.insert(0, " ") + return spaced_statement + + +class Sentence(Parsable): + """ A list of words. Non-whitespace words are typically separated with whitespace tokens. """ + + # ======== Begin overridden functions + + @staticmethod + def should_parse(lists): + """ Returns True if `lists` can be parseable as a `Sentence`-- that is, + every element is a string type. + + :param list lists: The raw unparsed list to check. + + :returns: whether this lists is parseable by `Sentence`. + """ + return isinstance(lists, list) and len(lists) > 0 and \ + all([isinstance(elem, six.string_types) for elem in lists]) + + def parse(self, raw_list, add_spaces=False): + """ Parses a list of string types into this object. + If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens.""" + if add_spaces: + raw_list = _space_list(raw_list) + if not isinstance(raw_list, list) or \ + any([not isinstance(elem, six.string_types) for elem in raw_list]): + raise errors.MisconfigurationError("Sentence parsing expects a list of string types.") + self._data = raw_list + + def iterate(self, expanded=False, match=None): + """ Simply yields itself. """ + if match is None or match(self): + yield self + + def set_tabs(self, tabs=" "): + """ Sets the tabbing on this sentence. Inserts a newline and `tabs` at the + beginning of `self._data`. """ + if self._data[0].isspace(): + return + self._data.insert(0, "\n" + tabs) + + def dump(self, include_spaces=False): + """ Dumps this sentence. If include_spaces is set, includes whitespace tokens.""" + if not include_spaces: + return self.words + return self._data + + def get_tabs(self): + """ Guesses at the tabbing of this sentence. If the first element is whitespace, + returns the whitespace after the rightmost newline in the string. """ + first = self._data[0] + if not first.isspace(): + return "" + rindex = first.rfind("\n") + return first[rindex+1:] + + # ======== End overridden functions + + @property + def words(self): + """ Iterates over words, but without spaces. Like Unspaced List. """ + return [word.strip("\"\'") for word in self._data if not word.isspace()] + + def __getitem__(self, index): + return self.words[index] + + def __contains__(self, word): + return word in self.words + + +class Block(Parsable): + """ Any sort of bloc, denoted by a block name and curly braces, like so: + The parsed block: + block name { + content 1; + content 2; + } + might be represented with the list [names, contents], where + names = ["block", " ", "name", " "] + contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"] + """ + def __init__(self, parent=None): + super(Block, self).__init__(parent) + self.names = None # type: Sentence + self.contents = None # type: Block + + @staticmethod + def should_parse(lists): + """ Returns True if `lists` can be parseable as a `Block`-- that is, + it's got a length of 2, the first element is a `Sentence` and the second can be + a `Statements`. + + :param list lists: The raw unparsed list to check. + + :returns: whether this lists is parseable by `Block`. """ + return isinstance(lists, list) and len(lists) == 2 and \ + Sentence.should_parse(lists[0]) and isinstance(lists[1], list) + + def set_tabs(self, tabs=" "): + """ Sets tabs by setting equivalent tabbing on names, then adding tabbing + to contents.""" + self.names.set_tabs(tabs) + self.contents.set_tabs(tabs + " ") + + def iterate(self, expanded=False, match=None): + """ Iterator over self, and if expanded is set, over its contents. """ + if match is None or match(self): + yield self + if expanded: + for elem in self.contents.iterate(expanded, match): + yield elem + + def parse(self, raw_list, add_spaces=False): + """ Parses a list that resembles a block. + + The assumptions that this routine makes are: + 1. the first element of `raw_list` is a valid Sentence. + 2. the second element of `raw_list` is a valid Statement. + If add_spaces is set, we call it recursively on `names` and `contents`, and + add an extra trailing space to `names` (to separate the block's opening bracket + and the block name). + """ + if not Block.should_parse(raw_list): + raise errors.MisconfigurationError("Block parsing expects a list of length 2. " + "First element should be a list of string types (the bloc names), " + "and second should be another list of statements (the bloc content).") + self.names = Sentence(self) + if add_spaces: + raw_list[0].append(" ") + self.names.parse(raw_list[0], add_spaces) + self.contents = Statements(self) + self.contents.parse(raw_list[1], add_spaces) + self._data = [self.names, self.contents] + + def get_tabs(self): + """ Guesses tabbing by retrieving tabbing guess of self.names. """ + return self.names.get_tabs() + +def _is_comment(parsed_obj): + """ Checks whether parsed_obj is a comment. + + :param .Parsable parsed_obj: + + :returns: whether parsed_obj represents a comment sentence. + :rtype bool: + """ + if not isinstance(parsed_obj, Sentence): + return False + return parsed_obj.words[0] == "#" + +def _is_certbot_comment(parsed_obj): + """ Checks whether parsed_obj is a "managed by Certbot" comment. + + :param .Parsable parsed_obj: + + :returns: whether parsed_obj is a "managed by Certbot" comment. + :rtype bool: + """ + if not _is_comment(parsed_obj): + return False + if len(parsed_obj.words) != len(COMMENT_BLOCK): + return False + for i, word in enumerate(parsed_obj.words): + if word != COMMENT_BLOCK[i]: + return False + return True + +def _certbot_comment(parent, preceding_spaces=4): + """ A "Managed by Certbot" comment. + :param int preceding_spaces: Number of spaces between the end of the previous + statement and the comment. + :returns: Sentence containing the comment. + :rtype: .Sentence + """ + result = Sentence(parent) + result.parse([" " * preceding_spaces] + COMMENT_BLOCK) + return result + +def _choose_parser(parent, list_): + """ Choose a parser from type(parent).parsing_hooks, depending on whichever hook + returns True first. """ + hooks = Parsable.parsing_hooks() + if parent: + hooks = type(parent).parsing_hooks() + for type_ in hooks: + if type_.should_parse(list_): + return type_(parent) + raise errors.MisconfigurationError( + "None of the parsing hooks succeeded, so we don't know how to parse this set of lists.") + +def parse_raw(lists_, parent=None, add_spaces=False): + """ Primary parsing factory function. + + :param list lists_: raw lists from pyparsing to parse. + :param .Parent parent: The parent containing this object. + :param bool add_spaces: Whether to pass add_spaces to the parser. + + :returns .Parsable: The parsed object. + + :raises errors.MisconfigurationError: If no parsing hook passes, and we can't + determine which type to parse the raw lists into. + """ + parser = _choose_parser(parent, lists_) + parser.parse(lists_, add_spaces) + return parser diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf new file mode 100644 index 000000000..731e38919 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf new file mode 100644 index 000000000..33771a189 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf new file mode 100644 index 000000000..91197d2c8 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf new file mode 100644 index 000000000..98b1c4ab9 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py deleted file mode 100644 index 6ec53430c..000000000 --- a/certbot-nginx/certbot_nginx/configurator.py +++ /dev/null @@ -1,1202 +0,0 @@ -"""Nginx Configuration""" -# https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error -import logging -import re -import socket -import subprocess -import tempfile -import time - -import pkg_resources - -import OpenSSL -import zope.interface - -from acme import challenges -from acme import crypto_util as acme_crypto_util -from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module - -from certbot import crypto_util -from certbot import errors -from certbot import interfaces -from certbot import util -from certbot.compat import os -from certbot.plugins import common - -from certbot_nginx import constants -from certbot_nginx import display_ops -from certbot_nginx import http_01 -from certbot_nginx import nginxparser -from certbot_nginx import obj # pylint: disable=unused-import -from certbot_nginx import parser - -NAME_RANK = 0 -START_WILDCARD_RANK = 1 -END_WILDCARD_RANK = 2 -REGEX_RANK = 3 -NO_SSL_MODIFIER = 4 - - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) -@zope.interface.provider(interfaces.IPluginFactory) -class NginxConfigurator(common.Installer): - """Nginx configurator. - - .. todo:: Add proper support for comments in the config. Currently, - config files modified by the configurator will lose all their comments. - - :ivar config: Configuration. - :type config: :class:`~certbot.interfaces.IConfig` - - :ivar parser: Handles low level parsing - :type parser: :class:`~certbot_nginx.parser` - - :ivar str save_notes: Human-readable config change notes - - :ivar reverter: saves and reverts checkpoints - :type reverter: :class:`certbot.reverter.Reverter` - - :ivar tup version: version of Nginx - - """ - - description = "Nginx Web Server plugin" - - DEFAULT_LISTEN_PORT = '80' - - # SSL directives that Certbot can add when installing a new certificate. - SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam'] - - @classmethod - def add_parser_arguments(cls, add): - default_server_root = _determine_default_server_root() - add("server-root", default=constants.CLI_DEFAULTS["server_root"], - help="Nginx server root directory. (default: %s)" % default_server_root) - add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the " - "'nginx' binary, used for 'configtest' and retrieving nginx " - "version number.") - - @property - def nginx_conf(self): - """Nginx config file path.""" - return os.path.join(self.conf("server_root"), "nginx.conf") - - def __init__(self, *args, **kwargs): - """Initialize an Nginx Configurator. - - :param tup version: version of Nginx as a tuple (1, 4, 7) - (used mostly for unittesting) - - :param tup openssl_version: version of OpenSSL linked to Nginx as a tuple (1, 4, 7) - (used mostly for unittesting) - - """ - version = kwargs.pop("version", None) - openssl_version = kwargs.pop("openssl_version", None) - super(NginxConfigurator, self).__init__(*args, **kwargs) - - # Files to save - self.save_notes = "" - - # For creating new vhosts if no names match - self.new_vhost = None - - # List of vhosts configured per wildcard domain on this run. - # used by deploy_cert() and enhance() - self._wildcard_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] - self._wildcard_redirect_vhosts = {} # type: Dict[str, List[obj.VirtualHost]] - - # Add number of outstanding challenges - self._chall_out = 0 - - # These will be set in the prepare function - self.parser = None - self.version = version - self.openssl_version = openssl_version - self._enhance_func = {"redirect": self._enable_redirect, - "ensure-http-header": self._set_http_header, - "staple-ocsp": self._enable_ocsp_stapling} - - self.reverter.recovery_routine() - - @property - def mod_ssl_conf_src(self): - """Full absolute path to SSL configuration file source.""" - - # Why all this complexity? Well, we want to support Mozilla's intermediate - # recommendations. But TLS1.3 is only supported by newer versions of Nginx. - # And as for session tickets, our ideal is to turn them off across the board. - # But! Turning them off at all is only supported with new enough versions of - # Nginx. And older versions of OpenSSL have a bug that leads to browser errors - # given certain configurations. While we'd prefer to have forward secrecy, we'd - # rather fail open than error out. Unfortunately, Nginx can be compiled against - # many versions of OpenSSL. So we have to check both for the two different features, - # leading to four different combinations of options. - # For a complete history, check out https://github.com/certbot/certbot/issues/7322 - - use_tls13 = self.version >= (1, 13, 0) - session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ - LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') - - if use_tls13: - if session_tix_off: - config_filename = "options-ssl-nginx.conf" - else: - config_filename = "options-ssl-nginx-tls13-session-tix-on.conf" - else: - if session_tix_off: - config_filename = "options-ssl-nginx-tls12-only.conf" - else: - config_filename = "options-ssl-nginx-old.conf" - - return pkg_resources.resource_filename( - "certbot_nginx", os.path.join("tls_configs", config_filename)) - - @property - def mod_ssl_conf(self): - """Full absolute path to SSL configuration file.""" - return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) - - @property - def updated_mod_ssl_conf_digest(self): - """Full absolute path to digest of updated SSL configuration file.""" - return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST) - - def install_ssl_options_conf(self, options_ssl, options_ssl_digest): - """Copy Certbot's SSL options file into the system's config dir if required.""" - return common.install_version_controlled_file(options_ssl, options_ssl_digest, - self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES) - - # This is called in determine_authenticator and determine_installer - def prepare(self): - """Prepare the authenticator/installer. - - :raises .errors.NoInstallationError: If Nginx ctl cannot be found - :raises .errors.MisconfigurationError: If Nginx is misconfigured - """ - # Verify Nginx is installed - if not util.exe_exists(self.conf('ctl')): - raise errors.NoInstallationError( - "Could not find a usable 'nginx' binary. Ensure nginx exists, " - "the binary is executable, and your PATH is set correctly.") - - # Make sure configuration is valid - self.config_test() - - self.parser = parser.NginxParser(self.conf('server-root')) - - # Set Version - if self.version is None: - self.version = self.get_version() - - if self.openssl_version is None: - self.openssl_version = self._get_openssl_version() - - self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) - - self.install_ssl_dhparams() - - # Prevent two Nginx plugins from modifying a config at once - try: - util.lock_dir_until_exit(self.conf('server-root')) - except (OSError, errors.LockError): - logger.debug('Encountered error:', exc_info=True) - raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root'))) - - # Entry point in main.py for installing cert - def deploy_cert(self, domain, cert_path, key_path, - chain_path=None, fullchain_path=None): - """Deploys certificate to specified virtual host. - - .. note:: Aborts if the vhost is missing ssl_certificate or - ssl_certificate_key. - - .. note:: This doesn't save the config files! - - :raises errors.PluginError: When unable to deploy certificate due to - a lack of directives or configuration - - """ - if not fullchain_path: - raise errors.PluginError( - "The nginx plugin currently requires --fullchain-path to " - "install a cert.") - - vhosts = self.choose_vhosts(domain, create_if_no_match=True) - for vhost in vhosts: - self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path) - - def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): # pylint: disable=unused-argument - """ - Helper function for deploy_cert() that handles the actual deployment - this exists because we might want to do multiple deployments per - domain originally passed for deploy_cert(). This is especially true - with wildcard certificates - """ - cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path], - ['\n ', 'ssl_certificate_key', ' ', key_path]] - - self.parser.update_or_add_server_directives(vhost, - cert_directives) - logger.info("Deploying Certificate to VirtualHost %s", vhost.filep) - - self.save_notes += ("Changed vhost at %s with addresses of %s\n" % - (vhost.filep, - ", ".join(str(addr) for addr in vhost.addrs))) - self.save_notes += "\tssl_certificate %s\n" % fullchain_path - self.save_notes += "\tssl_certificate_key %s\n" % key_path - - def _choose_vhosts_wildcard(self, domain, prefer_ssl, no_ssl_filter_port=None): - """Prompts user to choose vhosts to install a wildcard certificate for""" - if prefer_ssl: - vhosts_cache = self._wildcard_vhosts - preference_test = lambda x: x.ssl - else: - vhosts_cache = self._wildcard_redirect_vhosts - preference_test = lambda x: not x.ssl - - # Caching! - if domain in vhosts_cache: - # Vhosts for a wildcard domain were already selected - return vhosts_cache[domain] - - # Get all vhosts whether or not they are covered by the wildcard domain - vhosts = self.parser.get_vhosts() - - # Go through the vhosts, making sure that we cover all the names - # present, but preferring the SSL or non-SSL vhosts - filtered_vhosts = {} - for vhost in vhosts: - # Ensure we're listening non-sslishly on no_ssl_filter_port - if no_ssl_filter_port is not None: - if not self._vhost_listening_on_port_no_ssl(vhost, no_ssl_filter_port): - continue - for name in vhost.names: - if preference_test(vhost): - # Prefer either SSL or non-SSL vhosts - filtered_vhosts[name] = vhost - elif name not in filtered_vhosts: - # Add if not in list previously - filtered_vhosts[name] = vhost - - # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) - - # Ask the user which of names to enable, expect list of names back - return_vhosts = display_ops.select_vhost_multiple(list(dialog_input)) - - for vhost in return_vhosts: - if domain not in vhosts_cache: - vhosts_cache[domain] = [] - vhosts_cache[domain].append(vhost) - - return return_vhosts - - ####################### - # Vhost parsing methods - ####################### - def _choose_vhost_single(self, target_name): - matches = self._get_ranked_matches(target_name) - vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None] - return vhosts - - def choose_vhosts(self, target_name, create_if_no_match=False): - """Chooses a virtual host based on the given domain name. - - .. note:: This makes the vhost SSL-enabled if it isn't already. Follows - Nginx's server block selection rules preferring blocks that are - already SSL. - - .. todo:: This should maybe return list if no obvious answer - is presented. - - .. todo:: The special name "$hostname" corresponds to the machine's - hostname. Currently we just ignore this. - - :param str target_name: domain name - :param bool create_if_no_match: If we should create a new vhost from default - when there is no match found. If we can't choose a default, raise a - MisconfigurationError. - - :returns: ssl vhosts associated with name - :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` - - """ - if util.is_wildcard_domain(target_name): - # Ask user which VHosts to support. - vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=True) - else: - vhosts = self._choose_vhost_single(target_name) - if not vhosts: - if create_if_no_match: - # result will not be [None] because it errors on failure - vhosts = [self._vhost_from_duplicated_default(target_name, True, - str(self.config.https_port))] - else: - # No matches. Raise a misconfiguration error. - raise errors.MisconfigurationError( - ("Cannot find a VirtualHost matching domain %s. " - "In order for Certbot to correctly perform the challenge " - "please add a corresponding server_name directive to your " - "nginx configuration for every domain on your certificate: " - "https://nginx.org/en/docs/http/server_names.html") % (target_name)) - # Note: if we are enhancing with ocsp, vhost should already be ssl. - for vhost in vhosts: - if not vhost.ssl: - self._make_server_ssl(vhost) - - return vhosts - - def ipv6_info(self, port): - """Returns tuple of booleans (ipv6_active, ipv6only_present) - ipv6_active is true if any server block listens ipv6 address in any port - - ipv6only_present is true if ipv6only=on option exists in any server - block ipv6 listen directive for the specified port. - - :param str port: Port to check ipv6only=on directive for - - :returns: Tuple containing information if IPv6 is enabled in the global - configuration, and existence of ipv6only directive for specified port - :rtype: tuple of type (bool, bool) - """ - # port should be a string, but it's easy to mess up, so let's - # make sure it is one - port = str(port) - vhosts = self.parser.get_vhosts() - ipv6_active = False - ipv6only_present = False - for vh in vhosts: - for addr in vh.addrs: - if addr.ipv6: - ipv6_active = True - if addr.ipv6only and addr.get_port() == port: - ipv6only_present = True - return (ipv6_active, ipv6only_present) - - def _vhost_from_duplicated_default(self, domain, allow_port_mismatch, port): - """if allow_port_mismatch is False, only server blocks with matching ports will be - used as a default server block template. - """ - if self.new_vhost is None: - default_vhost = self._get_default_vhost(domain, allow_port_mismatch, port) - self.new_vhost = self.parser.duplicate_vhost(default_vhost, - remove_singleton_listen_params=True) - self.new_vhost.names = set() - - self._add_server_name_to_vhost(self.new_vhost, domain) - return self.new_vhost - - def _add_server_name_to_vhost(self, vhost, domain): - vhost.names.add(domain) - name_block = [['\n ', 'server_name']] - for name in vhost.names: - name_block[0].append(' ') - name_block[0].append(name) - self.parser.update_or_add_server_directives(vhost, name_block) - - def _get_default_vhost(self, domain, allow_port_mismatch, port): - """Helper method for _vhost_from_duplicated_default; see argument documentation there""" - vhost_list = self.parser.get_vhosts() - # if one has default_server set, return that one - all_default_vhosts = [] - port_matching_vhosts = [] - for vhost in vhost_list: - for addr in vhost.addrs: - if addr.default: - all_default_vhosts.append(vhost) - if self._port_matches(port, addr.get_port()): - port_matching_vhosts.append(vhost) - break - - if len(port_matching_vhosts) == 1: - return port_matching_vhosts[0] - elif len(all_default_vhosts) == 1 and allow_port_mismatch: - return all_default_vhosts[0] - - # TODO: present a list of vhosts for user to choose from - - raise errors.MisconfigurationError("Could not automatically find a matching server" - " block for %s. Set the `server_name` directive to use the Nginx installer." % domain) - - def _get_ranked_matches(self, target_name): - """Returns a ranked list of vhosts that match target_name. - The ranking gives preference to SSL vhosts. - - :param str target_name: The name to match - :returns: list of dicts containing the vhost, the matching name, and - the numerical rank - :rtype: list - - """ - vhost_list = self.parser.get_vhosts() - return self._rank_matches_by_name_and_ssl(vhost_list, target_name) - - def _select_best_name_match(self, matches): - """Returns the best name match of a ranked list of vhosts. - - :param list matches: list of dicts containing the vhost, the matching name, - and the numerical rank - :returns: the most matching vhost - :rtype: :class:`~certbot_nginx.obj.VirtualHost` - - """ - if not matches: - return None - elif matches[0]['rank'] in [START_WILDCARD_RANK, END_WILDCARD_RANK, - START_WILDCARD_RANK + NO_SSL_MODIFIER, END_WILDCARD_RANK + NO_SSL_MODIFIER]: - # Wildcard match - need to find the longest one - rank = matches[0]['rank'] - wildcards = [x for x in matches if x['rank'] == rank] - return max(wildcards, key=lambda x: len(x['name']))['vhost'] - # Exact or regex match - return matches[0]['vhost'] - - def _rank_matches_by_name(self, vhost_list, target_name): - """Returns a ranked list of vhosts from vhost_list that match target_name. - This method should always be followed by a call to _select_best_name_match. - - :param list vhost_list: list of vhosts to filter and rank - :param str target_name: The name to match - :returns: list of dicts containing the vhost, the matching name, and - the numerical rank - :rtype: list - - """ - # Nginx chooses a matching server name for a request with precedence: - # 1. exact name match - # 2. longest wildcard name starting with * - # 3. longest wildcard name ending with * - # 4. first matching regex in order of appearance in the file - matches = [] - for vhost in vhost_list: - name_type, name = parser.get_best_match(target_name, vhost.names) - if name_type == 'exact': - matches.append({'vhost': vhost, - 'name': name, - 'rank': NAME_RANK}) - elif name_type == 'wildcard_start': - matches.append({'vhost': vhost, - 'name': name, - 'rank': START_WILDCARD_RANK}) - elif name_type == 'wildcard_end': - matches.append({'vhost': vhost, - 'name': name, - 'rank': END_WILDCARD_RANK}) - elif name_type == 'regex': - matches.append({'vhost': vhost, - 'name': name, - 'rank': REGEX_RANK}) - return sorted(matches, key=lambda x: x['rank']) - - def _rank_matches_by_name_and_ssl(self, vhost_list, target_name): - """Returns a ranked list of vhosts from vhost_list that match target_name. - The ranking gives preference to SSLishness before name match level. - - :param list vhost_list: list of vhosts to filter and rank - :param str target_name: The name to match - :returns: list of dicts containing the vhost, the matching name, and - the numerical rank - :rtype: list - - """ - matches = self._rank_matches_by_name(vhost_list, target_name) - for match in matches: - if not match['vhost'].ssl: - match['rank'] += NO_SSL_MODIFIER - return sorted(matches, key=lambda x: x['rank']) - - def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False): - """Chooses a single virtual host for redirect enhancement. - - Chooses the vhost most closely matching target_name that is - listening to port without using ssl. - - .. todo:: This should maybe return list if no obvious answer - is presented. - - .. todo:: The special name "$hostname" corresponds to the machine's - hostname. Currently we just ignore this. - - :param str target_name: domain name - :param str port: port number - :param bool create_if_no_match: If we should create a new vhost from default - when there is no match found. If we can't choose a default, raise a - MisconfigurationError. - - :returns: vhosts associated with name - :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` - - """ - if util.is_wildcard_domain(target_name): - # Ask user which VHosts to enhance. - vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=False, - no_ssl_filter_port=port) - else: - matches = self._get_redirect_ranked_matches(target_name, port) - vhosts = [x for x in [self._select_best_name_match(matches)]if x is not None] - if not vhosts and create_if_no_match: - vhosts = [self._vhost_from_duplicated_default(target_name, False, port)] - return vhosts - - def _port_matches(self, test_port, matching_port): - # test_port is a number, matching is a number or "" or None - if matching_port == "" or matching_port is None: - # if no port is specified, Nginx defaults to listening on port 80. - return test_port == self.DEFAULT_LISTEN_PORT - return test_port == matching_port - - def _vhost_listening_on_port_no_ssl(self, vhost, port): - found_matching_port = False - if not vhost.addrs: - # if there are no listen directives at all, Nginx defaults to - # listening on port 80. - found_matching_port = (port == self.DEFAULT_LISTEN_PORT) - else: - for addr in vhost.addrs: - if self._port_matches(port, addr.get_port()) and not addr.ssl: - found_matching_port = True - - if found_matching_port: - # make sure we don't have an 'ssl on' directive - return not self.parser.has_ssl_on_directive(vhost) - return False - - def _get_redirect_ranked_matches(self, target_name, port): - """Gets a ranked list of plaintextish port-listening vhosts matching target_name - - Filter all hosts for those listening on port without using ssl. - Rank by how well these match target_name. - - :param str target_name: The name to match - :param str port: port number as a string - :returns: list of dicts containing the vhost, the matching name, and - the numerical rank - :rtype: list - - """ - all_vhosts = self.parser.get_vhosts() - - def _vhost_matches(vhost, port): - return self._vhost_listening_on_port_no_ssl(vhost, port) - - matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)] - - return self._rank_matches_by_name(matching_vhosts, target_name) - - def get_all_names(self): - """Returns all names found in the Nginx Configuration. - - :returns: All ServerNames, ServerAliases, and reverse DNS entries for - virtual host addresses - :rtype: set - - """ - all_names = set() # type: Set[str] - - for vhost in self.parser.get_vhosts(): - all_names.update(vhost.names) - - for addr in vhost.addrs: - host = addr.get_addr() - if common.hostname_regex.match(host): - # If it's a hostname, add it to the names. - all_names.add(host) - elif not common.private_ips_regex.match(host): - # If it isn't a private IP, do a reverse DNS lookup - try: - if addr.ipv6: - host = addr.get_ipv6_exploded() - socket.inet_pton(socket.AF_INET6, host) - else: - socket.inet_pton(socket.AF_INET, host) - all_names.add(socket.gethostbyaddr(host)[0]) - except (socket.error, socket.herror, socket.timeout): - continue - - return util.get_filtered_names(all_names) - - def _get_snakeoil_paths(self): - """Generate invalid certs that let us create ssl directives for Nginx""" - # TODO: generate only once - tmp_dir = os.path.join(self.config.work_dir, "snakeoil") - le_key = crypto_util.init_save_key( - key_size=1024, key_dir=tmp_dir, keyname="key.pem") - key = OpenSSL.crypto.load_privatekey( - OpenSSL.crypto.FILETYPE_PEM, le_key.pem) - cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()]) - cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - cert_file, cert_path = util.unique_file( - os.path.join(tmp_dir, "cert.pem"), mode="wb") - with cert_file: - cert_file.write(cert_pem) - return cert_path, le_key.file - - def _make_server_ssl(self, vhost): - """Make a server SSL. - - Make a server SSL by adding new listen and SSL directives. - - :param vhost: The vhost to add SSL to. - :type vhost: :class:`~certbot_nginx.obj.VirtualHost` - - """ - https_port = self.config.https_port - ipv6info = self.ipv6_info(https_port) - ipv6_block = [''] - ipv4_block = [''] - - # If the vhost was implicitly listening on the default Nginx port, - # have it continue to do so. - if not vhost.addrs: - listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]] - self.parser.add_server_directives(vhost, listen_block) - - if vhost.ipv6_enabled(): - ipv6_block = ['\n ', - 'listen', - ' ', - '[::]:{0}'.format(https_port), - ' ', - 'ssl'] - if not ipv6info[1]: - # ipv6only=on is absent in global config - ipv6_block.append(' ') - ipv6_block.append('ipv6only=on') - - if vhost.ipv4_enabled(): - ipv4_block = ['\n ', - 'listen', - ' ', - '{0}'.format(https_port), - ' ', - 'ssl'] - - snakeoil_cert, snakeoil_key = self._get_snakeoil_paths() - - ssl_block = ([ - ipv6_block, - ipv4_block, - ['\n ', 'ssl_certificate', ' ', snakeoil_cert], - ['\n ', 'ssl_certificate_key', ' ', snakeoil_key], - ['\n ', 'include', ' ', self.mod_ssl_conf], - ['\n ', 'ssl_dhparam', ' ', self.ssl_dhparams], - ]) - - self.parser.add_server_directives( - vhost, ssl_block) - - ################################## - # enhancement methods (IInstaller) - ################################## - def supported_enhancements(self): # pylint: disable=no-self-use - """Returns currently supported enhancements.""" - return ['redirect', 'ensure-http-header', 'staple-ocsp'] - - def enhance(self, domain, enhancement, options=None): - """Enhance configuration. - - :param str domain: domain to enhance - :param str enhancement: enhancement type defined in - :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - :param options: options for the enhancement - See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` - documentation for appropriate parameter. - - """ - try: - return self._enhance_func[enhancement](domain, options) - except (KeyError, ValueError): - raise errors.PluginError( - "Unsupported enhancement: {0}".format(enhancement)) - except errors.PluginError: - logger.warning("Failed %s for %s", enhancement, domain) - raise - - def _has_certbot_redirect(self, vhost, domain): - test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain)) - return vhost.contains_list(test_redirect_block) - - def _set_http_header(self, domain, header_substring): - """Enables header identified by header_substring on domain. - - If the vhost is listening plaintextishly, separates out the relevant - directives into a new server block, and only add header directive to - HTTPS block. - - :param str domain: the domain to enable header for. - :param str header_substring: String to uniquely identify a header. - e.g. Strict-Transport-Security, Upgrade-Insecure-Requests - :returns: Success - :raises .errors.PluginError: If no viable HTTPS host can be created or - set with header header_substring. - """ - vhosts = self.choose_vhosts(domain) - if not vhosts: - raise errors.PluginError( - "Unable to find corresponding HTTPS host for enhancement.") - for vhost in vhosts: - if vhost.has_header(header_substring): - raise errors.PluginEnhancementAlreadyPresent( - "Existing %s header" % (header_substring)) - - # if there is no separate SSL block, break the block into two and - # choose the SSL block. - if vhost.ssl and any([not addr.ssl for addr in vhost.addrs]): - _, vhost = self._split_block(vhost) - - header_directives = [ - ['\n ', 'add_header', ' ', header_substring, ' '] + - constants.HEADER_ARGS[header_substring], - ['\n']] - self.parser.add_server_directives(vhost, header_directives) - - def _add_redirect_block(self, vhost, domain): - """Add redirect directive to vhost - """ - redirect_block = _redirect_block_for_domain(domain) - - self.parser.add_server_directives( - vhost, redirect_block, insert_at_top=True) - - def _split_block(self, vhost, only_directives=None): - """Splits this "virtual host" (i.e. this nginx server block) into - separate HTTP and HTTPS blocks. - - :param vhost: The server block to break up into two. - :param list only_directives: If this exists, only duplicate these directives - when splitting the block. - :type vhost: :class:`~certbot_nginx.obj.VirtualHost` - :returns: tuple (http_vhost, https_vhost) - :rtype: tuple of type :class:`~certbot_nginx.obj.VirtualHost` - """ - http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives) - - def _ssl_match_func(directive): - return 'ssl' in directive - - def _ssl_config_match_func(directive): - return self.mod_ssl_conf in directive - - def _no_ssl_match_func(directive): - return 'ssl' not in directive - - # remove all ssl addresses and related directives from the new block - for directive in self.SSL_DIRECTIVES: - self.parser.remove_server_directives(http_vhost, directive) - self.parser.remove_server_directives(http_vhost, 'listen', match_func=_ssl_match_func) - self.parser.remove_server_directives(http_vhost, 'include', - match_func=_ssl_config_match_func) - - # remove all non-ssl addresses from the existing block - self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func) - return http_vhost, vhost - - def _enable_redirect(self, domain, unused_options): - """Redirect all equivalent HTTP traffic to ssl_vhost. - - If the vhost is listening plaintextishly, separate out the - relevant directives into a new server block and add a rewrite directive. - - .. note:: This function saves the configuration - - :param str domain: domain to enable redirect for - :param unused_options: Not currently used - :type unused_options: Not Available - """ - - port = self.DEFAULT_LISTEN_PORT - # If there are blocks listening plaintextishly on self.DEFAULT_LISTEN_PORT, - # choose the most name-matching one. - - vhosts = self.choose_redirect_vhosts(domain, port) - - if not vhosts: - logger.info("No matching insecure server blocks listening on port %s found.", - self.DEFAULT_LISTEN_PORT) - return - - for vhost in vhosts: - self._enable_redirect_single(domain, vhost) - - def _enable_redirect_single(self, domain, vhost): - """Redirect all equivalent HTTP traffic to ssl_vhost. - - If the vhost is listening plaintextishly, separate out the - relevant directives into a new server block and add a rewrite directive. - - .. note:: This function saves the configuration - - :param str domain: domain to enable redirect for - :param `~obj.Vhost` vhost: vhost to enable redirect for - """ - if vhost.ssl: - http_vhost, _ = self._split_block(vhost, ['listen', 'server_name']) - - # Add this at the bottom to get the right order of directives - return_404_directive = [['\n ', 'return', ' ', '404']] - self.parser.add_server_directives(http_vhost, return_404_directive) - - vhost = http_vhost - - if self._has_certbot_redirect(vhost, domain): - logger.info("Traffic on port %s already redirecting to ssl in %s", - self.DEFAULT_LISTEN_PORT, vhost.filep) - else: - # Redirect plaintextish host to https - self._add_redirect_block(vhost, domain) - logger.info("Redirecting all traffic on port %s to ssl in %s", - self.DEFAULT_LISTEN_PORT, vhost.filep) - - def _enable_ocsp_stapling(self, domain, chain_path): - """Include OCSP response in TLS handshake - - :param str domain: domain to enable OCSP response for - :param chain_path: chain file path - :type chain_path: `str` or `None` - - """ - vhosts = self.choose_vhosts(domain) - for vhost in vhosts: - self._enable_ocsp_stapling_single(vhost, chain_path) - - def _enable_ocsp_stapling_single(self, vhost, chain_path): - """Include OCSP response in TLS handshake - - :param str vhost: vhost to enable OCSP response for - :param chain_path: chain file path - :type chain_path: `str` or `None` - - """ - if self.version < (1, 3, 7): - raise errors.PluginError("Version 1.3.7 or greater of nginx " - "is needed to enable OCSP stapling") - - if chain_path is None: - raise errors.PluginError( - "--chain-path is required to enable " - "Online Certificate Status Protocol (OCSP) stapling " - "on nginx >= 1.3.7.") - - stapling_directives = [ - ['\n ', 'ssl_trusted_certificate', ' ', chain_path], - ['\n ', 'ssl_stapling', ' ', 'on'], - ['\n ', 'ssl_stapling_verify', ' ', 'on'], ['\n']] - - try: - self.parser.add_server_directives(vhost, - stapling_directives) - except errors.MisconfigurationError as error: - logger.debug(str(error)) - raise errors.PluginError("An error occurred while enabling OCSP " - "stapling for {0}.".format(vhost.names)) - - self.save_notes += ("OCSP Stapling was enabled " - "on SSL Vhost: {0}.\n".format(vhost.filep)) - self.save_notes += "\tssl_trusted_certificate {0}\n".format(chain_path) - self.save_notes += "\tssl_stapling on\n" - self.save_notes += "\tssl_stapling_verify on\n" - - ###################################### - # Nginx server management (IInstaller) - ###################################### - def restart(self): - """Restarts nginx server. - - :raises .errors.MisconfigurationError: If either the reload fails. - - """ - nginx_restart(self.conf('ctl'), self.nginx_conf) - - def config_test(self): # pylint: disable=no-self-use - """Check the configuration of Nginx for errors. - - :raises .errors.MisconfigurationError: If config_test fails - - """ - try: - util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"]) - except errors.SubprocessError as err: - raise errors.MisconfigurationError(str(err)) - - def _nginx_version(self): - """Return results of nginx -V - - :returns: version text - :rtype: str - - :raises .PluginError: - Unable to run Nginx version command - """ - try: - proc = subprocess.Popen( - [self.conf('ctl'), "-c", self.nginx_conf, "-V"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - text = proc.communicate()[1] # nginx prints output to stderr - except (OSError, ValueError) as error: - logger.debug(str(error), exc_info=True) - raise errors.PluginError( - "Unable to run %s -V" % self.conf('ctl')) - return text - - def get_version(self): - """Return version of Nginx Server. - - Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)) - - :returns: version - :rtype: tuple - - :raises .PluginError: - Unable to find Nginx version or version is unsupported - - """ - text = self._nginx_version() - - version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE) - version_matches = version_regex.findall(text) - - sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE) - sni_matches = sni_regex.findall(text) - - ssl_regex = re.compile(r" --with-http_ssl_module") - ssl_matches = ssl_regex.findall(text) - - if not version_matches: - raise errors.PluginError("Unable to find Nginx version") - if not ssl_matches: - raise errors.PluginError( - "Nginx build is missing SSL module (--with-http_ssl_module).") - if not sni_matches: - raise errors.PluginError("Nginx build doesn't support SNI") - - product_name, product_version = version_matches[0] - if product_name != 'nginx': - logger.warning("NGINX derivative %s is not officially supported by" - " certbot", product_name) - - nginx_version = tuple([int(i) for i in product_version.split(".")]) - - # nginx < 0.8.48 uses machine hostname as default server_name instead of - # the empty string - if nginx_version < (0, 8, 48): - raise errors.NotSupportedError("Nginx version must be 0.8.48+") - - return nginx_version - - def _get_openssl_version(self): - """Return version of OpenSSL linked to Nginx. - - Version is returned as string. If no version can be found, empty string is returned. - - :returns: openssl_version - :rtype: str - - :raises .PluginError: - Unable to run Nginx version command - """ - text = self._nginx_version() - - matches = re.findall(r"running with OpenSSL ([^ ]+) ", text) - if not matches: - matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) - if not matches: - logger.warning("NGINX configured with OpenSSL alternatives is not officially" - "supported by Certbot.") - return "" - return matches[0] - - def more_info(self): - """Human-readable string to help understand the module""" - return ( - "Configures Nginx to authenticate and install HTTPS.{0}" - "Server root: {root}{0}" - "Version: {version}".format( - os.linesep, root=self.parser.config_root, - version=".".join(str(i) for i in self.version)) - ) - - ################################################### - # Wrapper functions for Reverter class (IInstaller) - ################################################### - def save(self, title=None, temporary=False): - """Saves all changes to the configuration files. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (ie. challenges) - - :raises .errors.PluginError: If there was an error in - an attempt to save the configuration, or an error creating a - checkpoint - - """ - save_files = set(self.parser.parsed.keys()) - self.add_to_checkpoint(save_files, self.save_notes, temporary) - self.save_notes = "" - - # Change 'ext' to something else to not override existing conf files - self.parser.filedump(ext='') - if title and not temporary: - self.finalize_checkpoint(title) - - def recovery_routine(self): - """Revert all previously modified files. - - Reverts all modified files that have not been saved as a checkpoint - - :raises .errors.PluginError: If unable to recover the configuration - - """ - super(NginxConfigurator, self).recovery_routine() - self.new_vhost = None - self.parser.load() - - def revert_challenge_config(self): - """Used to cleanup challenge configurations. - - :raises .errors.PluginError: If unable to revert the challenge config. - - """ - self.revert_temporary_config() - self.new_vhost = None - self.parser.load() - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - - """ - super(NginxConfigurator, self).rollback_checkpoints(rollback) - self.new_vhost = None - self.parser.load() - - ########################################################################### - # Challenges Section for IAuthenticator - ########################################################################### - def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use - """Return list of challenge preferences.""" - return [challenges.HTTP01] - - # Entry point in main.py for performing challenges - def perform(self, achalls): - """Perform the configuration related challenge. - - This function currently assumes all challenges will be fulfilled. - If this turns out not to be the case in the future. Cleanup and - outstanding challenges will have to be designed better. - - """ - self._chall_out += len(achalls) - responses = [None] * len(achalls) - http_doer = http_01.NginxHttp01(self) - - for i, achall in enumerate(achalls): - # Currently also have chall_doer hold associated index of the - # challenge. This helps to put all of the responses back together - # when they are all complete. - http_doer.add_chall(achall, i) - - http_response = http_doer.perform() - # Must restart in order to activate the challenges. - # Handled here because we may be able to load up other challenge types - self.restart() - - # Go through all of the challenges and assign them to the proper place - # in the responses return value. All responses must be in the same order - # as the original challenges. - for i, resp in enumerate(http_response): - responses[http_doer.indices[i]] = resp - - return responses - - # called after challenges are performed - def cleanup(self, achalls): - """Revert all challenges.""" - self._chall_out -= len(achalls) - - # If all of the challenges have been finished, clean up everything - if self._chall_out <= 0: - self.revert_challenge_config() - self.restart() - - -def _test_block_from_block(block): - test_block = nginxparser.UnspacedList(block) - parser.comment_directive(test_block, 0) - return test_block[:-1] - - -def _redirect_block_for_domain(domain): - updated_domain = domain - match_symbol = '=' - if util.is_wildcard_domain(domain): - match_symbol = '~' - updated_domain = updated_domain.replace('.', r'\.') - updated_domain = updated_domain.replace('*', '[^.]+') - updated_domain = '^' + updated_domain + '$' - redirect_block = [[ - ['\n ', 'if', ' ', '($host', ' ', match_symbol, ' ', '%s)' % updated_domain, ' '], - [['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], - '\n ']], - ['\n']] - return redirect_block - - -def nginx_restart(nginx_ctl, nginx_conf): - """Restarts the Nginx Server. - - .. todo:: Nginx restart is fatal if the configuration references - non-existent SSL cert/key files. Remove references to /etc/letsencrypt - before restart. - - :param str nginx_ctl: Path to the Nginx binary. - - """ - try: - proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"]) - proc.communicate() - - if proc.returncode != 0: - # Maybe Nginx isn't running - # Write to temporary files instead of piping because of communication issues on Arch - # https://github.com/certbot/certbot/issues/4324 - with tempfile.TemporaryFile() as out: - with tempfile.TemporaryFile() as err: - nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf], - stdout=out, stderr=err) - nginx_proc.communicate() - if nginx_proc.returncode != 0: - # Enter recovery routine... - raise errors.MisconfigurationError( - "nginx restart failed:\n%s\n%s" % (out.read(), err.read())) - - except (OSError, ValueError): - raise errors.MisconfigurationError("nginx restart failed") - # Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep - # for a second. TODO: Check for expected servername and loop until it - # appears or return an error if looping too long. - time.sleep(1) - - -def _determine_default_server_root(): - if os.environ.get("CERTBOT_DOCS") == "1": - default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT, - constants.FREEBSD_DARWIN_SERVER_ROOT) - else: - default_server_root = constants.CLI_DEFAULTS["server_root"] - return default_server_root diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py deleted file mode 100644 index fbf6ed424..000000000 --- a/certbot-nginx/certbot_nginx/constants.py +++ /dev/null @@ -1,63 +0,0 @@ -"""nginx plugin constants.""" -import platform - -FREEBSD_DARWIN_SERVER_ROOT = "/usr/local/etc/nginx" -LINUX_SERVER_ROOT = "/etc/nginx" - -if platform.system() in ('FreeBSD', 'Darwin'): - server_root_tmp = FREEBSD_DARWIN_SERVER_ROOT -else: - server_root_tmp = LINUX_SERVER_ROOT - -CLI_DEFAULTS = dict( - server_root=server_root_tmp, - ctl="nginx", -) -"""CLI defaults.""" - - -MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" -"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" - -UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" -"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" - -ALL_SSL_OPTIONS_HASHES = [ - '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', - '9a7b32c49001fed4cff8ad24353329472a50e86ade1ef9b2b9e43566a619612e', - 'a6d9f1c7d6b36749b52ba061fff1421f9a0a3d2cfdafbd63c05d06f65b990937', - '7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff', - '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', - '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', - 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', - '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', - '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', - '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', - '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', - '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', - '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', - 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', - 'ef11e3fb17213e74d3e1816cde0ec37b8b95b4167cf21e7b8ff1eaa9c6f918ee', - 'af85f6193808a44789a1d293e6cffa249cad9a21135940800958b8e3c72dbc69', - 'a2a612fd21b02abaa32d9d11ac63d987d6e3054dbfa356de5800eea0d7ce17f3', - '2d9648302e3588a172c318e46bff88ade46fc7a16d6afc85322776a04800d473', -] -"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" - -def os_constant(key): - # XXX TODO: In the future, this could return different constants - # based on what OS we are running under. To see an - # approach to how to handle different OSes, see the - # apache version of this file. Currently, we do not - # actually have any OS-specific constants on Nginx. - """ - Get a constant value for operating system - - :param key: name of cli constant - :return: value of constant for active os - """ - return CLI_DEFAULTS[key] - -HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always'] - -HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS} diff --git a/certbot-nginx/certbot_nginx/display_ops.py b/certbot-nginx/certbot_nginx/display_ops.py deleted file mode 100644 index 9b973d8d3..000000000 --- a/certbot-nginx/certbot_nginx/display_ops.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Contains UI methods for Nginx operations.""" -import logging - -import zope.component - -from certbot import interfaces - -import certbot.display.util as display_util - - -logger = logging.getLogger(__name__) - - -def select_vhost_multiple(vhosts): - """Select multiple Vhosts to install the certificate for - :param vhosts: Available Nginx VirtualHosts - :type vhosts: :class:`list` of type `~obj.Vhost` - :returns: List of VirtualHosts - :rtype: :class:`list`of type `~obj.Vhost` - """ - if not vhosts: - return list() - tags_list = [vhost.display_repr()+"\n" for vhost in vhosts] - # Remove the extra newline from the last entry - if tags_list: - tags_list[-1] = tags_list[-1][:-1] - code, names = zope.component.getUtility(interfaces.IDisplay).checklist( - "Which server blocks would you like to modify?", - tags=tags_list, force_interactive=True) - if code == display_util.OK: - return_vhosts = _reversemap_vhosts(names, vhosts) - return return_vhosts - return [] - -def _reversemap_vhosts(names, vhosts): - """Helper function for select_vhost_multiple for mapping string - representations back to actual vhost objects""" - return_vhosts = list() - - for selection in names: - for vhost in vhosts: - if vhost.display_repr().strip() == selection.strip(): - return_vhosts.append(vhost) - return return_vhosts diff --git a/certbot-nginx/certbot_nginx/http_01.py b/certbot-nginx/certbot_nginx/http_01.py deleted file mode 100644 index 4dcc5324f..000000000 --- a/certbot-nginx/certbot_nginx/http_01.py +++ /dev/null @@ -1,207 +0,0 @@ -"""A class that performs HTTP-01 challenges for Nginx""" - -import logging - -from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import errors -from certbot.compat import os -from certbot.plugins import common - -from certbot_nginx import obj -from certbot_nginx import nginxparser - - -logger = logging.getLogger(__name__) - - -class NginxHttp01(common.ChallengePerformer): - """HTTP-01 authenticator for Nginx - - :ivar configurator: NginxConfigurator object - :type configurator: :class:`~nginx.configurator.NginxConfigurator` - - :ivar list achalls: Annotated - class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge` - challenges - - :param list indices: Meant to hold indices of challenges in a - larger array. NginxHttp01 is capable of solving many challenges - at once which causes an indexing issue within NginxConfigurator - who must return all responses in order. Imagine - NginxConfigurator maintaining state about where all of the - challenges, possibly of different types, belong in the response - array. This is an optional utility. - - """ - - def __init__(self, configurator): - super(NginxHttp01, self).__init__(configurator) - self.challenge_conf = os.path.join( - configurator.config.config_dir, "le_http_01_cert_challenge.conf") - - def perform(self): - """Perform a challenge on Nginx. - - :returns: list of :class:`certbot.acme.challenges.HTTP01Response` - :rtype: list - - """ - if not self.achalls: - return [] - - responses = [x.response(x.account_key) for x in self.achalls] - - # Set up the configuration - self._mod_config() - - # Save reversible changes - self.configurator.save("HTTP Challenge", True) - - return responses - - def _mod_config(self): - """Modifies Nginx config to include server_names_hash_bucket_size directive - and server challenge blocks. - - :raises .MisconfigurationError: - Unable to find a suitable HTTP block in which to include - authenticator hosts. - """ - included = False - include_directive = ['\n', 'include', ' ', self.challenge_conf] - root = self.configurator.parser.config_root - - bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128'] - - main = self.configurator.parser.parsed[root] - for line in main: - if line[0] == ['http']: - body = line[1] - found_bucket = False - posn = 0 - for inner_line in body: - if inner_line[0] == bucket_directive[1]: - if int(inner_line[1]) < int(bucket_directive[3]): - body[posn] = bucket_directive - found_bucket = True - posn += 1 - if not found_bucket: - body.insert(0, bucket_directive) - if include_directive not in body: - body.insert(0, include_directive) - included = True - break - if not included: - raise errors.MisconfigurationError( - 'Certbot could not find a block to include ' - 'challenges in %s.' % root) - config = [self._make_or_mod_server_block(achall) for achall in self.achalls] - config = [x for x in config if x is not None] - config = nginxparser.UnspacedList(config) - logger.debug("Generated server block:\n%s", str(config)) - - self.configurator.reverter.register_file_creation( - True, self.challenge_conf) - - with open(self.challenge_conf, "w") as new_conf: - nginxparser.dump(config, new_conf) - - def _default_listen_addresses(self): - """Finds addresses for a challenge block to listen on. - :returns: list of :class:`certbot_nginx.obj.Addr` to apply - :rtype: list - """ - addresses = [] # type: List[obj.Addr] - default_addr = "%s" % self.configurator.config.http01_port - ipv6_addr = "[::]:{0}".format( - self.configurator.config.http01_port) - port = self.configurator.config.http01_port - - ipv6, ipv6only = self.configurator.ipv6_info(port) - - if ipv6: - # If IPv6 is active in Nginx configuration - if not ipv6only: - # If ipv6only=on is not already present in the config - ipv6_addr = ipv6_addr + " ipv6only=on" - addresses = [obj.Addr.fromstring(default_addr), - obj.Addr.fromstring(ipv6_addr)] - logger.info(("Using default addresses %s and %s for authentication."), - default_addr, - ipv6_addr) - else: - addresses = [obj.Addr.fromstring(default_addr)] - logger.info("Using default address %s for authentication.", - default_addr) - return addresses - - def _get_validation_path(self, achall): - return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token")) - - def _make_server_block(self, achall): - """Creates a server block for a challenge. - :param achall: Annotated HTTP-01 challenge - :type achall: - :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` - :param list addrs: addresses of challenged domain - :class:`list` of type :class:`~nginx.obj.Addr` - :returns: server block for the challenge host - :rtype: list - """ - addrs = self._default_listen_addresses() - block = [['listen', ' ', addr.to_string(include_default=False)] for addr in addrs] - - # Ensure we 404 on any other request by setting a root - document_root = os.path.join( - self.configurator.config.work_dir, "http_01_nonexistent") - - block.extend([['server_name', ' ', achall.domain], - ['root', ' ', document_root], - self._location_directive_for_achall(achall) - ]) - # TODO: do we want to return something else if they otherwise access this block? - return [['server'], block] - - def _location_directive_for_achall(self, achall): - validation = achall.validation(achall.account_key) - validation_path = self._get_validation_path(achall) - - location_directive = [['location', ' ', '=', ' ', validation_path], - [['default_type', ' ', 'text/plain'], - ['return', ' ', '200', ' ', validation]]] - return location_directive - - - def _make_or_mod_server_block(self, achall): - """Modifies a server block to respond to a challenge. - - :param achall: Annotated HTTP-01 challenge - :type achall: - :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge` - - """ - try: - vhosts = self.configurator.choose_redirect_vhosts(achall.domain, - '%i' % self.configurator.config.http01_port, create_if_no_match=True) - except errors.MisconfigurationError: - # Couldn't find either a matching name+port server block - # or a port+default_server block, so create a dummy block - return self._make_server_block(achall) - - # len is max 1 because Nginx doesn't authenticate wildcards - # if len were or vhosts None, we would have errored - vhost = vhosts[0] - - # Modify existing server block - location_directive = [self._location_directive_for_achall(achall)] - - self.configurator.parser.add_server_directives(vhost, - location_directive) - - rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)', - ' ', '$1', ' ', 'break']] - self.configurator.parser.add_server_directives(vhost, - rewrite_directive, insert_at_top=True) - return None diff --git a/certbot-nginx/certbot_nginx/nginxparser.py b/certbot-nginx/certbot_nginx/nginxparser.py deleted file mode 100644 index f4603dcde..000000000 --- a/certbot-nginx/certbot_nginx/nginxparser.py +++ /dev/null @@ -1,268 +0,0 @@ -"""Very low-level nginx config parser based on pyparsing.""" -# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed) -import copy -import logging - -from pyparsing import ( - Literal, White, Forward, Group, Optional, OneOrMore, QuotedString, Regex, ZeroOrMore, Combine) -from pyparsing import stringEnd -from pyparsing import restOfLine -import six - -logger = logging.getLogger(__name__) - -class RawNginxParser(object): - # pylint: disable=expression-not-assigned - # pylint: disable=pointless-statement - """A class that parses nginx configuration with pyparsing.""" - - # constants - space = Optional(White()).leaveWhitespace() - required_space = White().leaveWhitespace() - - left_bracket = Literal("{").suppress() - right_bracket = space + Literal("}").suppress() - semicolon = Literal(";").suppress() - dquoted = QuotedString('"', multiline=True, unquoteResults=False, escChar='\\') - squoted = QuotedString("'", multiline=True, unquoteResults=False, escChar='\\') - quoted = dquoted | squoted - head_tokenchars = Regex(r"(\$\{)|[^{};\s'\"]") # if (last_space) - tail_tokenchars = Regex(r"(\$\{)|[^{;\s]") # else - tokenchars = Combine(head_tokenchars + ZeroOrMore(tail_tokenchars)) - paren_quote_extend = Combine(quoted + Literal(')') + ZeroOrMore(tail_tokenchars)) - # note: ')' allows extension, but then we fall into else, not last_space. - - token = paren_quote_extend | tokenchars | quoted - - whitespace_token_group = space + token + ZeroOrMore(required_space + token) + space - assignment = whitespace_token_group + semicolon - - comment = space + Literal('#') + restOfLine - - block = Forward() - - # order matters! see issue 518, and also http { # server { \n} - contents = Group(comment) | Group(block) | Group(assignment) - - block_begin = Group(whitespace_token_group) - block_innards = Group(ZeroOrMore(contents) + space).leaveWhitespace() - block << block_begin + left_bracket + block_innards + right_bracket - - script = OneOrMore(contents) + space + stringEnd - script.parseWithTabs().leaveWhitespace() - - def __init__(self, source): - self.source = source - - def parse(self): - """Returns the parsed tree.""" - return self.script.parseString(self.source) - - def as_list(self): - """Returns the parsed tree as a list.""" - return self.parse().asList() - -class RawNginxDumper(object): - """A class that dumps nginx configuration from the provided tree.""" - def __init__(self, blocks): - self.blocks = blocks - - def __iter__(self, blocks=None): - """Iterates the dumped nginx content.""" - blocks = blocks or self.blocks - for b0 in blocks: - if isinstance(b0, six.string_types): - yield b0 - continue - item = copy.deepcopy(b0) - if spacey(item[0]): - yield item.pop(0) # indentation - if not item: - continue - - if isinstance(item[0], list): # block - yield "".join(item.pop(0)) + '{' - for parameter in item.pop(0): - for line in self.__iter__([parameter]): # negate "for b0 in blocks" - yield line - yield '}' - else: # not a block - list of strings - semicolon = ";" - if isinstance(item[0], six.string_types) and item[0].strip() == '#': # comment - semicolon = "" - yield "".join(item) + semicolon - - def __str__(self): - """Return the parsed block as a string.""" - return ''.join(self) - - -# Shortcut functions to respect Python's serialization interface -# (like pyyaml, picker or json) - -def loads(source): - """Parses from a string. - - :param str source: The string to parse - :returns: The parsed tree - :rtype: list - - """ - return UnspacedList(RawNginxParser(source).as_list()) - - -def load(_file): - """Parses from a file. - - :param file _file: The file to parse - :returns: The parsed tree - :rtype: list - - """ - return loads(_file.read()) - - -def dumps(blocks): - """Dump to a string. - - :param UnspacedList block: The parsed tree - :param int indentation: The number of spaces to indent - :rtype: str - - """ - return str(RawNginxDumper(blocks.spaced)) - - -def dump(blocks, _file): - """Dump to a file. - - :param UnspacedList block: The parsed tree - :param file _file: The file to dump to - :param int indentation: The number of spaces to indent - :rtype: NoneType - - """ - return _file.write(dumps(blocks)) - - -spacey = lambda x: (isinstance(x, six.string_types) and x.isspace()) or x == '' - -class UnspacedList(list): - """Wrap a list [of lists], making any whitespace entries magically invisible""" - - def __init__(self, list_source): - # ensure our argument is not a generator, and duplicate any sublists - self.spaced = copy.deepcopy(list(list_source)) - self.dirty = False - - # Turn self into a version of the source list that has spaces removed - # and all sub-lists also UnspacedList()ed - list.__init__(self, list_source) - for i, entry in reversed(list(enumerate(self))): - if isinstance(entry, list): - sublist = UnspacedList(entry) - list.__setitem__(self, i, sublist) - self.spaced[i] = sublist.spaced - elif spacey(entry): - # don't delete comments - if "#" not in self[:i]: - list.__delitem__(self, i) - - def _coerce(self, inbound): - """ - Coerce some inbound object to be appropriately usable in this object - - :param inbound: string or None or list or UnspacedList - :returns: (coerced UnspacedList or string or None, spaced equivalent) - :rtype: tuple - - """ - if not isinstance(inbound, list): # str or None - return (inbound, inbound) - else: - if not hasattr(inbound, "spaced"): - inbound = UnspacedList(inbound) - return (inbound, inbound.spaced) - - - def insert(self, i, x): - item, spaced_item = self._coerce(x) - slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced) - self.spaced.insert(slicepos, spaced_item) - if not spacey(item): - list.insert(self, i, item) - self.dirty = True - - def append(self, x): - item, spaced_item = self._coerce(x) - self.spaced.append(spaced_item) - if not spacey(item): - list.append(self, item) - self.dirty = True - - def extend(self, x): - item, spaced_item = self._coerce(x) - self.spaced.extend(spaced_item) - list.extend(self, item) - self.dirty = True - - def __add__(self, other): - l = copy.deepcopy(self) - l.extend(other) - l.dirty = True - return l - - def pop(self, _i=None): - raise NotImplementedError("UnspacedList.pop() not yet implemented") - def remove(self, _): - raise NotImplementedError("UnspacedList.remove() not yet implemented") - def reverse(self): - raise NotImplementedError("UnspacedList.reverse() not yet implemented") - def sort(self, _cmp=None, _key=None, _Rev=None): - raise NotImplementedError("UnspacedList.sort() not yet implemented") - def __setslice__(self, _i, _j, _newslice): - raise NotImplementedError("Slice operations on UnspacedLists not yet implemented") - - def __setitem__(self, i, value): - if isinstance(i, slice): - raise NotImplementedError("Slice operations on UnspacedLists not yet implemented") - item, spaced_item = self._coerce(value) - self.spaced.__setitem__(self._spaced_position(i), spaced_item) - if not spacey(item): - list.__setitem__(self, i, item) - self.dirty = True - - def __delitem__(self, i): - self.spaced.__delitem__(self._spaced_position(i)) - list.__delitem__(self, i) - self.dirty = True - - def __deepcopy__(self, memo): - new_spaced = copy.deepcopy(self.spaced, memo=memo) - l = UnspacedList(new_spaced) - l.dirty = self.dirty - return l - - def is_dirty(self): - """Recurse through the parse tree to figure out if any sublists are dirty""" - if self.dirty: - return True - return any((isinstance(x, UnspacedList) and x.is_dirty() for x in self)) - - def _spaced_position(self, idx): - "Convert from indexes in the unspaced list to positions in the spaced one" - pos = spaces = 0 - # Normalize indexes like list[-1] etc, and save the result - if idx < 0: - idx = len(self) + idx - if not 0 <= idx < len(self): - raise IndexError("list index out of range") - idx0 = idx - # Count the number of spaces in the spaced list before idx in the unspaced one - while idx != -1: - if spacey(self.spaced[pos]): - spaces += 1 - else: - idx -= 1 - pos += 1 - return idx0 + spaces diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/obj.py deleted file mode 100644 index 1a92c8b37..000000000 --- a/certbot-nginx/certbot_nginx/obj.py +++ /dev/null @@ -1,263 +0,0 @@ -"""Module contains classes used by the Nginx Configurator.""" -import re - -import six - -from certbot.plugins import common - -ADD_HEADER_DIRECTIVE = 'add_header' - -class Addr(common.Addr): - r"""Represents an Nginx address, i.e. what comes after the 'listen' - directive. - - According to the `documentation`_, this may be address[:port], port, - or unix:path. The latter is ignored here. - - The default value if no directive is specified is \*:80 (superuser) - or \*:8000 (otherwise). If no port is specified, the default is - 80. If no address is specified, listen on all addresses. - - .. _documentation: - http://nginx.org/en/docs/http/ngx_http_core_module.html#listen - - .. todo:: Old-style nginx configs define SSL vhosts in a separate - block instead of using 'ssl' in the listen directive. - - :param str addr: addr part of vhost address, may be hostname, IPv4, IPv6, - "", or "\*" - :param str port: port number or "\*" or "" - :param bool ssl: Whether the directive includes 'ssl' - :param bool default: Whether the directive includes 'default_server' - :param bool default: Whether this is an IPv6 address - :param bool ipv6only: Whether the directive includes 'ipv6only=on' - - """ - UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0') - CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] - - def __init__(self, host, port, ssl, default, ipv6, ipv6only): - super(Addr, self).__init__((host, port)) - self.ssl = ssl - self.default = default - self.ipv6 = ipv6 - self.ipv6only = ipv6only - self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES - - @classmethod - def fromstring(cls, str_addr): - """Initialize Addr from string.""" - parts = str_addr.split(' ') - ssl = False - default = False - ipv6 = False - ipv6only = False - host = '' - port = '' - - # The first part must be the address - addr = parts.pop(0) - - # Ignore UNIX-domain sockets - if addr.startswith('unix:'): - return None - - # IPv6 check - ipv6_match = re.match(r'\[.*\]', addr) - if ipv6_match: - ipv6 = True - # IPv6 handling - host = ipv6_match.group() - # The rest of the addr string will be the port, if any - port = addr[ipv6_match.end()+1:] - else: - # IPv4 handling - tup = addr.partition(':') - if re.match(r'^\d+$', tup[0]): - # This is a bare port, not a hostname. E.g. listen 80 - host = '' - port = tup[0] - else: - # This is a host-port tuple. E.g. listen 127.0.0.1:* - host = tup[0] - port = tup[2] - - # The rest of the parts are options; we only care about ssl and default - while parts: - nextpart = parts.pop() - if nextpart == 'ssl': - ssl = True - elif nextpart == 'default_server': - default = True - elif nextpart == 'default': - default = True - elif nextpart == "ipv6only=on": - ipv6only = True - - return cls(host, port, ssl, default, ipv6, ipv6only) - - def to_string(self, include_default=True): - """Return string representation of Addr""" - parts = '' - if self.tup[0] and self.tup[1]: - parts = "%s:%s" % self.tup - elif self.tup[0]: - parts = self.tup[0] - else: - parts = self.tup[1] - - if self.default and include_default: - parts += ' default_server' - if self.ssl: - parts += ' ssl' - - return parts - - def __str__(self): - return self.to_string() - - def __repr__(self): - return "Addr(" + self.__str__() + ")" - - def __hash__(self): # pylint: disable=useless-super-delegation - # Python 3 requires explicit overridden for __hash__ - # See certbot-apache/certbot_apache/_internal/obj.py for more information - return super(Addr, self).__hash__() - - def super_eq(self, other): - """Check ip/port equality, with IPv6 support. - """ - # If both addresses got an unspecified address, then make sure the - # host representation in each match when doing the comparison. - if self.unspecified_address and other.unspecified_address: - return common.Addr((self.CANONICAL_UNSPECIFIED_ADDRESS, - self.tup[1]), self.ipv6) == \ - common.Addr((other.CANONICAL_UNSPECIFIED_ADDRESS, - other.tup[1]), other.ipv6) - return super(Addr, self).__eq__(other) - - def __eq__(self, other): - if isinstance(other, self.__class__): - return (self.super_eq(other) and - self.ssl == other.ssl and - self.default == other.default) - return False - - -class VirtualHost(object): - """Represents an Nginx Virtualhost. - - :ivar str filep: file path of VH - :ivar set addrs: Virtual Host addresses (:class:`set` of :class:`Addr`) - :ivar set names: Server names/aliases of vhost - (:class:`list` of :class:`str`) - :ivar list raw: The raw form of the parsed server block - - :ivar bool ssl: SSLEngine on in vhost - :ivar bool enabled: Virtual host is enabled - :ivar list path: The indices into the parsed file used to access - the server block defining the vhost - - """ - - def __init__(self, filep, addrs, ssl, enabled, names, raw, path): - """Initialize a VH.""" - self.filep = filep - self.addrs = addrs - self.names = names - self.ssl = ssl - self.enabled = enabled - self.raw = raw - self.path = path - - def __str__(self): - addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str)) - # names might be a set, and it has different representations in Python - # 2 and 3. Force it to be a list here for consistent outputs - return ("file: %s\n" - "addrs: %s\n" - "names: %s\n" - "ssl: %s\n" - "enabled: %s" % (self.filep, addr_str, - list(self.names), self.ssl, self.enabled)) - - def __repr__(self): - return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n" - - def __eq__(self, other): - if isinstance(other, self.__class__): - return (self.filep == other.filep and - sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and - self.names == other.names and - self.ssl == other.ssl and - self.enabled == other.enabled and - self.path == other.path) - - return False - - def __hash__(self): - return hash((self.filep, tuple(self.path), - tuple(self.addrs), tuple(self.names), - self.ssl, self.enabled)) - - def has_header(self, header_name): - """Determine if this server block has a particular header set. - :param str header_name: The name of the header to check for, e.g. - 'Strict-Transport-Security' - """ - found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name) - return found is not None - - def contains_list(self, test): - """Determine if raw server block contains test list at top level - """ - for i in six.moves.range(0, len(self.raw) - len(test) + 1): - if self.raw[i:i + len(test)] == test: - return True - return False - - def ipv6_enabled(self): - """Return true if one or more of the listen directives in vhost supports - IPv6""" - for a in self.addrs: - if a.ipv6: - return True - return False - - def ipv4_enabled(self): - """Return true if one or more of the listen directives in vhost are IPv4 - only""" - if not self.addrs: - return True - for a in self.addrs: - if not a.ipv6: - return True - return False - - def display_repr(self): - """Return a representation of VHost to be used in dialog""" - return ( - "File: {filename}\n" - "Addresses: {addrs}\n" - "Names: {names}\n" - "HTTPS: {https}\n".format( - filename=self.filep, - addrs=", ".join(str(addr) for addr in self.addrs), - names=", ".join(self.names), - https="Yes" if self.ssl else "No")) - -def _find_directive(directives, directive_name, match_content=None): - """Find a directive of type directive_name in directives. If match_content is given, - Searches for `match_content` in the directive arguments. - """ - if not directives or isinstance(directives, six.string_types): - return None - - # If match_content is None, just match on directive type. Otherwise, match on - # both directive type -and- the content! - if directives[0] == directive_name and \ - (match_content is None or match_content in directives): - return directives - - matches = (_find_directive(line, directive_name, match_content) for line in directives) - return next((m for m in matches if m is not None), None) diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/parser.py deleted file mode 100644 index d50606fc5..000000000 --- a/certbot-nginx/certbot_nginx/parser.py +++ /dev/null @@ -1,762 +0,0 @@ -"""NginxParser is a member object of the NginxConfigurator class.""" -import copy -import functools -import glob -import logging -import re -import pyparsing - -import six - -from certbot import errors -from certbot.compat import os - -from certbot_nginx import obj -from certbot_nginx import nginxparser -from acme.magic_typing import Union, Dict, Set, Any, List, Tuple # pylint: disable=unused-import, no-name-in-module - -logger = logging.getLogger(__name__) - - -class NginxParser(object): - """Class handles the fine details of parsing the Nginx Configuration. - - :ivar str root: Normalized absolute path to the server root - directory. Without trailing slash. - :ivar dict parsed: Mapping of file paths to parsed trees - - """ - - def __init__(self, root): - self.parsed = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] - self.root = os.path.abspath(root) - self.config_root = self._find_config_root() - - # Parse nginx.conf and included files. - # TODO: Check sites-available/ as well. For now, the configurator does - # not enable sites from there. - self.load() - - def load(self): - """Loads Nginx files into a parsed tree. - - """ - self.parsed = {} - self._parse_recursively(self.config_root) - - def _parse_recursively(self, filepath): - """Parses nginx config files recursively by looking at 'include' - directives inside 'http' and 'server' blocks. Note that this only - reads Nginx files that potentially declare a virtual host. - - :param str filepath: The path to the files to parse, as a glob - - """ - # pylint: disable=too-many-nested-blocks - filepath = self.abs_path(filepath) - trees = self._parse_files(filepath) - for tree in trees: - for entry in tree: - if _is_include_directive(entry): - # Parse the top-level included file - self._parse_recursively(entry[1]) - elif entry[0] == ['http'] or entry[0] == ['server']: - # Look for includes in the top-level 'http'/'server' context - for subentry in entry[1]: - if _is_include_directive(subentry): - self._parse_recursively(subentry[1]) - elif entry[0] == ['http'] and subentry[0] == ['server']: - # Look for includes in a 'server' context within - # an 'http' context - for server_entry in subentry[1]: - if _is_include_directive(server_entry): - self._parse_recursively(server_entry[1]) - - def abs_path(self, path): - """Converts a relative path to an absolute path relative to the root. - Does nothing for paths that are already absolute. - - :param str path: The path - :returns: The absolute path - :rtype: str - - """ - if not os.path.isabs(path): - return os.path.normpath(os.path.join(self.root, path)) - return os.path.normpath(path) - - def _build_addr_to_ssl(self): - """Builds a map from address to whether it listens on ssl in any server block - """ - servers = self._get_raw_servers() - - addr_to_ssl = {} # type: Dict[Tuple[str, str], bool] - for filename in servers: - for server, _ in servers[filename]: - # Parse the server block to save addr info - parsed_server = _parse_server_raw(server) - for addr in parsed_server['addrs']: - addr_tuple = addr.normalized_tuple() - if addr_tuple not in addr_to_ssl: - addr_to_ssl[addr_tuple] = addr.ssl - addr_to_ssl[addr_tuple] = addr.ssl or addr_to_ssl[addr_tuple] - return addr_to_ssl - - def _get_raw_servers(self): - # pylint: disable=cell-var-from-loop - # type: () -> Dict - """Get a map of unparsed all server blocks - """ - servers = {} # type: Dict[str, Union[List, nginxparser.UnspacedList]] - for filename in self.parsed: - tree = self.parsed[filename] - servers[filename] = [] - srv = servers[filename] # workaround undefined loop var in lambdas - - # Find all the server blocks - _do_for_subarray(tree, lambda x: len(x) >= 2 and x[0] == ['server'], - lambda x, y: srv.append((x[1], y))) - - # Find 'include' statements in server blocks and append their trees - for i, (server, path) in enumerate(servers[filename]): - new_server = self._get_included_directives(server) - servers[filename][i] = (new_server, path) - return servers - - def get_vhosts(self): - # pylint: disable=cell-var-from-loop - """Gets list of all 'virtual hosts' found in Nginx configuration. - Technically this is a misnomer because Nginx does not have virtual - hosts, it has 'server blocks'. - - :returns: List of :class:`~certbot_nginx.obj.VirtualHost` - objects found in configuration - :rtype: list - - """ - enabled = True # We only look at enabled vhosts for now - servers = self._get_raw_servers() - - vhosts = [] - for filename in servers: - for server, path in servers[filename]: - # Parse the server block into a VirtualHost object - - parsed_server = _parse_server_raw(server) - vhost = obj.VirtualHost(filename, - parsed_server['addrs'], - parsed_server['ssl'], - enabled, - parsed_server['names'], - server, - path) - vhosts.append(vhost) - - self._update_vhosts_addrs_ssl(vhosts) - - return vhosts - - def _update_vhosts_addrs_ssl(self, vhosts): - """Update a list of raw parsed vhosts to include global address sslishness - """ - addr_to_ssl = self._build_addr_to_ssl() - for vhost in vhosts: - for addr in vhost.addrs: - addr.ssl = addr_to_ssl[addr.normalized_tuple()] - if addr.ssl: - vhost.ssl = True - - def _get_included_directives(self, block): - """Returns array with the "include" directives expanded out by - concatenating the contents of the included file to the block. - - :param list block: - :rtype: list - - """ - result = copy.deepcopy(block) # Copy the list to keep self.parsed idempotent - for directive in block: - if _is_include_directive(directive): - included_files = glob.glob( - self.abs_path(directive[1])) - for incl in included_files: - try: - result.extend(self.parsed[incl]) - except KeyError: - pass - return result - - def _parse_files(self, filepath, override=False): - """Parse files from a glob - - :param str filepath: Nginx config file path - :param bool override: Whether to parse a file that has been parsed - :returns: list of parsed tree structures - :rtype: list - - """ - files = glob.glob(filepath) # nginx on unix calls glob(3) for this - # XXX Windows nginx uses FindFirstFile, and - # should have a narrower call here - trees = [] - for item in files: - if item in self.parsed and not override: - continue - try: - with open(item) as _file: - parsed = nginxparser.load(_file) - self.parsed[item] = parsed - trees.append(parsed) - except IOError: - logger.warning("Could not open file: %s", item) - except pyparsing.ParseException as err: - logger.debug("Could not parse file: %s due to %s", item, err) - return trees - - def _find_config_root(self): - """Return the Nginx Configuration Root file.""" - location = ['nginx.conf'] - - for name in location: - if os.path.isfile(os.path.join(self.root, name)): - return os.path.join(self.root, name) - - raise errors.NoInstallationError( - "Could not find Nginx root configuration file (nginx.conf)") - - def filedump(self, ext='tmp', lazy=True): - """Dumps parsed configurations into files. - - :param str ext: The file extension to use for the dumped files. If - empty, this overrides the existing conf files. - :param bool lazy: Only write files that have been modified - - """ - # Best-effort atomicity is enforced above us by reverter.py - for filename in self.parsed: - tree = self.parsed[filename] - if ext: - filename = filename + os.path.extsep + ext - try: - if lazy and not tree.is_dirty(): - continue - out = nginxparser.dumps(tree) - logger.debug('Writing nginx conf tree to %s:\n%s', filename, out) - with open(filename, 'w') as _file: - _file.write(out) - - except IOError: - logger.error("Could not open file for writing: %s", filename) - - def parse_server(self, server): - """Parses a list of server directives, accounting for global address sslishness. - - :param list server: list of directives in a server block - :rtype: dict - """ - addr_to_ssl = self._build_addr_to_ssl() - parsed_server = _parse_server_raw(server) - _apply_global_addr_ssl(addr_to_ssl, parsed_server) - return parsed_server - - def has_ssl_on_directive(self, vhost): - """Does vhost have ssl on for all ports? - - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost in question - - :returns: True if 'ssl on' directive is included - :rtype: bool - - """ - server = vhost.raw - for directive in server: - if not directive: - continue - elif _is_ssl_on_directive(directive): - return True - - return False - - def add_server_directives(self, vhost, directives, insert_at_top=False): - """Add directives to the server block identified by vhost. - - This method modifies vhost to be fully consistent with the new directives. - - ..note :: It's an error to try and add a nonrepeatable directive that already - exists in the config block with a conflicting value. - - ..todo :: Doesn't match server blocks whose server_name directives are - split across multiple conf files. - - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost - whose information we use to match on - :param list directives: The directives to add - :param bool insert_at_top: True if the directives need to be inserted at the top - of the server block instead of the bottom - - """ - self._modify_server_directives(vhost, - functools.partial(_add_directives, directives, insert_at_top)) - - def update_or_add_server_directives(self, vhost, directives, insert_at_top=False): - """Add or replace directives in the server block identified by vhost. - - This method modifies vhost to be fully consistent with the new directives. - - ..note :: When a directive with the same name already exists in the - config block, the first instance will be replaced. Otherwise, the directive - will be appended/prepended to the config block as in add_server_directives. - - ..todo :: Doesn't match server blocks whose server_name directives are - split across multiple conf files. - - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost - whose information we use to match on - :param list directives: The directives to add - :param bool insert_at_top: True if the directives need to be inserted at the top - of the server block instead of the bottom - - """ - self._modify_server_directives(vhost, - functools.partial(_update_or_add_directives, directives, insert_at_top)) - - def remove_server_directives(self, vhost, directive_name, match_func=None): - """Remove all directives of type directive_name. - - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost - to remove directives from - :param string directive_name: The directive type to remove - :param callable match_func: Function of the directive that returns true for directives - to be deleted. - """ - self._modify_server_directives(vhost, - functools.partial(_remove_directives, directive_name, match_func)) - - def _update_vhost_based_on_new_directives(self, vhost, directives_list): - new_server = self._get_included_directives(directives_list) - parsed_server = self.parse_server(new_server) - vhost.addrs = parsed_server['addrs'] - vhost.ssl = parsed_server['ssl'] - vhost.names = parsed_server['names'] - vhost.raw = new_server - - def _modify_server_directives(self, vhost, block_func): - filename = vhost.filep - try: - result = self.parsed[filename] - for index in vhost.path: - result = result[index] - if not isinstance(result, list) or len(result) != 2: - raise errors.MisconfigurationError("Not a server block.") - result = result[1] - block_func(result) - - self._update_vhost_based_on_new_directives(vhost, result) - except errors.MisconfigurationError as err: - raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err))) - - def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False, - only_directives=None): - """Duplicate the vhost in the configuration files. - - :param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost - whose information we copy - :param bool remove_singleton_listen_params: If we should remove parameters - from listen directives in the block that can only be used once per address - :param list only_directives: If it exists, only duplicate the named directives. Only - looks at first level of depth; does not expand includes. - - :returns: A vhost object for the newly created vhost - :rtype: :class:`~certbot_nginx.obj.VirtualHost` - """ - # TODO: https://github.com/certbot/certbot/issues/5185 - # put it in the same file as the template, at the same level - new_vhost = copy.deepcopy(vhost_template) - - enclosing_block = self.parsed[vhost_template.filep] - for index in vhost_template.path[:-1]: - enclosing_block = enclosing_block[index] - raw_in_parsed = copy.deepcopy(enclosing_block[vhost_template.path[-1]]) - - if only_directives is not None: - new_directives = nginxparser.UnspacedList([]) - for directive in raw_in_parsed[1]: - if directive and directive[0] in only_directives: - new_directives.append(directive) - raw_in_parsed[1] = new_directives - - self._update_vhost_based_on_new_directives(new_vhost, new_directives) - - enclosing_block.append(raw_in_parsed) - new_vhost.path[-1] = len(enclosing_block) - 1 - if remove_singleton_listen_params: - for addr in new_vhost.addrs: - addr.default = False - addr.ipv6only = False - for directive in enclosing_block[new_vhost.path[-1]][1]: - if directive and directive[0] == 'listen': - # Exclude one-time use parameters which will cause an error if repeated. - # https://nginx.org/en/docs/http/ngx_http_core_module.html#listen - exclude = set(('default_server', 'default', 'setfib', 'fastopen', 'backlog', - 'rcvbuf', 'sndbuf', 'accept_filter', 'deferred', 'bind', - 'ipv6only', 'reuseport', 'so_keepalive')) - - for param in exclude: - # See: github.com/certbot/certbot/pull/6223#pullrequestreview-143019225 - keys = [x.split('=')[0] for x in directive] - if param in keys: - del directive[keys.index(param)] - return new_vhost - - -def _parse_ssl_options(ssl_options): - if ssl_options is not None: - try: - with open(ssl_options) as _file: - return nginxparser.load(_file) - except IOError: - 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 [] - -def _do_for_subarray(entry, condition, func, path=None): - """Executes a function for a subarray of a nested array if it matches - the given condition. - - :param list entry: The list to iterate over - :param function condition: Returns true iff func should be executed on item - :param function func: The function to call for each matching item - - """ - if path is None: - path = [] - if isinstance(entry, list): - if condition(entry): - func(entry, path) - else: - for index, item in enumerate(entry): - _do_for_subarray(item, condition, func, path + [index]) - - -def get_best_match(target_name, names): - """Finds the best match for target_name out of names using the Nginx - name-matching rules (exact > longest wildcard starting with * > - longest wildcard ending with * > regex). - - :param str target_name: The name to match - :param set names: The candidate server names - :returns: Tuple of (type of match, the name that matched) - :rtype: tuple - - """ - exact = [] - wildcard_start = [] - wildcard_end = [] - regex = [] - - for name in names: - if _exact_match(target_name, name): - exact.append(name) - elif _wildcard_match(target_name, name, True): - wildcard_start.append(name) - elif _wildcard_match(target_name, name, False): - wildcard_end.append(name) - elif _regex_match(target_name, name): - regex.append(name) - - if exact: - # There can be more than one exact match; e.g. eff.org, .eff.org - match = min(exact, key=len) - return ('exact', match) - if wildcard_start: - # Return the longest wildcard - match = max(wildcard_start, key=len) - return ('wildcard_start', match) - if wildcard_end: - # Return the longest wildcard - match = max(wildcard_end, key=len) - return ('wildcard_end', match) - if regex: - # Just return the first one for now - match = regex[0] - return ('regex', match) - - return (None, None) - - -def _exact_match(target_name, name): - return target_name == name or '.' + target_name == name - - -def _wildcard_match(target_name, name, start): - # Degenerate case - if name == '*': - return True - - parts = target_name.split('.') - match_parts = name.split('.') - - # If the domain ends in a wildcard, do the match procedure in reverse - if not start: - parts.reverse() - match_parts.reverse() - - # The first part must be a wildcard or blank, e.g. '.eff.org' - first = match_parts.pop(0) - if first != '*' and first != '': - return False - - target_name = '.'.join(parts) - name = '.'.join(match_parts) - - # Ex: www.eff.org matches *.eff.org, eff.org does not match *.eff.org - return target_name.endswith('.' + name) - - -def _regex_match(target_name, name): - # Must start with a tilde - if len(name) < 2 or name[0] != '~': - return False - - # After tilde is a perl-compatible regex - try: - regex = re.compile(name[1:]) - return re.match(regex, target_name) - except re.error: # pragma: no cover - # perl-compatible regexes are sometimes not recognized by python - return False - - -def _is_include_directive(entry): - """Checks if an nginx parsed entry is an 'include' directive. - - :param list entry: the parsed entry - :returns: Whether it's an 'include' directive - :rtype: bool - - """ - return (isinstance(entry, list) and - len(entry) == 2 and entry[0] == 'include' and - isinstance(entry[1], six.string_types)) - -def _is_ssl_on_directive(entry): - """Checks if an nginx parsed entry is an 'ssl on' directive. - - :param list entry: the parsed entry - :returns: Whether it's an 'ssl on' directive - :rtype: bool - - """ - return (isinstance(entry, list) and - len(entry) == 2 and entry[0] == 'ssl' and - entry[1] == 'on') - -def _add_directives(directives, insert_at_top, block): - """Adds directives to a config block.""" - for directive in directives: - _add_directive(block, directive, insert_at_top) - if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! - block.append(nginxparser.UnspacedList('\n')) - -def _update_or_add_directives(directives, insert_at_top, block): - """Adds or replaces directives in a config block.""" - for directive in directives: - _update_or_add_directive(block, directive, insert_at_top) - if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! - block.append(nginxparser.UnspacedList('\n')) - - -INCLUDE = 'include' -REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'rewrite', 'add_header']) -COMMENT = ' managed by Certbot' -COMMENT_BLOCK = [' ', '#', COMMENT] - -def comment_directive(block, location): - """Add a ``#managed by Certbot`` comment to the end of the line at location. - - :param list block: The block containing the directive to be commented - :param int location: The location within ``block`` of the directive to be commented - """ - next_entry = block[location + 1] if location + 1 < len(block) else None - if isinstance(next_entry, list) and next_entry: - if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: - return - elif isinstance(next_entry, nginxparser.UnspacedList): - next_entry = next_entry.spaced[0] - else: - next_entry = next_entry[0] - - block.insert(location + 1, COMMENT_BLOCK[:]) - if next_entry is not None and "\n" not in next_entry: - block.insert(location + 2, '\n') - -def _comment_out_directive(block, location, include_location): - """Comment out the line at location, with a note of explanation.""" - comment_message = ' duplicated in {0}'.format(include_location) - # add the end comment - # create a dumpable object out of block[location] (so it includes the ;) - directive = block[location] - new_dir_block = nginxparser.UnspacedList([]) # just a wrapper - new_dir_block.append(directive) - dumped = nginxparser.dumps(new_dir_block) - commented = dumped + ' #' + comment_message # add the comment directly to the one-line string - new_dir = nginxparser.loads(commented) # reload into UnspacedList - - # add the beginning comment - insert_location = 0 - if new_dir[0].spaced[0] != new_dir[0][0]: # if there's whitespace at the beginning - insert_location = 1 - new_dir[0].spaced.insert(insert_location, "# ") # comment out the line - new_dir[0].spaced.append(";") # directly add in the ;, because now dumping won't work properly - dumped = nginxparser.dumps(new_dir) - new_dir = nginxparser.loads(dumped) # reload into an UnspacedList - - block[location] = new_dir[0] # set the now-single-line-comment directive back in place - -def _find_location(block, directive_name, match_func=None): - """Finds the index of the first instance of directive_name in block. - If no line exists, use None.""" - return next((index for index, line in enumerate(block) \ - if line and line[0] == directive_name and (match_func is None or match_func(line))), None) - -def _is_whitespace_or_comment(directive): - """Is this directive either a whitespace or comment directive?""" - return len(directive) == 0 or directive[0] == '#' - -def _add_directive(block, directive, insert_at_top): - if not isinstance(directive, nginxparser.UnspacedList): - directive = nginxparser.UnspacedList(directive) - if _is_whitespace_or_comment(directive): - # whitespace or comment - block.append(directive) - return - - location = _find_location(block, directive[0]) - - # Append or prepend directive. Fail if the name is not a repeatable directive name, - # and there is already a copy of that directive with a different value - # in the config file. - - # handle flat include files - - directive_name = directive[0] - def can_append(loc, dir_name): - """ Can we append this directive to the block? """ - return loc is None or (isinstance(dir_name, six.string_types) - and dir_name in REPEATABLE_DIRECTIVES) - - err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".' - - # Give a better error message about the specific directive than Nginx's "fail to restart" - if directive_name == INCLUDE: - # in theory, we might want to do this recursively, but in practice, that's really not - # necessary because we know what file we're talking about (and if we don't recurse, we - # just give a worse error message) - included_directives = _parse_ssl_options(directive[1]) - - for included_directive in included_directives: - included_dir_loc = _find_location(block, included_directive[0]) - included_dir_name = included_directive[0] - if not _is_whitespace_or_comment(included_directive) \ - and not can_append(included_dir_loc, included_dir_name): - if block[included_dir_loc] != included_directive: - raise errors.MisconfigurationError(err_fmt.format(included_directive, - block[included_dir_loc])) - else: - _comment_out_directive(block, included_dir_loc, directive[1]) - - if can_append(location, directive_name): - if insert_at_top: - # Add a newline so the comment doesn't comment - # out existing directives - block.insert(0, nginxparser.UnspacedList('\n')) - block.insert(0, directive) - comment_directive(block, 0) - else: - block.append(directive) - comment_directive(block, len(block) - 1) - elif block[location] != directive: - raise errors.MisconfigurationError(err_fmt.format(directive, block[location])) - -def _update_directive(block, directive, location): - block[location] = directive - comment_directive(block, location) - -def _update_or_add_directive(block, directive, insert_at_top): - if not isinstance(directive, nginxparser.UnspacedList): - directive = nginxparser.UnspacedList(directive) - if _is_whitespace_or_comment(directive): - # whitespace or comment - block.append(directive) - return - - location = _find_location(block, directive[0]) - - # we can update directive - if location is not None: - _update_directive(block, directive, location) - return - - _add_directive(block, directive, insert_at_top) - -def _is_certbot_comment(directive): - return '#' in directive and COMMENT in directive - -def _remove_directives(directive_name, match_func, block): - """Removes directives of name directive_name from a config block if match_func matches. - """ - while True: - location = _find_location(block, directive_name, match_func=match_func) - if location is None: - return - # if the directive was made by us, remove the comment following - if location + 1 < len(block) and _is_certbot_comment(block[location + 1]): - del block[location + 1] - del block[location] - -def _apply_global_addr_ssl(addr_to_ssl, parsed_server): - """Apply global sslishness information to the parsed server block - """ - for addr in parsed_server['addrs']: - addr.ssl = addr_to_ssl[addr.normalized_tuple()] - if addr.ssl: - parsed_server['ssl'] = True - -def _parse_server_raw(server): - """Parses a list of server directives. - - :param list server: list of directives in a server block - :rtype: dict - - """ - addrs = set() # type: Set[obj.Addr] - ssl = False # type: bool - names = set() # type: Set[str] - - apply_ssl_to_all_addrs = False - - for directive in server: - if not directive: - continue - if directive[0] == 'listen': - addr = obj.Addr.fromstring(" ".join(directive[1:])) - if addr: - addrs.add(addr) - if addr.ssl: - ssl = True - elif directive[0] == 'server_name': - names.update(x.strip('"\'') for x in directive[1:]) - elif _is_ssl_on_directive(directive): - ssl = True - apply_ssl_to_all_addrs = True - - if apply_ssl_to_all_addrs: - for addr in addrs: - addr.ssl = True - - return { - 'addrs': addrs, - 'ssl': ssl, - 'names': names - } diff --git a/certbot-nginx/certbot_nginx/parser_obj.py b/certbot-nginx/certbot_nginx/parser_obj.py deleted file mode 100644 index 71e8c6088..000000000 --- a/certbot-nginx/certbot_nginx/parser_obj.py +++ /dev/null @@ -1,396 +0,0 @@ -""" This file contains parsing routines and object classes to help derive meaning from -raw lists of tokens from pyparsing. """ - -import abc -import logging -import six - -from certbot import errors - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -logger = logging.getLogger(__name__) -COMMENT = " managed by Certbot" -COMMENT_BLOCK = ["#", COMMENT] - - -class Parsable(object): - """ Abstract base class for "Parsable" objects whose underlying representation - is a tree of lists. - - :param .Parsable parent: This object's parsed parent in the tree - """ - - __metaclass__ = abc.ABCMeta - - def __init__(self, parent=None): - self._data = [] # type: List[object] - self._tabs = None - self.parent = parent - - @classmethod - def parsing_hooks(cls): - """Returns object types that this class should be able to `parse` recusrively. - The order of the objects indicates the order in which the parser should - try to parse each subitem. - :returns: A list of Parsable classes. - :rtype list: - """ - return (Block, Sentence, Statements) - - @staticmethod - @abc.abstractmethod - def should_parse(lists): - """ Returns whether the contents of `lists` can be parsed into this object. - - :returns: Whether `lists` can be parsed as this object. - :rtype bool: - """ - raise NotImplementedError() - - @abc.abstractmethod - def parse(self, raw_list, add_spaces=False): - """ Loads information into this object from underlying raw_list structure. - Each Parsable object might make different assumptions about the structure of - raw_list. - - :param list raw_list: A list or sublist of tokens from pyparsing, containing whitespace - as separate tokens. - :param bool add_spaces: If set, the method can and should manipulate and insert spacing - between non-whitespace tokens and lists to delimit them. - :raises .errors.MisconfigurationError: when the assumptions about the structure of - raw_list are not met. - """ - raise NotImplementedError() - - @abc.abstractmethod - def iterate(self, expanded=False, match=None): - """ Iterates across this object. If this object is a leaf object, only yields - itself. If it contains references other parsing objects, and `expanded` is set, - this function should first yield itself, then recursively iterate across all of them. - :param bool expanded: Whether to recursively iterate on possible children. - :param callable match: If provided, an object is only iterated if this callable - returns True when called on that object. - - :returns: Iterator over desired objects. - """ - raise NotImplementedError() - - @abc.abstractmethod - def get_tabs(self): - """ Guess at the tabbing style of this parsed object, based on whitespace. - - If this object is a leaf, it deducts the tabbing based on its own contents. - Other objects may guess by calling `get_tabs` recursively on child objects. - - :returns: Guess at tabbing for this object. Should only return whitespace strings - that does not contain newlines. - :rtype str: - """ - raise NotImplementedError() - - @abc.abstractmethod - def set_tabs(self, tabs=" "): - """This tries to set and alter the tabbing of the current object to a desired - whitespace string. Primarily meant for objects that were constructed, so they - can conform to surrounding whitespace. - - :param str tabs: A whitespace string (not containing newlines). - """ - raise NotImplementedError() - - def dump(self, include_spaces=False): - """ Dumps back to pyparsing-like list tree. The opposite of `parse`. - - Note: if this object has not been modified, `dump` with `include_spaces=True` - should always return the original input of `parse`. - - :param bool include_spaces: If set to False, magically hides whitespace tokens from - dumped output. - - :returns: Pyparsing-like list tree. - :rtype list: - """ - return [elem.dump(include_spaces) for elem in self._data] - - -class Statements(Parsable): - """ A group or list of "Statements". A Statement is either a Block or a Sentence. - - The underlying representation is simply a list of these Statement objects, with - an extra `_trailing_whitespace` string to keep track of the whitespace that does not - precede any more statements. - """ - def __init__(self, parent=None): - super(Statements, self).__init__(parent) - self._trailing_whitespace = None - - # ======== Begin overridden functions - - @staticmethod - def should_parse(lists): - return isinstance(lists, list) - - def set_tabs(self, tabs=" "): - """ Sets the tabbing for this set of statements. Does this by calling `set_tabs` - on each of the child statements. - - Then, if a parent is present, sets trailing whitespace to parent tabbing. This - is so that the trailing } of any Block that contains Statements lines up - with parent tabbing. - """ - for statement in self._data: - statement.set_tabs(tabs) - if self.parent is not None: - self._trailing_whitespace = "\n" + self.parent.get_tabs() - - def parse(self, raw_list, add_spaces=False): - """ Parses a list of statements. - Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`, - with an optional whitespace string at the last index of `raw_list`. - """ - if not isinstance(raw_list, list): - raise errors.MisconfigurationError("Statements parsing expects a list!") - # If there's a trailing whitespace in the list of statements, keep track of it. - if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace(): - self._trailing_whitespace = raw_list[-1] - raw_list = raw_list[:-1] - self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list] - - def get_tabs(self): - """ Takes a guess at the tabbing of all contained Statements by retrieving the - tabbing of the first Statement.""" - if self._data: - return self._data[0].get_tabs() - return "" - - def dump(self, include_spaces=False): - """ Dumps this object by first dumping each statement, then appending its - trailing whitespace (if `include_spaces` is set) """ - data = super(Statements, self).dump(include_spaces) - if include_spaces and self._trailing_whitespace is not None: - return data + [self._trailing_whitespace] - return data - - def iterate(self, expanded=False, match=None): - """ Combines each statement's iterator. """ - for elem in self._data: - for sub_elem in elem.iterate(expanded, match): - yield sub_elem - - # ======== End overridden functions - - -def _space_list(list_): - """ Inserts whitespace between adjacent non-whitespace tokens. """ - spaced_statement = [] # type: List[str] - for i in reversed(six.moves.xrange(len(list_))): - spaced_statement.insert(0, list_[i]) - if i > 0 and not list_[i].isspace() and not list_[i-1].isspace(): - spaced_statement.insert(0, " ") - return spaced_statement - - -class Sentence(Parsable): - """ A list of words. Non-whitespace words are typically separated with whitespace tokens. """ - - # ======== Begin overridden functions - - @staticmethod - def should_parse(lists): - """ Returns True if `lists` can be parseable as a `Sentence`-- that is, - every element is a string type. - - :param list lists: The raw unparsed list to check. - - :returns: whether this lists is parseable by `Sentence`. - """ - return isinstance(lists, list) and len(lists) > 0 and \ - all([isinstance(elem, six.string_types) for elem in lists]) - - def parse(self, raw_list, add_spaces=False): - """ Parses a list of string types into this object. - If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens.""" - if add_spaces: - raw_list = _space_list(raw_list) - if not isinstance(raw_list, list) or \ - any([not isinstance(elem, six.string_types) for elem in raw_list]): - raise errors.MisconfigurationError("Sentence parsing expects a list of string types.") - self._data = raw_list - - def iterate(self, expanded=False, match=None): - """ Simply yields itself. """ - if match is None or match(self): - yield self - - def set_tabs(self, tabs=" "): - """ Sets the tabbing on this sentence. Inserts a newline and `tabs` at the - beginning of `self._data`. """ - if self._data[0].isspace(): - return - self._data.insert(0, "\n" + tabs) - - def dump(self, include_spaces=False): - """ Dumps this sentence. If include_spaces is set, includes whitespace tokens.""" - if not include_spaces: - return self.words - return self._data - - def get_tabs(self): - """ Guesses at the tabbing of this sentence. If the first element is whitespace, - returns the whitespace after the rightmost newline in the string. """ - first = self._data[0] - if not first.isspace(): - return "" - rindex = first.rfind("\n") - return first[rindex+1:] - - # ======== End overridden functions - - @property - def words(self): - """ Iterates over words, but without spaces. Like Unspaced List. """ - return [word.strip("\"\'") for word in self._data if not word.isspace()] - - def __getitem__(self, index): - return self.words[index] - - def __contains__(self, word): - return word in self.words - - -class Block(Parsable): - """ Any sort of bloc, denoted by a block name and curly braces, like so: - The parsed block: - block name { - content 1; - content 2; - } - might be represented with the list [names, contents], where - names = ["block", " ", "name", " "] - contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"] - """ - def __init__(self, parent=None): - super(Block, self).__init__(parent) - self.names = None # type: Sentence - self.contents = None # type: Block - - @staticmethod - def should_parse(lists): - """ Returns True if `lists` can be parseable as a `Block`-- that is, - it's got a length of 2, the first element is a `Sentence` and the second can be - a `Statements`. - - :param list lists: The raw unparsed list to check. - - :returns: whether this lists is parseable by `Block`. """ - return isinstance(lists, list) and len(lists) == 2 and \ - Sentence.should_parse(lists[0]) and isinstance(lists[1], list) - - def set_tabs(self, tabs=" "): - """ Sets tabs by setting equivalent tabbing on names, then adding tabbing - to contents.""" - self.names.set_tabs(tabs) - self.contents.set_tabs(tabs + " ") - - def iterate(self, expanded=False, match=None): - """ Iterator over self, and if expanded is set, over its contents. """ - if match is None or match(self): - yield self - if expanded: - for elem in self.contents.iterate(expanded, match): - yield elem - - def parse(self, raw_list, add_spaces=False): - """ Parses a list that resembles a block. - - The assumptions that this routine makes are: - 1. the first element of `raw_list` is a valid Sentence. - 2. the second element of `raw_list` is a valid Statement. - If add_spaces is set, we call it recursively on `names` and `contents`, and - add an extra trailing space to `names` (to separate the block's opening bracket - and the block name). - """ - if not Block.should_parse(raw_list): - raise errors.MisconfigurationError("Block parsing expects a list of length 2. " - "First element should be a list of string types (the bloc names), " - "and second should be another list of statements (the bloc content).") - self.names = Sentence(self) - if add_spaces: - raw_list[0].append(" ") - self.names.parse(raw_list[0], add_spaces) - self.contents = Statements(self) - self.contents.parse(raw_list[1], add_spaces) - self._data = [self.names, self.contents] - - def get_tabs(self): - """ Guesses tabbing by retrieving tabbing guess of self.names. """ - return self.names.get_tabs() - -def _is_comment(parsed_obj): - """ Checks whether parsed_obj is a comment. - - :param .Parsable parsed_obj: - - :returns: whether parsed_obj represents a comment sentence. - :rtype bool: - """ - if not isinstance(parsed_obj, Sentence): - return False - return parsed_obj.words[0] == "#" - -def _is_certbot_comment(parsed_obj): - """ Checks whether parsed_obj is a "managed by Certbot" comment. - - :param .Parsable parsed_obj: - - :returns: whether parsed_obj is a "managed by Certbot" comment. - :rtype bool: - """ - if not _is_comment(parsed_obj): - return False - if len(parsed_obj.words) != len(COMMENT_BLOCK): - return False - for i, word in enumerate(parsed_obj.words): - if word != COMMENT_BLOCK[i]: - return False - return True - -def _certbot_comment(parent, preceding_spaces=4): - """ A "Managed by Certbot" comment. - :param int preceding_spaces: Number of spaces between the end of the previous - statement and the comment. - :returns: Sentence containing the comment. - :rtype: .Sentence - """ - result = Sentence(parent) - result.parse([" " * preceding_spaces] + COMMENT_BLOCK) - return result - -def _choose_parser(parent, list_): - """ Choose a parser from type(parent).parsing_hooks, depending on whichever hook - returns True first. """ - hooks = Parsable.parsing_hooks() - if parent: - hooks = type(parent).parsing_hooks() - for type_ in hooks: - if type_.should_parse(list_): - return type_(parent) - raise errors.MisconfigurationError( - "None of the parsing hooks succeeded, so we don't know how to parse this set of lists.") - -def parse_raw(lists_, parent=None, add_spaces=False): - """ Primary parsing factory function. - - :param list lists_: raw lists from pyparsing to parse. - :param .Parent parent: The parent containing this object. - :param bool add_spaces: Whether to pass add_spaces to the parser. - - :returns .Parsable: The parsed object. - - :raises errors.MisconfigurationError: If no parsing hook passes, and we can't - determine which type to parse the raw lists into. - """ - parser = _choose_parser(parent, lists_) - parser.parse(lists_, add_spaces) - return parser diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py index 6b3438d89..a7327166f 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_nginx.configurator.""" +"""Test for certbot_nginx._internal.configurator.""" import unittest import OpenSSL @@ -12,10 +12,10 @@ from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_test_util -from certbot_nginx import obj -from certbot_nginx import parser -from certbot_nginx.configurator import _redirect_block_for_domain -from certbot_nginx.nginxparser import UnspacedList +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser +from certbot_nginx._internal.configurator import _redirect_block_for_domain +from certbot_nginx._internal.nginxparser import UnspacedList from certbot_nginx.tests import util @@ -29,7 +29,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) - @mock.patch("certbot_nginx.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") def test_prepare_no_install(self, mock_exe_exists): mock_exe_exists.return_value = False self.assertRaises( @@ -39,8 +39,8 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual((1, 6, 2), self.config.version) self.assertEqual(11, len(self.config.parser.parsed)) - @mock.patch("certbot_nginx.configurator.util.exe_exists") - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_prepare_initializes_version(self, mock_popen, mock_exe_exists): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.6.2", @@ -66,7 +66,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.config_test = mock.Mock() certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) - @mock.patch("certbot_nginx.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") def _test_prepare_locked(self, unused_exe_exists): try: self.config.prepare() @@ -77,7 +77,7 @@ class NginxConfiguratorTest(util.NginxTest): else: # pragma: no cover self.fail("Exception wasn't raised!") - @mock.patch("certbot_nginx.configurator.socket.gethostbyaddr") + @mock.patch("certbot_nginx._internal.configurator.socket.gethostbyaddr") def test_get_all_names(self, mock_gethostbyaddr): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) names = self.config.get_all_names() @@ -217,7 +217,7 @@ class NginxConfiguratorTest(util.NginxTest): "example/chain.pem", None) - @mock.patch('certbot_nginx.parser.NginxParser.update_or_add_server_directives') + @mock.patch('certbot_nginx._internal.parser.NginxParser.update_or_add_server_directives') def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() self.assertRaises( @@ -315,9 +315,9 @@ class NginxConfiguratorTest(util.NginxTest): ]], parsed_migration_conf[0]) - @mock.patch("certbot_nginx.configurator.http_01.NginxHttp01.perform") - @mock.patch("certbot_nginx.configurator.NginxConfigurator.restart") - @mock.patch("certbot_nginx.configurator.NginxConfigurator.revert_challenge_config") + @mock.patch("certbot_nginx._internal.configurator.http_01.NginxHttp01.perform") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.restart") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.revert_challenge_config") def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -343,7 +343,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual(mock_revert.call_count, 1) self.assertEqual(mock_restart.call_count, 2) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", @@ -393,7 +393,7 @@ class NginxConfiguratorTest(util.NginxTest): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_get_openssl_version(self, mock_popen): # pylint: disable=protected-access mock_popen().communicate.return_value = ( @@ -455,21 +455,21 @@ class NginxConfiguratorTest(util.NginxTest): """) self.assertEqual(self.config._get_openssl_version(), "") - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_nginx_restart(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 0 self.config.restart() - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_nginx_restart_fail(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 1 self.assertRaises(errors.MisconfigurationError, self.config.restart) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_no_nginx_start(self, mock_popen): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.MisconfigurationError, self.config.restart) @@ -623,20 +623,20 @@ class NginxConfiguratorTest(util.NginxTest): "ensure-http-header", "Strict-Transport-Security") - @mock.patch('certbot_nginx.obj.VirtualHost.contains_list') + @mock.patch('certbot_nginx._internal.obj.VirtualHost.contains_list') def test_certbot_redirect_exists(self, mock_contains_list): # Test that we add no redirect statement if there is already a # redirect in the block that is managed by certbot # Has a certbot redirect mock_contains_list.return_value = True - with mock.patch("certbot_nginx.configurator.logger") as mock_logger: + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("www.example.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], "Traffic on port %s already redirecting to ssl in %s") def test_redirect_dont_enhance(self): # Test that we don't accidentally add redirect to ssl-only block - with mock.patch("certbot_nginx.configurator.logger") as mock_logger: + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("geese.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], 'No matching insecure server blocks listening on port %s found.') @@ -827,7 +827,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) @mock.patch('certbot.reverter.logger') - @mock.patch('certbot_nginx.parser.NginxParser.load') + @mock.patch('certbot_nginx._internal.parser.NginxParser.load') def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger): self.config.recovery_routine() self.config.revert_challenge_config() @@ -836,7 +836,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -852,7 +852,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard_redirect(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -873,7 +873,7 @@ class NginxConfiguratorTest(util.NginxTest): if 'geese.com' in x.names][0] mock_choose_vhosts.return_value = [vhost] self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_nginx.configurator.NginxConfigurator._deploy_cert" + mock_d = "certbot_nginx._internal.configurator.NginxConfigurator._deploy_cert" with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.com", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @@ -881,7 +881,7 @@ class NginxConfiguratorTest(util.NginxTest): 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") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] @@ -890,7 +890,7 @@ class NginxConfiguratorTest(util.NginxTest): "*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_ocsp_after_install(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() @@ -899,7 +899,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertFalse(mock_dialog.called) - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -907,7 +907,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertTrue(mock_dialog.called) - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_double_redirect(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() @@ -918,7 +918,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard_no_ssl_filter_port(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [] self.config._choose_vhosts_wildcard("*.com", @@ -974,14 +974,14 @@ class InstallSslOptionsConfTest(util.NginxTest): return _hash def test_prev_file_updates_to_current(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() def test_prev_file_updates_to_current_old_nginx(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES self.config.version = (1, 5, 8) with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): @@ -1018,7 +1018,7 @@ class InstallSslOptionsConfTest(util.NginxTest): self.assertFalse(mock_logger.warning.called) def test_current_file_hash_in_all_hashes(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") @@ -1030,10 +1030,11 @@ class InstallSslOptionsConfTest(util.NginxTest): file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES import pkg_resources all_files = [ - pkg_resources.resource_filename("certbot_nginx", os.path.join("tls_configs", x)) + pkg_resources.resource_filename("certbot_nginx", + os.path.join("_internal", "tls_configs", x)) for x in ("options-ssl-nginx.conf", "options-ssl-nginx-old.conf", "options-ssl-nginx-tls12-only.conf") @@ -1070,10 +1071,10 @@ class InstallSslOptionsConfTest(util.NginxTest): class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): - """Tests for certbot_nginx.configurator._determine_default_server_root.""" + """Tests for certbot_nginx._internal.configurator._determine_default_server_root.""" def _call(self): - from certbot_nginx.configurator import _determine_default_server_root + from certbot_nginx._internal.configurator import _determine_default_server_root return _determine_default_server_root() @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) diff --git a/certbot-nginx/certbot_nginx/tests/display_ops_test.py b/certbot-nginx/certbot_nginx/tests/display_ops_test.py index d36765767..a03c5d265 100644 --- a/certbot-nginx/certbot_nginx/tests/display_ops_test.py +++ b/certbot-nginx/certbot_nginx/tests/display_ops_test.py @@ -5,14 +5,14 @@ from certbot.display import util as display_util from certbot.tests import util as certbot_util -from certbot_nginx import parser +from certbot_nginx._internal import parser -from certbot_nginx.display_ops import select_vhost_multiple +from certbot_nginx._internal.display_ops import select_vhost_multiple from certbot_nginx.tests import util class SelectVhostMultiTest(util.NginxTest): - """Tests for certbot_nginx.display_ops.select_vhost_multiple.""" + """Tests for certbot_nginx._internal.display_ops.select_vhost_multiple.""" def setUp(self): super(SelectVhostMultiTest, self).setUp() diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py index 8e0450f6a..0335aab01 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/certbot_nginx/tests/http_01_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_nginx.http_01""" +"""Tests for certbot_nginx._internal.http_01""" import unittest import josepy as jose @@ -12,7 +12,7 @@ from certbot import achallenges from certbot.tests import acme_util from certbot.tests import util as test_util -from certbot_nginx.obj import Addr +from certbot_nginx._internal.obj import Addr from certbot_nginx.tests import util AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -53,14 +53,14 @@ class HttpPerformTest(util.NginxTest): config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) - from certbot_nginx import http_01 + from certbot_nginx._internal import http_01 self.http01 = http_01.NginxHttp01(config) def test_perform0(self): responses = self.http01.perform() self.assertEqual([], responses) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.save") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.save") def test_perform1(self, mock_save): self.http01.add_chall(self.achalls[0]) response = self.achalls[0].response(self.account_key) @@ -106,7 +106,7 @@ class HttpPerformTest(util.NginxTest): # self.assertEqual(vhost.addrs, set(v_addr2_print)) # self.assertEqual(vhost.names, set([response.z_domain.decode('ascii')])) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_no_memoization(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, True) @@ -116,7 +116,7 @@ class HttpPerformTest(util.NginxTest): self.http01._default_listen_addresses() self.assertEqual(ipv6_info.call_count, 2) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_t(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, True) @@ -125,7 +125,7 @@ class HttpPerformTest(util.NginxTest): http_ipv6_addr = Addr.fromstring("[::]:80") self.assertEqual(addrs, [http_addr, http_ipv6_addr]) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_f(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, False) @@ -134,7 +134,7 @@ class HttpPerformTest(util.NginxTest): http_ipv6_addr = Addr.fromstring("[::]:80 ipv6only=on") self.assertEqual(addrs, [http_addr, http_ipv6_addr]) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_f_f(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (False, False) diff --git a/certbot-nginx/certbot_nginx/tests/nginxparser_test.py b/certbot-nginx/certbot_nginx/tests/nginxparser_test.py index 7fc4576c3..74671a0ec 100644 --- a/certbot-nginx/certbot_nginx/tests/nginxparser_test.py +++ b/certbot-nginx/certbot_nginx/tests/nginxparser_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_nginx.nginxparser.""" +"""Test for certbot_nginx._internal.nginxparser.""" import copy import operator import tempfile @@ -6,7 +6,7 @@ import unittest from pyparsing import ParseException -from certbot_nginx.nginxparser import ( +from certbot_nginx._internal.nginxparser import ( RawNginxParser, loads, load, dumps, dump, UnspacedList) from certbot_nginx.tests import util diff --git a/certbot-nginx/certbot_nginx/tests/obj_test.py b/certbot-nginx/certbot_nginx/tests/obj_test.py index 9e5853c4a..351b33e82 100644 --- a/certbot-nginx/certbot_nginx/tests/obj_test.py +++ b/certbot-nginx/certbot_nginx/tests/obj_test.py @@ -1,4 +1,4 @@ -"""Test the helper objects in certbot_nginx.obj.""" +"""Test the helper objects in certbot_nginx._internal.obj.""" import unittest import itertools @@ -6,7 +6,7 @@ import itertools class AddrTest(unittest.TestCase): """Test the Addr class.""" def setUp(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr self.addr1 = Addr.fromstring("192.168.1.1") self.addr2 = Addr.fromstring("192.168.1.1:* ssl") self.addr3 = Addr.fromstring("192.168.1.1:80") @@ -71,14 +71,14 @@ class AddrTest(unittest.TestCase): self.assertEqual(self.addr6.to_string(include_default=False), "80") def test_eq(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr new_addr1 = Addr.fromstring("192.168.1.1 spdy") self.assertEqual(self.addr1, new_addr1) self.assertNotEqual(self.addr1, self.addr2) self.assertFalse(self.addr1 == 3333) def test_equivalent_any_addresses(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr any_addresses = ("0.0.0.0:80 default_server ssl", "80 default_server ssl", "*:80 default_server ssl", @@ -97,7 +97,7 @@ class AddrTest(unittest.TestCase): Addr.fromstring(any_address)) def test_set_inclusion(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr set_a = set([self.addr1, self.addr2]) addr1b = Addr.fromstring("192.168.1.1") addr2b = Addr.fromstring("192.168.1.1:* ssl") @@ -109,8 +109,8 @@ class AddrTest(unittest.TestCase): class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from certbot_nginx.obj import VirtualHost - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import VirtualHost + from certbot_nginx._internal.obj import Addr raw1 = [ ['listen', '69.50.225.155:9000'], [['if', '($scheme', '!=', '"https") '], @@ -159,8 +159,8 @@ class VirtualHostTest(unittest.TestCase): set(['localhost']), raw_has_hsts, []) def test_eq(self): - from certbot_nginx.obj import Addr - from certbot_nginx.obj import VirtualHost + from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.obj import VirtualHost vhost1b = VirtualHost( "filep", set([Addr.fromstring("localhost blah")]), False, False, @@ -183,9 +183,9 @@ class VirtualHostTest(unittest.TestCase): self.assertFalse(self.vhost1.has_header('Bogus-Header')) def test_contains_list(self): - from certbot_nginx.obj import VirtualHost - from certbot_nginx.obj import Addr - from certbot_nginx.configurator import _test_block_from_block + from certbot_nginx._internal.obj import VirtualHost + from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.configurator import _test_block_from_block test_block = [ ['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], ['\n'] diff --git a/certbot-nginx/certbot_nginx/tests/parser_obj_test.py b/certbot-nginx/certbot_nginx/tests/parser_obj_test.py index 2217be54f..084b17303 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_obj_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_obj_test.py @@ -3,18 +3,18 @@ import unittest import mock -from certbot_nginx.parser_obj import parse_raw -from certbot_nginx.parser_obj import COMMENT_BLOCK +from certbot_nginx._internal.parser_obj import parse_raw +from certbot_nginx._internal.parser_obj import COMMENT_BLOCK class CommentHelpersTest(unittest.TestCase): def test_is_comment(self): - from certbot_nginx.parser_obj import _is_comment + from certbot_nginx._internal.parser_obj import _is_comment self.assertTrue(_is_comment(parse_raw(['#']))) self.assertTrue(_is_comment(parse_raw(['#', ' literally anything else']))) self.assertFalse(_is_comment(parse_raw(['not', 'even', 'a', 'comment']))) def test_is_certbot_comment(self): - from certbot_nginx.parser_obj import _is_certbot_comment + from certbot_nginx._internal.parser_obj import _is_certbot_comment self.assertTrue(_is_certbot_comment( parse_raw(COMMENT_BLOCK))) self.assertFalse(_is_certbot_comment( @@ -25,7 +25,7 @@ class CommentHelpersTest(unittest.TestCase): parse_raw(['not', 'even', 'a', 'comment']))) def test_certbot_comment(self): - from certbot_nginx.parser_obj import _certbot_comment, _is_certbot_comment + from certbot_nginx._internal.parser_obj import _certbot_comment, _is_certbot_comment comment = _certbot_comment(None) self.assertTrue(_is_certbot_comment(comment)) self.assertEqual(comment.dump(), COMMENT_BLOCK) @@ -35,7 +35,7 @@ class CommentHelpersTest(unittest.TestCase): class ParsingHooksTest(unittest.TestCase): def test_is_sentence(self): - from certbot_nginx.parser_obj import Sentence + from certbot_nginx._internal.parser_obj import Sentence self.assertFalse(Sentence.should_parse([])) self.assertTrue(Sentence.should_parse([''])) self.assertTrue(Sentence.should_parse(['word'])) @@ -44,7 +44,7 @@ class ParsingHooksTest(unittest.TestCase): self.assertFalse(Sentence.should_parse(['word', []])) def test_is_block(self): - from certbot_nginx.parser_obj import Block + from certbot_nginx._internal.parser_obj import Block self.assertFalse(Block.should_parse([])) self.assertFalse(Block.should_parse([''])) self.assertFalse(Block.should_parse(['two', 'words'])) @@ -71,7 +71,7 @@ class ParsingHooksTest(unittest.TestCase): fake_parser1.not_called() fake_parser2.called_once() - @mock.patch("certbot_nginx.parser_obj.Parsable.parsing_hooks") + @mock.patch("certbot_nginx._internal.parser_obj.Parsable.parsing_hooks") def test_parse_raw_no_match(self, parsing_hooks): from certbot import errors fake_parser1 = mock.Mock() @@ -91,7 +91,7 @@ class ParsingHooksTest(unittest.TestCase): class SentenceTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Sentence + from certbot_nginx._internal.parser_obj import Sentence self.sentence = Sentence(None) def test_parse_bad_sentence_raises_error(self): @@ -137,7 +137,7 @@ class SentenceTest(unittest.TestCase): class BlockTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Block + from certbot_nginx._internal.parser_obj import Block self.bloc = Block(None) self.name = ['server', 'name'] self.contents = [['thing', '1'], ['thing', '2'], ['another', 'one']] @@ -153,7 +153,7 @@ class BlockTest(unittest.TestCase): def test_iterate_match(self): # can match on contents while expanded - from certbot_nginx.parser_obj import Block, Sentence + from certbot_nginx._internal.parser_obj import Block, Sentence 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)): @@ -192,7 +192,7 @@ class BlockTest(unittest.TestCase): class StatementsTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Statements + from certbot_nginx._internal.parser_obj import Statements self.statements = Statements(None) self.raw = [ ['sentence', 'one'], diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/certbot_nginx/tests/parser_test.py index 323830013..a65247dca 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/certbot_nginx/tests/parser_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_nginx.parser.""" +"""Tests for certbot_nginx._internal.parser.""" import glob import re import shutil @@ -9,9 +9,9 @@ from acme.magic_typing import List # pylint: disable=unused-import, no-name-in- from certbot import errors from certbot.compat import os -from certbot_nginx import nginxparser -from certbot_nginx import obj -from certbot_nginx import parser +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser from certbot_nginx.tests import util @@ -253,7 +253,7 @@ class NginxParserTest(util.NginxTest): [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -288,7 +288,7 @@ class NginxParserTest(util.NginxTest): nparser.add_server_directives(mock_vhost, [['\n ', 'include', ' ', nparser.abs_path('comment_in_file.conf')]]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -308,7 +308,7 @@ class NginxParserTest(util.NginxTest): mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0]) nparser.update_or_add_server_directives( mock_vhost, [['server_name', 'foobar.com']]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual( nparser.parsed[filep], [[['server'], [['listen', '69.50.225.155:9000'], @@ -367,7 +367,7 @@ class NginxParserTest(util.NginxTest): ["\n", "a", " ", "b", "\n"], ["c", " ", "d"], ["\n", "e", " ", "f"]]) - from certbot_nginx.parser import comment_directive, COMMENT_BLOCK + from certbot_nginx._internal.parser import comment_directive, COMMENT_BLOCK comment_directive(block, 1) comment_directive(block, 0) self.assertEqual(block.spaced, [ @@ -391,7 +391,7 @@ class NginxParserTest(util.NginxTest): ssl_prefer_server_ciphers on; }""") block = server_block[0][1] - from certbot_nginx.parser import _comment_out_directive + from certbot_nginx._internal.parser import _comment_out_directive _comment_out_directive(block, 4, "blah1") _comment_out_directive(block, 5, "blah2") _comment_out_directive(block, 6, "blah3") diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py index 8be285050..b238e6232 100644 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ b/certbot-nginx/certbot_nginx/tests/util.py @@ -13,8 +13,8 @@ from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util -from certbot_nginx import configurator -from certbot_nginx import nginxparser +from certbot_nginx._internal import configurator +from certbot_nginx._internal import nginxparser class NginxTest(test_util.ConfigTestCase): @@ -64,9 +64,9 @@ class NginxTest(test_util.ConfigTestCase): self.configuration.http01_port = 80 self.configuration.https_port = 5001 - with mock.patch("certbot_nginx.configurator.NginxConfigurator." + with mock.patch("certbot_nginx._internal.configurator.NginxConfigurator." "config_test"): - with mock.patch("certbot_nginx.configurator.util." + with mock.patch("certbot_nginx._internal.configurator.util." "exe_exists") as mock_exe_exists: mock_exe_exists.return_value = True config = configurator.NginxConfigurator( diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf deleted file mode 100644 index 731e38919..000000000 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-old.conf +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; - -ssl_protocols TLSv1.2; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf deleted file mode 100644 index 33771a189..000000000 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls12-only.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; -ssl_session_tickets off; - -ssl_protocols TLSv1.2; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf deleted file mode 100644 index 91197d2c8..000000000 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; - -ssl_protocols TLSv1.2 TLSv1.3; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf deleted file mode 100644 index 98b1c4ab9..000000000 --- a/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:10m; -ssl_session_timeout 1440m; -ssl_session_tickets off; - -ssl_protocols TLSv1.2 TLSv1.3; -ssl_prefer_server_ciphers off; - -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 671fcef9c..f7ee46a6a 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -71,7 +71,7 @@ setup( install_requires=install_requires, entry_points={ 'certbot.plugins': [ - 'nginx = certbot_nginx.configurator:NginxConfigurator', + 'nginx = certbot_nginx._internal.configurator:NginxConfigurator', ], }, test_suite='certbot_nginx', -- cgit v1.2.3 From 345bdb46e041b0da6fe56874f3ce53a2829fdc29 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 15:42:01 -0800 Subject: Update pull_request_template.md (#7596) * Update pull_request_template.md * Remove line breaks Github seems to be keeping the line breaks rather than ignoring them, making it be formatted weirdly, so remove them. --- pull_request_template.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pull_request_template.md b/pull_request_template.md index 9bf8f3f2d..227dda49c 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,10 +1,5 @@ ## Pull Request Checklist -- [ ] If the change being made is to a [distributed - component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), - edit the `master` section of `CHANGELOG.md` to include a description of the - change being made. -- [ ] Add [mypy type - annotations](https://certbot.eff.org/docs/contributing.html#mypy-type-annotations) - for any functions that were added or modified. +- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made. +- [ ] Add [mypy type annotations](https://certbot.eff.org/docs/contributing.html#mypy-type-annotations) for any functions that were added or modified. - [ ] Include your name in `AUTHORS.md` if you like. -- cgit v1.2.3 From 5c8083851a230df42eb4493020086eef51ab073e Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 18:24:20 -0800 Subject: Fix refactor (#7597) Clean up some places missed by #7544. Found this when running test farm tests. They were working as of 5d90544, and I will truly shocked if subsequent changes (all to the windows installer) made them stop working. * Release script needs to target new CHANGELOG location * Clean up various other CHANGELOG path references * Update windows paths for new certbot location * Add certbot to packages list for windows installer --- .azure-pipelines/templates/changelog.yml | 2 +- certbot-compatibility-test/Dockerfile | 2 +- certbot/README.rst | 2 +- tools/_release.sh | 14 +++++++------- tools/extract_changelog.py | 2 +- windows-installer/construct.py | 5 +++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.azure-pipelines/templates/changelog.yml b/.azure-pipelines/templates/changelog.yml index 7619858a9..4a65e2c2b 100644 --- a/.azure-pipelines/templates/changelog.yml +++ b/.azure-pipelines/templates/changelog.yml @@ -4,7 +4,7 @@ jobs: vmImage: vs2017-win2016 steps: - bash: | - CERTBOT_VERSION="$(python -c "import certbot; print(certbot.__version__)")" + CERTBOT_VERSION="$(cd certbot && python -c "import certbot; print(certbot.__version__)" && cd ~-)" "${BUILD_REPOSITORY_LOCALPATH}\tools\extract_changelog.py" "${CERTBOT_VERSION}" >> "${BUILD_ARTIFACTSTAGINGDIRECTORY}/release_notes.md" displayName: Prepare changelog - task: PublishPipelineArtifact@1 diff --git a/certbot-compatibility-test/Dockerfile b/certbot-compatibility-test/Dockerfile index c32bc0bd6..a9996f779 100644 --- a/certbot-compatibility-test/Dockerfile +++ b/certbot-compatibility-test/Dockerfile @@ -14,7 +14,7 @@ RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only # the above is not likely to change, so by putting it further up the # Dockerfile we make sure we cache as much as possible -COPY certbot/setup.py certbot/README.rst CHANGELOG.md certbot/MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ +COPY certbot/setup.py certbot/README.rst certbot/CHANGELOG.md certbot/MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ # all above files are necessary for setup.py, however, package source # code directory has to be copied separately to a subdirectory... diff --git a/certbot/README.rst b/certbot/README.rst index 5f5ea17a1..2c934ce59 100644 --- a/certbot/README.rst +++ b/certbot/README.rst @@ -16,7 +16,7 @@ configuring webservers to use them. This client runs on Unix-based operating systems. To see the changes made to Certbot between versions please refer to our -`changelog `_. +`changelog `_. Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``, depending on install method. Instructions on the Internet, and some pieces of the diff --git a/tools/_release.sh b/tools/_release.sh index 224c69d94..89f2a3737 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -65,8 +65,8 @@ fi git checkout "$RELEASE_BRANCH" # Update changelog -sed -i "s/master/$(date +'%Y-%m-%d')/" CHANGELOG.md -git add CHANGELOG.md +sed -i "s/master/$(date +'%Y-%m-%d')/" certbot/CHANGELOG.md +git add certbot/CHANGELOG.md git diff --cached git commit -m "Update changelog for $version release" @@ -249,17 +249,17 @@ echo gpg2 -U $RELEASE_GPG_KEY --detach-sign --armor $name.$rev.tar.xz cd ~- # Add master section to CHANGELOG.md -header=$(head -n 4 CHANGELOG.md) +header=$(head -n 4 certbot/CHANGELOG.md) body=$(sed s/nextversion/$nextversion/ tools/_changelog_top.txt) -footer=$(tail -n +5 CHANGELOG.md) +footer=$(tail -n +5 certbot/CHANGELOG.md) echo "$header $body -$footer" > CHANGELOG.md -git add CHANGELOG.md +$footer" > certbot/CHANGELOG.md +git add certbot/CHANGELOG.md git diff --cached -git commit -m "Add contents to CHANGELOG.md for next version" +git commit -m "Add contents to certbot/CHANGELOG.md for next version" echo "New root: $root" echo "Test commands (in the letstest repo):" diff --git a/tools/extract_changelog.py b/tools/extract_changelog.py index d3bd2aa44..695870278 100755 --- a/tools/extract_changelog.py +++ b/tools/extract_changelog.py @@ -16,7 +16,7 @@ def main(): section_pattern = re.compile(r'^##\s*{0}\s*-\s*[\d-]+$' .format(version.replace('.', '\\.'))) - with open(os.path.join(CERTBOT_ROOT, 'CHANGELOG.md')) as file_h: + with open(os.path.join(CERTBOT_ROOT, 'certbot', 'CHANGELOG.md')) as file_h: lines = file_h.read().splitlines() changelog = [] diff --git a/windows-installer/construct.py b/windows-installer/construct.py index cdf309f13..699786411 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -40,7 +40,7 @@ def _compile_wheels(repo_path, build_path, venv_python): wheels_path = os.path.join(build_path, 'wheels') os.makedirs(wheels_path) - certbot_packages = ['acme', '.'] + certbot_packages = ['acme', 'certbot'] # Uncomment following line to include all DNS plugins in the installer # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')]) wheels_project = [os.path.join(repo_path, package) for package in certbot_packages] @@ -119,8 +119,9 @@ imp.load_dynamic('pythoncom', pcom) installer_cfg_path = os.path.join(build_path, 'installer.cfg') + certbot_pkg_path = os.path.join(repo_path, 'certbot') certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'], - universal_newlines=True, cwd=repo_path).strip() + universal_newlines=True, cwd=certbot_pkg_path).strip() with open(installer_cfg_path, 'w') as file_h: file_h.write('''\ -- cgit v1.2.3 From 6d1472bf8c75450d0d2318f19ce13539bd644cc0 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 25 Nov 2019 18:53:20 -0800 Subject: Implement redirect by default (#7595) * Change redirect default to yes so that it happens automatically in noninteractive mode * Update changelog --- certbot/CHANGELOG.md | 3 +++ certbot/certbot/_internal/display/enhancements.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index dfb6acde8..b5794c2dd 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -28,6 +28,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). `certbot.reverter.Reverter.view_config_changes`, and `certbot.util.get_systemd_os_info` have been removed * Certbot's `register --update-registration` subcommand has been removed +* When possible, default to automatically configuring the webserver so all requests + redirect to secure HTTPS access. This is mostly relevant when running Certbot + in non-interactive mode. Previously, the default was to not redirect all requests. ### Fixed diff --git a/certbot/certbot/_internal/display/enhancements.py b/certbot/certbot/_internal/display/enhancements.py index 5498b9547..0529f53c6 100644 --- a/certbot/certbot/_internal/display/enhancements.py +++ b/certbot/certbot/_internal/display/enhancements.py @@ -50,7 +50,7 @@ def redirect_by_default(): code, selection = util(interfaces.IDisplay).menu( "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", - choices, default=0, + choices, default=1, cli_flag="--redirect / --no-redirect", force_interactive=True) if code != display_util.OK: -- cgit v1.2.3 From b624172f6827b35214bc3a2df076df0bc524cba2 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 26 Nov 2019 15:25:28 -0800 Subject: Refactor tests out of packaged module for dns plugins (#7599) * Refactor tests out of module for certbot-dns-cloudflare * Refactor tests out of module for certbot-dns-cloudxns * Refactor tests out of module for certbot-dns-digitalocean * Refactor tests out of module for certbot-dns-dnsimple * Refactor tests out of module for certbot-dns-dnsmadeeasy * Refactor tests out of module for certbot-dns-gehirn * Refactor tests out of module for certbot-dns-google * Refactor tests out of module for certbot-dns-linode * Refactor tests out of module for certbot-dns-luadns * Refactor tests out of module for certbot-dns-nsone * Refactor tests out of module for certbot-dns-ovh * Refactor tests out of module for certbot-dns-rfc2136 * Refactor tests out of module for certbot-dns-sakuracloud * Refactor tests out of module for certbot-dns-route53 * Move certbot-dns-google testdata/ under tests/ * Use pytest for dns plugins * Exclude pycache and .py[cod] --- certbot-dns-cloudflare/MANIFEST.in | 3 + .../certbot_dns_cloudflare/dns_cloudflare_test.py | 174 --- certbot-dns-cloudflare/setup.py | 18 + .../tests/dns_cloudflare_test.py | 174 +++ certbot-dns-cloudxns/MANIFEST.in | 3 + .../certbot_dns_cloudxns/dns_cloudxns_test.py | 54 - certbot-dns-cloudxns/setup.py | 18 + certbot-dns-cloudxns/tests/dns_cloudxns_test.py | 54 + certbot-dns-digitalocean/MANIFEST.in | 3 + .../dns_digitalocean_test.py | 171 --- certbot-dns-digitalocean/setup.py | 18 + .../tests/dns_digitalocean_test.py | 171 +++ certbot-dns-dnsimple/MANIFEST.in | 3 + .../certbot_dns_dnsimple/dns_dnsimple_test.py | 51 - certbot-dns-dnsimple/setup.py | 18 + certbot-dns-dnsimple/tests/dns_dnsimple_test.py | 51 + certbot-dns-dnsmadeeasy/MANIFEST.in | 3 + .../dns_dnsmadeeasy_test.py | 56 - certbot-dns-dnsmadeeasy/setup.py | 18 + .../tests/dns_dnsmadeeasy_test.py | 56 + certbot-dns-gehirn/MANIFEST.in | 3 + .../certbot_dns_gehirn/dns_gehirn_test.py | 55 - certbot-dns-gehirn/setup.py | 18 + certbot-dns-gehirn/tests/dns_gehirn_test.py | 55 + certbot-dns-google/MANIFEST.in | 3 + .../certbot_dns_google/dns_google_test.py | 332 ----- .../certbot_dns_google/testdata/discovery.json | 1401 -------------------- certbot-dns-google/setup.py | 18 + certbot-dns-google/tests/dns_google_test.py | 332 +++++ certbot-dns-google/tests/testdata/discovery.json | 1401 ++++++++++++++++++++ certbot-dns-linode/MANIFEST.in | 3 + .../certbot_dns_linode/dns_linode_test.py | 144 -- certbot-dns-linode/setup.py | 18 + certbot-dns-linode/tests/dns_linode_test.py | 144 ++ certbot-dns-luadns/MANIFEST.in | 3 + .../certbot_dns_luadns/dns_luadns_test.py | 52 - certbot-dns-luadns/setup.py | 18 + certbot-dns-luadns/tests/dns_luadns_test.py | 52 + certbot-dns-nsone/MANIFEST.in | 3 + .../certbot_dns_nsone/dns_nsone_test.py | 52 - certbot-dns-nsone/setup.py | 18 + certbot-dns-nsone/tests/dns_nsone_test.py | 52 + certbot-dns-ovh/MANIFEST.in | 3 + certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py | 62 - certbot-dns-ovh/setup.py | 18 + certbot-dns-ovh/tests/dns_ovh_test.py | 62 + certbot-dns-rfc2136/MANIFEST.in | 3 + .../certbot_dns_rfc2136/dns_rfc2136_test.py | 212 --- certbot-dns-rfc2136/setup.py | 18 + certbot-dns-rfc2136/tests/dns_rfc2136_test.py | 212 +++ certbot-dns-route53/MANIFEST.in | 3 + .../certbot_dns_route53/dns_route53_test.py | 263 ---- certbot-dns-route53/setup.py | 18 + certbot-dns-route53/tests/dns_route53_test.py | 263 ++++ certbot-dns-sakuracloud/MANIFEST.in | 3 + .../dns_sakuracloud_test.py | 56 - certbot-dns-sakuracloud/setup.py | 19 +- .../tests/dns_sakuracloud_test.py | 56 + 58 files changed, 3429 insertions(+), 3136 deletions(-) delete mode 100644 certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py create mode 100644 certbot-dns-cloudflare/tests/dns_cloudflare_test.py delete mode 100644 certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py create mode 100644 certbot-dns-cloudxns/tests/dns_cloudxns_test.py delete mode 100644 certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py create mode 100644 certbot-dns-digitalocean/tests/dns_digitalocean_test.py delete mode 100644 certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py create mode 100644 certbot-dns-dnsimple/tests/dns_dnsimple_test.py delete mode 100644 certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py create mode 100644 certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py delete mode 100644 certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py create mode 100644 certbot-dns-gehirn/tests/dns_gehirn_test.py delete mode 100644 certbot-dns-google/certbot_dns_google/dns_google_test.py delete mode 100644 certbot-dns-google/certbot_dns_google/testdata/discovery.json create mode 100644 certbot-dns-google/tests/dns_google_test.py create mode 100644 certbot-dns-google/tests/testdata/discovery.json delete mode 100644 certbot-dns-linode/certbot_dns_linode/dns_linode_test.py create mode 100644 certbot-dns-linode/tests/dns_linode_test.py delete mode 100644 certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py create mode 100644 certbot-dns-luadns/tests/dns_luadns_test.py delete mode 100644 certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py create mode 100644 certbot-dns-nsone/tests/dns_nsone_test.py delete mode 100644 certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py create mode 100644 certbot-dns-ovh/tests/dns_ovh_test.py delete mode 100644 certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py create mode 100644 certbot-dns-rfc2136/tests/dns_rfc2136_test.py delete mode 100644 certbot-dns-route53/certbot_dns_route53/dns_route53_test.py create mode 100644 certbot-dns-route53/tests/dns_route53_test.py delete mode 100644 certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py create mode 100644 certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py diff --git a/certbot-dns-cloudflare/MANIFEST.in b/certbot-dns-cloudflare/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-cloudflare/MANIFEST.in +++ b/certbot-dns-cloudflare/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py deleted file mode 100644 index b24628b0d..000000000 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Tests for certbot_dns_cloudflare._internal.dns_cloudflare.""" - -import unittest - -import CloudFlare -import mock - -from certbot import errors -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '') -API_KEY = 'an-api-key' -EMAIL = 'example@example.com' - - -class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): - - def setUp(self): - from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator - - super(AuthenticatorTest, self).setUp() - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"cloudflare_email": EMAIL, "cloudflare_api_key": API_KEY}, path) - - self.config = mock.MagicMock(cloudflare_credentials=path, - cloudflare_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "cloudflare") - - self.mock_client = mock.MagicMock() - # _get_cloudflare_client | pylint: disable=protected-access - self.auth._get_cloudflare_client = mock.MagicMock(return_value=self.mock_client) - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - # _attempt_cleanup | pylint: disable=protected-access - self.auth._attempt_cleanup = True - self.auth.cleanup([self.achall]) - - expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - -class CloudflareClientTest(unittest.TestCase): - record_name = "foo" - record_content = "bar" - record_ttl = 42 - zone_id = 1 - record_id = 2 - - def setUp(self): - from certbot_dns_cloudflare._internal.dns_cloudflare import _CloudflareClient - - self.cloudflare_client = _CloudflareClient(EMAIL, API_KEY) - - self.cf = mock.MagicMock() - self.cloudflare_client.cf = self.cf - - def test_add_txt_record(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - - self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, - self.record_ttl) - - self.cf.zones.dns_records.post.assert_called_with(self.zone_id, data=mock.ANY) - - post_data = self.cf.zones.dns_records.post.call_args[1]['data'] - - self.assertEqual('TXT', post_data['type']) - self.assertEqual(self.record_name, post_data['name']) - self.assertEqual(self.record_content, post_data['content']) - self.assertEqual(self.record_ttl, post_data['ttl']) - - def test_add_txt_record_error(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - - self.cf.zones.dns_records.post.side_effect = API_ERROR - - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - def test_add_txt_record_error_during_zone_lookup(self): - self.cf.zones.get.side_effect = API_ERROR - - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - def test_add_txt_record_zone_not_found(self): - self.cf.zones.get.return_value = [] - - self.assertRaises( - errors.PluginError, - self.cloudflare_client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - def test_del_txt_record(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - expected = [mock.call.zones.get(params=mock.ANY), - mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), - mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] - - self.assertEqual(expected, self.cf.mock_calls) - - get_data = self.cf.zones.dns_records.get.call_args[1]['params'] - - self.assertEqual('TXT', get_data['type']) - self.assertEqual(self.record_name, get_data['name']) - self.assertEqual(self.record_content, get_data['content']) - - def test_del_txt_record_error_during_zone_lookup(self): - self.cf.zones.get.side_effect = API_ERROR - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_error_during_delete(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] - self.cf.zones.dns_records.delete.side_effect = API_ERROR - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - expected = [mock.call.zones.get(params=mock.ANY), - mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), - mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] - - self.assertEqual(expected, self.cf.mock_calls) - - def test_del_txt_record_error_during_get(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.get.side_effect = API_ERROR - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - expected = [mock.call.zones.get(params=mock.ANY), - mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] - - self.assertEqual(expected, self.cf.mock_calls) - - def test_del_txt_record_no_record(self): - self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.get.return_value = [] - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - expected = [mock.call.zones.get(params=mock.ANY), - mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] - - self.assertEqual(expected, self.cf.mock_calls) - - def test_del_txt_record_no_zone(self): - self.cf.zones.get.return_value = [{'id': None}] - - self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - expected = [mock.call.zones.get(params=mock.ANY)] - - self.assertEqual(expected, self.cf.mock_calls) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index c059a1ed5..05a85bb66 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-cloudflare', version=version, @@ -63,5 +79,7 @@ setup( 'dns-cloudflare = certbot_dns_cloudflare._internal.dns_cloudflare:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_cloudflare', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py new file mode 100644 index 000000000..b24628b0d --- /dev/null +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -0,0 +1,174 @@ +"""Tests for certbot_dns_cloudflare._internal.dns_cloudflare.""" + +import unittest + +import CloudFlare +import mock + +from certbot import errors +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '') +API_KEY = 'an-api-key' +EMAIL = 'example@example.com' + + +class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): + + def setUp(self): + from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator + + super(AuthenticatorTest, self).setUp() + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"cloudflare_email": EMAIL, "cloudflare_api_key": API_KEY}, path) + + self.config = mock.MagicMock(cloudflare_credentials=path, + cloudflare_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "cloudflare") + + self.mock_client = mock.MagicMock() + # _get_cloudflare_client | pylint: disable=protected-access + self.auth._get_cloudflare_client = mock.MagicMock(return_value=self.mock_client) + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + # _attempt_cleanup | pylint: disable=protected-access + self.auth._attempt_cleanup = True + self.auth.cleanup([self.achall]) + + expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + +class CloudflareClientTest(unittest.TestCase): + record_name = "foo" + record_content = "bar" + record_ttl = 42 + zone_id = 1 + record_id = 2 + + def setUp(self): + from certbot_dns_cloudflare._internal.dns_cloudflare import _CloudflareClient + + self.cloudflare_client = _CloudflareClient(EMAIL, API_KEY) + + self.cf = mock.MagicMock() + self.cloudflare_client.cf = self.cf + + def test_add_txt_record(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + + self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, self.record_content, + self.record_ttl) + + self.cf.zones.dns_records.post.assert_called_with(self.zone_id, data=mock.ANY) + + post_data = self.cf.zones.dns_records.post.call_args[1]['data'] + + self.assertEqual('TXT', post_data['type']) + self.assertEqual(self.record_name, post_data['name']) + self.assertEqual(self.record_content, post_data['content']) + self.assertEqual(self.record_ttl, post_data['ttl']) + + def test_add_txt_record_error(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + + self.cf.zones.dns_records.post.side_effect = API_ERROR + + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + def test_add_txt_record_error_during_zone_lookup(self): + self.cf.zones.get.side_effect = API_ERROR + + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + def test_add_txt_record_zone_not_found(self): + self.cf.zones.get.return_value = [] + + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + def test_del_txt_record(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + expected = [mock.call.zones.get(params=mock.ANY), + mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), + mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] + + self.assertEqual(expected, self.cf.mock_calls) + + get_data = self.cf.zones.dns_records.get.call_args[1]['params'] + + self.assertEqual('TXT', get_data['type']) + self.assertEqual(self.record_name, get_data['name']) + self.assertEqual(self.record_content, get_data['content']) + + def test_del_txt_record_error_during_zone_lookup(self): + self.cf.zones.get.side_effect = API_ERROR + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_error_during_delete(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] + self.cf.zones.dns_records.delete.side_effect = API_ERROR + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + expected = [mock.call.zones.get(params=mock.ANY), + mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY), + mock.call.zones.dns_records.delete(self.zone_id, self.record_id)] + + self.assertEqual(expected, self.cf.mock_calls) + + def test_del_txt_record_error_during_get(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + self.cf.zones.dns_records.get.side_effect = API_ERROR + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + expected = [mock.call.zones.get(params=mock.ANY), + mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] + + self.assertEqual(expected, self.cf.mock_calls) + + def test_del_txt_record_no_record(self): + self.cf.zones.get.return_value = [{'id': self.zone_id}] + self.cf.zones.dns_records.get.return_value = [] + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + expected = [mock.call.zones.get(params=mock.ANY), + mock.call.zones.dns_records.get(self.zone_id, params=mock.ANY)] + + self.assertEqual(expected, self.cf.mock_calls) + + def test_del_txt_record_no_zone(self): + self.cf.zones.get.return_value = [{'id': None}] + + self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + expected = [mock.call.zones.get(params=mock.ANY)] + + self.assertEqual(expected, self.cf.mock_calls) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-cloudxns/MANIFEST.in b/certbot-dns-cloudxns/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-cloudxns/MANIFEST.in +++ b/certbot-dns-cloudxns/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py deleted file mode 100644 index 7b8d0944d..000000000 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Tests for certbot_dns_cloudxns._internal.dns_cloudxns.""" - -import unittest - -import mock -from requests.exceptions import HTTPError, RequestException - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.tests import util as test_util - -DOMAIN_NOT_FOUND = Exception('No domain found') -GENERIC_ERROR = RequestException -LOGIN_ERROR = HTTPError('400 Client Error: ...') - -API_KEY = 'foo' -SECRET = 'bar' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"cloudxns_api_key": API_KEY, "cloudxns_secret_key": SECRET}, path) - - self.config = mock.MagicMock(cloudxns_credentials=path, - cloudxns_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "cloudxns") - - self.mock_client = mock.MagicMock() - # _get_cloudxns_client | pylint: disable=protected-access - self.auth._get_cloudxns_client = mock.MagicMock(return_value=self.mock_client) - - -class CloudXNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - def setUp(self): - from certbot_dns_cloudxns._internal.dns_cloudxns import _CloudXNSLexiconClient - - self.client = _CloudXNSLexiconClient(API_KEY, SECRET, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 399692ae5..189af0a55 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-cloudxns', version=version, @@ -63,5 +79,7 @@ setup( 'dns-cloudxns = certbot_dns_cloudxns._internal.dns_cloudxns:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_cloudxns', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-cloudxns/tests/dns_cloudxns_test.py b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py new file mode 100644 index 000000000..7b8d0944d --- /dev/null +++ b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py @@ -0,0 +1,54 @@ +"""Tests for certbot_dns_cloudxns._internal.dns_cloudxns.""" + +import unittest + +import mock +from requests.exceptions import HTTPError, RequestException + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.tests import util as test_util + +DOMAIN_NOT_FOUND = Exception('No domain found') +GENERIC_ERROR = RequestException +LOGIN_ERROR = HTTPError('400 Client Error: ...') + +API_KEY = 'foo' +SECRET = 'bar' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"cloudxns_api_key": API_KEY, "cloudxns_secret_key": SECRET}, path) + + self.config = mock.MagicMock(cloudxns_credentials=path, + cloudxns_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "cloudxns") + + self.mock_client = mock.MagicMock() + # _get_cloudxns_client | pylint: disable=protected-access + self.auth._get_cloudxns_client = mock.MagicMock(return_value=self.mock_client) + + +class CloudXNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + def setUp(self): + from certbot_dns_cloudxns._internal.dns_cloudxns import _CloudXNSLexiconClient + + self.client = _CloudXNSLexiconClient(API_KEY, SECRET, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-digitalocean/MANIFEST.in b/certbot-dns-digitalocean/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-digitalocean/MANIFEST.in +++ b/certbot-dns-digitalocean/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py deleted file mode 100644 index 71301a47c..000000000 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py +++ /dev/null @@ -1,171 +0,0 @@ -"""Tests for certbot_dns_digitalocean._internal.dns_digitalocean.""" - -import unittest - -import digitalocean -import mock - -from certbot import errors -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_ERROR = digitalocean.DataReadError() -TOKEN = 'a-token' - - -class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): - - def setUp(self): - from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator - - super(AuthenticatorTest, self).setUp() - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"digitalocean_token": TOKEN}, path) - - self.config = mock.MagicMock(digitalocean_credentials=path, - digitalocean_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "digitalocean") - - self.mock_client = mock.MagicMock() - # _get_digitalocean_client | pylint: disable=protected-access - self.auth._get_digitalocean_client = mock.MagicMock(return_value=self.mock_client) - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - # _attempt_cleanup | pylint: disable=protected-access - self.auth._attempt_cleanup = True - self.auth.cleanup([self.achall]) - - expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - -class DigitalOceanClientTest(unittest.TestCase): - - id_num = 1 - record_prefix = "_acme-challenge" - record_name = record_prefix + "." + DOMAIN - record_content = "bar" - - def setUp(self): - from certbot_dns_digitalocean._internal.dns_digitalocean import _DigitalOceanClient - - self.digitalocean_client = _DigitalOceanClient(TOKEN) - - self.manager = mock.MagicMock() - self.digitalocean_client.manager = self.manager - - def test_add_txt_record(self): - wrong_domain_mock = mock.MagicMock() - wrong_domain_mock.name = "other.invalid" - wrong_domain_mock.create_new_domain_record.side_effect = AssertionError('Wrong Domain') - - domain_mock = mock.MagicMock() - domain_mock.name = DOMAIN - domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id_num}} - - self.manager.get_all_domains.return_value = [wrong_domain_mock, domain_mock] - - self.digitalocean_client.add_txt_record(DOMAIN, self.record_name, self.record_content) - - domain_mock.create_new_domain_record.assert_called_with(type='TXT', - name=self.record_prefix, - data=self.record_content) - - def test_add_txt_record_fail_to_find_domain(self): - self.manager.get_all_domains.return_value = [] - - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_error_finding_domain(self): - self.manager.get_all_domains.side_effect = API_ERROR - - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_add_txt_record_error_creating_record(self): - domain_mock = mock.MagicMock() - domain_mock.name = DOMAIN - domain_mock.create_new_domain_record.side_effect = API_ERROR - - self.manager.get_all_domains.return_value = [domain_mock] - - self.assertRaises(errors.PluginError, - self.digitalocean_client.add_txt_record, - DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record(self): - first_record_mock = mock.MagicMock() - first_record_mock.type = 'TXT' - first_record_mock.name = "DIFFERENT" - first_record_mock.data = self.record_content - - correct_record_mock = mock.MagicMock() - correct_record_mock.type = 'TXT' - correct_record_mock.name = self.record_prefix - correct_record_mock.data = self.record_content - - last_record_mock = mock.MagicMock() - last_record_mock.type = 'TXT' - last_record_mock.name = self.record_prefix - last_record_mock.data = "DIFFERENT" - - domain_mock = mock.MagicMock() - domain_mock.name = DOMAIN - domain_mock.get_records.return_value = [first_record_mock, - correct_record_mock, - last_record_mock] - - self.manager.get_all_domains.return_value = [domain_mock] - - self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - self.assertTrue(correct_record_mock.destroy.called) - - self.assertFalse(first_record_mock.destroy.call_args_list) - self.assertFalse(last_record_mock.destroy.call_args_list) - - def test_del_txt_record_error_finding_domain(self): - self.manager.get_all_domains.side_effect = API_ERROR - - self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_error_finding_record(self): - domain_mock = mock.MagicMock() - domain_mock.name = DOMAIN - domain_mock.get_records.side_effect = API_ERROR - - self.manager.get_all_domains.return_value = [domain_mock] - - self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - def test_del_txt_record_error_deleting_record(self): - record_mock = mock.MagicMock() - record_mock.type = 'TXT' - record_mock.name = self.record_prefix - record_mock.data = self.record_content - record_mock.destroy.side_effect = API_ERROR - - domain_mock = mock.MagicMock() - domain_mock.name = DOMAIN - domain_mock.get_records.return_value = [record_mock] - - self.manager.get_all_domains.return_value = [domain_mock] - - self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index d626da66f..d5e500be0 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -21,6 +23,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-digitalocean', version=version, @@ -64,5 +80,7 @@ setup( 'dns-digitalocean = certbot_dns_digitalocean._internal.dns_digitalocean:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_digitalocean', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-digitalocean/tests/dns_digitalocean_test.py b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py new file mode 100644 index 000000000..71301a47c --- /dev/null +++ b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py @@ -0,0 +1,171 @@ +"""Tests for certbot_dns_digitalocean._internal.dns_digitalocean.""" + +import unittest + +import digitalocean +import mock + +from certbot import errors +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_ERROR = digitalocean.DataReadError() +TOKEN = 'a-token' + + +class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): + + def setUp(self): + from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator + + super(AuthenticatorTest, self).setUp() + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"digitalocean_token": TOKEN}, path) + + self.config = mock.MagicMock(digitalocean_credentials=path, + digitalocean_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "digitalocean") + + self.mock_client = mock.MagicMock() + # _get_digitalocean_client | pylint: disable=protected-access + self.auth._get_digitalocean_client = mock.MagicMock(return_value=self.mock_client) + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + # _attempt_cleanup | pylint: disable=protected-access + self.auth._attempt_cleanup = True + self.auth.cleanup([self.achall]) + + expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + +class DigitalOceanClientTest(unittest.TestCase): + + id_num = 1 + record_prefix = "_acme-challenge" + record_name = record_prefix + "." + DOMAIN + record_content = "bar" + + def setUp(self): + from certbot_dns_digitalocean._internal.dns_digitalocean import _DigitalOceanClient + + self.digitalocean_client = _DigitalOceanClient(TOKEN) + + self.manager = mock.MagicMock() + self.digitalocean_client.manager = self.manager + + def test_add_txt_record(self): + wrong_domain_mock = mock.MagicMock() + wrong_domain_mock.name = "other.invalid" + wrong_domain_mock.create_new_domain_record.side_effect = AssertionError('Wrong Domain') + + domain_mock = mock.MagicMock() + domain_mock.name = DOMAIN + domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id_num}} + + self.manager.get_all_domains.return_value = [wrong_domain_mock, domain_mock] + + self.digitalocean_client.add_txt_record(DOMAIN, self.record_name, self.record_content) + + domain_mock.create_new_domain_record.assert_called_with(type='TXT', + name=self.record_prefix, + data=self.record_content) + + def test_add_txt_record_fail_to_find_domain(self): + self.manager.get_all_domains.return_value = [] + + self.assertRaises(errors.PluginError, + self.digitalocean_client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_error_finding_domain(self): + self.manager.get_all_domains.side_effect = API_ERROR + + self.assertRaises(errors.PluginError, + self.digitalocean_client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_add_txt_record_error_creating_record(self): + domain_mock = mock.MagicMock() + domain_mock.name = DOMAIN + domain_mock.create_new_domain_record.side_effect = API_ERROR + + self.manager.get_all_domains.return_value = [domain_mock] + + self.assertRaises(errors.PluginError, + self.digitalocean_client.add_txt_record, + DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record(self): + first_record_mock = mock.MagicMock() + first_record_mock.type = 'TXT' + first_record_mock.name = "DIFFERENT" + first_record_mock.data = self.record_content + + correct_record_mock = mock.MagicMock() + correct_record_mock.type = 'TXT' + correct_record_mock.name = self.record_prefix + correct_record_mock.data = self.record_content + + last_record_mock = mock.MagicMock() + last_record_mock.type = 'TXT' + last_record_mock.name = self.record_prefix + last_record_mock.data = "DIFFERENT" + + domain_mock = mock.MagicMock() + domain_mock.name = DOMAIN + domain_mock.get_records.return_value = [first_record_mock, + correct_record_mock, + last_record_mock] + + self.manager.get_all_domains.return_value = [domain_mock] + + self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + self.assertTrue(correct_record_mock.destroy.called) + + self.assertFalse(first_record_mock.destroy.call_args_list) + self.assertFalse(last_record_mock.destroy.call_args_list) + + def test_del_txt_record_error_finding_domain(self): + self.manager.get_all_domains.side_effect = API_ERROR + + self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_error_finding_record(self): + domain_mock = mock.MagicMock() + domain_mock.name = DOMAIN + domain_mock.get_records.side_effect = API_ERROR + + self.manager.get_all_domains.return_value = [domain_mock] + + self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + def test_del_txt_record_error_deleting_record(self): + record_mock = mock.MagicMock() + record_mock.type = 'TXT' + record_mock.name = self.record_prefix + record_mock.data = self.record_content + record_mock.destroy.side_effect = API_ERROR + + domain_mock = mock.MagicMock() + domain_mock.name = DOMAIN + domain_mock.get_records.return_value = [record_mock] + + self.manager.get_all_domains.return_value = [domain_mock] + + self.digitalocean_client.del_txt_record(DOMAIN, self.record_name, self.record_content) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-dnsimple/MANIFEST.in b/certbot-dns-dnsimple/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-dnsimple/MANIFEST.in +++ b/certbot-dns-dnsimple/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py deleted file mode 100644 index ca5eb4f36..000000000 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Tests for certbot_dns_dnsimple._internal.dns_dnsimple.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.tests import util as test_util - -TOKEN = 'foo' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"dnsimple_token": TOKEN}, path) - - self.config = mock.MagicMock(dnsimple_credentials=path, - dnsimple_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "dnsimple") - - self.mock_client = mock.MagicMock() - # _get_dnsimple_client | pylint: disable=protected-access - self.auth._get_dnsimple_client = mock.MagicMock(return_value=self.mock_client) - - -class DNSimpleLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: ...') - - def setUp(self): - from certbot_dns_dnsimple._internal.dns_dnsimple import _DNSimpleLexiconClient - - self.client = _DNSimpleLexiconClient(TOKEN, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 3359fc578..09c90ff0d 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -1,6 +1,8 @@ import os from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -32,6 +34,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-dnsimple', version=version, @@ -75,5 +91,7 @@ setup( 'dns-dnsimple = certbot_dns_dnsimple._internal.dns_dnsimple:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_dnsimple', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-dnsimple/tests/dns_dnsimple_test.py b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py new file mode 100644 index 000000000..ca5eb4f36 --- /dev/null +++ b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py @@ -0,0 +1,51 @@ +"""Tests for certbot_dns_dnsimple._internal.dns_dnsimple.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.tests import util as test_util + +TOKEN = 'foo' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"dnsimple_token": TOKEN}, path) + + self.config = mock.MagicMock(dnsimple_credentials=path, + dnsimple_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "dnsimple") + + self.mock_client = mock.MagicMock() + # _get_dnsimple_client | pylint: disable=protected-access + self.auth._get_dnsimple_client = mock.MagicMock(return_value=self.mock_client) + + +class DNSimpleLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: ...') + + def setUp(self): + from certbot_dns_dnsimple._internal.dns_dnsimple import _DNSimpleLexiconClient + + self.client = _DNSimpleLexiconClient(TOKEN, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-dnsmadeeasy/MANIFEST.in b/certbot-dns-dnsmadeeasy/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-dnsmadeeasy/MANIFEST.in +++ b/certbot-dns-dnsmadeeasy/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py deleted file mode 100644 index b94cc7d05..000000000 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Tests for certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_KEY = 'foo' -SECRET_KEY = 'bar' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"dnsmadeeasy_api_key": API_KEY, - "dnsmadeeasy_secret_key": SECRET_KEY}, - path) - - self.config = mock.MagicMock(dnsmadeeasy_credentials=path, - dnsmadeeasy_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "dnsmadeeasy") - - self.mock_client = mock.MagicMock() - # _get_dnsmadeeasy_client | pylint: disable=protected-access - self.auth._get_dnsmadeeasy_client = mock.MagicMock(return_value=self.mock_client) - - -class DNSMadeEasyLexiconClientTest(unittest.TestCase, - dns_test_common_lexicon.BaseLexiconClientTest): - DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) - LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: {0}.'.format(DOMAIN)) - - def setUp(self): - from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient - - self.client = _DNSMadeEasyLexiconClient(API_KEY, SECRET_KEY, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 5812765e4..8c9c73319 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-dnsmadeeasy', version=version, @@ -63,5 +79,7 @@ setup( 'dns-dnsmadeeasy = certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_dnsmadeeasy', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py new file mode 100644 index 000000000..b94cc7d05 --- /dev/null +++ b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py @@ -0,0 +1,56 @@ +"""Tests for certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_KEY = 'foo' +SECRET_KEY = 'bar' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"dnsmadeeasy_api_key": API_KEY, + "dnsmadeeasy_secret_key": SECRET_KEY}, + path) + + self.config = mock.MagicMock(dnsmadeeasy_credentials=path, + dnsmadeeasy_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "dnsmadeeasy") + + self.mock_client = mock.MagicMock() + # _get_dnsmadeeasy_client | pylint: disable=protected-access + self.auth._get_dnsmadeeasy_client = mock.MagicMock(return_value=self.mock_client) + + +class DNSMadeEasyLexiconClientTest(unittest.TestCase, + dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) + LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: {0}.'.format(DOMAIN)) + + def setUp(self): + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient + + self.client = _DNSMadeEasyLexiconClient(API_KEY, SECRET_KEY, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-gehirn/MANIFEST.in b/certbot-dns-gehirn/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-gehirn/MANIFEST.in +++ b/certbot-dns-gehirn/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py b/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py deleted file mode 100644 index f5b95b6c3..000000000 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Tests for certbot_dns_gehirn._internal.dns_gehirn.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_TOKEN = '00000000-0000-0000-0000-000000000000' -API_SECRET = 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw' - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_gehirn._internal.dns_gehirn import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write( - {"gehirn_api_token": API_TOKEN, "gehirn_api_secret": API_SECRET}, - path - ) - - self.config = mock.MagicMock(gehirn_credentials=path, - gehirn_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "gehirn") - - self.mock_client = mock.MagicMock() - # _get_gehirn_client | pylint: disable=protected-access - self.auth._get_gehirn_client = mock.MagicMock(return_value=self.mock_client) - - -class GehirnLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) - LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) - - def setUp(self): - from certbot_dns_gehirn._internal.dns_gehirn import _GehirnLexiconClient - - self.client = _GehirnLexiconClient(API_TOKEN, API_SECRET, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 5fb46576b..cb7768a29 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -19,6 +21,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-gehirn', version=version, @@ -62,5 +78,7 @@ setup( 'dns-gehirn = certbot_dns_gehirn._internal.dns_gehirn:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_gehirn', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-gehirn/tests/dns_gehirn_test.py b/certbot-dns-gehirn/tests/dns_gehirn_test.py new file mode 100644 index 000000000..f5b95b6c3 --- /dev/null +++ b/certbot-dns-gehirn/tests/dns_gehirn_test.py @@ -0,0 +1,55 @@ +"""Tests for certbot_dns_gehirn._internal.dns_gehirn.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_TOKEN = '00000000-0000-0000-0000-000000000000' +API_SECRET = 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw' + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_gehirn._internal.dns_gehirn import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write( + {"gehirn_api_token": API_TOKEN, "gehirn_api_secret": API_SECRET}, + path + ) + + self.config = mock.MagicMock(gehirn_credentials=path, + gehirn_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "gehirn") + + self.mock_client = mock.MagicMock() + # _get_gehirn_client | pylint: disable=protected-access + self.auth._get_gehirn_client = mock.MagicMock(return_value=self.mock_client) + + +class GehirnLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) + LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) + + def setUp(self): + from certbot_dns_gehirn._internal.dns_gehirn import _GehirnLexiconClient + + self.client = _GehirnLexiconClient(API_TOKEN, API_SECRET, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-google/MANIFEST.in b/certbot-dns-google/MANIFEST.in index c91330e38..a7301ee7f 100644 --- a/certbot-dns-google/MANIFEST.in +++ b/certbot-dns-google/MANIFEST.in @@ -2,3 +2,6 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_dns_google/testdata * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-google/certbot_dns_google/dns_google_test.py b/certbot-dns-google/certbot_dns_google/dns_google_test.py deleted file mode 100644 index e91db58ab..000000000 --- a/certbot-dns-google/certbot_dns_google/dns_google_test.py +++ /dev/null @@ -1,332 +0,0 @@ -"""Tests for certbot_dns_google._internal.dns_google.""" - -import unittest - -import mock -from googleapiclient import discovery -from googleapiclient.errors import Error -from googleapiclient.http import HttpMock -from httplib2 import ServerNotFoundError - -from certbot import errors -from certbot.compat import os -from certbot.errors import PluginError -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -ACCOUNT_JSON_PATH = '/not/a/real/path.json' -API_ERROR = Error() -PROJECT_ID = "test-test-1" - - -class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_google._internal.dns_google import Authenticator - - path = os.path.join(self.tempdir, 'file.json') - open(path, "wb").close() - - super(AuthenticatorTest, self).setUp() - self.config = mock.MagicMock(google_credentials=path, - google_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "google") - - self.mock_client = mock.MagicMock() - # _get_google_client | pylint: disable=protected-access - self.auth._get_google_client = mock.MagicMock(return_value=self.mock_client) - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - # _attempt_cleanup | pylint: disable=protected-access - self.auth._attempt_cleanup = True - self.auth.cleanup([self.achall]) - - expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - @mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError) - def test_without_auth(self, unused_mock): - self.config.google_credentials = None - self.assertRaises(PluginError, self.auth.perform, [self.achall]) - - -class GoogleClientTest(unittest.TestCase): - record_name = "foo" - record_content = "bar" - record_ttl = 42 - zone = "ZONE_ID" - change = "an-id" - - def _setUp_client_with_mock(self, zone_request_side_effect): - from certbot_dns_google._internal.dns_google import _GoogleClient - - pwd = os.path.dirname(__file__) - rel_path = 'testdata/discovery.json' - discovery_file = os.path.join(pwd, rel_path) - http_mock = HttpMock(discovery_file, {'status': '200'}) - dns_api = discovery.build('dns', 'v1', http=http_mock) - - client = _GoogleClient(ACCOUNT_JSON_PATH, dns_api) - - # Setup - mock_mz = mock.MagicMock() - mock_mz.list.return_value.execute.side_effect = zone_request_side_effect - - mock_rrs = mock.MagicMock() - rrsets = {"rrsets": [{"name": "_acme-challenge.example.org.", "type": "TXT", - "rrdatas": ["\"example-txt-contents\""]}]} - mock_rrs.list.return_value.execute.return_value = rrsets - mock_changes = mock.MagicMock() - - client.dns.managedZones = mock.MagicMock(return_value=mock_mz) - client.dns.changes = mock.MagicMock(return_value=mock_changes) - client.dns.resourceRecordSets = mock.MagicMock(return_value=mock_rrs) - - return client, mock_changes - - @mock.patch('googleapiclient.discovery.build') - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') - def test_client_without_credentials(self, get_project_id_mock, credential_mock, - unused_discovery_mock): - from certbot_dns_google._internal.dns_google import _GoogleClient - _GoogleClient(None) - self.assertFalse(credential_mock.called) - self.assertTrue(get_project_id_mock.called) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') - def test_add_txt_record(self, get_project_id_mock, credential_mock): - client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - credential_mock.assert_called_once_with('/not/a/real/path.json', mock.ANY) - self.assertFalse(get_project_id_mock.called) - - client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - - expected_body = { - "kind": "dns#change", - "additions": [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": self.record_name + ".", - "rrdatas": [self.record_content, ], - "ttl": self.record_ttl, - }, - ], - } - - changes.create.assert_called_with(body=expected_body, - managedZone=self.zone, - project=PROJECT_ID) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_and_poll(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - changes.create.return_value.execute.return_value = {'status': 'pending', 'id': self.change} - changes.get.return_value.execute.return_value = {'status': 'done'} - - client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - - changes.create.assert_called_with(body=mock.ANY, - managedZone=self.zone, - project=PROJECT_ID) - - changes.get.assert_called_with(changeId=self.change, - managedZone=self.zone, - project=PROJECT_ID) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_delete_old(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock( - [{'managedZones': [{'id': self.zone}]}]) - # pylint: disable=line-too-long - mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" - with mock.patch(mock_get_rrs) as mock_rrs: - mock_rrs.return_value = ["sample-txt-contents"] - client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - self.assertTrue(changes.create.called) - self.assertTrue("sample-txt-contents" in - changes.create.call_args_list[0][1]["body"]["deletions"][0]["rrdatas"]) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_noop(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock( - [{'managedZones': [{'id': self.zone}]}]) - client.add_txt_record(DOMAIN, "_acme-challenge.example.org", - "example-txt-contents", self.record_ttl) - self.assertFalse(changes.create.called) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_error_during_zone_lookup(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock(API_ERROR) - - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_zone_not_found(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, - {'managedZones': []}]) - - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_add_txt_record_error_during_add(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - changes.create.side_effect = API_ERROR - - self.assertRaises(errors.PluginError, client.add_txt_record, - DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_del_txt_record(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - - # pylint: disable=line-too-long - mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" - with mock.patch(mock_get_rrs) as mock_rrs: - mock_rrs.return_value = ["\"sample-txt-contents\"", - "\"example-txt-contents\""] - client.del_txt_record(DOMAIN, "_acme-challenge.example.org", - "example-txt-contents", self.record_ttl) - - expected_body = { - "kind": "dns#change", - "deletions": [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": "_acme-challenge.example.org.", - "rrdatas": ["\"sample-txt-contents\"", "\"example-txt-contents\""], - "ttl": self.record_ttl, - }, - ], - "additions": [ - { - "kind": "dns#resourceRecordSet", - "type": "TXT", - "name": "_acme-challenge.example.org.", - "rrdatas": ["\"sample-txt-contents\"", ], - "ttl": self.record_ttl, - }, - ], - } - - changes.create.assert_called_with(body=expected_body, - managedZone=self.zone, - project=PROJECT_ID) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_del_txt_record_error_during_zone_lookup(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock(API_ERROR) - - client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_del_txt_record_zone_not_found(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, - {'managedZones': []}]) - - client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_del_txt_record_error_during_delete(self, unused_credential_mock): - client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - changes.create.side_effect = API_ERROR - - client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_get_existing(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock( - [{'managedZones': [{'id': self.zone}]}]) - # Record name mocked in setUp - found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") - self.assertEqual(found, ["\"example-txt-contents\""]) - not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld") - self.assertEqual(not_found, None) - - @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google._internal.dns_google.open', - mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - def test_get_existing_fallback(self, unused_credential_mock): - client, unused_changes = self._setUp_client_with_mock( - [{'managedZones': [{'id': self.zone}]}]) - # pylint: disable=no-member - mock_execute = client.dns.resourceRecordSets.return_value.list.return_value.execute - mock_execute.side_effect = API_ERROR - - rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") - self.assertFalse(rrset) - - def test_get_project_id(self): - from certbot_dns_google._internal.dns_google import _GoogleClient - - response = DummyResponse() - response.status = 200 - - with mock.patch('httplib2.Http.request', return_value=(response, 'test-test-1')): - project_id = _GoogleClient.get_project_id() - self.assertEqual(project_id, 'test-test-1') - - with mock.patch('httplib2.Http.request', return_value=(response, b'test-test-1')): - project_id = _GoogleClient.get_project_id() - self.assertEqual(project_id, 'test-test-1') - - failed_response = DummyResponse() - failed_response.status = 404 - - with mock.patch('httplib2.Http.request', - return_value=(failed_response, "some detailed http error response")): - self.assertRaises(ValueError, _GoogleClient.get_project_id) - - with mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError): - self.assertRaises(ServerNotFoundError, _GoogleClient.get_project_id) - - -class DummyResponse(object): - """ - Dummy object to create a fake HTTPResponse (the actual one requires a socket and we only - need the status attribute) - """ - def __init__(self): - self.status = 200 - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-google/certbot_dns_google/testdata/discovery.json b/certbot-dns-google/certbot_dns_google/testdata/discovery.json deleted file mode 100644 index 79a406645..000000000 --- a/certbot-dns-google/certbot_dns_google/testdata/discovery.json +++ /dev/null @@ -1,1401 +0,0 @@ -{ - "kind": "discovery#restDescription", - "etag": "\"-iA1DTNe4s-I6JZXPt1t1Ypy8IU/gSzgHqX4Zwypnde2YApimTf_qmE\"", - "discoveryVersion": "v1", - "id": "dns:v1", - "name": "dns", - "version": "v1", - "revision": "20180314", - "title": "Google Cloud DNS API", - "description": "Configures and serves authoritative DNS records.", - "ownerDomain": "google.com", - "ownerName": "Google", - "icons": { - "x16": "https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png", - "x32": "https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png" - }, - "documentationLink": "https://developers.google.com/cloud-dns", - "protocol": "rest", - "baseUrl": "https://www.googleapis.com/dns/v1/projects/", - "basePath": "/dns/v1/projects/", - "rootUrl": "https://www.googleapis.com/", - "servicePath": "dns/v1/projects/", - "batchPath": "batch/dns/v1", - "parameters": { - "alt": { - "type": "string", - "description": "Data format for the response.", - "default": "json", - "enum": [ - "json" - ], - "enumDescriptions": [ - "Responses with Content-Type of application/json" - ], - "location": "query" - }, - "fields": { - "type": "string", - "description": "Selector specifying which fields to include in a partial response.", - "location": "query" - }, - "key": { - "type": "string", - "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", - "location": "query" - }, - "oauth_token": { - "type": "string", - "description": "OAuth 2.0 token for the current user.", - "location": "query" - }, - "prettyPrint": { - "type": "boolean", - "description": "Returns response with indentations and line breaks.", - "default": "true", - "location": "query" - }, - "quotaUser": { - "type": "string", - "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", - "location": "query" - }, - "userIp": { - "type": "string", - "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", - "location": "query" - } - }, - "auth": { - "oauth2": { - "scopes": { - "https://www.googleapis.com/auth/cloud-platform": { - "description": "View and manage your data across Google Cloud Platform services" - }, - "https://www.googleapis.com/auth/cloud-platform.read-only": { - "description": "View your data across Google Cloud Platform services" - }, - "https://www.googleapis.com/auth/ndev.clouddns.readonly": { - "description": "View your DNS records hosted by Google Cloud DNS" - }, - "https://www.googleapis.com/auth/ndev.clouddns.readwrite": { - "description": "View and manage your DNS records hosted by Google Cloud DNS" - } - } - } - }, - "schemas": { - "Change": { - "id": "Change", - "type": "object", - "description": "An atomic update to a collection of ResourceRecordSets.", - "properties": { - "additions": { - "type": "array", - "description": "Which ResourceRecordSets to add?", - "items": { - "$ref": "ResourceRecordSet" - } - }, - "deletions": { - "type": "array", - "description": "Which ResourceRecordSets to remove? Must match existing data exactly.", - "items": { - "$ref": "ResourceRecordSet" - } - }, - "id": { - "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." - }, - "isServing": { - "type": "boolean", - "description": "If the DNS queries for the zone will be served." - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#change\".", - "default": "dns#change" - }, - "startTime": { - "type": "string", - "description": "The time that this operation was started by the server (output only). This is in RFC3339 text format." - }, - "status": { - "type": "string", - "description": "Status of the operation (output only).", - "enum": [ - "done", - "pending" - ], - "enumDescriptions": [ - "", - "" - ] - } - } - }, - "ChangesListResponse": { - "id": "ChangesListResponse", - "type": "object", - "description": "The response to a request to enumerate Changes to a ResourceRecordSets collection.", - "properties": { - "changes": { - "type": "array", - "description": "The requested changes.", - "items": { - "$ref": "Change" - } - }, - "header": { - "$ref": "ResponseHeader" - }, - "kind": { - "type": "string", - "description": "Type of resource.", - "default": "dns#changesListResponse" - }, - "nextPageToken": { - "type": "string", - "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." - } - } - }, - "DnsKey": { - "id": "DnsKey", - "type": "object", - "description": "A DNSSEC key pair.", - "properties": { - "algorithm": { - "type": "string", - "description": "String mnemonic specifying the DNSSEC algorithm of this key. Immutable after creation time.", - "enum": [ - "ecdsap256sha256", - "ecdsap384sha384", - "rsasha1", - "rsasha256", - "rsasha512" - ], - "enumDescriptions": [ - "", - "", - "", - "", - "" - ] - }, - "creationTime": { - "type": "string", - "description": "The time that this resource was created in the control plane. This is in RFC3339 text format. Output only." - }, - "description": { - "type": "string", - "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the resource's function." - }, - "digests": { - "type": "array", - "description": "Cryptographic hashes of the DNSKEY resource record associated with this DnsKey. These digests are needed to construct a DS record that points at this DNS key. Output only.", - "items": { - "$ref": "DnsKeyDigest" - } - }, - "id": { - "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." - }, - "isActive": { - "type": "boolean", - "description": "Active keys will be used to sign subsequent changes to the ManagedZone. Inactive keys will still be present as DNSKEY Resource Records for the use of resolvers validating existing signatures." - }, - "keyLength": { - "type": "integer", - "description": "Length of the key in bits. Specified at creation time then immutable.", - "format": "uint32" - }, - "keyTag": { - "type": "integer", - "description": "The key tag is a non-cryptographic hash of the a DNSKEY resource record associated with this DnsKey. The key tag can be used to identify a DNSKEY more quickly (but it is not a unique identifier). In particular, the key tag is used in a parent zone's DS record to point at the DNSKEY in this child ManagedZone. The key tag is a number in the range [0, 65535] and the algorithm to calculate it is specified in RFC4034 Appendix B. Output only.", - "format": "int32" - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKey\".", - "default": "dns#dnsKey" - }, - "publicKey": { - "type": "string", - "description": "Base64 encoded public half of this key. Output only." - }, - "type": { - "type": "string", - "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types. Immutable after creation time.", - "enum": [ - "keySigning", - "zoneSigning" - ], - "enumDescriptions": [ - "", - "" - ] - } - } - }, - "DnsKeyDigest": { - "id": "DnsKeyDigest", - "type": "object", - "properties": { - "digest": { - "type": "string", - "description": "The base-16 encoded bytes of this digest. Suitable for use in a DS resource record." - }, - "type": { - "type": "string", - "description": "Specifies the algorithm used to calculate this digest.", - "enum": [ - "sha1", - "sha256", - "sha384" - ], - "enumDescriptions": [ - "", - "", - "" - ] - } - } - }, - "DnsKeySpec": { - "id": "DnsKeySpec", - "type": "object", - "description": "Parameters for DnsKey key generation. Used for generating initial keys for a new ManagedZone and as default when adding a new DnsKey.", - "properties": { - "algorithm": { - "type": "string", - "description": "String mnemonic specifying the DNSSEC algorithm of this key.", - "enum": [ - "ecdsap256sha256", - "ecdsap384sha384", - "rsasha1", - "rsasha256", - "rsasha512" - ], - "enumDescriptions": [ - "", - "", - "", - "", - "" - ] - }, - "keyLength": { - "type": "integer", - "description": "Length of the keys in bits.", - "format": "uint32" - }, - "keyType": { - "type": "string", - "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types.", - "enum": [ - "keySigning", - "zoneSigning" - ], - "enumDescriptions": [ - "", - "" - ] - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKeySpec\".", - "default": "dns#dnsKeySpec" - } - } - }, - "DnsKeysListResponse": { - "id": "DnsKeysListResponse", - "type": "object", - "description": "The response to a request to enumerate DnsKeys in a ManagedZone.", - "properties": { - "dnsKeys": { - "type": "array", - "description": "The requested resources.", - "items": { - "$ref": "DnsKey" - } - }, - "header": { - "$ref": "ResponseHeader" - }, - "kind": { - "type": "string", - "description": "Type of resource.", - "default": "dns#dnsKeysListResponse" - }, - "nextPageToken": { - "type": "string", - "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." - } - } - }, - "ManagedZone": { - "id": "ManagedZone", - "type": "object", - "description": "A zone is a subtree of the DNS namespace under one administrative responsibility. A ManagedZone is a resource that represents a DNS zone hosted by the Cloud DNS service.", - "properties": { - "creationTime": { - "type": "string", - "description": "The time that this resource was created on the server. This is in RFC3339 text format. Output only." - }, - "description": { - "type": "string", - "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the managed zone's function." - }, - "dnsName": { - "type": "string", - "description": "The DNS name of this managed zone, for instance \"example.com.\"." - }, - "dnssecConfig": { - "$ref": "ManagedZoneDnsSecConfig", - "description": "DNSSEC configuration." - }, - "id": { - "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)", - "format": "uint64" - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZone\".", - "default": "dns#managedZone" - }, - "labels": { - "type": "object", - "description": "User labels.", - "additionalProperties": { - "type": "string" - } - }, - "name": { - "type": "string", - "description": "User assigned name for this resource. Must be unique within the project. The name must be 1-63 characters long, must begin with a letter, end with a letter or digit, and only contain lowercase letters, digits or dashes." - }, - "nameServerSet": { - "type": "string", - "description": "Optionally specifies the NameServerSet for this ManagedZone. A NameServerSet is a set of DNS name servers that all host the same ManagedZones. Most users will leave this field unset." - }, - "nameServers": { - "type": "array", - "description": "Delegate your managed_zone to these virtual name servers; defined by the server (output only)", - "items": { - "type": "string" - } - } - } - }, - "ManagedZoneDnsSecConfig": { - "id": "ManagedZoneDnsSecConfig", - "type": "object", - "properties": { - "defaultKeySpecs": { - "type": "array", - "description": "Specifies parameters that will be used for generating initial DnsKeys for this ManagedZone. Output only while state is not OFF.", - "items": { - "$ref": "DnsKeySpec" - } - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZoneDnsSecConfig\".", - "default": "dns#managedZoneDnsSecConfig" - }, - "nonExistence": { - "type": "string", - "description": "Specifies the mechanism used to provide authenticated denial-of-existence responses. Output only while state is not OFF.", - "enum": [ - "nsec", - "nsec3" - ], - "enumDescriptions": [ - "", - "" - ] - }, - "state": { - "type": "string", - "description": "Specifies whether DNSSEC is enabled, and what mode it is in.", - "enum": [ - "off", - "on", - "transfer" - ], - "enumDescriptions": [ - "", - "", - "" - ] - } - } - }, - "ManagedZoneOperationsListResponse": { - "id": "ManagedZoneOperationsListResponse", - "type": "object", - "properties": { - "header": { - "$ref": "ResponseHeader" - }, - "kind": { - "type": "string", - "description": "Type of resource.", - "default": "dns#managedZoneOperationsListResponse" - }, - "nextPageToken": { - "type": "string", - "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." - }, - "operations": { - "type": "array", - "description": "The operation resources.", - "items": { - "$ref": "Operation" - } - } - } - }, - "ManagedZonesListResponse": { - "id": "ManagedZonesListResponse", - "type": "object", - "properties": { - "header": { - "$ref": "ResponseHeader" - }, - "kind": { - "type": "string", - "description": "Type of resource.", - "default": "dns#managedZonesListResponse" - }, - "managedZones": { - "type": "array", - "description": "The managed zone resources.", - "items": { - "$ref": "ManagedZone" - } - }, - "nextPageToken": { - "type": "string", - "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." - } - } - }, - "Operation": { - "id": "Operation", - "type": "object", - "description": "An operation represents a successful mutation performed on a Cloud DNS resource. Operations provide: - An audit log of server resource mutations. - A way to recover/retry API calls in the case where the response is never received by the caller. Use the caller specified client_operation_id.", - "properties": { - "dnsKeyContext": { - "$ref": "OperationDnsKeyContext", - "description": "Only populated if the operation targeted a DnsKey (output only)." - }, - "id": { - "type": "string", - "description": "Unique identifier for the resource. This is the client_operation_id if the client specified it when the mutation was initiated, otherwise, it is generated by the server. The name must be 1-63 characters long and match the regular expression [-a-z0-9]? (output only)" - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#operation\".", - "default": "dns#operation" - }, - "startTime": { - "type": "string", - "description": "The time that this operation was started by the server. This is in RFC3339 text format (output only)." - }, - "status": { - "type": "string", - "description": "Status of the operation. Can be one of the following: \"PENDING\" or \"DONE\" (output only).", - "enum": [ - "done", - "pending" - ], - "enumDescriptions": [ - "", - "" - ] - }, - "type": { - "type": "string", - "description": "Type of the operation. Operations include insert, update, and delete (output only)." - }, - "user": { - "type": "string", - "description": "User who requested the operation, for example: user@example.com. cloud-dns-system for operations automatically done by the system. (output only)" - }, - "zoneContext": { - "$ref": "OperationManagedZoneContext", - "description": "Only populated if the operation targeted a ManagedZone (output only)." - } - } - }, - "OperationDnsKeyContext": { - "id": "OperationDnsKeyContext", - "type": "object", - "properties": { - "newValue": { - "$ref": "DnsKey", - "description": "The post-operation DnsKey resource." - }, - "oldValue": { - "$ref": "DnsKey", - "description": "The pre-operation DnsKey resource." - } - } - }, - "OperationManagedZoneContext": { - "id": "OperationManagedZoneContext", - "type": "object", - "properties": { - "newValue": { - "$ref": "ManagedZone", - "description": "The post-operation ManagedZone resource." - }, - "oldValue": { - "$ref": "ManagedZone", - "description": "The pre-operation ManagedZone resource." - } - } - }, - "Project": { - "id": "Project", - "type": "object", - "description": "A project resource. The project is a top level container for resources including Cloud DNS ManagedZones. Projects can be created only in the APIs console.", - "properties": { - "id": { - "type": "string", - "description": "User assigned unique identifier for the resource (output only)." - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#project\".", - "default": "dns#project" - }, - "number": { - "type": "string", - "description": "Unique numeric identifier for the resource; defined by the server (output only).", - "format": "uint64" - }, - "quota": { - "$ref": "Quota", - "description": "Quotas assigned to this project (output only)." - } - } - }, - "Quota": { - "id": "Quota", - "type": "object", - "description": "Limits associated with a Project.", - "properties": { - "dnsKeysPerManagedZone": { - "type": "integer", - "description": "Maximum allowed number of DnsKeys per ManagedZone.", - "format": "int32" - }, - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#quota\".", - "default": "dns#quota" - }, - "managedZones": { - "type": "integer", - "description": "Maximum allowed number of managed zones in the project.", - "format": "int32" - }, - "resourceRecordsPerRrset": { - "type": "integer", - "description": "Maximum allowed number of ResourceRecords per ResourceRecordSet.", - "format": "int32" - }, - "rrsetAdditionsPerChange": { - "type": "integer", - "description": "Maximum allowed number of ResourceRecordSets to add per ChangesCreateRequest.", - "format": "int32" - }, - "rrsetDeletionsPerChange": { - "type": "integer", - "description": "Maximum allowed number of ResourceRecordSets to delete per ChangesCreateRequest.", - "format": "int32" - }, - "rrsetsPerManagedZone": { - "type": "integer", - "description": "Maximum allowed number of ResourceRecordSets per zone in the project.", - "format": "int32" - }, - "totalRrdataSizePerChange": { - "type": "integer", - "description": "Maximum allowed size for total rrdata in one ChangesCreateRequest in bytes.", - "format": "int32" - }, - "whitelistedKeySpecs": { - "type": "array", - "description": "DNSSEC algorithm and key length types that can be used for DnsKeys.", - "items": { - "$ref": "DnsKeySpec" - } - } - } - }, - "ResourceRecordSet": { - "id": "ResourceRecordSet", - "type": "object", - "description": "A unit of data that will be returned by the DNS servers.", - "properties": { - "kind": { - "type": "string", - "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#resourceRecordSet\".", - "default": "dns#resourceRecordSet" - }, - "name": { - "type": "string", - "description": "For example, www.example.com." - }, - "rrdatas": { - "type": "array", - "description": "As defined in RFC 1035 (section 5) and RFC 1034 (section 3.6.1).", - "items": { - "type": "string" - } - }, - "signatureRrdatas": { - "type": "array", - "description": "As defined in RFC 4034 (section 3.2).", - "items": { - "type": "string" - } - }, - "ttl": { - "type": "integer", - "description": "Number of seconds that this ResourceRecordSet can be cached by resolvers.", - "format": "int32" - }, - "type": { - "type": "string", - "description": "The identifier of a supported record type, for example, A, AAAA, MX, TXT, and so on." - } - } - }, - "ResourceRecordSetsListResponse": { - "id": "ResourceRecordSetsListResponse", - "type": "object", - "properties": { - "header": { - "$ref": "ResponseHeader" - }, - "kind": { - "type": "string", - "description": "Type of resource.", - "default": "dns#resourceRecordSetsListResponse" - }, - "nextPageToken": { - "type": "string", - "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." - }, - "rrsets": { - "type": "array", - "description": "The resource record set resources.", - "items": { - "$ref": "ResourceRecordSet" - } - } - } - }, - "ResponseHeader": { - "id": "ResponseHeader", - "type": "object", - "description": "Elements common to every response.", - "properties": { - "operationId": { - "type": "string", - "description": "For mutating operation requests that completed successfully. This is the client_operation_id if the client specified it, otherwise it is generated by the server (output only)." - } - } - } - }, - "resources": { - "changes": { - "methods": { - "create": { - "id": "dns.changes.create", - "path": "{project}/managedZones/{managedZone}/changes", - "httpMethod": "POST", - "description": "Atomically update the ResourceRecordSet collection.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "request": { - "$ref": "Change" - }, - "response": { - "$ref": "Change" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "get": { - "id": "dns.changes.get", - "path": "{project}/managedZones/{managedZone}/changes/{changeId}", - "httpMethod": "GET", - "description": "Fetch the representation of an existing Change.", - "parameters": { - "changeId": { - "type": "string", - "description": "The identifier of the requested change, from a previous ResourceRecordSetsChangeResponse.", - "required": true, - "location": "path" - }, - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone", - "changeId" - ], - "response": { - "$ref": "Change" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "list": { - "id": "dns.changes.list", - "path": "{project}/managedZones/{managedZone}/changes", - "httpMethod": "GET", - "description": "Enumerate Changes to a ResourceRecordSet collection.", - "parameters": { - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "maxResults": { - "type": "integer", - "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", - "format": "int32", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - }, - "sortBy": { - "type": "string", - "description": "Sorting criterion. The only supported value is change sequence.", - "default": "changeSequence", - "enum": [ - "changeSequence" - ], - "enumDescriptions": [ - "" - ], - "location": "query" - }, - "sortOrder": { - "type": "string", - "description": "Sorting order direction: 'ascending' or 'descending'.", - "location": "query" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "response": { - "$ref": "ChangesListResponse" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - }, - "dnsKeys": { - "methods": { - "get": { - "id": "dns.dnsKeys.get", - "path": "{project}/managedZones/{managedZone}/dnsKeys/{dnsKeyId}", - "httpMethod": "GET", - "description": "Fetch the representation of an existing DnsKey.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "digestType": { - "type": "string", - "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", - "location": "query" - }, - "dnsKeyId": { - "type": "string", - "description": "The identifier of the requested DnsKey.", - "required": true, - "location": "path" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone", - "dnsKeyId" - ], - "response": { - "$ref": "DnsKey" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "list": { - "id": "dns.dnsKeys.list", - "path": "{project}/managedZones/{managedZone}/dnsKeys", - "httpMethod": "GET", - "description": "Enumerate DnsKeys to a ResourceRecordSet collection.", - "parameters": { - "digestType": { - "type": "string", - "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "maxResults": { - "type": "integer", - "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", - "format": "int32", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "response": { - "$ref": "DnsKeysListResponse" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - }, - "managedZoneOperations": { - "methods": { - "get": { - "id": "dns.managedZoneOperations.get", - "path": "{project}/managedZones/{managedZone}/operations/{operation}", - "httpMethod": "GET", - "description": "Fetch the representation of an existing Operation.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request.", - "required": true, - "location": "path" - }, - "operation": { - "type": "string", - "description": "Identifies the operation addressed by this request.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone", - "operation" - ], - "response": { - "$ref": "Operation" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "list": { - "id": "dns.managedZoneOperations.list", - "path": "{project}/managedZones/{managedZone}/operations", - "httpMethod": "GET", - "description": "Enumerate Operations for the given ManagedZone.", - "parameters": { - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request.", - "required": true, - "location": "path" - }, - "maxResults": { - "type": "integer", - "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", - "format": "int32", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - }, - "sortBy": { - "type": "string", - "description": "Sorting criterion. The only supported values are START_TIME and ID.", - "default": "startTime", - "enum": [ - "id", - "startTime" - ], - "enumDescriptions": [ - "", - "" - ], - "location": "query" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "response": { - "$ref": "ManagedZoneOperationsListResponse" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - }, - "managedZones": { - "methods": { - "create": { - "id": "dns.managedZones.create", - "path": "{project}/managedZones", - "httpMethod": "POST", - "description": "Create a new ManagedZone.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project" - ], - "request": { - "$ref": "ManagedZone" - }, - "response": { - "$ref": "ManagedZone" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "delete": { - "id": "dns.managedZones.delete", - "path": "{project}/managedZones/{managedZone}", - "httpMethod": "DELETE", - "description": "Delete a previously created ManagedZone.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "get": { - "id": "dns.managedZones.get", - "path": "{project}/managedZones/{managedZone}", - "httpMethod": "GET", - "description": "Fetch the representation of an existing ManagedZone.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "response": { - "$ref": "ManagedZone" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "list": { - "id": "dns.managedZones.list", - "path": "{project}/managedZones", - "httpMethod": "GET", - "description": "Enumerate ManagedZones that have been created but not yet deleted.", - "parameters": { - "dnsName": { - "type": "string", - "description": "Restricts the list to return only zones with this domain name.", - "location": "query" - }, - "maxResults": { - "type": "integer", - "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", - "format": "int32", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project" - ], - "response": { - "$ref": "ManagedZonesListResponse" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "patch": { - "id": "dns.managedZones.patch", - "path": "{project}/managedZones/{managedZone}", - "httpMethod": "PATCH", - "description": "Update an existing ManagedZone. This method supports patch semantics.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "request": { - "$ref": "ManagedZone" - }, - "response": { - "$ref": "Operation" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - }, - "update": { - "id": "dns.managedZones.update", - "path": "{project}/managedZones/{managedZone}", - "httpMethod": "PUT", - "description": "Update an existing ManagedZone.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "request": { - "$ref": "ManagedZone" - }, - "response": { - "$ref": "Operation" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - }, - "projects": { - "methods": { - "get": { - "id": "dns.projects.get", - "path": "{project}", - "httpMethod": "GET", - "description": "Fetch the representation of an existing Project.", - "parameters": { - "clientOperationId": { - "type": "string", - "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - } - }, - "parameterOrder": [ - "project" - ], - "response": { - "$ref": "Project" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - }, - "resourceRecordSets": { - "methods": { - "list": { - "id": "dns.resourceRecordSets.list", - "path": "{project}/managedZones/{managedZone}/rrsets", - "httpMethod": "GET", - "description": "Enumerate ResourceRecordSets that have been created but not yet deleted.", - "parameters": { - "managedZone": { - "type": "string", - "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", - "required": true, - "location": "path" - }, - "maxResults": { - "type": "integer", - "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", - "format": "int32", - "location": "query" - }, - "name": { - "type": "string", - "description": "Restricts the list to return only records with this fully qualified domain name.", - "location": "query" - }, - "pageToken": { - "type": "string", - "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", - "location": "query" - }, - "project": { - "type": "string", - "description": "Identifies the project addressed by this request.", - "required": true, - "location": "path" - }, - "type": { - "type": "string", - "description": "Restricts the list to return only records of this type. If present, the \"name\" parameter must also be present.", - "location": "query" - } - }, - "parameterOrder": [ - "project", - "managedZone" - ], - "response": { - "$ref": "ResourceRecordSetsListResponse" - }, - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-platform.read-only", - "https://www.googleapis.com/auth/ndev.clouddns.readonly", - "https://www.googleapis.com/auth/ndev.clouddns.readwrite" - ] - } - } - } - } -} diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index d53862610..21ea2eb38 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -23,6 +25,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-google', version=version, @@ -66,5 +82,7 @@ setup( 'dns-google = certbot_dns_google._internal.dns_google:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_google', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-google/tests/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py new file mode 100644 index 000000000..e91db58ab --- /dev/null +++ b/certbot-dns-google/tests/dns_google_test.py @@ -0,0 +1,332 @@ +"""Tests for certbot_dns_google._internal.dns_google.""" + +import unittest + +import mock +from googleapiclient import discovery +from googleapiclient.errors import Error +from googleapiclient.http import HttpMock +from httplib2 import ServerNotFoundError + +from certbot import errors +from certbot.compat import os +from certbot.errors import PluginError +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +ACCOUNT_JSON_PATH = '/not/a/real/path.json' +API_ERROR = Error() +PROJECT_ID = "test-test-1" + + +class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_google._internal.dns_google import Authenticator + + path = os.path.join(self.tempdir, 'file.json') + open(path, "wb").close() + + super(AuthenticatorTest, self).setUp() + self.config = mock.MagicMock(google_credentials=path, + google_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "google") + + self.mock_client = mock.MagicMock() + # _get_google_client | pylint: disable=protected-access + self.auth._get_google_client = mock.MagicMock(return_value=self.mock_client) + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + # _attempt_cleanup | pylint: disable=protected-access + self.auth._attempt_cleanup = True + self.auth.cleanup([self.achall]) + + expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + @mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError) + def test_without_auth(self, unused_mock): + self.config.google_credentials = None + self.assertRaises(PluginError, self.auth.perform, [self.achall]) + + +class GoogleClientTest(unittest.TestCase): + record_name = "foo" + record_content = "bar" + record_ttl = 42 + zone = "ZONE_ID" + change = "an-id" + + def _setUp_client_with_mock(self, zone_request_side_effect): + from certbot_dns_google._internal.dns_google import _GoogleClient + + pwd = os.path.dirname(__file__) + rel_path = 'testdata/discovery.json' + discovery_file = os.path.join(pwd, rel_path) + http_mock = HttpMock(discovery_file, {'status': '200'}) + dns_api = discovery.build('dns', 'v1', http=http_mock) + + client = _GoogleClient(ACCOUNT_JSON_PATH, dns_api) + + # Setup + mock_mz = mock.MagicMock() + mock_mz.list.return_value.execute.side_effect = zone_request_side_effect + + mock_rrs = mock.MagicMock() + rrsets = {"rrsets": [{"name": "_acme-challenge.example.org.", "type": "TXT", + "rrdatas": ["\"example-txt-contents\""]}]} + mock_rrs.list.return_value.execute.return_value = rrsets + mock_changes = mock.MagicMock() + + client.dns.managedZones = mock.MagicMock(return_value=mock_mz) + client.dns.changes = mock.MagicMock(return_value=mock_changes) + client.dns.resourceRecordSets = mock.MagicMock(return_value=mock_rrs) + + return client, mock_changes + + @mock.patch('googleapiclient.discovery.build') + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') + def test_client_without_credentials(self, get_project_id_mock, credential_mock, + unused_discovery_mock): + from certbot_dns_google._internal.dns_google import _GoogleClient + _GoogleClient(None) + self.assertFalse(credential_mock.called) + self.assertTrue(get_project_id_mock.called) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') + def test_add_txt_record(self, get_project_id_mock, credential_mock): + client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) + credential_mock.assert_called_once_with('/not/a/real/path.json', mock.ANY) + self.assertFalse(get_project_id_mock.called) + + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + + expected_body = { + "kind": "dns#change", + "additions": [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": self.record_name + ".", + "rrdatas": [self.record_content, ], + "ttl": self.record_ttl, + }, + ], + } + + changes.create.assert_called_with(body=expected_body, + managedZone=self.zone, + project=PROJECT_ID) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_and_poll(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) + changes.create.return_value.execute.return_value = {'status': 'pending', 'id': self.change} + changes.get.return_value.execute.return_value = {'status': 'done'} + + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + + changes.create.assert_called_with(body=mock.ANY, + managedZone=self.zone, + project=PROJECT_ID) + + changes.get.assert_called_with(changeId=self.change, + managedZone=self.zone, + project=PROJECT_ID) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_delete_old(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock( + [{'managedZones': [{'id': self.zone}]}]) + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" + with mock.patch(mock_get_rrs) as mock_rrs: + mock_rrs.return_value = ["sample-txt-contents"] + client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + self.assertTrue(changes.create.called) + self.assertTrue("sample-txt-contents" in + changes.create.call_args_list[0][1]["body"]["deletions"][0]["rrdatas"]) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_noop(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock( + [{'managedZones': [{'id': self.zone}]}]) + client.add_txt_record(DOMAIN, "_acme-challenge.example.org", + "example-txt-contents", self.record_ttl) + self.assertFalse(changes.create.called) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_error_during_zone_lookup(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock(API_ERROR) + + self.assertRaises(errors.PluginError, client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_zone_not_found(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, + {'managedZones': []}]) + + self.assertRaises(errors.PluginError, client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_add_txt_record_error_during_add(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) + changes.create.side_effect = API_ERROR + + self.assertRaises(errors.PluginError, client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_del_txt_record(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) + + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" + with mock.patch(mock_get_rrs) as mock_rrs: + mock_rrs.return_value = ["\"sample-txt-contents\"", + "\"example-txt-contents\""] + client.del_txt_record(DOMAIN, "_acme-challenge.example.org", + "example-txt-contents", self.record_ttl) + + expected_body = { + "kind": "dns#change", + "deletions": [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": "_acme-challenge.example.org.", + "rrdatas": ["\"sample-txt-contents\"", "\"example-txt-contents\""], + "ttl": self.record_ttl, + }, + ], + "additions": [ + { + "kind": "dns#resourceRecordSet", + "type": "TXT", + "name": "_acme-challenge.example.org.", + "rrdatas": ["\"sample-txt-contents\"", ], + "ttl": self.record_ttl, + }, + ], + } + + changes.create.assert_called_with(body=expected_body, + managedZone=self.zone, + project=PROJECT_ID) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_del_txt_record_error_during_zone_lookup(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock(API_ERROR) + + client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_del_txt_record_zone_not_found(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, + {'managedZones': []}]) + + client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_del_txt_record_error_during_delete(self, unused_credential_mock): + client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) + changes.create.side_effect = API_ERROR + + client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_get_existing(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock( + [{'managedZones': [{'id': self.zone}]}]) + # Record name mocked in setUp + found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") + self.assertEqual(found, ["\"example-txt-contents\""]) + not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld") + self.assertEqual(not_found, None) + + @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') + @mock.patch('certbot_dns_google._internal.dns_google.open', + mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) + def test_get_existing_fallback(self, unused_credential_mock): + client, unused_changes = self._setUp_client_with_mock( + [{'managedZones': [{'id': self.zone}]}]) + # pylint: disable=no-member + mock_execute = client.dns.resourceRecordSets.return_value.list.return_value.execute + mock_execute.side_effect = API_ERROR + + rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org") + self.assertFalse(rrset) + + def test_get_project_id(self): + from certbot_dns_google._internal.dns_google import _GoogleClient + + response = DummyResponse() + response.status = 200 + + with mock.patch('httplib2.Http.request', return_value=(response, 'test-test-1')): + project_id = _GoogleClient.get_project_id() + self.assertEqual(project_id, 'test-test-1') + + with mock.patch('httplib2.Http.request', return_value=(response, b'test-test-1')): + project_id = _GoogleClient.get_project_id() + self.assertEqual(project_id, 'test-test-1') + + failed_response = DummyResponse() + failed_response.status = 404 + + with mock.patch('httplib2.Http.request', + return_value=(failed_response, "some detailed http error response")): + self.assertRaises(ValueError, _GoogleClient.get_project_id) + + with mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError): + self.assertRaises(ServerNotFoundError, _GoogleClient.get_project_id) + + +class DummyResponse(object): + """ + Dummy object to create a fake HTTPResponse (the actual one requires a socket and we only + need the status attribute) + """ + def __init__(self): + self.status = 200 + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-google/tests/testdata/discovery.json b/certbot-dns-google/tests/testdata/discovery.json new file mode 100644 index 000000000..79a406645 --- /dev/null +++ b/certbot-dns-google/tests/testdata/discovery.json @@ -0,0 +1,1401 @@ +{ + "kind": "discovery#restDescription", + "etag": "\"-iA1DTNe4s-I6JZXPt1t1Ypy8IU/gSzgHqX4Zwypnde2YApimTf_qmE\"", + "discoveryVersion": "v1", + "id": "dns:v1", + "name": "dns", + "version": "v1", + "revision": "20180314", + "title": "Google Cloud DNS API", + "description": "Configures and serves authoritative DNS records.", + "ownerDomain": "google.com", + "ownerName": "Google", + "icons": { + "x16": "https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png", + "x32": "https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png" + }, + "documentationLink": "https://developers.google.com/cloud-dns", + "protocol": "rest", + "baseUrl": "https://www.googleapis.com/dns/v1/projects/", + "basePath": "/dns/v1/projects/", + "rootUrl": "https://www.googleapis.com/", + "servicePath": "dns/v1/projects/", + "batchPath": "batch/dns/v1", + "parameters": { + "alt": { + "type": "string", + "description": "Data format for the response.", + "default": "json", + "enum": [ + "json" + ], + "enumDescriptions": [ + "Responses with Content-Type of application/json" + ], + "location": "query" + }, + "fields": { + "type": "string", + "description": "Selector specifying which fields to include in a partial response.", + "location": "query" + }, + "key": { + "type": "string", + "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", + "location": "query" + }, + "oauth_token": { + "type": "string", + "description": "OAuth 2.0 token for the current user.", + "location": "query" + }, + "prettyPrint": { + "type": "boolean", + "description": "Returns response with indentations and line breaks.", + "default": "true", + "location": "query" + }, + "quotaUser": { + "type": "string", + "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", + "location": "query" + }, + "userIp": { + "type": "string", + "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", + "location": "query" + } + }, + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/cloud-platform": { + "description": "View and manage your data across Google Cloud Platform services" + }, + "https://www.googleapis.com/auth/cloud-platform.read-only": { + "description": "View your data across Google Cloud Platform services" + }, + "https://www.googleapis.com/auth/ndev.clouddns.readonly": { + "description": "View your DNS records hosted by Google Cloud DNS" + }, + "https://www.googleapis.com/auth/ndev.clouddns.readwrite": { + "description": "View and manage your DNS records hosted by Google Cloud DNS" + } + } + } + }, + "schemas": { + "Change": { + "id": "Change", + "type": "object", + "description": "An atomic update to a collection of ResourceRecordSets.", + "properties": { + "additions": { + "type": "array", + "description": "Which ResourceRecordSets to add?", + "items": { + "$ref": "ResourceRecordSet" + } + }, + "deletions": { + "type": "array", + "description": "Which ResourceRecordSets to remove? Must match existing data exactly.", + "items": { + "$ref": "ResourceRecordSet" + } + }, + "id": { + "type": "string", + "description": "Unique identifier for the resource; defined by the server (output only)." + }, + "isServing": { + "type": "boolean", + "description": "If the DNS queries for the zone will be served." + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#change\".", + "default": "dns#change" + }, + "startTime": { + "type": "string", + "description": "The time that this operation was started by the server (output only). This is in RFC3339 text format." + }, + "status": { + "type": "string", + "description": "Status of the operation (output only).", + "enum": [ + "done", + "pending" + ], + "enumDescriptions": [ + "", + "" + ] + } + } + }, + "ChangesListResponse": { + "id": "ChangesListResponse", + "type": "object", + "description": "The response to a request to enumerate Changes to a ResourceRecordSets collection.", + "properties": { + "changes": { + "type": "array", + "description": "The requested changes.", + "items": { + "$ref": "Change" + } + }, + "header": { + "$ref": "ResponseHeader" + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "dns#changesListResponse" + }, + "nextPageToken": { + "type": "string", + "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." + } + } + }, + "DnsKey": { + "id": "DnsKey", + "type": "object", + "description": "A DNSSEC key pair.", + "properties": { + "algorithm": { + "type": "string", + "description": "String mnemonic specifying the DNSSEC algorithm of this key. Immutable after creation time.", + "enum": [ + "ecdsap256sha256", + "ecdsap384sha384", + "rsasha1", + "rsasha256", + "rsasha512" + ], + "enumDescriptions": [ + "", + "", + "", + "", + "" + ] + }, + "creationTime": { + "type": "string", + "description": "The time that this resource was created in the control plane. This is in RFC3339 text format. Output only." + }, + "description": { + "type": "string", + "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the resource's function." + }, + "digests": { + "type": "array", + "description": "Cryptographic hashes of the DNSKEY resource record associated with this DnsKey. These digests are needed to construct a DS record that points at this DNS key. Output only.", + "items": { + "$ref": "DnsKeyDigest" + } + }, + "id": { + "type": "string", + "description": "Unique identifier for the resource; defined by the server (output only)." + }, + "isActive": { + "type": "boolean", + "description": "Active keys will be used to sign subsequent changes to the ManagedZone. Inactive keys will still be present as DNSKEY Resource Records for the use of resolvers validating existing signatures." + }, + "keyLength": { + "type": "integer", + "description": "Length of the key in bits. Specified at creation time then immutable.", + "format": "uint32" + }, + "keyTag": { + "type": "integer", + "description": "The key tag is a non-cryptographic hash of the a DNSKEY resource record associated with this DnsKey. The key tag can be used to identify a DNSKEY more quickly (but it is not a unique identifier). In particular, the key tag is used in a parent zone's DS record to point at the DNSKEY in this child ManagedZone. The key tag is a number in the range [0, 65535] and the algorithm to calculate it is specified in RFC4034 Appendix B. Output only.", + "format": "int32" + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKey\".", + "default": "dns#dnsKey" + }, + "publicKey": { + "type": "string", + "description": "Base64 encoded public half of this key. Output only." + }, + "type": { + "type": "string", + "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types. Immutable after creation time.", + "enum": [ + "keySigning", + "zoneSigning" + ], + "enumDescriptions": [ + "", + "" + ] + } + } + }, + "DnsKeyDigest": { + "id": "DnsKeyDigest", + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "The base-16 encoded bytes of this digest. Suitable for use in a DS resource record." + }, + "type": { + "type": "string", + "description": "Specifies the algorithm used to calculate this digest.", + "enum": [ + "sha1", + "sha256", + "sha384" + ], + "enumDescriptions": [ + "", + "", + "" + ] + } + } + }, + "DnsKeySpec": { + "id": "DnsKeySpec", + "type": "object", + "description": "Parameters for DnsKey key generation. Used for generating initial keys for a new ManagedZone and as default when adding a new DnsKey.", + "properties": { + "algorithm": { + "type": "string", + "description": "String mnemonic specifying the DNSSEC algorithm of this key.", + "enum": [ + "ecdsap256sha256", + "ecdsap384sha384", + "rsasha1", + "rsasha256", + "rsasha512" + ], + "enumDescriptions": [ + "", + "", + "", + "", + "" + ] + }, + "keyLength": { + "type": "integer", + "description": "Length of the keys in bits.", + "format": "uint32" + }, + "keyType": { + "type": "string", + "description": "One of \"KEY_SIGNING\" or \"ZONE_SIGNING\". Keys of type KEY_SIGNING have the Secure Entry Point flag set and, when active, will be used to sign only resource record sets of type DNSKEY. Otherwise, the Secure Entry Point flag will be cleared and this key will be used to sign only resource record sets of other types.", + "enum": [ + "keySigning", + "zoneSigning" + ], + "enumDescriptions": [ + "", + "" + ] + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#dnsKeySpec\".", + "default": "dns#dnsKeySpec" + } + } + }, + "DnsKeysListResponse": { + "id": "DnsKeysListResponse", + "type": "object", + "description": "The response to a request to enumerate DnsKeys in a ManagedZone.", + "properties": { + "dnsKeys": { + "type": "array", + "description": "The requested resources.", + "items": { + "$ref": "DnsKey" + } + }, + "header": { + "$ref": "ResponseHeader" + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "dns#dnsKeysListResponse" + }, + "nextPageToken": { + "type": "string", + "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a \"snapshot\" of collections larger than the maximum page size." + } + } + }, + "ManagedZone": { + "id": "ManagedZone", + "type": "object", + "description": "A zone is a subtree of the DNS namespace under one administrative responsibility. A ManagedZone is a resource that represents a DNS zone hosted by the Cloud DNS service.", + "properties": { + "creationTime": { + "type": "string", + "description": "The time that this resource was created on the server. This is in RFC3339 text format. Output only." + }, + "description": { + "type": "string", + "description": "A mutable string of at most 1024 characters associated with this resource for the user's convenience. Has no effect on the managed zone's function." + }, + "dnsName": { + "type": "string", + "description": "The DNS name of this managed zone, for instance \"example.com.\"." + }, + "dnssecConfig": { + "$ref": "ManagedZoneDnsSecConfig", + "description": "DNSSEC configuration." + }, + "id": { + "type": "string", + "description": "Unique identifier for the resource; defined by the server (output only)", + "format": "uint64" + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZone\".", + "default": "dns#managedZone" + }, + "labels": { + "type": "object", + "description": "User labels.", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "type": "string", + "description": "User assigned name for this resource. Must be unique within the project. The name must be 1-63 characters long, must begin with a letter, end with a letter or digit, and only contain lowercase letters, digits or dashes." + }, + "nameServerSet": { + "type": "string", + "description": "Optionally specifies the NameServerSet for this ManagedZone. A NameServerSet is a set of DNS name servers that all host the same ManagedZones. Most users will leave this field unset." + }, + "nameServers": { + "type": "array", + "description": "Delegate your managed_zone to these virtual name servers; defined by the server (output only)", + "items": { + "type": "string" + } + } + } + }, + "ManagedZoneDnsSecConfig": { + "id": "ManagedZoneDnsSecConfig", + "type": "object", + "properties": { + "defaultKeySpecs": { + "type": "array", + "description": "Specifies parameters that will be used for generating initial DnsKeys for this ManagedZone. Output only while state is not OFF.", + "items": { + "$ref": "DnsKeySpec" + } + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#managedZoneDnsSecConfig\".", + "default": "dns#managedZoneDnsSecConfig" + }, + "nonExistence": { + "type": "string", + "description": "Specifies the mechanism used to provide authenticated denial-of-existence responses. Output only while state is not OFF.", + "enum": [ + "nsec", + "nsec3" + ], + "enumDescriptions": [ + "", + "" + ] + }, + "state": { + "type": "string", + "description": "Specifies whether DNSSEC is enabled, and what mode it is in.", + "enum": [ + "off", + "on", + "transfer" + ], + "enumDescriptions": [ + "", + "", + "" + ] + } + } + }, + "ManagedZoneOperationsListResponse": { + "id": "ManagedZoneOperationsListResponse", + "type": "object", + "properties": { + "header": { + "$ref": "ResponseHeader" + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "dns#managedZoneOperationsListResponse" + }, + "nextPageToken": { + "type": "string", + "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." + }, + "operations": { + "type": "array", + "description": "The operation resources.", + "items": { + "$ref": "Operation" + } + } + } + }, + "ManagedZonesListResponse": { + "id": "ManagedZonesListResponse", + "type": "object", + "properties": { + "header": { + "$ref": "ResponseHeader" + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "dns#managedZonesListResponse" + }, + "managedZones": { + "type": "array", + "description": "The managed zone resources.", + "items": { + "$ref": "ManagedZone" + } + }, + "nextPageToken": { + "type": "string", + "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your page token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." + } + } + }, + "Operation": { + "id": "Operation", + "type": "object", + "description": "An operation represents a successful mutation performed on a Cloud DNS resource. Operations provide: - An audit log of server resource mutations. - A way to recover/retry API calls in the case where the response is never received by the caller. Use the caller specified client_operation_id.", + "properties": { + "dnsKeyContext": { + "$ref": "OperationDnsKeyContext", + "description": "Only populated if the operation targeted a DnsKey (output only)." + }, + "id": { + "type": "string", + "description": "Unique identifier for the resource. This is the client_operation_id if the client specified it when the mutation was initiated, otherwise, it is generated by the server. The name must be 1-63 characters long and match the regular expression [-a-z0-9]? (output only)" + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#operation\".", + "default": "dns#operation" + }, + "startTime": { + "type": "string", + "description": "The time that this operation was started by the server. This is in RFC3339 text format (output only)." + }, + "status": { + "type": "string", + "description": "Status of the operation. Can be one of the following: \"PENDING\" or \"DONE\" (output only).", + "enum": [ + "done", + "pending" + ], + "enumDescriptions": [ + "", + "" + ] + }, + "type": { + "type": "string", + "description": "Type of the operation. Operations include insert, update, and delete (output only)." + }, + "user": { + "type": "string", + "description": "User who requested the operation, for example: user@example.com. cloud-dns-system for operations automatically done by the system. (output only)" + }, + "zoneContext": { + "$ref": "OperationManagedZoneContext", + "description": "Only populated if the operation targeted a ManagedZone (output only)." + } + } + }, + "OperationDnsKeyContext": { + "id": "OperationDnsKeyContext", + "type": "object", + "properties": { + "newValue": { + "$ref": "DnsKey", + "description": "The post-operation DnsKey resource." + }, + "oldValue": { + "$ref": "DnsKey", + "description": "The pre-operation DnsKey resource." + } + } + }, + "OperationManagedZoneContext": { + "id": "OperationManagedZoneContext", + "type": "object", + "properties": { + "newValue": { + "$ref": "ManagedZone", + "description": "The post-operation ManagedZone resource." + }, + "oldValue": { + "$ref": "ManagedZone", + "description": "The pre-operation ManagedZone resource." + } + } + }, + "Project": { + "id": "Project", + "type": "object", + "description": "A project resource. The project is a top level container for resources including Cloud DNS ManagedZones. Projects can be created only in the APIs console.", + "properties": { + "id": { + "type": "string", + "description": "User assigned unique identifier for the resource (output only)." + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#project\".", + "default": "dns#project" + }, + "number": { + "type": "string", + "description": "Unique numeric identifier for the resource; defined by the server (output only).", + "format": "uint64" + }, + "quota": { + "$ref": "Quota", + "description": "Quotas assigned to this project (output only)." + } + } + }, + "Quota": { + "id": "Quota", + "type": "object", + "description": "Limits associated with a Project.", + "properties": { + "dnsKeysPerManagedZone": { + "type": "integer", + "description": "Maximum allowed number of DnsKeys per ManagedZone.", + "format": "int32" + }, + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#quota\".", + "default": "dns#quota" + }, + "managedZones": { + "type": "integer", + "description": "Maximum allowed number of managed zones in the project.", + "format": "int32" + }, + "resourceRecordsPerRrset": { + "type": "integer", + "description": "Maximum allowed number of ResourceRecords per ResourceRecordSet.", + "format": "int32" + }, + "rrsetAdditionsPerChange": { + "type": "integer", + "description": "Maximum allowed number of ResourceRecordSets to add per ChangesCreateRequest.", + "format": "int32" + }, + "rrsetDeletionsPerChange": { + "type": "integer", + "description": "Maximum allowed number of ResourceRecordSets to delete per ChangesCreateRequest.", + "format": "int32" + }, + "rrsetsPerManagedZone": { + "type": "integer", + "description": "Maximum allowed number of ResourceRecordSets per zone in the project.", + "format": "int32" + }, + "totalRrdataSizePerChange": { + "type": "integer", + "description": "Maximum allowed size for total rrdata in one ChangesCreateRequest in bytes.", + "format": "int32" + }, + "whitelistedKeySpecs": { + "type": "array", + "description": "DNSSEC algorithm and key length types that can be used for DnsKeys.", + "items": { + "$ref": "DnsKeySpec" + } + } + } + }, + "ResourceRecordSet": { + "id": "ResourceRecordSet", + "type": "object", + "description": "A unit of data that will be returned by the DNS servers.", + "properties": { + "kind": { + "type": "string", + "description": "Identifies what kind of resource this is. Value: the fixed string \"dns#resourceRecordSet\".", + "default": "dns#resourceRecordSet" + }, + "name": { + "type": "string", + "description": "For example, www.example.com." + }, + "rrdatas": { + "type": "array", + "description": "As defined in RFC 1035 (section 5) and RFC 1034 (section 3.6.1).", + "items": { + "type": "string" + } + }, + "signatureRrdatas": { + "type": "array", + "description": "As defined in RFC 4034 (section 3.2).", + "items": { + "type": "string" + } + }, + "ttl": { + "type": "integer", + "description": "Number of seconds that this ResourceRecordSet can be cached by resolvers.", + "format": "int32" + }, + "type": { + "type": "string", + "description": "The identifier of a supported record type, for example, A, AAAA, MX, TXT, and so on." + } + } + }, + "ResourceRecordSetsListResponse": { + "id": "ResourceRecordSetsListResponse", + "type": "object", + "properties": { + "header": { + "$ref": "ResponseHeader" + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "dns#resourceRecordSetsListResponse" + }, + "nextPageToken": { + "type": "string", + "description": "The presence of this field indicates that there exist more results following your last page of results in pagination order. To fetch them, make another list request using this value as your pagination token.\n\nIn this way you can retrieve the complete contents of even very large collections one page at a time. However, if the contents of the collection change between the first and last paginated list request, the set of all elements returned will be an inconsistent view of the collection. There is no way to retrieve a consistent snapshot of a collection larger than the maximum page size." + }, + "rrsets": { + "type": "array", + "description": "The resource record set resources.", + "items": { + "$ref": "ResourceRecordSet" + } + } + } + }, + "ResponseHeader": { + "id": "ResponseHeader", + "type": "object", + "description": "Elements common to every response.", + "properties": { + "operationId": { + "type": "string", + "description": "For mutating operation requests that completed successfully. This is the client_operation_id if the client specified it, otherwise it is generated by the server (output only)." + } + } + } + }, + "resources": { + "changes": { + "methods": { + "create": { + "id": "dns.changes.create", + "path": "{project}/managedZones/{managedZone}/changes", + "httpMethod": "POST", + "description": "Atomically update the ResourceRecordSet collection.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "request": { + "$ref": "Change" + }, + "response": { + "$ref": "Change" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "get": { + "id": "dns.changes.get", + "path": "{project}/managedZones/{managedZone}/changes/{changeId}", + "httpMethod": "GET", + "description": "Fetch the representation of an existing Change.", + "parameters": { + "changeId": { + "type": "string", + "description": "The identifier of the requested change, from a previous ResourceRecordSetsChangeResponse.", + "required": true, + "location": "path" + }, + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone", + "changeId" + ], + "response": { + "$ref": "Change" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "list": { + "id": "dns.changes.list", + "path": "{project}/managedZones/{managedZone}/changes", + "httpMethod": "GET", + "description": "Enumerate Changes to a ResourceRecordSet collection.", + "parameters": { + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + "format": "int32", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + }, + "sortBy": { + "type": "string", + "description": "Sorting criterion. The only supported value is change sequence.", + "default": "changeSequence", + "enum": [ + "changeSequence" + ], + "enumDescriptions": [ + "" + ], + "location": "query" + }, + "sortOrder": { + "type": "string", + "description": "Sorting order direction: 'ascending' or 'descending'.", + "location": "query" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "response": { + "$ref": "ChangesListResponse" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + }, + "dnsKeys": { + "methods": { + "get": { + "id": "dns.dnsKeys.get", + "path": "{project}/managedZones/{managedZone}/dnsKeys/{dnsKeyId}", + "httpMethod": "GET", + "description": "Fetch the representation of an existing DnsKey.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "digestType": { + "type": "string", + "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", + "location": "query" + }, + "dnsKeyId": { + "type": "string", + "description": "The identifier of the requested DnsKey.", + "required": true, + "location": "path" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone", + "dnsKeyId" + ], + "response": { + "$ref": "DnsKey" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "list": { + "id": "dns.dnsKeys.list", + "path": "{project}/managedZones/{managedZone}/dnsKeys", + "httpMethod": "GET", + "description": "Enumerate DnsKeys to a ResourceRecordSet collection.", + "parameters": { + "digestType": { + "type": "string", + "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + "format": "int32", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "response": { + "$ref": "DnsKeysListResponse" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + }, + "managedZoneOperations": { + "methods": { + "get": { + "id": "dns.managedZoneOperations.get", + "path": "{project}/managedZones/{managedZone}/operations/{operation}", + "httpMethod": "GET", + "description": "Fetch the representation of an existing Operation.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request.", + "required": true, + "location": "path" + }, + "operation": { + "type": "string", + "description": "Identifies the operation addressed by this request.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone", + "operation" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "list": { + "id": "dns.managedZoneOperations.list", + "path": "{project}/managedZones/{managedZone}/operations", + "httpMethod": "GET", + "description": "Enumerate Operations for the given ManagedZone.", + "parameters": { + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request.", + "required": true, + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + "format": "int32", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + }, + "sortBy": { + "type": "string", + "description": "Sorting criterion. The only supported values are START_TIME and ID.", + "default": "startTime", + "enum": [ + "id", + "startTime" + ], + "enumDescriptions": [ + "", + "" + ], + "location": "query" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "response": { + "$ref": "ManagedZoneOperationsListResponse" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + }, + "managedZones": { + "methods": { + "create": { + "id": "dns.managedZones.create", + "path": "{project}/managedZones", + "httpMethod": "POST", + "description": "Create a new ManagedZone.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "request": { + "$ref": "ManagedZone" + }, + "response": { + "$ref": "ManagedZone" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "delete": { + "id": "dns.managedZones.delete", + "path": "{project}/managedZones/{managedZone}", + "httpMethod": "DELETE", + "description": "Delete a previously created ManagedZone.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "get": { + "id": "dns.managedZones.get", + "path": "{project}/managedZones/{managedZone}", + "httpMethod": "GET", + "description": "Fetch the representation of an existing ManagedZone.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "response": { + "$ref": "ManagedZone" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "list": { + "id": "dns.managedZones.list", + "path": "{project}/managedZones", + "httpMethod": "GET", + "description": "Enumerate ManagedZones that have been created but not yet deleted.", + "parameters": { + "dnsName": { + "type": "string", + "description": "Restricts the list to return only zones with this domain name.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + "format": "int32", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "response": { + "$ref": "ManagedZonesListResponse" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "patch": { + "id": "dns.managedZones.patch", + "path": "{project}/managedZones/{managedZone}", + "httpMethod": "PATCH", + "description": "Update an existing ManagedZone. This method supports patch semantics.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "request": { + "$ref": "ManagedZone" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + }, + "update": { + "id": "dns.managedZones.update", + "path": "{project}/managedZones/{managedZone}", + "httpMethod": "PUT", + "description": "Update an existing ManagedZone.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "request": { + "$ref": "ManagedZone" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + }, + "projects": { + "methods": { + "get": { + "id": "dns.projects.get", + "path": "{project}", + "httpMethod": "GET", + "description": "Fetch the representation of an existing Project.", + "parameters": { + "clientOperationId": { + "type": "string", + "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "response": { + "$ref": "Project" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + }, + "resourceRecordSets": { + "methods": { + "list": { + "id": "dns.resourceRecordSets.list", + "path": "{project}/managedZones/{managedZone}/rrsets", + "httpMethod": "GET", + "description": "Enumerate ResourceRecordSets that have been created but not yet deleted.", + "parameters": { + "managedZone": { + "type": "string", + "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + "required": true, + "location": "path" + }, + "maxResults": { + "type": "integer", + "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + "format": "int32", + "location": "query" + }, + "name": { + "type": "string", + "description": "Restricts the list to return only records with this fully qualified domain name.", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Identifies the project addressed by this request.", + "required": true, + "location": "path" + }, + "type": { + "type": "string", + "description": "Restricts the list to return only records of this type. If present, the \"name\" parameter must also be present.", + "location": "query" + } + }, + "parameterOrder": [ + "project", + "managedZone" + ], + "response": { + "$ref": "ResourceRecordSetsListResponse" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/ndev.clouddns.readonly", + "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + ] + } + } + } + } +} diff --git a/certbot-dns-linode/MANIFEST.in b/certbot-dns-linode/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-linode/MANIFEST.in +++ b/certbot-dns-linode/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py b/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py deleted file mode 100644 index 3cf615486..000000000 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Tests for certbot_dns_linode._internal.dns_linode.""" - -import unittest - -import mock - -from certbot import errors -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.tests import util as test_util -from certbot_dns_linode._internal.dns_linode import Authenticator - -TOKEN = 'a-token' -TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64' -TOKEN_V4 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"linode_key": TOKEN}, path) - - self.config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "linode") - - self.mock_client = mock.MagicMock() - # _get_linode_client | pylint: disable=protected-access - self.auth._get_linode_client = mock.MagicMock(return_value=self.mock_client) - - # pylint: disable=protected-access - def test_api_version_3_detection(self): - path = os.path.join(self.tempdir, 'file_3_auto.ini') - dns_test_common.write({"linode_key": TOKEN_V3}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(3, client.api_version) - - # pylint: disable=protected-access - def test_api_version_4_detection(self): - path = os.path.join(self.tempdir, 'file_4_auto.ini') - dns_test_common.write({"linode_key": TOKEN_V4}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(4, client.api_version) - - # pylint: disable=protected-access - def test_api_version_3_detection_empty_version(self): - path = os.path.join(self.tempdir, 'file_3_auto_empty.ini') - dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": ""}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(3, client.api_version) - - # pylint: disable=protected-access - def test_api_version_4_detection_empty_version(self): - path = os.path.join(self.tempdir, 'file_4_auto_empty.ini') - dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": ""}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(4, client.api_version) - - # pylint: disable=protected-access - def test_api_version_3_manual(self): - path = os.path.join(self.tempdir, 'file_3_manual.ini') - dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": 3}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(3, client.api_version) - - # pylint: disable=protected-access - def test_api_version_4_manual(self): - path = os.path.join(self.tempdir, 'file_4_manual.ini') - dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 4}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - client = auth._get_linode_client() - self.assertEqual(4, client.api_version) - - # pylint: disable=protected-access - def test_api_version_error(self): - path = os.path.join(self.tempdir, 'file_version_error.ini') - dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 5}, path) - - config = mock.MagicMock(linode_credentials=path, - linode_propagation_seconds=0) - auth = Authenticator(config, "linode") - auth._setup_credentials() - self.assertRaises(errors.PluginError, auth._get_linode_client) - -class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - DOMAIN_NOT_FOUND = Exception('Domain not found') - - def setUp(self): - from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient - - self.client = _LinodeLexiconClient(TOKEN, 3) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - -class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - DOMAIN_NOT_FOUND = Exception('Domain not found') - - def setUp(self): - from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient - - self.client = _LinodeLexiconClient(TOKEN, 4) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index ce2c91078..b6e20ec74 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -18,6 +20,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-linode', version=version, @@ -61,5 +77,7 @@ setup( 'dns-linode = certbot_dns_linode._internal.dns_linode:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_linode', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-linode/tests/dns_linode_test.py b/certbot-dns-linode/tests/dns_linode_test.py new file mode 100644 index 000000000..3cf615486 --- /dev/null +++ b/certbot-dns-linode/tests/dns_linode_test.py @@ -0,0 +1,144 @@ +"""Tests for certbot_dns_linode._internal.dns_linode.""" + +import unittest + +import mock + +from certbot import errors +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.tests import util as test_util +from certbot_dns_linode._internal.dns_linode import Authenticator + +TOKEN = 'a-token' +TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64' +TOKEN_V4 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"linode_key": TOKEN}, path) + + self.config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "linode") + + self.mock_client = mock.MagicMock() + # _get_linode_client | pylint: disable=protected-access + self.auth._get_linode_client = mock.MagicMock(return_value=self.mock_client) + + # pylint: disable=protected-access + def test_api_version_3_detection(self): + path = os.path.join(self.tempdir, 'file_3_auto.ini') + dns_test_common.write({"linode_key": TOKEN_V3}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_detection(self): + path = os.path.join(self.tempdir, 'file_4_auto.ini') + dns_test_common.write({"linode_key": TOKEN_V4}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_3_detection_empty_version(self): + path = os.path.join(self.tempdir, 'file_3_auto_empty.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": ""}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_detection_empty_version(self): + path = os.path.join(self.tempdir, 'file_4_auto_empty.ini') + dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": ""}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_3_manual(self): + path = os.path.join(self.tempdir, 'file_3_manual.ini') + dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": 3}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(3, client.api_version) + + # pylint: disable=protected-access + def test_api_version_4_manual(self): + path = os.path.join(self.tempdir, 'file_4_manual.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 4}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + client = auth._get_linode_client() + self.assertEqual(4, client.api_version) + + # pylint: disable=protected-access + def test_api_version_error(self): + path = os.path.join(self.tempdir, 'file_version_error.ini') + dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 5}, path) + + config = mock.MagicMock(linode_credentials=path, + linode_propagation_seconds=0) + auth = Authenticator(config, "linode") + auth._setup_credentials() + self.assertRaises(errors.PluginError, auth._get_linode_client) + +class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + DOMAIN_NOT_FOUND = Exception('Domain not found') + + def setUp(self): + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient + + self.client = _LinodeLexiconClient(TOKEN, 3) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + +class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + DOMAIN_NOT_FOUND = Exception('Domain not found') + + def setUp(self): + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient + + self.client = _LinodeLexiconClient(TOKEN, 4) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-luadns/MANIFEST.in b/certbot-dns-luadns/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-luadns/MANIFEST.in +++ b/certbot-dns-luadns/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py b/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py deleted file mode 100644 index 934d3e103..000000000 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Tests for certbot_dns_luadns._internal.dns_luadns.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.tests import util as test_util - -EMAIL = 'fake@example.com' -TOKEN = 'foo' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_luadns._internal.dns_luadns import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"luadns_email": EMAIL, "luadns_token": TOKEN}, path) - - self.config = mock.MagicMock(luadns_credentials=path, - luadns_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "luadns") - - self.mock_client = mock.MagicMock() - # _get_luadns_client | pylint: disable=protected-access - self.auth._get_luadns_client = mock.MagicMock(return_value=self.mock_client) - - -class LuaDNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - - LOGIN_ERROR = HTTPError("401 Client Error: Unauthorized for url: ...") - - def setUp(self): - from certbot_dns_luadns._internal.dns_luadns import _LuaDNSLexiconClient - - self.client = _LuaDNSLexiconClient(EMAIL, TOKEN, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index eb0d5b69b..2350638b5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-luadns', version=version, @@ -63,5 +79,7 @@ setup( 'dns-luadns = certbot_dns_luadns._internal.dns_luadns:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_luadns', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-luadns/tests/dns_luadns_test.py b/certbot-dns-luadns/tests/dns_luadns_test.py new file mode 100644 index 000000000..934d3e103 --- /dev/null +++ b/certbot-dns-luadns/tests/dns_luadns_test.py @@ -0,0 +1,52 @@ +"""Tests for certbot_dns_luadns._internal.dns_luadns.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.tests import util as test_util + +EMAIL = 'fake@example.com' +TOKEN = 'foo' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_luadns._internal.dns_luadns import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"luadns_email": EMAIL, "luadns_token": TOKEN}, path) + + self.config = mock.MagicMock(luadns_credentials=path, + luadns_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "luadns") + + self.mock_client = mock.MagicMock() + # _get_luadns_client | pylint: disable=protected-access + self.auth._get_luadns_client = mock.MagicMock(return_value=self.mock_client) + + +class LuaDNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + + LOGIN_ERROR = HTTPError("401 Client Error: Unauthorized for url: ...") + + def setUp(self): + from certbot_dns_luadns._internal.dns_luadns import _LuaDNSLexiconClient + + self.client = _LuaDNSLexiconClient(EMAIL, TOKEN, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-nsone/MANIFEST.in b/certbot-dns-nsone/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-nsone/MANIFEST.in +++ b/certbot-dns-nsone/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py deleted file mode 100644 index dd6168f08..000000000 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Tests for certbot_dns_nsone._internal.dns_nsone.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_KEY = 'foo' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_nsone._internal.dns_nsone import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write({"nsone_api_key": API_KEY}, path) - - self.config = mock.MagicMock(nsone_credentials=path, - nsone_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "nsone") - - self.mock_client = mock.MagicMock() - # _get_nsone_client | pylint: disable=protected-access - self.auth._get_nsone_client = mock.MagicMock(return_value=self.mock_client) - - -class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) - LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) - - def setUp(self): - from certbot_dns_nsone._internal.dns_nsone import _NS1LexiconClient - - self.client = _NS1LexiconClient(API_KEY, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index f3abb4d9d..09a5b7a93 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-nsone', version=version, @@ -63,5 +79,7 @@ setup( 'dns-nsone = certbot_dns_nsone._internal.dns_nsone:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_nsone', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-nsone/tests/dns_nsone_test.py b/certbot-dns-nsone/tests/dns_nsone_test.py new file mode 100644 index 000000000..dd6168f08 --- /dev/null +++ b/certbot-dns-nsone/tests/dns_nsone_test.py @@ -0,0 +1,52 @@ +"""Tests for certbot_dns_nsone._internal.dns_nsone.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_KEY = 'foo' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_nsone._internal.dns_nsone import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"nsone_api_key": API_KEY}, path) + + self.config = mock.MagicMock(nsone_credentials=path, + nsone_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "nsone") + + self.mock_client = mock.MagicMock() + # _get_nsone_client | pylint: disable=protected-access + self.auth._get_nsone_client = mock.MagicMock(return_value=self.mock_client) + + +class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) + LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) + + def setUp(self): + from certbot_dns_nsone._internal.dns_nsone import _NS1LexiconClient + + self.client = _NS1LexiconClient(API_KEY, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-ovh/MANIFEST.in b/certbot-dns-ovh/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-ovh/MANIFEST.in +++ b/certbot-dns-ovh/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py b/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py deleted file mode 100644 index a420239ab..000000000 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Tests for certbot_dns_ovh._internal.dns_ovh.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.tests import util as test_util - -ENDPOINT = 'ovh-eu' -APPLICATION_KEY = 'foo' -APPLICATION_SECRET = 'bar' -CONSUMER_KEY = 'spam' - - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_ovh._internal.dns_ovh import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - credentials = { - "ovh_endpoint": ENDPOINT, - "ovh_application_key": APPLICATION_KEY, - "ovh_application_secret": APPLICATION_SECRET, - "ovh_consumer_key": CONSUMER_KEY, - } - dns_test_common.write(credentials, path) - - self.config = mock.MagicMock(ovh_credentials=path, - ovh_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "ovh") - - self.mock_client = mock.MagicMock() - # _get_ovh_client | pylint: disable=protected-access - self.auth._get_ovh_client = mock.MagicMock(return_value=self.mock_client) - - -class OVHLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): - DOMAIN_NOT_FOUND = Exception('Domain example.com not found') - LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/...') - - def setUp(self): - from certbot_dns_ovh._internal.dns_ovh import _OVHLexiconClient - - self.client = _OVHLexiconClient( - ENDPOINT, APPLICATION_KEY, APPLICATION_SECRET, CONSUMER_KEY, 0 - ) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 308229ade..99b6e365b 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-ovh', version=version, @@ -63,5 +79,7 @@ setup( 'dns-ovh = certbot_dns_ovh._internal.dns_ovh:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_ovh', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-ovh/tests/dns_ovh_test.py b/certbot-dns-ovh/tests/dns_ovh_test.py new file mode 100644 index 000000000..a420239ab --- /dev/null +++ b/certbot-dns-ovh/tests/dns_ovh_test.py @@ -0,0 +1,62 @@ +"""Tests for certbot_dns_ovh._internal.dns_ovh.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.tests import util as test_util + +ENDPOINT = 'ovh-eu' +APPLICATION_KEY = 'foo' +APPLICATION_SECRET = 'bar' +CONSUMER_KEY = 'spam' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_ovh._internal.dns_ovh import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + credentials = { + "ovh_endpoint": ENDPOINT, + "ovh_application_key": APPLICATION_KEY, + "ovh_application_secret": APPLICATION_SECRET, + "ovh_consumer_key": CONSUMER_KEY, + } + dns_test_common.write(credentials, path) + + self.config = mock.MagicMock(ovh_credentials=path, + ovh_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "ovh") + + self.mock_client = mock.MagicMock() + # _get_ovh_client | pylint: disable=protected-access + self.auth._get_ovh_client = mock.MagicMock(return_value=self.mock_client) + + +class OVHLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = Exception('Domain example.com not found') + LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/...') + + def setUp(self): + from certbot_dns_ovh._internal.dns_ovh import _OVHLexiconClient + + self.client = _OVHLexiconClient( + ENDPOINT, APPLICATION_KEY, APPLICATION_SECRET, CONSUMER_KEY, 0 + ) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-rfc2136/MANIFEST.in b/certbot-dns-rfc2136/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-rfc2136/MANIFEST.in +++ b/certbot-dns-rfc2136/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py deleted file mode 100644 index c767dba23..000000000 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Tests for certbot_dns_rfc2136._internal.dns_rfc2136.""" - -import unittest - -import dns.flags -import dns.rcode -import dns.tsig -import mock - -from certbot import errors -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -SERVER = '192.0.2.1' -PORT = 53 -NAME = 'a-tsig-key.' -SECRET = 'SSB3b25kZXIgd2hvIHdpbGwgYm90aGVyIHRvIGRlY29kZSB0aGlzIHRleHQK' -VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret": SECRET} - - -class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): - - def setUp(self): - from certbot_dns_rfc2136._internal.dns_rfc2136 import Authenticator - - super(AuthenticatorTest, self).setUp() - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write(VALID_CONFIG, path) - - self.config = mock.MagicMock(rfc2136_credentials=path, - rfc2136_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "rfc2136") - - self.mock_client = mock.MagicMock() - # _get_rfc2136_client | pylint: disable=protected-access - self.auth._get_rfc2136_client = mock.MagicMock(return_value=self.mock_client) - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - # _attempt_cleanup | pylint: disable=protected-access - self.auth._attempt_cleanup = True - self.auth.cleanup([self.achall]) - - expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN, mock.ANY)] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_invalid_algorithm_raises(self): - config = VALID_CONFIG.copy() - config["rfc2136_algorithm"] = "INVALID" - dns_test_common.write(config, self.config.rfc2136_credentials) - - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) - - def test_valid_algorithm_passes(self): - config = VALID_CONFIG.copy() - config["rfc2136_algorithm"] = "HMAC-sha512" - dns_test_common.write(config, self.config.rfc2136_credentials) - - self.auth.perform([self.achall]) - - -class RFC2136ClientTest(unittest.TestCase): - - def setUp(self): - from certbot_dns_rfc2136._internal.dns_rfc2136 import _RFC2136Client - - self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) - - @mock.patch("dns.query.tcp") - def test_add_txt_record(self, query_mock): - query_mock.return_value.rcode.return_value = dns.rcode.NOERROR - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.rfc2136_client.add_txt_record("bar", "baz", 42) - - query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0])) - - @mock.patch("dns.query.tcp") - def test_add_txt_record_wraps_errors(self, query_mock): - query_mock.side_effect = Exception - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.assertRaises( - errors.PluginError, - self.rfc2136_client.add_txt_record, - "bar", "baz", 42) - - @mock.patch("dns.query.tcp") - def test_add_txt_record_server_error(self, query_mock): - query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.assertRaises( - errors.PluginError, - self.rfc2136_client.add_txt_record, - "bar", "baz", 42) - - @mock.patch("dns.query.tcp") - def test_del_txt_record(self, query_mock): - query_mock.return_value.rcode.return_value = dns.rcode.NOERROR - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.rfc2136_client.del_txt_record("bar", "baz") - - query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0])) - - @mock.patch("dns.query.tcp") - def test_del_txt_record_wraps_errors(self, query_mock): - query_mock.side_effect = Exception - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.assertRaises( - errors.PluginError, - self.rfc2136_client.del_txt_record, - "bar", "baz") - - @mock.patch("dns.query.tcp") - def test_del_txt_record_server_error(self, query_mock): - query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") - - self.assertRaises( - errors.PluginError, - self.rfc2136_client.del_txt_record, - "bar", "baz") - - def test_find_domain(self): - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) - - # _find_domain | pylint: disable=protected-access - domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN) - - self.assertTrue(domain == DOMAIN) - - def test_find_domain_wraps_errors(self): - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa = mock.MagicMock(return_value=False) - - self.assertRaises( - errors.PluginError, - # _find_domain | pylint: disable=protected-access - self.rfc2136_client._find_domain, - 'foo.bar.'+DOMAIN) - - @mock.patch("dns.query.tcp") - def test_query_soa_found(self, query_mock): - query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) - query_mock.return_value.rcode.return_value = dns.rcode.NOERROR - - # _query_soa | pylint: disable=protected-access - result = self.rfc2136_client._query_soa(DOMAIN) - - query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue(result) - - @mock.patch("dns.query.tcp") - def test_query_soa_not_found(self, query_mock): - query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN - - # _query_soa | pylint: disable=protected-access - result = self.rfc2136_client._query_soa(DOMAIN) - - query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertFalse(result) - - @mock.patch("dns.query.tcp") - def test_query_soa_wraps_errors(self, query_mock): - query_mock.side_effect = Exception - - self.assertRaises( - errors.PluginError, - # _query_soa | pylint: disable=protected-access - self.rfc2136_client._query_soa, - DOMAIN) - - @mock.patch("dns.query.udp") - @mock.patch("dns.query.tcp") - def test_query_soa_fallback_to_udp(self, tcp_mock, udp_mock): - tcp_mock.side_effect = OSError - udp_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) - udp_mock.return_value.rcode.return_value = dns.rcode.NOERROR - - # _query_soa | pylint: disable=protected-access - result = self.rfc2136_client._query_soa(DOMAIN) - - tcp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - udp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) - self.assertTrue(result) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 37b0e600c..d767caa1f 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-rfc2136', version=version, @@ -63,5 +79,7 @@ setup( 'dns-rfc2136 = certbot_dns_rfc2136._internal.dns_rfc2136:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_rfc2136', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-rfc2136/tests/dns_rfc2136_test.py b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py new file mode 100644 index 000000000..c767dba23 --- /dev/null +++ b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py @@ -0,0 +1,212 @@ +"""Tests for certbot_dns_rfc2136._internal.dns_rfc2136.""" + +import unittest + +import dns.flags +import dns.rcode +import dns.tsig +import mock + +from certbot import errors +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +SERVER = '192.0.2.1' +PORT = 53 +NAME = 'a-tsig-key.' +SECRET = 'SSB3b25kZXIgd2hvIHdpbGwgYm90aGVyIHRvIGRlY29kZSB0aGlzIHRleHQK' +VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret": SECRET} + + +class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): + + def setUp(self): + from certbot_dns_rfc2136._internal.dns_rfc2136 import Authenticator + + super(AuthenticatorTest, self).setUp() + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write(VALID_CONFIG, path) + + self.config = mock.MagicMock(rfc2136_credentials=path, + rfc2136_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "rfc2136") + + self.mock_client = mock.MagicMock() + # _get_rfc2136_client | pylint: disable=protected-access + self.auth._get_rfc2136_client = mock.MagicMock(return_value=self.mock_client) + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + # _attempt_cleanup | pylint: disable=protected-access + self.auth._attempt_cleanup = True + self.auth.cleanup([self.achall]) + + expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_invalid_algorithm_raises(self): + config = VALID_CONFIG.copy() + config["rfc2136_algorithm"] = "INVALID" + dns_test_common.write(config, self.config.rfc2136_credentials) + + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_valid_algorithm_passes(self): + config = VALID_CONFIG.copy() + config["rfc2136_algorithm"] = "HMAC-sha512" + dns_test_common.write(config, self.config.rfc2136_credentials) + + self.auth.perform([self.achall]) + + +class RFC2136ClientTest(unittest.TestCase): + + def setUp(self): + from certbot_dns_rfc2136._internal.dns_rfc2136 import _RFC2136Client + + self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) + + @mock.patch("dns.query.tcp") + def test_add_txt_record(self, query_mock): + query_mock.return_value.rcode.return_value = dns.rcode.NOERROR + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.rfc2136_client.add_txt_record("bar", "baz", 42) + + query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0])) + + @mock.patch("dns.query.tcp") + def test_add_txt_record_wraps_errors(self, query_mock): + query_mock.side_effect = Exception + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.assertRaises( + errors.PluginError, + self.rfc2136_client.add_txt_record, + "bar", "baz", 42) + + @mock.patch("dns.query.tcp") + def test_add_txt_record_server_error(self, query_mock): + query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.assertRaises( + errors.PluginError, + self.rfc2136_client.add_txt_record, + "bar", "baz", 42) + + @mock.patch("dns.query.tcp") + def test_del_txt_record(self, query_mock): + query_mock.return_value.rcode.return_value = dns.rcode.NOERROR + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.rfc2136_client.del_txt_record("bar", "baz") + + query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0])) + + @mock.patch("dns.query.tcp") + def test_del_txt_record_wraps_errors(self, query_mock): + query_mock.side_effect = Exception + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.assertRaises( + errors.PluginError, + self.rfc2136_client.del_txt_record, + "bar", "baz") + + @mock.patch("dns.query.tcp") + def test_del_txt_record_server_error(self, query_mock): + query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com") + + self.assertRaises( + errors.PluginError, + self.rfc2136_client.del_txt_record, + "bar", "baz") + + def test_find_domain(self): + # _query_soa | pylint: disable=protected-access + self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True]) + + # _find_domain | pylint: disable=protected-access + domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN) + + self.assertTrue(domain == DOMAIN) + + def test_find_domain_wraps_errors(self): + # _query_soa | pylint: disable=protected-access + self.rfc2136_client._query_soa = mock.MagicMock(return_value=False) + + self.assertRaises( + errors.PluginError, + # _find_domain | pylint: disable=protected-access + self.rfc2136_client._find_domain, + 'foo.bar.'+DOMAIN) + + @mock.patch("dns.query.tcp") + def test_query_soa_found(self, query_mock): + query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) + query_mock.return_value.rcode.return_value = dns.rcode.NOERROR + + # _query_soa | pylint: disable=protected-access + result = self.rfc2136_client._query_soa(DOMAIN) + + query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue(result) + + @mock.patch("dns.query.tcp") + def test_query_soa_not_found(self, query_mock): + query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN + + # _query_soa | pylint: disable=protected-access + result = self.rfc2136_client._query_soa(DOMAIN) + + query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertFalse(result) + + @mock.patch("dns.query.tcp") + def test_query_soa_wraps_errors(self, query_mock): + query_mock.side_effect = Exception + + self.assertRaises( + errors.PluginError, + # _query_soa | pylint: disable=protected-access + self.rfc2136_client._query_soa, + DOMAIN) + + @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") + def test_query_soa_fallback_to_udp(self, tcp_mock, udp_mock): + tcp_mock.side_effect = OSError + udp_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) + udp_mock.return_value.rcode.return_value = dns.rcode.NOERROR + + # _query_soa | pylint: disable=protected-access + result = self.rfc2136_client._query_soa(DOMAIN) + + tcp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + udp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue(result) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-route53/MANIFEST.in b/certbot-dns-route53/MANIFEST.in index ca37a7baf..fc62028b0 100644 --- a/certbot-dns-route53/MANIFEST.in +++ b/certbot-dns-route53/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py b/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py deleted file mode 100644 index 180ebdf6b..000000000 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py +++ /dev/null @@ -1,263 +0,0 @@ -"""Tests for certbot_dns_route53._internal.dns_route53.Authenticator""" - -import unittest - -import mock -from botocore.exceptions import NoCredentialsError, ClientError - -from certbot import errors -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN - - -class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest): - # pylint: disable=protected-access - - def setUp(self): - from certbot_dns_route53._internal.dns_route53 import Authenticator - - super(AuthenticatorTest, self).setUp() - - self.config = mock.MagicMock() - - # Set up dummy credentials for testing - os.environ["AWS_ACCESS_KEY_ID"] = "dummy_access_key" - os.environ["AWS_SECRET_ACCESS_KEY"] = "dummy_secret_access_key" - - self.auth = Authenticator(self.config, "route53") - - def tearDown(self): - # Remove the dummy credentials from env vars - del os.environ["AWS_ACCESS_KEY_ID"] - del os.environ["AWS_SECRET_ACCESS_KEY"] - super(AuthenticatorTest, self).tearDown() - - def test_perform(self): - self.auth._change_txt_record = mock.MagicMock() - self.auth._wait_for_change = mock.MagicMock() - - self.auth.perform([self.achall]) - - self.auth._change_txt_record.assert_called_once_with("UPSERT", - '_acme-challenge.' + DOMAIN, - mock.ANY) - self.assertEqual(self.auth._wait_for_change.call_count, 1) - - def test_perform_no_credentials_error(self): - self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError) - - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) - - def test_perform_client_error(self): - self.auth._change_txt_record = mock.MagicMock( - side_effect=ClientError({"Error": {"Code": "foo"}}, "bar")) - - self.assertRaises(errors.PluginError, - self.auth.perform, - [self.achall]) - - def test_cleanup(self): - self.auth._attempt_cleanup = True - - self.auth._change_txt_record = mock.MagicMock() - - self.auth.cleanup([self.achall]) - - self.auth._change_txt_record.assert_called_once_with("DELETE", - '_acme-challenge.'+DOMAIN, - mock.ANY) - - def test_cleanup_no_credentials_error(self): - self.auth._attempt_cleanup = True - - self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError) - - self.auth.cleanup([self.achall]) - - def test_cleanup_client_error(self): - self.auth._attempt_cleanup = True - - self.auth._change_txt_record = mock.MagicMock( - side_effect=ClientError({"Error": {"Code": "foo"}}, "bar")) - - self.auth.cleanup([self.achall]) - - -class ClientTest(unittest.TestCase): - # pylint: disable=protected-access - - PRIVATE_ZONE = { - "Id": "BAD-PRIVATE", - "Name": "example.com", - "Config": { - "PrivateZone": True - } - } - - EXAMPLE_NET_ZONE = { - "Id": "BAD-WRONG-TLD", - "Name": "example.net", - "Config": { - "PrivateZone": False - } - } - - EXAMPLE_COM_ZONE = { - "Id": "EXAMPLE", - "Name": "example.com", - "Config": { - "PrivateZone": False - } - } - - FOO_EXAMPLE_COM_ZONE = { - "Id": "FOO", - "Name": "foo.example.com", - "Config": { - "PrivateZone": False - } - } - - def setUp(self): - from certbot_dns_route53._internal.dns_route53 import Authenticator - - super(ClientTest, self).setUp() - - self.config = mock.MagicMock() - - # Set up dummy credentials for testing - os.environ["AWS_ACCESS_KEY_ID"] = "dummy_access_key" - os.environ["AWS_SECRET_ACCESS_KEY"] = "dummy_secret_access_key" - - self.client = Authenticator(self.config, "route53") - - def tearDown(self): - # Remove the dummy credentials from env vars - del os.environ["AWS_ACCESS_KEY_ID"] - del os.environ["AWS_SECRET_ACCESS_KEY"] - super(ClientTest, self).tearDown() - - def test_find_zone_id_for_domain(self): - self.client.r53.get_paginator = mock.MagicMock() - self.client.r53.get_paginator().paginate.return_value = [ - { - "HostedZones": [ - self.EXAMPLE_NET_ZONE, - self.EXAMPLE_COM_ZONE, - ] - } - ] - - result = self.client._find_zone_id_for_domain("foo.example.com") - self.assertEqual(result, "EXAMPLE") - - def test_find_zone_id_for_domain_pagination(self): - self.client.r53.get_paginator = mock.MagicMock() - self.client.r53.get_paginator().paginate.return_value = [ - { - "HostedZones": [ - self.PRIVATE_ZONE, - self.EXAMPLE_COM_ZONE, - ] - }, - { - "HostedZones": [ - self.PRIVATE_ZONE, - self.FOO_EXAMPLE_COM_ZONE, - ] - } - ] - - result = self.client._find_zone_id_for_domain("foo.example.com") - self.assertEqual(result, "FOO") - - def test_find_zone_id_for_domain_no_results(self): - self.client.r53.get_paginator = mock.MagicMock() - self.client.r53.get_paginator().paginate.return_value = [] - - self.assertRaises(errors.PluginError, - self.client._find_zone_id_for_domain, - "foo.example.com") - - def test_find_zone_id_for_domain_no_correct_results(self): - self.client.r53.get_paginator = mock.MagicMock() - self.client.r53.get_paginator().paginate.return_value = [ - { - "HostedZones": [ - self.PRIVATE_ZONE, - self.EXAMPLE_NET_ZONE, - ] - }, - ] - - self.assertRaises(errors.PluginError, - self.client._find_zone_id_for_domain, - "foo.example.com") - - def test_change_txt_record(self): - self.client._find_zone_id_for_domain = mock.MagicMock() - self.client.r53.change_resource_record_sets = mock.MagicMock( - return_value={"ChangeInfo": {"Id": 1}}) - - self.client._change_txt_record("FOO", DOMAIN, "foo") - - call_count = self.client.r53.change_resource_record_sets.call_count - self.assertEqual(call_count, 1) - - def test_change_txt_record_delete(self): - self.client._find_zone_id_for_domain = mock.MagicMock() - self.client.r53.change_resource_record_sets = mock.MagicMock( - return_value={"ChangeInfo": {"Id": 1}}) - - validation = "some-value" - validation_record = {"Value": '"{0}"'.format(validation)} - self.client._resource_records[DOMAIN] = [validation_record] - - self.client._change_txt_record("DELETE", DOMAIN, validation) - - call_count = self.client.r53.change_resource_record_sets.call_count - self.assertEqual(call_count, 1) - call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] - call_args_batch = call_args["ChangeBatch"]["Changes"][0] - self.assertEqual(call_args_batch["Action"], "DELETE") - self.assertEqual( - call_args_batch["ResourceRecordSet"]["ResourceRecords"], - [validation_record]) - - def test_change_txt_record_multirecord(self): - self.client._find_zone_id_for_domain = mock.MagicMock() - self.client._get_validation_rrset = mock.MagicMock() - self.client._resource_records[DOMAIN] = [ - {"Value": "\"pre-existing-value\""}, - {"Value": "\"pre-existing-value-two\""}, - ] - self.client.r53.change_resource_record_sets = mock.MagicMock( - return_value={"ChangeInfo": {"Id": 1}}) - - self.client._change_txt_record("DELETE", DOMAIN, "pre-existing-value") - - call_count = self.client.r53.change_resource_record_sets.call_count - call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] - call_args_batch = call_args["ChangeBatch"]["Changes"][0] - self.assertEqual(call_args_batch["Action"], "UPSERT") - self.assertEqual( - call_args_batch["ResourceRecordSet"]["ResourceRecords"], - [{"Value": "\"pre-existing-value-two\""}]) - - self.assertEqual(call_count, 1) - - def test_wait_for_change(self): - self.client.r53.get_change = mock.MagicMock( - side_effect=[{"ChangeInfo": {"Status": "PENDING"}}, - {"ChangeInfo": {"Status": "INSYNC"}}]) - - self.client._wait_for_change(1) - - self.assertTrue(self.client.r53.get_change.called) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index e766bf684..eb66a2d43 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,5 +1,7 @@ from setuptools import setup from setuptools import find_packages +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -14,6 +16,20 @@ install_requires = [ 'zope.interface', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-route53', version=version, @@ -55,5 +71,7 @@ setup( 'certbot-route53:auth = certbot_dns_route53.authenticator:Authenticator' ], }, + tests_require=["pytest"], test_suite='certbot_dns_route53', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-route53/tests/dns_route53_test.py b/certbot-dns-route53/tests/dns_route53_test.py new file mode 100644 index 000000000..180ebdf6b --- /dev/null +++ b/certbot-dns-route53/tests/dns_route53_test.py @@ -0,0 +1,263 @@ +"""Tests for certbot_dns_route53._internal.dns_route53.Authenticator""" + +import unittest + +import mock +from botocore.exceptions import NoCredentialsError, ClientError + +from certbot import errors +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN + + +class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest): + # pylint: disable=protected-access + + def setUp(self): + from certbot_dns_route53._internal.dns_route53 import Authenticator + + super(AuthenticatorTest, self).setUp() + + self.config = mock.MagicMock() + + # Set up dummy credentials for testing + os.environ["AWS_ACCESS_KEY_ID"] = "dummy_access_key" + os.environ["AWS_SECRET_ACCESS_KEY"] = "dummy_secret_access_key" + + self.auth = Authenticator(self.config, "route53") + + def tearDown(self): + # Remove the dummy credentials from env vars + del os.environ["AWS_ACCESS_KEY_ID"] + del os.environ["AWS_SECRET_ACCESS_KEY"] + super(AuthenticatorTest, self).tearDown() + + def test_perform(self): + self.auth._change_txt_record = mock.MagicMock() + self.auth._wait_for_change = mock.MagicMock() + + self.auth.perform([self.achall]) + + self.auth._change_txt_record.assert_called_once_with("UPSERT", + '_acme-challenge.' + DOMAIN, + mock.ANY) + self.assertEqual(self.auth._wait_for_change.call_count, 1) + + def test_perform_no_credentials_error(self): + self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError) + + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_perform_client_error(self): + self.auth._change_txt_record = mock.MagicMock( + side_effect=ClientError({"Error": {"Code": "foo"}}, "bar")) + + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_cleanup(self): + self.auth._attempt_cleanup = True + + self.auth._change_txt_record = mock.MagicMock() + + self.auth.cleanup([self.achall]) + + self.auth._change_txt_record.assert_called_once_with("DELETE", + '_acme-challenge.'+DOMAIN, + mock.ANY) + + def test_cleanup_no_credentials_error(self): + self.auth._attempt_cleanup = True + + self.auth._change_txt_record = mock.MagicMock(side_effect=NoCredentialsError) + + self.auth.cleanup([self.achall]) + + def test_cleanup_client_error(self): + self.auth._attempt_cleanup = True + + self.auth._change_txt_record = mock.MagicMock( + side_effect=ClientError({"Error": {"Code": "foo"}}, "bar")) + + self.auth.cleanup([self.achall]) + + +class ClientTest(unittest.TestCase): + # pylint: disable=protected-access + + PRIVATE_ZONE = { + "Id": "BAD-PRIVATE", + "Name": "example.com", + "Config": { + "PrivateZone": True + } + } + + EXAMPLE_NET_ZONE = { + "Id": "BAD-WRONG-TLD", + "Name": "example.net", + "Config": { + "PrivateZone": False + } + } + + EXAMPLE_COM_ZONE = { + "Id": "EXAMPLE", + "Name": "example.com", + "Config": { + "PrivateZone": False + } + } + + FOO_EXAMPLE_COM_ZONE = { + "Id": "FOO", + "Name": "foo.example.com", + "Config": { + "PrivateZone": False + } + } + + def setUp(self): + from certbot_dns_route53._internal.dns_route53 import Authenticator + + super(ClientTest, self).setUp() + + self.config = mock.MagicMock() + + # Set up dummy credentials for testing + os.environ["AWS_ACCESS_KEY_ID"] = "dummy_access_key" + os.environ["AWS_SECRET_ACCESS_KEY"] = "dummy_secret_access_key" + + self.client = Authenticator(self.config, "route53") + + def tearDown(self): + # Remove the dummy credentials from env vars + del os.environ["AWS_ACCESS_KEY_ID"] + del os.environ["AWS_SECRET_ACCESS_KEY"] + super(ClientTest, self).tearDown() + + def test_find_zone_id_for_domain(self): + self.client.r53.get_paginator = mock.MagicMock() + self.client.r53.get_paginator().paginate.return_value = [ + { + "HostedZones": [ + self.EXAMPLE_NET_ZONE, + self.EXAMPLE_COM_ZONE, + ] + } + ] + + result = self.client._find_zone_id_for_domain("foo.example.com") + self.assertEqual(result, "EXAMPLE") + + def test_find_zone_id_for_domain_pagination(self): + self.client.r53.get_paginator = mock.MagicMock() + self.client.r53.get_paginator().paginate.return_value = [ + { + "HostedZones": [ + self.PRIVATE_ZONE, + self.EXAMPLE_COM_ZONE, + ] + }, + { + "HostedZones": [ + self.PRIVATE_ZONE, + self.FOO_EXAMPLE_COM_ZONE, + ] + } + ] + + result = self.client._find_zone_id_for_domain("foo.example.com") + self.assertEqual(result, "FOO") + + def test_find_zone_id_for_domain_no_results(self): + self.client.r53.get_paginator = mock.MagicMock() + self.client.r53.get_paginator().paginate.return_value = [] + + self.assertRaises(errors.PluginError, + self.client._find_zone_id_for_domain, + "foo.example.com") + + def test_find_zone_id_for_domain_no_correct_results(self): + self.client.r53.get_paginator = mock.MagicMock() + self.client.r53.get_paginator().paginate.return_value = [ + { + "HostedZones": [ + self.PRIVATE_ZONE, + self.EXAMPLE_NET_ZONE, + ] + }, + ] + + self.assertRaises(errors.PluginError, + self.client._find_zone_id_for_domain, + "foo.example.com") + + def test_change_txt_record(self): + self.client._find_zone_id_for_domain = mock.MagicMock() + self.client.r53.change_resource_record_sets = mock.MagicMock( + return_value={"ChangeInfo": {"Id": 1}}) + + self.client._change_txt_record("FOO", DOMAIN, "foo") + + call_count = self.client.r53.change_resource_record_sets.call_count + self.assertEqual(call_count, 1) + + def test_change_txt_record_delete(self): + self.client._find_zone_id_for_domain = mock.MagicMock() + self.client.r53.change_resource_record_sets = mock.MagicMock( + return_value={"ChangeInfo": {"Id": 1}}) + + validation = "some-value" + validation_record = {"Value": '"{0}"'.format(validation)} + self.client._resource_records[DOMAIN] = [validation_record] + + self.client._change_txt_record("DELETE", DOMAIN, validation) + + call_count = self.client.r53.change_resource_record_sets.call_count + self.assertEqual(call_count, 1) + call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] + call_args_batch = call_args["ChangeBatch"]["Changes"][0] + self.assertEqual(call_args_batch["Action"], "DELETE") + self.assertEqual( + call_args_batch["ResourceRecordSet"]["ResourceRecords"], + [validation_record]) + + def test_change_txt_record_multirecord(self): + self.client._find_zone_id_for_domain = mock.MagicMock() + self.client._get_validation_rrset = mock.MagicMock() + self.client._resource_records[DOMAIN] = [ + {"Value": "\"pre-existing-value\""}, + {"Value": "\"pre-existing-value-two\""}, + ] + self.client.r53.change_resource_record_sets = mock.MagicMock( + return_value={"ChangeInfo": {"Id": 1}}) + + self.client._change_txt_record("DELETE", DOMAIN, "pre-existing-value") + + call_count = self.client.r53.change_resource_record_sets.call_count + call_args = self.client.r53.change_resource_record_sets.call_args_list[0][1] + call_args_batch = call_args["ChangeBatch"]["Changes"][0] + self.assertEqual(call_args_batch["Action"], "UPSERT") + self.assertEqual( + call_args_batch["ResourceRecordSet"]["ResourceRecords"], + [{"Value": "\"pre-existing-value-two\""}]) + + self.assertEqual(call_count, 1) + + def test_wait_for_change(self): + self.client.r53.get_change = mock.MagicMock( + side_effect=[{"ChangeInfo": {"Status": "PENDING"}}, + {"ChangeInfo": {"Status": "INSYNC"}}]) + + self.client._wait_for_change(1) + + self.assertTrue(self.client.r53.get_change.called) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-sakuracloud/MANIFEST.in b/certbot-dns-sakuracloud/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-sakuracloud/MANIFEST.in +++ b/certbot-dns-sakuracloud/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py deleted file mode 100644 index 16890b5a9..000000000 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud.""" - -import unittest - -import mock -from requests.exceptions import HTTPError - -from certbot.compat import os -from certbot.plugins import dns_test_common -from certbot.plugins import dns_test_common_lexicon -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -API_TOKEN = '00000000-0000-0000-0000-000000000000' -API_SECRET = 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw' - -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_sakuracloud._internal.dns_sakuracloud import Authenticator - - path = os.path.join(self.tempdir, 'file.ini') - dns_test_common.write( - {"sakuracloud_api_token": API_TOKEN, "sakuracloud_api_secret": API_SECRET}, - path - ) - - self.config = mock.MagicMock(sakuracloud_credentials=path, - sakuracloud_propagation_seconds=0) # don't wait during tests - - self.auth = Authenticator(self.config, "sakuracloud") - - self.mock_client = mock.MagicMock() - # _get_sakuracloud_client | pylint: disable=protected-access - self.auth._get_sakuracloud_client = mock.MagicMock(return_value=self.mock_client) - - -class SakuraCloudLexiconClientTest(unittest.TestCase, - dns_test_common_lexicon.BaseLexiconClientTest): - DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) - LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) - - def setUp(self): - from certbot_dns_sakuracloud._internal.dns_sakuracloud import _SakuraCloudLexiconClient - - self.client = _SakuraCloudLexiconClient(API_TOKEN, API_SECRET, 0) - - self.provider_mock = mock.MagicMock() - self.client.provider = self.provider_mock - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 779d7a9d8..b9584234b 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -1,6 +1,7 @@ from setuptools import setup from setuptools import find_packages - +from setuptools.command.test import test as TestCommand +import sys version = '1.0.0.dev0' @@ -19,6 +20,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-sakuracloud', version=version, @@ -62,5 +77,7 @@ setup( 'dns-sakuracloud = certbot_dns_sakuracloud._internal.dns_sakuracloud:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_sakuracloud', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py new file mode 100644 index 000000000..16890b5a9 --- /dev/null +++ b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py @@ -0,0 +1,56 @@ +"""Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud.""" + +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_TOKEN = '00000000-0000-0000-0000-000000000000' +API_SECRET = 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw' + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_sakuracloud._internal.dns_sakuracloud import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write( + {"sakuracloud_api_token": API_TOKEN, "sakuracloud_api_secret": API_SECRET}, + path + ) + + self.config = mock.MagicMock(sakuracloud_credentials=path, + sakuracloud_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "sakuracloud") + + self.mock_client = mock.MagicMock() + # _get_sakuracloud_client | pylint: disable=protected-access + self.auth._get_sakuracloud_client = mock.MagicMock(return_value=self.mock_client) + + +class SakuraCloudLexiconClientTest(unittest.TestCase, + dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) + LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) + + def setUp(self): + from certbot_dns_sakuracloud._internal.dns_sakuracloud import _SakuraCloudLexiconClient + + self.client = _SakuraCloudLexiconClient(API_TOKEN, API_SECRET, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover -- cgit v1.2.3 From d2b65b47f2e2968df62e0feea5ebf28bfdd3e4b2 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 26 Nov 2019 15:25:41 -0800 Subject: Refactor tests out of packaged module for acme plugin (#7600) * Move acme tests to tests/ directory outside of acme module * Fix call to messages_test in client_test * Move test_util.py and testdata/ into tests/ * Update manifest to package tests * Exclude pycache and .py[cod] --- acme/MANIFEST.in | 4 +- acme/acme/challenges_test.py | 419 ----------- acme/acme/client_test.py | 1308 ---------------------------------- acme/acme/crypto_util_test.py | 267 ------- acme/acme/errors_test.py | 53 -- acme/acme/fields_test.py | 72 -- acme/acme/jose_test.py | 53 -- acme/acme/jws_test.py | 67 -- acme/acme/magic_typing_test.py | 41 -- acme/acme/messages_test.py | 476 ------------- acme/acme/standalone_test.py | 190 ----- acme/acme/test_util.py | 68 -- acme/acme/testdata/README | 15 - acme/acme/testdata/cert-100sans.pem | 44 -- acme/acme/testdata/cert-idnsans.pem | 30 - acme/acme/testdata/cert-nocn.der | Bin 1397 -> 0 bytes acme/acme/testdata/cert-san.pem | 14 - acme/acme/testdata/cert.der | Bin 771 -> 0 bytes acme/acme/testdata/cert.pem | 13 - acme/acme/testdata/critical-san.pem | 28 - acme/acme/testdata/csr-100sans.pem | 41 -- acme/acme/testdata/csr-6sans.pem | 12 - acme/acme/testdata/csr-idnsans.pem | 27 - acme/acme/testdata/csr-nosans.pem | 8 - acme/acme/testdata/csr-san.pem | 10 - acme/acme/testdata/csr.der | Bin 607 -> 0 bytes acme/acme/testdata/csr.pem | 10 - acme/acme/testdata/dsa512_key.pem | 14 - acme/acme/testdata/rsa1024_key.pem | 15 - acme/acme/testdata/rsa2048_cert.pem | 22 - acme/acme/testdata/rsa2048_key.pem | 28 - acme/acme/testdata/rsa256_key.pem | 6 - acme/acme/testdata/rsa512_key.pem | 9 - acme/acme/util_test.py | 16 - acme/tests/challenges_test.py | 419 +++++++++++ acme/tests/client_test.py | 1308 ++++++++++++++++++++++++++++++++++ acme/tests/crypto_util_test.py | 267 +++++++ acme/tests/errors_test.py | 53 ++ acme/tests/fields_test.py | 72 ++ acme/tests/jose_test.py | 53 ++ acme/tests/jws_test.py | 67 ++ acme/tests/magic_typing_test.py | 41 ++ acme/tests/messages_test.py | 476 +++++++++++++ acme/tests/standalone_test.py | 190 +++++ acme/tests/test_util.py | 68 ++ acme/tests/testdata/README | 15 + acme/tests/testdata/cert-100sans.pem | 44 ++ acme/tests/testdata/cert-idnsans.pem | 30 + acme/tests/testdata/cert-nocn.der | Bin 0 -> 1397 bytes acme/tests/testdata/cert-san.pem | 14 + acme/tests/testdata/cert.der | Bin 0 -> 771 bytes acme/tests/testdata/cert.pem | 13 + acme/tests/testdata/critical-san.pem | 28 + acme/tests/testdata/csr-100sans.pem | 41 ++ acme/tests/testdata/csr-6sans.pem | 12 + acme/tests/testdata/csr-idnsans.pem | 27 + acme/tests/testdata/csr-nosans.pem | 8 + acme/tests/testdata/csr-san.pem | 10 + acme/tests/testdata/csr.der | Bin 0 -> 607 bytes acme/tests/testdata/csr.pem | 10 + acme/tests/testdata/dsa512_key.pem | 14 + acme/tests/testdata/rsa1024_key.pem | 15 + acme/tests/testdata/rsa2048_cert.pem | 22 + acme/tests/testdata/rsa2048_key.pem | 28 + acme/tests/testdata/rsa256_key.pem | 6 + acme/tests/testdata/rsa512_key.pem | 9 + acme/tests/util_test.py | 16 + 67 files changed, 3379 insertions(+), 3377 deletions(-) delete mode 100644 acme/acme/challenges_test.py delete mode 100644 acme/acme/client_test.py delete mode 100644 acme/acme/crypto_util_test.py delete mode 100644 acme/acme/errors_test.py delete mode 100644 acme/acme/fields_test.py delete mode 100644 acme/acme/jose_test.py delete mode 100644 acme/acme/jws_test.py delete mode 100644 acme/acme/magic_typing_test.py delete mode 100644 acme/acme/messages_test.py delete mode 100644 acme/acme/standalone_test.py delete mode 100644 acme/acme/test_util.py delete mode 100644 acme/acme/testdata/README delete mode 100644 acme/acme/testdata/cert-100sans.pem delete mode 100644 acme/acme/testdata/cert-idnsans.pem delete mode 100644 acme/acme/testdata/cert-nocn.der delete mode 100644 acme/acme/testdata/cert-san.pem delete mode 100644 acme/acme/testdata/cert.der delete mode 100644 acme/acme/testdata/cert.pem delete mode 100644 acme/acme/testdata/critical-san.pem delete mode 100644 acme/acme/testdata/csr-100sans.pem delete mode 100644 acme/acme/testdata/csr-6sans.pem delete mode 100644 acme/acme/testdata/csr-idnsans.pem delete mode 100644 acme/acme/testdata/csr-nosans.pem delete mode 100644 acme/acme/testdata/csr-san.pem delete mode 100644 acme/acme/testdata/csr.der delete mode 100644 acme/acme/testdata/csr.pem delete mode 100644 acme/acme/testdata/dsa512_key.pem delete mode 100644 acme/acme/testdata/rsa1024_key.pem delete mode 100644 acme/acme/testdata/rsa2048_cert.pem delete mode 100644 acme/acme/testdata/rsa2048_key.pem delete mode 100644 acme/acme/testdata/rsa256_key.pem delete mode 100644 acme/acme/testdata/rsa512_key.pem delete mode 100644 acme/acme/util_test.py create mode 100644 acme/tests/challenges_test.py create mode 100644 acme/tests/client_test.py create mode 100644 acme/tests/crypto_util_test.py create mode 100644 acme/tests/errors_test.py create mode 100644 acme/tests/fields_test.py create mode 100644 acme/tests/jose_test.py create mode 100644 acme/tests/jws_test.py create mode 100644 acme/tests/magic_typing_test.py create mode 100644 acme/tests/messages_test.py create mode 100644 acme/tests/standalone_test.py create mode 100644 acme/tests/test_util.py create mode 100644 acme/tests/testdata/README create mode 100644 acme/tests/testdata/cert-100sans.pem create mode 100644 acme/tests/testdata/cert-idnsans.pem create mode 100644 acme/tests/testdata/cert-nocn.der create mode 100644 acme/tests/testdata/cert-san.pem create mode 100644 acme/tests/testdata/cert.der create mode 100644 acme/tests/testdata/cert.pem create mode 100644 acme/tests/testdata/critical-san.pem create mode 100644 acme/tests/testdata/csr-100sans.pem create mode 100644 acme/tests/testdata/csr-6sans.pem create mode 100644 acme/tests/testdata/csr-idnsans.pem create mode 100644 acme/tests/testdata/csr-nosans.pem create mode 100644 acme/tests/testdata/csr-san.pem create mode 100644 acme/tests/testdata/csr.der create mode 100644 acme/tests/testdata/csr.pem create mode 100644 acme/tests/testdata/dsa512_key.pem create mode 100644 acme/tests/testdata/rsa1024_key.pem create mode 100644 acme/tests/testdata/rsa2048_cert.pem create mode 100644 acme/tests/testdata/rsa2048_key.pem create mode 100644 acme/tests/testdata/rsa256_key.pem create mode 100644 acme/tests/testdata/rsa512_key.pem create mode 100644 acme/tests/util_test.py diff --git a/acme/MANIFEST.in b/acme/MANIFEST.in index 1619bef69..de254250e 100644 --- a/acme/MANIFEST.in +++ b/acme/MANIFEST.in @@ -3,4 +3,6 @@ include README.rst include pytest.ini recursive-include docs * recursive-include examples * -recursive-include acme/testdata * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py deleted file mode 100644 index 4f728e4a4..000000000 --- a/acme/acme/challenges_test.py +++ /dev/null @@ -1,419 +0,0 @@ -"""Tests for acme.challenges.""" -import unittest - -import josepy as jose -import mock -import requests - -from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import - -from acme import test_util - -CERT = test_util.load_comparable_cert('cert.pem') -KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) - - -class ChallengeTest(unittest.TestCase): - - def test_from_json_unrecognized(self): - from acme.challenges import Challenge - from acme.challenges import UnrecognizedChallenge - chall = UnrecognizedChallenge({"type": "foo"}) - # pylint: disable=no-member - self.assertEqual(chall, Challenge.from_json(chall.jobj)) - - -class UnrecognizedChallengeTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import UnrecognizedChallenge - self.jobj = {"type": "foo"} - self.chall = UnrecognizedChallenge(self.jobj) - - def test_to_partial_json(self): - self.assertEqual(self.jobj, self.chall.to_partial_json()) - - def test_from_json(self): - from acme.challenges import UnrecognizedChallenge - self.assertEqual( - self.chall, UnrecognizedChallenge.from_json(self.jobj)) - - -class KeyAuthorizationChallengeResponseTest(unittest.TestCase): - - def setUp(self): - def _encode(name): - assert name == "token" - return "foo" - self.chall = mock.Mock() - self.chall.encode.side_effect = _encode - - def test_verify_ok(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertTrue(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_token(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_thumbprint(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - def test_verify_wrong_form(self): - from acme.challenges import KeyAuthorizationChallengeResponse - response = KeyAuthorizationChallengeResponse( - key_authorization='.foo.oKGqedy-b-acd5eoybm2f-' - 'NVFxvyOoET5CNy3xnv8WY') - self.assertFalse(response.verify(self.chall, KEY.public_key())) - - -class DNS01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS01Response - self.msg = DNS01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'dns-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import DNS01 - self.chall = DNS01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS01Response - self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS01Response - hash(DNS01Response.from_json(self.jmsg)) - - def test_simple_verify_failure(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - public_key = key2.public_key() - verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertFalse(verified) - - def test_simple_verify_success(self): - public_key = KEY.public_key() - verified = self.response.simple_verify(self.chall, "local", public_key) - self.assertTrue(verified) - - -class DNS01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS01 - self.msg = DNS01(token=jose.decode_b64jose( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) - self.jmsg = { - 'type': 'dns-01', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_validation_domain_name(self): - self.assertEqual('_acme-challenge.www.example.com', - self.msg.validation_domain_name('www.example.com')) - - def test_validation(self): - self.assertEqual( - "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk", - self.msg.validation(KEY)) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS01 - self.assertEqual(self.msg, DNS01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS01 - hash(DNS01.from_json(self.jmsg)) - - -class HTTP01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import HTTP01Response - self.msg = HTTP01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'http-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import HTTP01 - self.chall = HTTP01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import HTTP01Response - self.assertEqual( - self.msg, HTTP01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import HTTP01Response - hash(HTTP01Response.from_json(self.jmsg)) - - def test_simple_verify_bad_key_authorization(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - self.response.simple_verify(self.chall, "local", key2.public_key()) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_good_validation(self, mock_get): - validation = self.chall.validation(KEY) - mock_get.return_value = mock.MagicMock(text=validation) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_bad_validation(self, mock_get): - mock_get.return_value = mock.MagicMock(text="!") - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_whitespace_validation(self, mock_get): - from acme.challenges import HTTP01Response - mock_get.return_value = mock.MagicMock( - text=(self.chall.validation(KEY) + - HTTP01Response.WHITESPACE_CUTSET)) - self.assertTrue(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - mock_get.assert_called_once_with(self.chall.uri("local")) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_connection_error(self, mock_get): - mock_get.side_effect = requests.exceptions.RequestException - self.assertFalse(self.response.simple_verify( - self.chall, "local", KEY.public_key())) - - @mock.patch("acme.challenges.requests.get") - def test_simple_verify_port(self, mock_get): - self.response.simple_verify( - self.chall, domain="local", - account_public_key=KEY.public_key(), port=8080) - self.assertEqual("local:8080", urllib_parse.urlparse( - mock_get.mock_calls[0][1][0]).netloc) - - -class HTTP01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import HTTP01 - self.msg = HTTP01( - token=jose.decode_b64jose( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) - self.jmsg = { - 'type': 'http-01', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_path(self): - self.assertEqual(self.msg.path, '/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') - - def test_uri(self): - self.assertEqual( - 'http://example.com/.well-known/acme-challenge/' - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - self.msg.uri('example.com')) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import HTTP01 - self.assertEqual(self.msg, HTTP01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import HTTP01 - hash(HTTP01.from_json(self.jmsg)) - - def test_good_token(self): - self.assertTrue(self.msg.good_token) - self.assertFalse( - self.msg.update(token=b'..').good_token) - - -class TLSALPN01ResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import TLSALPN01Response - self.msg = TLSALPN01Response(key_authorization=u'foo') - self.jmsg = { - 'resource': 'challenge', - 'type': 'tls-alpn-01', - 'keyAuthorization': u'foo', - } - - from acme.challenges import TLSALPN01 - self.chall = TLSALPN01(token=(b'x' * 16)) - self.response = self.chall.response(KEY) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSALPN01Response - self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSALPN01Response - hash(TLSALPN01Response.from_json(self.jmsg)) - - -class TLSALPN01Test(unittest.TestCase): - - def setUp(self): - from acme.challenges import TLSALPN01 - self.msg = TLSALPN01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - self.jmsg = { - 'type': 'tls-alpn-01', - 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSALPN01 - self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSALPN01 - hash(TLSALPN01.from_json(self.jmsg)) - - def test_from_json_invalid_token_length(self): - from acme.challenges import TLSALPN01 - self.jmsg['token'] = jose.encode_b64jose(b'abcd') - self.assertRaises( - jose.DeserializationError, TLSALPN01.from_json, self.jmsg) - - def test_validation(self): - self.assertRaises(NotImplementedError, self.msg.validation, KEY) - - -class DNSTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS - self.msg = DNS(token=jose.b64decode( - b'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) - self.jmsg = { - 'type': 'dns', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNS - self.assertEqual(self.msg, DNS.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import DNS - hash(DNS.from_json(self.jmsg)) - - def test_gen_check_validation(self): - self.assertTrue(self.msg.check_validation( - self.msg.gen_validation(KEY), KEY.public_key())) - - def test_gen_check_validation_wrong_key(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem')) - self.assertFalse(self.msg.check_validation( - self.msg.gen_validation(KEY), key2.public_key())) - - def test_check_validation_wrong_payload(self): - validations = tuple( - jose.JWS.sign(payload=payload, alg=jose.RS256, key=KEY) - for payload in (b'', b'{}') - ) - for validation in validations: - self.assertFalse(self.msg.check_validation( - validation, KEY.public_key())) - - def test_check_validation_wrong_fields(self): - bad_validation = jose.JWS.sign( - payload=self.msg.update( - token=b'x' * 20).json_dumps().encode('utf-8'), - alg=jose.RS256, key=KEY) - self.assertFalse(self.msg.check_validation( - bad_validation, KEY.public_key())) - - def test_gen_response(self): - with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen: - mock_gen.return_value = mock.sentinel.validation - response = self.msg.gen_response(KEY) - from acme.challenges import DNSResponse - self.assertTrue(isinstance(response, DNSResponse)) - self.assertEqual(response.validation, mock.sentinel.validation) - - def test_validation_domain_name(self): - self.assertEqual( - '_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf')) - - -class DNSResponseTest(unittest.TestCase): - - def setUp(self): - from acme.challenges import DNS - self.chall = DNS(token=jose.b64decode( - b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA")) - self.validation = jose.JWS.sign( - payload=self.chall.json_dumps(sort_keys=True).encode(), - key=KEY, alg=jose.RS256) - - from acme.challenges import DNSResponse - self.msg = DNSResponse(validation=self.validation) - self.jmsg_to = { - 'resource': 'challenge', - 'type': 'dns', - 'validation': self.validation, - } - self.jmsg_from = { - 'resource': 'challenge', - 'type': 'dns', - 'validation': self.validation.to_json(), - } - - def test_to_partial_json(self): - self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import DNSResponse - self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from)) - - def test_from_json_hashable(self): - from acme.challenges import DNSResponse - hash(DNSResponse.from_json(self.jmsg_from)) - - def test_check_validation(self): - self.assertTrue( - self.msg.check_validation(self.chall, KEY.public_key())) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py deleted file mode 100644 index a53ce799b..000000000 --- a/acme/acme/client_test.py +++ /dev/null @@ -1,1308 +0,0 @@ -"""Tests for acme.client.""" -# pylint: disable=too-many-lines -import copy -import datetime -import json -import unittest - -from six.moves import http_client # pylint: disable=import-error - -import josepy as jose -import mock -import OpenSSL -import requests - -from acme import challenges -from acme import errors -from acme import jws as acme_jws -from acme import messages -from acme import messages_test -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - - -CERT_DER = test_util.load_vector('cert.der') -CERT_SAN_PEM = test_util.load_vector('cert-san.pem') -CSR_SAN_PEM = test_util.load_vector('csr-san.pem') -KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) -KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - -DIRECTORY_V1 = messages.Directory({ - messages.NewRegistration: - 'https://www.letsencrypt-demo.org/acme/new-reg', - messages.Revocation: - 'https://www.letsencrypt-demo.org/acme/revoke-cert', - messages.NewAuthorization: - 'https://www.letsencrypt-demo.org/acme/new-authz', - messages.CertificateRequest: - 'https://www.letsencrypt-demo.org/acme/new-cert', -}) - -DIRECTORY_V2 = messages.Directory({ - 'newAccount': 'https://www.letsencrypt-demo.org/acme/new-account', - 'newNonce': 'https://www.letsencrypt-demo.org/acme/new-nonce', - 'newOrder': 'https://www.letsencrypt-demo.org/acme/new-order', - 'revokeCert': 'https://www.letsencrypt-demo.org/acme/revoke-cert', -}) - - -class ClientTestBase(unittest.TestCase): - """Base for tests in acme.client.""" - - def setUp(self): - self.response = mock.MagicMock( - ok=True, status_code=http_client.OK, headers={}, links={}) - self.net = mock.MagicMock() - self.net.post.return_value = self.response - self.net.get.return_value = self.response - - self.identifier = messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='example.com') - - # Registration - self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') - reg = messages.Registration( - contact=self.contact, key=KEY.public_key()) - the_arg = dict(reg) # type: Dict - self.new_reg = messages.NewRegistration(**the_arg) - self.regr = messages.RegistrationResource( - body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') - - # Authorization - authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1' - challb = messages.ChallengeBody( - uri=(authzr_uri + '/1'), status=messages.STATUS_VALID, - chall=challenges.DNS(token=jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'))) - self.challr = messages.ChallengeResource( - body=challb, authzr_uri=authzr_uri) - self.authz = messages.Authorization( - identifier=messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='example.com'), - challenges=(challb,), combinations=None) - self.authzr = messages.AuthorizationResource( - body=self.authz, uri=authzr_uri) - - # Reason code for revocation - self.rsn = 1 - - -class BackwardsCompatibleClientV2Test(ClientTestBase): - """Tests for acme.client.BackwardsCompatibleClientV2.""" - - def setUp(self): - super(BackwardsCompatibleClientV2Test, self).setUp() - # contains a loaded cert - self.certr = messages.CertificateResource( - body=messages_test.CERT) - - loaded = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, CERT_SAN_PEM) - wrapped = jose.ComparableX509(loaded) - self.chain = [wrapped, wrapped] - - self.cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode() - - single_chain = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, loaded).decode() - self.chain_pem = single_chain + single_chain - - self.fullchain_pem = self.cert_pem + self.chain_pem - - self.orderr = messages.OrderResource( - csr_pem=CSR_SAN_PEM) - - def _init(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import BackwardsCompatibleClientV2 - return BackwardsCompatibleClientV2(net=self.net, - key=KEY, server=uri) - - def test_init_downloads_directory(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import BackwardsCompatibleClientV2 - BackwardsCompatibleClientV2(net=self.net, - key=KEY, server=uri) - self.net.get.assert_called_once_with(uri) - - def test_init_acme_version(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - client = self._init() - self.assertEqual(client.acme_version, 1) - - self.response.json.return_value = DIRECTORY_V2.to_json() - client = self._init() - self.assertEqual(client.acme_version, 2) - - def test_query_registration_client_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - client = self._init() - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, client.query_registration(self.regr)) - - def test_forwarding(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - client = self._init() - self.assertEqual(client.directory, client.client.directory) - self.assertEqual(client.key, KEY) - self.assertEqual(client.deactivate_registration, client.client.deactivate_registration) - self.assertRaises(AttributeError, client.__getattr__, 'nonexistent') - self.assertRaises(AttributeError, client.__getattr__, 'new_account_and_tos') - self.assertRaises(AttributeError, client.__getattr__, 'new_account') - - def test_new_account_and_tos(self): - # v2 no tos - self.response.json.return_value = DIRECTORY_V2.to_json() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().new_account.assert_called_with(self.new_reg) - - # v2 tos good - with mock.patch('acme.client.ClientV2') as mock_client: - mock_client().directory.meta.__contains__.return_value = True - client = self._init() - client.new_account_and_tos(self.new_reg, lambda x: True) - mock_client().new_account.assert_called_with( - self.new_reg.update(terms_of_service_agreed=True)) - - # v2 tos bad - with mock.patch('acme.client.ClientV2') as mock_client: - mock_client().directory.meta.__contains__.return_value = True - client = self._init() - def _tos_cb(tos): - raise errors.Error - self.assertRaises(errors.Error, client.new_account_and_tos, - self.new_reg, _tos_cb) - mock_client().new_account.assert_not_called() - - # v1 yes tos - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - regr = mock.MagicMock(terms_of_service="TOS") - mock_client().register.return_value = regr - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().register.assert_called_once_with(self.new_reg) - mock_client().agree_to_tos.assert_called_once_with(regr) - - # v1 no tos - with mock.patch('acme.client.Client') as mock_client: - regr = mock.MagicMock(terms_of_service=None) - mock_client().register.return_value = regr - client = self._init() - client.new_account_and_tos(self.new_reg) - mock_client().register.assert_called_once_with(self.new_reg) - mock_client().agree_to_tos.assert_not_called() - - @mock.patch('OpenSSL.crypto.load_certificate_request') - @mock.patch('acme.crypto_util._pyopenssl_cert_or_req_all_names') - def test_new_order_v1(self, mock__pyopenssl_cert_or_req_all_names, - unused_mock_load_certificate_request): - self.response.json.return_value = DIRECTORY_V1.to_json() - mock__pyopenssl_cert_or_req_all_names.return_value = ['example.com', 'www.example.com'] - mock_csr_pem = mock.MagicMock() - with mock.patch('acme.client.Client') as mock_client: - mock_client().request_domain_challenges.return_value = mock.sentinel.auth - client = self._init() - orderr = client.new_order(mock_csr_pem) - self.assertEqual(orderr.authorizations, [mock.sentinel.auth, mock.sentinel.auth]) - - def test_new_order_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - mock_csr_pem = mock.MagicMock() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.new_order(mock_csr_pem) - mock_client().new_order.assert_called_once_with(mock_csr_pem) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_success(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - mock_client().fetch_chain.return_value = self.chain - - deadline = datetime.datetime(9999, 9, 9) - client = self._init() - result = client.finalize_order(self.orderr, deadline) - self.assertEqual(result.fullchain_pem, self.fullchain_pem) - mock_client().fetch_chain.assert_called_once_with(self.certr) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_fetch_chain_error(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - mock_client().fetch_chain.return_value = self.chain - mock_client().fetch_chain.side_effect = [errors.Error, self.chain] - - deadline = datetime.datetime(9999, 9, 9) - client = self._init() - result = client.finalize_order(self.orderr, deadline) - self.assertEqual(result.fullchain_pem, self.fullchain_pem) - self.assertEqual(mock_client().fetch_chain.call_count, 2) - - @mock.patch('acme.client.Client') - def test_finalize_order_v1_timeout(self, mock_client): - self.response.json.return_value = DIRECTORY_V1.to_json() - - mock_client().request_issuance.return_value = self.certr - - deadline = deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) - client = self._init() - self.assertRaises(errors.TimeoutError, client.finalize_order, - self.orderr, deadline) - - def test_finalize_order_v2(self): - self.response.json.return_value = DIRECTORY_V2.to_json() - mock_orderr = mock.MagicMock() - mock_deadline = mock.MagicMock() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.finalize_order(mock_orderr, mock_deadline) - mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline) - - def test_revoke(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - client = self._init() - client.revoke(messages_test.CERT, self.rsn) - mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) - - self.response.json.return_value = DIRECTORY_V2.to_json() - with mock.patch('acme.client.ClientV2') as mock_client: - client = self._init() - client.revoke(messages_test.CERT, self.rsn) - mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) - - def test_update_registration(self): - self.response.json.return_value = DIRECTORY_V1.to_json() - with mock.patch('acme.client.Client') as mock_client: - client = self._init() - 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.""" - - def setUp(self): - super(ClientTest, self).setUp() - - self.directory = DIRECTORY_V1 - - # Registration - self.regr = self.regr.update( - terms_of_service='https://www.letsencrypt-demo.org/tos') - - # Request issuance - self.certr = messages.CertificateResource( - body=messages_test.CERT, authzrs=(self.authzr,), - uri='https://www.letsencrypt-demo.org/acme/cert/1', - cert_chain_uri='https://www.letsencrypt-demo.org/ca') - - from acme.client import Client - self.client = Client( - directory=self.directory, key=KEY, alg=jose.RS256, net=self.net) - - def test_init_downloads_directory(self): - uri = 'http://www.letsencrypt-demo.org/directory' - from acme.client import Client - self.client = Client( - directory=uri, key=KEY, alg=jose.RS256, net=self.net) - self.net.get.assert_called_once_with(uri) - - @mock.patch('acme.client.ClientNetwork') - def test_init_without_net(self, mock_net): - mock_net.return_value = mock.sentinel.net - alg = jose.RS256 - from acme.client import Client - self.client = Client( - directory=self.directory, key=KEY, alg=alg) - mock_net.called_once_with(KEY, alg=alg, verify_ssl=True) - self.assertEqual(self.client.net, mock.sentinel.net) - - def test_register(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() - self.response.headers['Location'] = self.regr.uri - self.response.links.update({ - 'terms-of-service': {'url': self.regr.terms_of_service}, - }) - - self.assertEqual(self.regr, self.client.register(self.new_reg)) - # TODO: test POST call arguments - - def test_update_registration(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.update_registration(self.regr)) - # TODO: test POST call arguments - - # TODO: split here and separate test - self.response.json.return_value = self.regr.body.update( - contact=()).to_json() - - def test_deactivate_account(self): - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, - self.client.deactivate_registration(self.regr)) - - def test_query_registration(self): - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.query_registration(self.regr)) - - def test_agree_to_tos(self): - self.client.update_registration = mock.Mock() - self.client.agree_to_tos(self.regr) - regr = self.client.update_registration.call_args[0][0] - self.assertEqual(self.regr.terms_of_service, regr.body.agreement) - - def _prepare_response_for_request_challenges(self): - self.response.status_code = http_client.CREATED - self.response.headers['Location'] = self.authzr.uri - self.response.json.return_value = self.authz.to_json() - - def test_request_challenges(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier) - self.net.post.assert_called_once_with( - self.directory.new_authz, - messages.NewAuthorization(identifier=self.identifier), - acme_version=1) - - def test_request_challenges_deprecated_arg(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier, new_authzr_uri="hi") - self.net.post.assert_called_once_with( - self.directory.new_authz, - messages.NewAuthorization(identifier=self.identifier), - acme_version=1) - - def test_request_challenges_custom_uri(self): - self._prepare_response_for_request_challenges() - self.client.request_challenges(self.identifier) - self.net.post.assert_called_once_with( - 'https://www.letsencrypt-demo.org/acme/new-authz', mock.ANY, - acme_version=1) - - def test_request_challenges_unexpected_update(self): - self._prepare_response_for_request_challenges() - self.response.json.return_value = self.authz.update( - identifier=self.identifier.update(value='foo')).to_json() - self.assertRaises( - errors.UnexpectedUpdate, self.client.request_challenges, - self.identifier) - - def test_request_challenges_wildcard(self): - wildcard_identifier = messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='*.example.org') - self.assertRaises( - errors.WildcardUnsupportedError, self.client.request_challenges, - wildcard_identifier) - - def test_request_domain_challenges(self): - self.client.request_challenges = mock.MagicMock() - self.assertEqual( - self.client.request_challenges(self.identifier), - self.client.request_domain_challenges('example.com')) - - def test_answer_challenge(self): - self.response.links['up'] = {'url': self.challr.authzr_uri} - self.response.json.return_value = self.challr.body.to_json() - - chall_response = challenges.DNSResponse(validation=None) - - self.client.answer_challenge(self.challr.body, chall_response) - - # TODO: split here and separate test - self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, - self.challr.body.update(uri='foo'), chall_response) - - def test_answer_challenge_missing_next(self): - self.assertRaises( - errors.ClientError, self.client.answer_challenge, - self.challr.body, challenges.DNSResponse(validation=None)) - - def test_retry_after_date(self): - self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' - self.assertEqual( - datetime.datetime(1999, 12, 31, 23, 59, 59), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_invalid(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.response.headers['Retry-After'] = 'foooo' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_overflow(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - dt_mock.datetime.side_effect = datetime.datetime - - self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST" - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_seconds(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.response.headers['Retry-After'] = '50' - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 50), - self.client.retry_after(response=self.response, default=10)) - - @mock.patch('acme.client.datetime') - def test_retry_after_missing(self, dt_mock): - dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) - dt_mock.timedelta = datetime.timedelta - - self.assertEqual( - datetime.datetime(2015, 3, 27, 0, 0, 10), - self.client.retry_after(response=self.response, default=10)) - - def test_poll(self): - self.response.json.return_value = self.authzr.body.to_json() - self.assertEqual((self.authzr, self.response), - self.client.poll(self.authzr)) - - # TODO: split here and separate test - self.response.json.return_value = self.authz.update( - identifier=self.identifier.update(value='foo')).to_json() - self.assertRaises( - errors.UnexpectedUpdate, self.client.poll, self.authzr) - - def test_request_issuance(self): - self.response.content = CERT_DER - self.response.headers['Location'] = self.certr.uri - self.response.links['up'] = {'url': self.certr.cert_chain_uri} - self.assertEqual(self.certr, self.client.request_issuance( - messages_test.CSR, (self.authzr,))) - # TODO: check POST args - - def test_request_issuance_missing_up(self): - self.response.content = CERT_DER - self.response.headers['Location'] = self.certr.uri - self.assertEqual( - self.certr.update(cert_chain_uri=None), - self.client.request_issuance(messages_test.CSR, (self.authzr,))) - - def test_request_issuance_missing_location(self): - self.assertRaises( - errors.ClientError, self.client.request_issuance, - messages_test.CSR, (self.authzr,)) - - @mock.patch('acme.client.datetime') - @mock.patch('acme.client.time') - def test_poll_and_request_issuance(self, time_mock, dt_mock): - # clock.dt | pylint: disable=no-member - clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27)) - - def sleep(seconds): - """increment clock""" - clock.dt += datetime.timedelta(seconds=seconds) - time_mock.sleep.side_effect = sleep - - def now(): - """return current clock value""" - return clock.dt - dt_mock.datetime.now.side_effect = now - dt_mock.timedelta = datetime.timedelta - - def poll(authzr): # pylint: disable=missing-docstring - # record poll start time based on the current clock value - authzr.times.append(clock.dt) - - # suppose it takes 2 seconds for server to produce the - # result, increment clock - clock.dt += datetime.timedelta(seconds=2) - - if len(authzr.retries) == 1: # no more retries - done = mock.MagicMock(uri=authzr.uri, times=authzr.times) - done.body.status = authzr.retries[0] - return done, [] - - # response (2nd result tuple element) is reduced to only - # Retry-After header contents represented as integer - # seconds; authzr.retries is a list of Retry-After - # headers, head(retries) is peeled of as a current - # Retry-After header, and tail(retries) is persisted for - # later poll() calls - return (mock.MagicMock(retries=authzr.retries[1:], - uri=authzr.uri + '.', times=authzr.times), - authzr.retries[0]) - self.client.poll = mock.MagicMock(side_effect=poll) - - mintime = 7 - - def retry_after(response, default): - # pylint: disable=missing-docstring - # check that poll_and_request_issuance correctly passes mintime - self.assertEqual(default, mintime) - return clock.dt + datetime.timedelta(seconds=response) - self.client.retry_after = mock.MagicMock(side_effect=retry_after) - - def request_issuance(csr, authzrs): # pylint: disable=missing-docstring - return csr, authzrs - self.client.request_issuance = mock.MagicMock( - side_effect=request_issuance) - - csr = mock.MagicMock() - authzrs = ( - mock.MagicMock(uri='a', times=[], retries=( - 8, 20, 30, messages.STATUS_VALID)), - mock.MagicMock(uri='b', times=[], retries=( - 5, messages.STATUS_VALID)), - ) - - cert, updated_authzrs = self.client.poll_and_request_issuance( - csr, authzrs, mintime=mintime, - # make sure that max_attempts is per-authorization, rather - # than global - max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries))) - self.assertTrue(cert[0] is csr) - self.assertTrue(cert[1] is updated_authzrs) - self.assertEqual(updated_authzrs[0].uri, 'a...') - self.assertEqual(updated_authzrs[1].uri, 'b.') - self.assertEqual(updated_authzrs[0].times, [ - datetime.datetime(2015, 3, 27), - # a is scheduled for 10, but b is polling [9..11), so it - # will be picked up as soon as b is finished, without - # additional sleeping - datetime.datetime(2015, 3, 27, 0, 0, 11), - datetime.datetime(2015, 3, 27, 0, 0, 33), - datetime.datetime(2015, 3, 27, 0, 1, 5), - ]) - self.assertEqual(updated_authzrs[1].times, [ - datetime.datetime(2015, 3, 27, 0, 0, 2), - datetime.datetime(2015, 3, 27, 0, 0, 9), - ]) - self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7)) - - # CA sets invalid | TODO: move to a separate test - invalid_authzr = mock.MagicMock( - times=[], retries=[messages.STATUS_INVALID]) - self.assertRaises( - errors.PollError, self.client.poll_and_request_issuance, - csr, authzrs=(invalid_authzr,), mintime=mintime) - - # exceeded max_attempts | TODO: move to a separate test - self.assertRaises( - errors.PollError, self.client.poll_and_request_issuance, - csr, authzrs, mintime=mintime, max_attempts=2) - - def test_deactivate_authorization(self): - authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) - self.response.json.return_value = authzb.to_json() - authzr = self.client.deactivate_authorization(self.authzr) - self.assertEqual(authzb, authzr.body) - self.assertEqual(self.client.net.post.call_count, 1) - self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) - - def test_check_cert(self): - self.response.headers['Location'] = self.certr.uri - self.response.content = CERT_DER - self.assertEqual(self.certr.update(body=messages_test.CERT), - self.client.check_cert(self.certr)) - - # TODO: split here and separate test - self.response.headers['Location'] = 'foo' - self.assertRaises( - errors.UnexpectedUpdate, self.client.check_cert, self.certr) - - def test_check_cert_missing_location(self): - self.response.content = CERT_DER - self.assertRaises( - errors.ClientError, self.client.check_cert, self.certr) - - def test_refresh(self): - self.client.check_cert = mock.MagicMock() - self.assertEqual( - self.client.check_cert(self.certr), self.client.refresh(self.certr)) - - def test_fetch_chain_no_up_link(self): - self.assertEqual([], self.client.fetch_chain(self.certr.update( - cert_chain_uri=None))) - - def test_fetch_chain_single(self): - # pylint: disable=protected-access - self.client._get_cert = mock.MagicMock() - self.client._get_cert.return_value = ( - mock.MagicMock(links={}), "certificate") - self.assertEqual([self.client._get_cert(self.certr.cert_chain_uri)[1]], - self.client.fetch_chain(self.certr)) - - def test_fetch_chain_max(self): - # pylint: disable=protected-access - up_response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) - noup_response = mock.MagicMock(links={}) - self.client._get_cert = mock.MagicMock() - self.client._get_cert.side_effect = [ - (up_response, "cert")] * 9 + [(noup_response, "last_cert")] - chain = self.client.fetch_chain(self.certr, max_length=10) - self.assertEqual(chain, ["cert"] * 9 + ["last_cert"]) - - def test_fetch_chain_too_many(self): # recursive - # pylint: disable=protected-access - response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) - self.client._get_cert = mock.MagicMock() - self.client._get_cert.return_value = (response, "certificate") - self.assertRaises(errors.Error, self.client.fetch_chain, self.certr) - - def test_revoke(self): - self.client.revoke(self.certr.body, self.rsn) - self.net.post.assert_called_once_with( - self.directory[messages.Revocation], mock.ANY, acme_version=1) - - 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.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 - self.assertRaises( - errors.ClientError, - self.client.revoke, - self.certr, - self.rsn) - - -class ClientV2Test(ClientTestBase): - """Tests for acme.client.ClientV2.""" - - def setUp(self): - super(ClientV2Test, self).setUp() - - self.directory = DIRECTORY_V2 - - from acme.client import ClientV2 - self.client = ClientV2(self.directory, self.net) - - self.new_reg = self.new_reg.update(terms_of_service_agreed=True) - - self.authzr_uri2 = 'https://www.letsencrypt-demo.org/acme/authz/2' - self.authz2 = self.authz.update(identifier=messages.Identifier( - typ=messages.IDENTIFIER_FQDN, value='www.example.com'), - status=messages.STATUS_PENDING) - self.authzr2 = messages.AuthorizationResource( - body=self.authz2, uri=self.authzr_uri2) - - self.order = messages.Order( - identifiers=(self.authz.identifier, self.authz2.identifier), - status=messages.STATUS_PENDING, - authorizations=(self.authzr.uri, self.authzr_uri2), - finalize='https://www.letsencrypt-demo.org/acme/acct/1/order/1/finalize') - self.orderr = messages.OrderResource( - body=self.order, - uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1', - authorizations=[self.authzr, self.authzr2], csr_pem=CSR_SAN_PEM) - - def test_new_account(self): - self.response.status_code = http_client.CREATED - self.response.json.return_value = self.regr.body.to_json() - self.response.headers['Location'] = self.regr.uri - - self.assertEqual(self.regr, self.client.new_account(self.new_reg)) - - def test_new_account_conflict(self): - self.response.status_code = http_client.OK - self.response.headers['Location'] = self.regr.uri - self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg) - - def test_new_order(self): - order_response = copy.deepcopy(self.response) - order_response.status_code = http_client.CREATED - order_response.json.return_value = self.order.to_json() - order_response.headers['Location'] = self.orderr.uri - self.net.post.return_value = order_response - - authz_response = copy.deepcopy(self.response) - authz_response.json.return_value = self.authz.to_json() - authz_response.headers['Location'] = self.authzr.uri - authz_response2 = self.response - authz_response2.json.return_value = self.authz2.to_json() - authz_response2.headers['Location'] = self.authzr2.uri - - 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): - mock_datetime.datetime.now.return_value = datetime.datetime(2018, 2, 15) - mock_datetime.timedelta = datetime.timedelta - expected_deadline = mock_datetime.datetime.now() + datetime.timedelta(seconds=90) - - self.client.poll_authorizations = mock.Mock(return_value=self.orderr) - self.client.finalize_order = mock.Mock(return_value=self.orderr) - - self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr) - self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline) - self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline) - - @mock.patch('acme.client.datetime') - def test_poll_authorizations_timeout(self, mock_datetime): - now_side_effect = [datetime.datetime(2018, 2, 15), - datetime.datetime(2018, 2, 16), - datetime.datetime(2018, 2, 17)] - mock_datetime.datetime.now.side_effect = now_side_effect - self.response.json.side_effect = [ - self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()] - - self.assertRaises( - errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1]) - - def test_poll_authorizations_failure(self): - deadline = datetime.datetime(9999, 9, 9) - challb = self.challr.body.update(status=messages.STATUS_INVALID, - error=messages.Error.with_code('unauthorized')) - authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) - self.response.json.return_value = authz.to_json() - - self.assertRaises( - errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline) - - def test_poll_authorizations_success(self): - deadline = datetime.datetime(9999, 9, 9) - updated_authz2 = self.authz2.update(status=messages.STATUS_VALID) - updated_authzr2 = messages.AuthorizationResource( - body=updated_authz2, uri=self.authzr_uri2) - updated_orderr = self.orderr.update(authorizations=[self.authzr, updated_authzr2]) - - self.response.json.side_effect = ( - self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json()) - self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr) - - def test_finalize_order_success(self): - updated_order = self.order.update( - certificate='https://www.letsencrypt-demo.org/acme/cert/') - updated_orderr = self.orderr.update(body=updated_order, fullchain_pem=CERT_SAN_PEM) - - self.response.json.return_value = updated_order.to_json() - self.response.text = CERT_SAN_PEM - - deadline = datetime.datetime(9999, 9, 9) - self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr) - - def test_finalize_order_error(self): - updated_order = self.order.update(error=messages.Error.with_code('unauthorized')) - self.response.json.return_value = updated_order.to_json() - - deadline = datetime.datetime(9999, 9, 9) - self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline) - - def test_finalize_order_timeout(self): - deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) - self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline) - - def test_revoke(self): - self.client.revoke(messages_test.CERT, self.rsn) - self.net.post.assert_called_once_with( - self.directory["revokeCert"], mock.ANY, acme_version=2, - new_nonce_url=DIRECTORY_V2['newNonce']) - - def test_update_registration(self): - # "Instance of 'Field' has no to_json/update member" bug: - self.response.headers['Location'] = self.regr.uri - self.response.json.return_value = self.regr.body.to_json() - self.assertEqual(self.regr, self.client.update_registration(self.regr)) - self.assertNotEqual(self.client.net.account, None) - self.assertEqual(self.client.net.post.call_count, 2) - self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0]) - - 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): - """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 - def __init__(self, value): - self.value = value - - def to_partial_json(self): - return {'foo': self.value} - - @classmethod - def from_json(cls, jobj): - pass # pragma: no cover - - -class ClientNetworkTest(unittest.TestCase): - """Tests for acme.client.ClientNetwork.""" - - def setUp(self): - self.verify_ssl = mock.MagicMock() - self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped) - - from acme.client import ClientNetwork - self.net = ClientNetwork( - key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl, - user_agent='acme-python-test') - - self.response = mock.MagicMock(ok=True, status_code=http_client.OK) - self.response.headers = {} - self.response.links = {} - - def test_init(self): - self.assertTrue(self.net.verify_ssl is self.verify_ssl) - - def test_wrap_in_jws(self): - # pylint: disable=protected-access - jws_dump = self.net._wrap_in_jws( - MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", - acme_version=1) - jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') - - def test_wrap_in_jws_v2(self): - self.net.account = {'uri': 'acct-uri'} - # pylint: disable=protected-access - jws_dump = self.net._wrap_in_jws( - MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", - acme_version=2) - jws = acme_jws.JWS.json_loads(jws_dump) - self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) - self.assertEqual(jws.signature.combined.nonce, b'Tg') - 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 = {} - with mock.patch('acme.client.messages.Error.from_json') as from_json: - from_json.side_effect = jose.DeserializationError - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) - - def test_check_response_not_ok_jobj_error(self): - self.response.ok = False - self.response.json.return_value = messages.Error( - detail='foo', typ='serverInternal', title='some title').to_json() - # pylint: disable=protected-access - self.assertRaises( - messages.Error, self.net._check_response, self.response) - - def test_check_response_not_ok_no_jobj(self): - self.response.ok = False - self.response.json.side_effect = ValueError - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response) - - def test_check_response_ok_no_jobj_ct_required(self): - self.response.json.side_effect = ValueError - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access - self.assertRaises( - errors.ClientError, self.net._check_response, self.response, - content_type=self.net.JSON_CONTENT_TYPE) - - def test_check_response_ok_no_jobj_no_ct(self): - self.response.json.side_effect = ValueError - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter - self.assertEqual( - self.response, self.net._check_response(self.response)) - - def test_check_response_conflict(self): - self.response.ok = False - self.response.status_code = 409 - # pylint: disable=protected-access - self.assertRaises(errors.ConflictError, self.net._check_response, self.response) - - def test_check_response_jobj(self): - self.response.json.return_value = {} - for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: - self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter - self.assertEqual( - self.response, self.net._check_response(self.response)) - - def test_send_request(self): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'HEAD', 'http://example.com/', 'foo', bar='baz')) - self.net.session.request.assert_called_once_with( - 'HEAD', 'http://example.com/', 'foo', - headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz') - - @mock.patch('acme.client.logger') - def test_send_request_get_der(self, mock_logger): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = mock.MagicMock( - ok=True, status_code=http_client.OK, - headers={"Content-Type": "application/pkix-cert"}, - content=b"hi") - # pylint: disable=protected-access - self.net._send_request('HEAD', 'http://example.com/', 'foo', - timeout=mock.ANY, bar='baz') - mock_logger.debug.assert_called_with( - 'Received response:\nHTTP %d\n%s\n\n%s', 200, - 'Content-Type: application/pkix-cert', b'aGk=') - - def test_send_request_post(self): - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - # pylint: disable=protected-access - self.assertEqual(self.response, self.net._send_request( - 'POST', 'http://example.com/', 'foo', data='qux', bar='baz')) - self.net.session.request.assert_called_once_with( - 'POST', 'http://example.com/', 'foo', - headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz') - - def test_send_request_verify_ssl(self): - # pylint: disable=protected-access - for verify in True, False: - self.net.session = mock.MagicMock() - self.net.session.request.return_value = self.response - self.net.verify_ssl = verify - # pylint: disable=protected-access - self.assertEqual( - self.response, - self.net._send_request('GET', 'http://example.com/')) - self.net.session.request.assert_called_once_with( - 'GET', 'http://example.com/', verify=verify, - timeout=mock.ANY, headers=mock.ANY) - - def test_send_request_user_agent(self): - self.net.session = mock.MagicMock() - # pylint: disable=protected-access - self.net._send_request('GET', 'http://example.com/', - headers={'bar': 'baz'}) - self.net.session.request.assert_called_once_with( - 'GET', 'http://example.com/', verify=mock.ANY, - timeout=mock.ANY, - headers={'User-Agent': 'acme-python-test', 'bar': 'baz'}) - - self.net._send_request('GET', 'http://example.com/', - headers={'User-Agent': 'foo2'}) - self.net.session.request.assert_called_with( - 'GET', 'http://example.com/', - verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'}) - - def test_send_request_timeout(self): - self.net.session = mock.MagicMock() - # pylint: disable=protected-access - self.net._send_request('GET', 'http://example.com/', - headers={'bar': 'baz'}) - self.net.session.request.assert_called_once_with( - mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY, - timeout=45) - - def test_del(self, close_exception=None): - sess = mock.MagicMock() - - if close_exception is not None: - sess.close.side_effect = close_exception - - self.net.session = sess - del self.net - sess.close.assert_called_once_with() - - def test_del_error(self): - self.test_del(ReferenceError) - - @mock.patch('acme.client.requests') - def test_requests_error_passthrough(self, mock_requests): - mock_requests.exceptions = requests.exceptions - mock_requests.request.side_effect = requests.exceptions.RequestException - # pylint: disable=protected-access - self.assertRaises(requests.exceptions.RequestException, - self.net._send_request, 'GET', 'uri') - - def test_urllib_error(self): - # Using a connection error to test a properly formatted error message - try: - # pylint: disable=protected-access - self.net._send_request('GET', "http://localhost:19123/nonexistent.txt") - - # Value Error Generated Exceptions - except ValueError as y: - self.assertEqual("Requesting localhost/nonexistent: " - "Connection refused", str(y)) - - # Requests Library Exceptions - except requests.exceptions.ConnectionError as z: #pragma: no cover - 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.""" - - def setUp(self): - from acme.client import ClientNetwork - self.net = ClientNetwork(key=None, alg=None) - - self.response = mock.MagicMock(ok=True, status_code=http_client.OK) - self.response.headers = {} - self.response.links = {} - self.response.checked = False - self.acmev1_nonce_response = mock.MagicMock(ok=False, - status_code=http_client.METHOD_NOT_ALLOWED) - self.acmev1_nonce_response.headers = {} - self.obj = mock.MagicMock() - self.wrapped_obj = mock.MagicMock() - self.content_type = mock.sentinel.content_type - - self.all_nonces = [ - jose.b64encode(b'Nonce'), - jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')] - self.available_nonces = self.all_nonces[:] - - def send_request(*args, **kwargs): - # pylint: disable=unused-argument,missing-docstring - self.assertFalse("new_nonce_url" in kwargs) - method = args[0] - uri = args[1] - if method == 'HEAD' and uri != "new_nonce_uri": - response = self.acmev1_nonce_response - else: - response = self.response - - if self.available_nonces: - response.headers = { - self.net.REPLAY_NONCE_HEADER: - self.available_nonces.pop().decode()} - else: - response.headers = {} - return response - - # pylint: disable=protected-access - self.net._send_request = self.send_request = mock.MagicMock( - side_effect=send_request) - self.net._check_response = self.check_response - self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj) - - def check_response(self, response, content_type): - # pylint: disable=missing-docstring - self.assertEqual(self.response, response) - self.assertEqual(self.content_type, content_type) - self.assertTrue(self.response.ok) - self.response.checked = True - return self.response - - def test_head(self): - self.assertEqual(self.acmev1_nonce_response, self.net.head( - 'http://example.com/', 'foo', bar='baz')) - self.send_request.assert_called_once_with( - 'HEAD', 'http://example.com/', 'foo', bar='baz') - - def test_head_v2(self): - self.assertEqual(self.response, self.net.head( - 'new_nonce_uri', 'foo', bar='baz')) - self.send_request.assert_called_once_with( - 'HEAD', 'new_nonce_uri', 'foo', bar='baz') - - def test_get(self): - self.assertEqual(self.response, self.net.get( - 'http://example.com/', content_type=self.content_type, bar='baz')) - self.assertTrue(self.response.checked) - self.send_request.assert_called_once_with( - 'GET', 'http://example.com/', bar='baz') - - def test_post_no_content_type(self): - self.content_type = self.net.JOSE_CONTENT_TYPE - self.assertEqual(self.response, self.net.post('uri', self.obj)) - self.assertTrue(self.response.checked) - - def test_post(self): - # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) - self.assertTrue(self.response.checked) - self.net._wrap_in_jws.assert_called_once_with( - self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) - - self.available_nonces = [] - self.assertRaises(errors.MissingNonce, self.net.post, - 'uri', self.obj, content_type=self.content_type) - self.net._wrap_in_jws.assert_called_with( - self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) - - def test_post_wrong_initial_nonce(self): # HEAD - self.available_nonces = [b'f', jose.b64encode(b'good')] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_wrong_post_response_nonce(self): - self.available_nonces = [jose.b64encode(b'good'), b'f'] - self.assertRaises(errors.BadNonce, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_failed_retry(self): - check_response = mock.MagicMock() - check_response.side_effect = messages.Error.with_code('badNonce') - - # pylint: disable=protected-access - self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_not_retried(self): - check_response = mock.MagicMock() - check_response.side_effect = [messages.Error.with_code('malformed'), - self.response] - - # pylint: disable=protected-access - self.net._check_response = check_response - self.assertRaises(messages.Error, self.net.post, 'uri', - self.obj, content_type=self.content_type) - - def test_post_successful_retry(self): - post_once = mock.MagicMock() - post_once.side_effect = [messages.Error.with_code('badNonce'), - self.response] - - # pylint: disable=protected-access - self.assertEqual(self.response, self.net.post( - 'uri', self.obj, content_type=self.content_type)) - - def test_head_get_post_error_passthrough(self): - self.send_request.side_effect = requests.exceptions.RequestException - for method in self.net.head, self.net.get: - self.assertRaises( - requests.exceptions.RequestException, method, 'GET', 'uri') - self.assertRaises(requests.exceptions.RequestException, - self.net.post, 'uri', obj=self.obj) - - def test_post_bad_nonce_head(self): - # pylint: disable=protected-access - # regression test for https://github.com/certbot/certbot/issues/6092 - bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE) - self.net._send_request = mock.MagicMock() - self.net._send_request.return_value = bad_response - self.content_type = None - check_response = mock.MagicMock() - self.net._check_response = check_response - self.assertRaises(errors.ClientError, self.net.post, 'uri', - self.obj, content_type=self.content_type, acme_version=2, - new_nonce_url='new_nonce_uri') - self.assertEqual(check_response.call_count, 1) - - def test_new_nonce_uri_removed(self): - self.content_type = None - self.net.post('uri', self.obj, content_type=None, - acme_version=2, new_nonce_url='new_nonce_uri') - - -class ClientNetworkSourceAddressBindingTest(unittest.TestCase): - """Tests that if ClientNetwork has a source IP set manually, the underlying library has - used the provided source address.""" - - def setUp(self): - self.source_address = "8.8.8.8" - - def test_source_address_set(self): - from acme.client import ClientNetwork - net = ClientNetwork(key=None, alg=None, source_address=self.source_address) - for adapter in net.session.adapters.values(): - self.assertTrue(self.source_address in adapter.source_address) - - def test_behavior_assumption(self): - """This is a test that guardrails the HTTPAdapter behavior so that if the default for - a Session() changes, the assumptions here aren't violated silently.""" - from acme.client import ClientNetwork - # Source address not specified, so the default adapter type should be bound -- this - # test should fail if the default adapter type is changed by requests - net = ClientNetwork(key=None, alg=None) - session = requests.Session() - for scheme in session.adapters.keys(): - client_network_adapter = net.session.adapters.get(scheme) - default_adapter = session.adapters.get(scheme) - self.assertEqual(client_network_adapter.__class__, default_adapter.__class__) - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py deleted file mode 100644 index 6477f6501..000000000 --- a/acme/acme/crypto_util_test.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Tests for acme.crypto_util.""" -import itertools -import socket -import threading -import time -import unittest - -import six -from six.moves import socketserver #type: ignore # pylint: disable=import-error - -import josepy as jose -import OpenSSL - -from acme import errors -from acme import test_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - - -class SSLSocketAndProbeSNITest(unittest.TestCase): - """Tests for acme.crypto_util.SSLSocket/probe_sni.""" - - - def setUp(self): - self.cert = test_util.load_comparable_cert('rsa2048_cert.pem') - key = test_util.load_pyopenssl_private_key('rsa2048_key.pem') - # pylint: disable=protected-access - certs = {b'foo': (key, self.cert.wrapped)} - - from acme.crypto_util import SSLSocket - - class _TestServer(socketserver.TCPServer): - - # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init - - def server_bind(self): # pylint: disable=missing-docstring - self.socket = SSLSocket(socket.socket(), certs=certs) - socketserver.TCPServer.server_bind(self) - - self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) - self.port = self.server.socket.getsockname()[1] - self.server_thread = threading.Thread( - # pylint: disable=no-member - target=self.server.handle_request) - - def tearDown(self): - if self.server_thread.is_alive(): - # The thread may have already terminated. - self.server_thread.join() # pragma: no cover - - def _probe(self, name): - from acme.crypto_util import probe_sni - return jose.ComparableX509(probe_sni( - name, host='127.0.0.1', port=self.port)) - - def _start_server(self): - self.server_thread.start() - time.sleep(1) # TODO: avoid race conditions in other way - - def test_probe_ok(self): - self._start_server() - self.assertEqual(self.cert, self._probe(b'foo')) - - def test_probe_not_recognized_name(self): - self._start_server() - self.assertRaises(errors.Error, self._probe, b'bar') - - def test_probe_connection_error(self): - # pylint has a hard time with six - self.server.server_close() # pylint: disable=no-member - original_timeout = socket.getdefaulttimeout() - try: - socket.setdefaulttimeout(1) - self.assertRaises(errors.Error, self._probe, b'bar') - finally: - socket.setdefaulttimeout(original_timeout) - - -class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase): - """Test for acme.crypto_util._pyopenssl_cert_or_req_all_names.""" - - @classmethod - def _call(cls, loader, name): - # pylint: disable=protected-access - from acme.crypto_util import _pyopenssl_cert_or_req_all_names - return _pyopenssl_cert_or_req_all_names(loader(name)) - - def _call_cert(self, name): - return self._call(test_util.load_cert, name) - - def test_cert_one_san_no_common(self): - self.assertEqual(self._call_cert('cert-nocn.der'), - ['no-common-name.badssl.com']) - - def test_cert_no_sans_yes_common(self): - self.assertEqual(self._call_cert('cert.pem'), ['example.com']) - - def test_cert_two_sans_yes_common(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) - - -class PyOpenSSLCertOrReqSANTest(unittest.TestCase): - """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" - - - @classmethod - def _call(cls, loader, name): - # pylint: disable=protected-access - from acme.crypto_util import _pyopenssl_cert_or_req_san - return _pyopenssl_cert_or_req_san(loader(name)) - - @classmethod - def _get_idn_names(cls): - """Returns expected names from '{cert,csr}-idnsans.pem'.""" - chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), - range(0x641, 0x6fc), - range(0x1820, 0x1877))] - return [''.join(chars[i: i + 45]) + '.invalid' - for i in range(0, len(chars), 45)] - - def _call_cert(self, name): - return self._call(test_util.load_cert, name) - - def _call_csr(self, name): - return self._call(test_util.load_csr, name) - - def test_cert_no_sans(self): - self.assertEqual(self._call_cert('cert.pem'), []) - - def test_cert_two_sans(self): - self.assertEqual(self._call_cert('cert-san.pem'), - ['example.com', 'www.example.com']) - - def test_cert_hundred_sans(self): - self.assertEqual(self._call_cert('cert-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) - - def test_cert_idn_sans(self): - self.assertEqual(self._call_cert('cert-idnsans.pem'), - self._get_idn_names()) - - def test_csr_no_sans(self): - self.assertEqual(self._call_csr('csr-nosans.pem'), []) - - def test_csr_one_san(self): - self.assertEqual(self._call_csr('csr.pem'), ['example.com']) - - def test_csr_two_sans(self): - self.assertEqual(self._call_csr('csr-san.pem'), - ['example.com', 'www.example.com']) - - def test_csr_six_sans(self): - self.assertEqual(self._call_csr('csr-6sans.pem'), - ['example.com', 'example.org', 'example.net', - 'example.info', 'subdomain.example.com', - 'other.subdomain.example.com']) - - def test_csr_hundred_sans(self): - self.assertEqual(self._call_csr('csr-100sans.pem'), - ['example{0}.com'.format(i) for i in range(1, 101)]) - - def test_csr_idn_sans(self): - self.assertEqual(self._call_csr('csr-idnsans.pem'), - self._get_idn_names()) - - def test_critical_san(self): - self.assertEqual(self._call_cert('critical-san.pem'), - ['chicago-cubs.venafi.example', 'cubs.venafi.example']) - - - -class RandomSnTest(unittest.TestCase): - """Test for random certificate serial numbers.""" - - - def setUp(self): - self.cert_count = 5 - self.serial_num = [] # type: List[int] - self.key = OpenSSL.crypto.PKey() - self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - - def test_sn_collisions(self): - from acme.crypto_util import gen_ss_cert - - for _ in range(self.cert_count): - cert = gen_ss_cert(self.key, ['dummy'], force_san=True) - self.serial_num.append(cert.get_serial_number()) - self.assertTrue(len(set(self.serial_num)) > 1) - -class MakeCSRTest(unittest.TestCase): - """Test for standalone functions.""" - - @classmethod - def _call_with_key(cls, *args, **kwargs): - privkey = OpenSSL.crypto.PKey() - privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) - from acme.crypto_util import make_csr - return make_csr(privkey_pem, *args, **kwargs) - - def test_make_csr(self): - csr_pem = self._call_with_key(["a.example", "b.example"]) - self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem) - self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem) - csr = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_pem) - # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't - # have a get_extensions() method, so we skip this test if the method - # isn't available. - if hasattr(csr, 'get_extensions'): - self.assertEqual(len(csr.get_extensions()), 1) - self.assertEqual(csr.get_extensions()[0].get_data(), - OpenSSL.crypto.X509Extension( - b'subjectAltName', - critical=False, - value=b'DNS:a.example, DNS:b.example', - ).get_data(), - ) - - def test_make_csr_must_staple(self): - csr_pem = self._call_with_key(["a.example"], must_staple=True) - csr = OpenSSL.crypto.load_certificate_request( - OpenSSL.crypto.FILETYPE_PEM, csr_pem) - - # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't - # have a get_extensions() method, so we skip this test if the method - # isn't available. - if hasattr(csr, 'get_extensions'): - 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" - must_staple_exts = [e for e in csr.get_extensions() - if e.get_data() == b"0\x03\x02\x01\x05"] - self.assertEqual(len(must_staple_exts), 1, - "Expected exactly one Must Staple extension") - - -class DumpPyopensslChainTest(unittest.TestCase): - """Test for dump_pyopenssl_chain.""" - - @classmethod - def _call(cls, loaded): - # pylint: disable=protected-access - from acme.crypto_util import dump_pyopenssl_chain - return dump_pyopenssl_chain(loaded) - - def test_dump_pyopenssl_chain(self): - names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] - loaded = [test_util.load_cert(name) for name in names] - length = sum( - len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)) - for cert in loaded) - self.assertEqual(len(self._call(loaded)), length) - - def test_dump_pyopenssl_chain_wrapped(self): - names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] - loaded = [test_util.load_cert(name) for name in names] - wrap_func = jose.ComparableX509 - wrapped = [wrap_func(cert) for cert in loaded] - dump_func = OpenSSL.crypto.dump_certificate - length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) - self.assertEqual(len(self._call(wrapped)), length) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/errors_test.py b/acme/acme/errors_test.py deleted file mode 100644 index 1e5f3d479..000000000 --- a/acme/acme/errors_test.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Tests for acme.errors.""" -import unittest - -import mock - - -class BadNonceTest(unittest.TestCase): - """Tests for acme.errors.BadNonce.""" - - def setUp(self): - from acme.errors import BadNonce - self.error = BadNonce(nonce="xxx", error="error") - - def test_str(self): - self.assertEqual("Invalid nonce ('xxx'): error", str(self.error)) - - -class MissingNonceTest(unittest.TestCase): - """Tests for acme.errors.MissingNonce.""" - - def setUp(self): - from acme.errors import MissingNonce - self.response = mock.MagicMock(headers={}) - self.response.request.method = 'FOO' - self.error = MissingNonce(self.response) - - def test_str(self): - self.assertTrue("FOO" in str(self.error)) - self.assertTrue("{}" in str(self.error)) - - -class PollErrorTest(unittest.TestCase): - """Tests for acme.errors.PollError.""" - - def setUp(self): - from acme.errors import PollError - self.timeout = PollError( - exhausted=set([mock.sentinel.AR]), - updated={}) - self.invalid = PollError(exhausted=set(), updated={ - mock.sentinel.AR: mock.sentinel.AR2}) - - def test_timeout(self): - self.assertTrue(self.timeout.timeout) - self.assertFalse(self.invalid.timeout) - - def test_repr(self): - self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: ' - 'sentinel.AR2})' % repr(set()), repr(self.invalid)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/acme/acme/fields_test.py b/acme/acme/fields_test.py deleted file mode 100644 index 69dde8b89..000000000 --- a/acme/acme/fields_test.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Tests for acme.fields.""" -import datetime -import unittest - -import josepy as jose -import pytz - - -class FixedTest(unittest.TestCase): - """Tests for acme.fields.Fixed.""" - - def setUp(self): - from acme.fields import Fixed - self.field = Fixed('name', 'x') - - def test_decode(self): - self.assertEqual('x', self.field.decode('x')) - - def test_decode_bad(self): - self.assertRaises(jose.DeserializationError, self.field.decode, 'y') - - def test_encode(self): - self.assertEqual('x', self.field.encode('x')) - - def test_encode_override(self): - self.assertEqual('y', self.field.encode('y')) - - -class RFC3339FieldTest(unittest.TestCase): - """Tests for acme.fields.RFC3339Field.""" - - def setUp(self): - self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc) - self.encoded = '2015-03-27T00:00:00Z' - - def test_default_encoder(self): - from acme.fields import RFC3339Field - self.assertEqual( - self.encoded, RFC3339Field.default_encoder(self.decoded)) - - def test_default_encoder_naive_fails(self): - from acme.fields import RFC3339Field - self.assertRaises( - ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) - - def test_default_decoder(self): - from acme.fields import RFC3339Field - self.assertEqual( - self.decoded, RFC3339Field.default_decoder(self.encoded)) - - def test_default_decoder_raises_deserialization_error(self): - from acme.fields import RFC3339Field - self.assertRaises( - jose.DeserializationError, RFC3339Field.default_decoder, '') - - -class ResourceTest(unittest.TestCase): - """Tests for acme.fields.Resource.""" - - def setUp(self): - from acme.fields import Resource - self.field = Resource('x') - - def test_decode_good(self): - self.assertEqual('x', self.field.decode('x')) - - def test_decode_wrong(self): - self.assertRaises(jose.DeserializationError, self.field.decode, 'y') - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/jose_test.py b/acme/acme/jose_test.py deleted file mode 100644 index 340624a4f..000000000 --- a/acme/acme/jose_test.py +++ /dev/null @@ -1,53 +0,0 @@ -"""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 diff --git a/acme/acme/jws_test.py b/acme/acme/jws_test.py deleted file mode 100644 index aa3ccb700..000000000 --- a/acme/acme/jws_test.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Tests for acme.jws.""" -import unittest - -import josepy as jose - -from acme import test_util - - -KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) - - -class HeaderTest(unittest.TestCase): - """Tests for acme.jws.Header.""" - - good_nonce = jose.encode_b64jose(b'foo') - wrong_nonce = u'F' - # Following just makes sure wrong_nonce is wrong - try: - jose.b64decode(wrong_nonce) - except (ValueError, TypeError): - assert True - else: - assert False # pragma: no cover - - def test_nonce_decoder(self): - from acme.jws import Header - nonce_field = Header._fields['nonce'] - - self.assertRaises( - jose.DeserializationError, nonce_field.decode, self.wrong_nonce) - self.assertEqual(b'foo', nonce_field.decode(self.good_nonce)) - - -class JWSTest(unittest.TestCase): - """Tests for acme.jws.JWS.""" - - def setUp(self): - self.privkey = KEY - self.pubkey = self.privkey.public_key() - self.nonce = jose.b64encode(b'Nonce') - self.url = 'hi' - self.kid = 'baaaaa' - - def test_kid_serialize(self): - from acme.jws import JWS - jws = JWS.sign(payload=b'foo', key=self.privkey, - alg=jose.RS256, nonce=self.nonce, - url=self.url, kid=self.kid) - self.assertEqual(jws.signature.combined.nonce, self.nonce) - self.assertEqual(jws.signature.combined.url, self.url) - self.assertEqual(jws.signature.combined.kid, self.kid) - self.assertEqual(jws.signature.combined.jwk, None) - # TODO: check that nonce is in protected header - - self.assertEqual(jws, JWS.from_json(jws.to_json())) - - def test_jwk_serialize(self): - from acme.jws import JWS - jws = JWS.sign(payload=b'foo', key=self.privkey, - alg=jose.RS256, nonce=self.nonce, - url=self.url) - self.assertEqual(jws.signature.combined.kid, None) - self.assertEqual(jws.signature.combined.jwk, self.pubkey) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/magic_typing_test.py b/acme/acme/magic_typing_test.py deleted file mode 100644 index 23dfe3367..000000000 --- a/acme/acme/magic_typing_test.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tests for acme.magic_typing.""" -import sys -import unittest - -import mock - - -class MagicTypingTest(unittest.TestCase): - """Tests for acme.magic_typing.""" - def test_import_success(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - typing_class_mock = mock.MagicMock() - text_mock = mock.MagicMock() - typing_class_mock.Text = text_mock - sys.modules['typing'] = typing_class_mock - if 'acme.magic_typing' in sys.modules: - del sys.modules['acme.magic_typing'] # pragma: no cover - from acme.magic_typing import Text # pylint: disable=no-name-in-module - self.assertEqual(Text, text_mock) - del sys.modules['acme.magic_typing'] - sys.modules['typing'] = temp_typing - - def test_import_failure(self): - try: - import typing as temp_typing - except ImportError: # pragma: no cover - temp_typing = None # pragma: no cover - sys.modules['typing'] = None - if 'acme.magic_typing' in sys.modules: - del sys.modules['acme.magic_typing'] # pragma: no cover - from acme.magic_typing import Text # pylint: disable=no-name-in-module - self.assertTrue(Text is None) - del sys.modules['acme.magic_typing'] - sys.modules['typing'] = temp_typing - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py deleted file mode 100644 index 7efaaa1a3..000000000 --- a/acme/acme/messages_test.py +++ /dev/null @@ -1,476 +0,0 @@ -"""Tests for acme.messages.""" -import unittest - -import josepy as jose -import mock - -from acme import challenges -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - - -CERT = test_util.load_comparable_cert('cert.der') -CSR = test_util.load_comparable_csr('csr.der') -KEY = test_util.load_rsa_private_key('rsa512_key.pem') - - -class ErrorTest(unittest.TestCase): - """Tests for acme.messages.Error.""" - - def setUp(self): - from acme.messages import Error, ERROR_PREFIX - self.error = Error( - detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') - self.jobj = { - 'detail': 'foo', - 'title': 'some title', - 'type': ERROR_PREFIX + 'malformed', - } - self.error_custom = Error(typ='custom', detail='bar') - self.empty_error = Error() - self.jobj_custom = {'type': 'custom', 'detail': 'bar'} - - def test_default_typ(self): - from acme.messages import Error - self.assertEqual(Error().typ, 'about:blank') - - def test_from_json_empty(self): - from acme.messages import Error - self.assertEqual(Error(), Error.from_json('{}')) - - def test_from_json_hashable(self): - from acme.messages import Error - hash(Error.from_json(self.error.to_json())) - - def test_description(self): - self.assertEqual( - 'The request message was malformed', self.error.description) - self.assertTrue(self.error_custom.description is None) - - def test_code(self): - from acme.messages import Error - self.assertEqual('malformed', self.error.code) - self.assertEqual(None, self.error_custom.code) - self.assertEqual(None, Error().code) - - def test_is_acme_error(self): - from acme.messages import is_acme_error - self.assertTrue(is_acme_error(self.error)) - self.assertFalse(is_acme_error(self.error_custom)) - self.assertFalse(is_acme_error(self.empty_error)) - self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) - - def test_unicode_error(self): - from acme.messages import Error, ERROR_PREFIX, is_acme_error - arabic_error = Error( - detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', - title='title') - self.assertTrue(is_acme_error(arabic_error)) - - def test_with_code(self): - from acme.messages import Error, is_acme_error - self.assertTrue(is_acme_error(Error.with_code('badCSR'))) - self.assertRaises(ValueError, Error.with_code, 'not an ACME error code') - - def test_str(self): - self.assertEqual( - str(self.error), - u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" - .format(self.error)) - - -class ConstantTest(unittest.TestCase): - """Tests for acme.messages._Constant.""" - - def setUp(self): - from acme.messages import _Constant - - class MockConstant(_Constant): # pylint: disable=missing-docstring - POSSIBLE_NAMES = {} # type: Dict - - self.MockConstant = MockConstant # pylint: disable=invalid-name - self.const_a = MockConstant('a') - self.const_b = MockConstant('b') - - def test_to_partial_json(self): - self.assertEqual('a', self.const_a.to_partial_json()) - self.assertEqual('b', self.const_b.to_partial_json()) - - def test_from_json(self): - self.assertEqual(self.const_a, self.MockConstant.from_json('a')) - self.assertRaises( - jose.DeserializationError, self.MockConstant.from_json, 'c') - - def test_from_json_hashable(self): - hash(self.MockConstant.from_json('a')) - - def test_repr(self): - self.assertEqual('MockConstant(a)', repr(self.const_a)) - self.assertEqual('MockConstant(b)', repr(self.const_b)) - - def test_equality(self): - const_a_prime = self.MockConstant('a') - self.assertFalse(self.const_a == self.const_b) - self.assertTrue(self.const_a == const_a_prime) - - self.assertTrue(self.const_a != self.const_b) - self.assertFalse(self.const_a != const_a_prime) - - -class DirectoryTest(unittest.TestCase): - """Tests for acme.messages.Directory.""" - - def setUp(self): - from acme.messages import Directory - self.dir = Directory({ - 'new-reg': 'reg', - mock.MagicMock(resource_type='new-cert'): 'cert', - 'meta': Directory.Meta( - terms_of_service='https://example.com/acme/terms', - website='https://www.example.com/', - caa_identities=['example.com'], - ), - }) - - def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use - from acme.messages import Directory - Directory({'foo': 'bar'}) - - def test_getitem(self): - self.assertEqual('reg', self.dir['new-reg']) - from acme.messages import NewRegistration - self.assertEqual('reg', self.dir[NewRegistration]) - self.assertEqual('reg', self.dir[NewRegistration()]) - - def test_getitem_fails_with_key_error(self): - self.assertRaises(KeyError, self.dir.__getitem__, 'foo') - - def test_getattr(self): - self.assertEqual('reg', self.dir.new_reg) - - def test_getattr_fails_with_attribute_error(self): - self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') - - def test_to_json(self): - self.assertEqual(self.dir.to_json(), { - 'new-reg': 'reg', - 'new-cert': 'cert', - 'meta': { - 'terms-of-service': 'https://example.com/acme/terms', - 'website': 'https://www.example.com/', - 'caaIdentities': ['example.com'], - }, - }) - - def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use - from acme.messages import Directory - Directory.from_json({'foo': 'bar'}) - - def test_iter_meta(self): - result = False - for k in self.dir.meta: - if k == 'terms_of_service': - result = self.dir.meta[k] == 'https://example.com/acme/terms' - 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.""" - - def setUp(self): - key = jose.jwk.JWKRSA(key=KEY.public_key()) - contact = ( - 'mailto:admin@foo.com', - 'tel:1234', - ) - agreement = 'https://letsencrypt.org/terms' - - from acme.messages import Registration - self.reg = Registration(key=key, contact=contact, agreement=agreement) - self.reg_none = Registration() - - self.jobj_to = { - 'contact': contact, - 'agreement': agreement, - 'key': key, - } - self.jobj_from = self.jobj_to.copy() - self.jobj_from['key'] = key.to_json() - - def test_from_data(self): - from acme.messages import Registration - reg = Registration.from_data(phone='1234', email='admin@foo.com') - self.assertEqual(reg.contact, ( - 'tel:1234', - '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) - - def test_emails(self): - self.assertEqual(('admin@foo.com',), self.reg.emails) - - def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.reg.to_partial_json()) - - def test_from_json(self): - from acme.messages import Registration - self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) - - def test_from_json_hashable(self): - from acme.messages import Registration - hash(Registration.from_json(self.jobj_from)) - - -class UpdateRegistrationTest(unittest.TestCase): - """Tests for acme.messages.UpdateRegistration.""" - - def test_empty(self): - from acme.messages import UpdateRegistration - jstring = '{"resource": "reg"}' - self.assertEqual(jstring, UpdateRegistration().json_dumps()) - self.assertEqual( - UpdateRegistration(), UpdateRegistration.json_loads(jstring)) - - -class RegistrationResourceTest(unittest.TestCase): - """Tests for acme.messages.RegistrationResource.""" - - def setUp(self): - from acme.messages import RegistrationResource - self.regr = RegistrationResource( - body=mock.sentinel.body, uri=mock.sentinel.uri, - terms_of_service=mock.sentinel.terms_of_service) - - def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { - 'body': mock.sentinel.body, - 'uri': mock.sentinel.uri, - 'terms_of_service': mock.sentinel.terms_of_service, - }) - - -class ChallengeResourceTest(unittest.TestCase): - """Tests for acme.messages.ChallengeResource.""" - - def test_uri(self): - from acme.messages import ChallengeResource - self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( - uri='http://challb'), authzr_uri='http://authz').uri) - - -class ChallengeBodyTest(unittest.TestCase): - """Tests for acme.messages.ChallengeBody.""" - - def setUp(self): - self.chall = challenges.DNS(token=jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) - - from acme.messages import ChallengeBody - from acme.messages import Error - from acme.messages import STATUS_INVALID - self.status = STATUS_INVALID - error = Error(typ='urn:ietf:params:acme:error:serverInternal', - detail='Unable to communicate with DNS server') - self.challb = ChallengeBody( - uri='http://challb', chall=self.chall, status=self.status, - error=error) - - self.jobj_to = { - 'uri': 'http://challb', - 'status': self.status, - 'type': 'dns', - 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', - 'error': error, - } - self.jobj_from = self.jobj_to.copy() - self.jobj_from['status'] = 'invalid' - self.jobj_from['error'] = { - 'type': 'urn:ietf:params:acme:error:serverInternal', - 'detail': 'Unable to communicate with DNS server', - } - - def test_encode(self): - self.assertEqual(self.challb.encode('uri'), self.challb.uri) - - def test_to_partial_json(self): - self.assertEqual(self.jobj_to, self.challb.to_partial_json()) - - def test_from_json(self): - from acme.messages import ChallengeBody - self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) - - def test_from_json_hashable(self): - from acme.messages import ChallengeBody - hash(ChallengeBody.from_json(self.jobj_from)) - - def test_proxy(self): - self.assertEqual(jose.b64decode( - 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token) - - -class AuthorizationTest(unittest.TestCase): - """Tests for acme.messages.Authorization.""" - - def setUp(self): - from acme.messages import ChallengeBody - from acme.messages import STATUS_VALID - - self.challbs = ( - ChallengeBody( - uri='http://challb1', status=STATUS_VALID, - chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')), - ChallengeBody(uri='http://challb2', status=STATUS_VALID, - chall=challenges.DNS( - token=b'DGyRejmCefe7v4NfDGDKfA')), - ) - combinations = ((0,), (1,)) - - from acme.messages import Authorization - from acme.messages import Identifier - from acme.messages import IDENTIFIER_FQDN - identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com') - self.authz = Authorization( - identifier=identifier, combinations=combinations, - challenges=self.challbs) - - self.jobj_from = { - 'identifier': identifier.to_json(), - 'challenges': [challb.to_json() for challb in self.challbs], - 'combinations': combinations, - } - - def test_from_json(self): - from acme.messages import Authorization - Authorization.from_json(self.jobj_from) - - def test_from_json_hashable(self): - from acme.messages import Authorization - hash(Authorization.from_json(self.jobj_from)) - - def test_resolved_combinations(self): - self.assertEqual(self.authz.resolved_combinations, ( - (self.challbs[0],), - (self.challbs[1],), - )) - - -class AuthorizationResourceTest(unittest.TestCase): - """Tests for acme.messages.AuthorizationResource.""" - - def test_json_de_serializable(self): - from acme.messages import AuthorizationResource - authzr = AuthorizationResource( - uri=mock.sentinel.uri, - body=mock.sentinel.body) - self.assertTrue(isinstance(authzr, jose.JSONDeSerializable)) - - -class CertificateRequestTest(unittest.TestCase): - """Tests for acme.messages.CertificateRequest.""" - - def setUp(self): - from acme.messages import CertificateRequest - self.req = CertificateRequest(csr=CSR) - - def test_json_de_serializable(self): - self.assertTrue(isinstance(self.req, jose.JSONDeSerializable)) - from acme.messages import CertificateRequest - self.assertEqual( - self.req, CertificateRequest.from_json(self.req.to_json())) - - -class CertificateResourceTest(unittest.TestCase): - """Tests for acme.messages.CertificateResourceTest.""" - - def setUp(self): - from acme.messages import CertificateResource - self.certr = CertificateResource( - body=CERT, uri=mock.sentinel.uri, authzrs=(), - cert_chain_uri=mock.sentinel.cert_chain_uri) - - def test_json_de_serializable(self): - self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable)) - from acme.messages import CertificateResource - self.assertEqual( - self.certr, CertificateResource.from_json(self.certr.to_json())) - - -class RevocationTest(unittest.TestCase): - """Tests for acme.messages.RevocationTest.""" - - def setUp(self): - from acme.messages import Revocation - self.rev = Revocation(certificate=CERT) - - def test_from_json_hashable(self): - from acme.messages import Revocation - hash(Revocation.from_json(self.rev.to_json())) - - -class OrderResourceTest(unittest.TestCase): - """Tests for acme.messages.OrderResource.""" - - def setUp(self): - from acme.messages import OrderResource - self.regr = OrderResource( - body=mock.sentinel.body, uri=mock.sentinel.uri) - - def test_to_partial_json(self): - self.assertEqual(self.regr.to_json(), { - 'body': mock.sentinel.body, - 'uri': mock.sentinel.uri, - 'authorizations': None, - }) - -class NewOrderTest(unittest.TestCase): - """Tests for acme.messages.NewOrder.""" - - def setUp(self): - from acme.messages import NewOrder - self.reg = NewOrder( - identifiers=mock.sentinel.identifiers) - - def test_to_partial_json(self): - self.assertEqual(self.reg.to_json(), { - 'identifiers': mock.sentinel.identifiers, - }) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py deleted file mode 100644 index 9f9249b07..000000000 --- a/acme/acme/standalone_test.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Tests for acme.standalone.""" -import socket -import threading -import unittest - -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error - -import josepy as jose -import mock -import requests - -from acme import challenges -from acme import test_util -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module - - -class TLSServerTest(unittest.TestCase): - """Tests for acme.standalone.TLSServer.""" - - - def test_bind(self): # pylint: disable=no-self-use - from acme.standalone import TLSServer - server = TLSServer( - ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) - server.server_close() - - def test_ipv6(self): - if socket.has_ipv6: - from acme.standalone import TLSServer - server = TLSServer( - ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True) - server.server_close() - - -class HTTP01ServerTest(unittest.TestCase): - """Tests for acme.standalone.HTTP01Server.""" - - - def setUp(self): - self.account_key = jose.JWK.load( - test_util.load_vector('rsa1024_key.pem')) - self.resources = set() # type: Set - - from acme.standalone import HTTP01Server - self.server = HTTP01Server(('', 0), resources=self.resources) - - self.port = self.server.socket.getsockname()[1] - self.thread = threading.Thread(target=self.server.serve_forever) - self.thread.start() - - def tearDown(self): - self.server.shutdown() - self.thread.join() - - def test_index(self): - response = requests.get( - 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) - - def test_404(self): - response = requests.get( - 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) - - def _test_http01(self, add): - chall = challenges.HTTP01(token=(b'x' * 16)) - response, validation = chall.response_and_validation(self.account_key) - - from acme.standalone import HTTP01RequestHandler - resource = HTTP01RequestHandler.HTTP01Resource( - chall=chall, response=response, validation=validation) - if add: - self.resources.add(resource) - return resource.response.simple_verify( - resource.chall, 'localhost', self.account_key.public_key(), - port=self.port) - - def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) - - def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) - - -class BaseDualNetworkedServersTest(unittest.TestCase): - """Test for acme.standalone.BaseDualNetworkedServers.""" - - - class SingleProtocolServer(socketserver.TCPServer): - """Server that only serves on a single protocol. FreeBSD has this behavior for AF_INET6.""" - def __init__(self, *args, **kwargs): - ipv6 = kwargs.pop("ipv6", False) - if ipv6: - self.address_family = socket.AF_INET6 - kwargs["bind_and_activate"] = False - else: - self.address_family = socket.AF_INET - socketserver.TCPServer.__init__(self, *args, **kwargs) - if ipv6: - # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. - # We use the corresponding value (41) instead. - level = getattr(socket, "IPPROTO_IPV6", 41) - self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) - try: - self.server_bind() - self.server_activate() - except: - self.server_close() - raise - - @mock.patch("socket.socket.bind") - def test_fail_to_bind(self, mock_bind): - mock_bind.side_effect = socket.error - from acme.standalone import BaseDualNetworkedServers - self.assertRaises(socket.error, BaseDualNetworkedServers, - BaseDualNetworkedServersTest.SingleProtocolServer, - ('', 0), - socketserver.BaseRequestHandler) - - def test_ports_equal(self): - from acme.standalone import BaseDualNetworkedServers - servers = BaseDualNetworkedServers( - BaseDualNetworkedServersTest.SingleProtocolServer, - ('', 0), - socketserver.BaseRequestHandler) - socknames = servers.getsocknames() - prev_port = None - # assert ports are equal - for sockname in socknames: - port = sockname[1] - if prev_port: - self.assertEqual(prev_port, port) - prev_port = port - - -class HTTP01DualNetworkedServersTest(unittest.TestCase): - """Tests for acme.standalone.HTTP01DualNetworkedServers.""" - - - def setUp(self): - self.account_key = jose.JWK.load( - test_util.load_vector('rsa1024_key.pem')) - self.resources = set() # type: Set - - from acme.standalone import HTTP01DualNetworkedServers - self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) - - self.port = self.servers.getsocknames()[0][1] - self.servers.serve_forever() - - def tearDown(self): - self.servers.shutdown_and_server_close() - - def test_index(self): - response = requests.get( - 'http://localhost:{0}'.format(self.port), verify=False) - self.assertEqual( - response.text, 'ACME client standalone challenge solver') - self.assertTrue(response.ok) - - def test_404(self): - response = requests.get( - 'http://localhost:{0}/foo'.format(self.port), verify=False) - self.assertEqual(response.status_code, http_client.NOT_FOUND) - - def _test_http01(self, add): - chall = challenges.HTTP01(token=(b'x' * 16)) - response, validation = chall.response_and_validation(self.account_key) - - from acme.standalone import HTTP01RequestHandler - resource = HTTP01RequestHandler.HTTP01Resource( - chall=chall, response=response, validation=validation) - if add: - self.resources.add(resource) - return resource.response.simple_verify( - resource.chall, 'localhost', self.account_key.public_key(), - port=self.port) - - def test_http01_found(self): - self.assertTrue(self._test_http01(add=True)) - - def test_http01_not_found(self): - self.assertFalse(self._test_http01(add=False)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py deleted file mode 100644 index 6737bff4e..000000000 --- a/acme/acme/test_util.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Test utilities. - -.. warning:: This module is not part of the public API. - -""" -import os -import pkg_resources - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -import josepy as jose -from OpenSSL import crypto - - -def load_vector(*names): - """Load contents of a test vector.""" - # luckily, resource_string opens file in binary mode - return pkg_resources.resource_string( - __name__, os.path.join('testdata', *names)) - - -def _guess_loader(filename, loader_pem, loader_der): - _, ext = os.path.splitext(filename) - if ext.lower() == '.pem': - return loader_pem - elif ext.lower() == '.der': - return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") - - -def load_cert(*names): - """Load certificate.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_certificate(loader, load_vector(*names)) - - -def load_comparable_cert(*names): - """Load ComparableX509 cert.""" - return jose.ComparableX509(load_cert(*names)) - - -def load_csr(*names): - """Load certificate request.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_certificate_request(loader, load_vector(*names)) - - -def load_comparable_csr(*names): - """Load ComparableX509 certificate request.""" - return jose.ComparableX509(load_csr(*names)) - - -def load_rsa_private_key(*names): - """Load RSA private key.""" - loader = _guess_loader(names[-1], serialization.load_pem_private_key, - serialization.load_der_private_key) - return jose.ComparableRSAKey(loader( - load_vector(*names), password=None, backend=default_backend())) - - -def load_pyopenssl_private_key(*names): - """Load pyOpenSSL private key.""" - loader = _guess_loader( - names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) - return crypto.load_privatekey(loader, load_vector(*names)) diff --git a/acme/acme/testdata/README b/acme/acme/testdata/README deleted file mode 100644 index dfe3f5405..000000000 --- a/acme/acme/testdata/README +++ /dev/null @@ -1,15 +0,0 @@ -In order for acme.test_util._guess_loader to work properly, make sure -to use appropriate extension for vector filenames: .pem for PEM and -.der for DER. - -The following command has been used to generate test keys: - - for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done - -and for the CSR: - - openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der - -and for the certificate: - - openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der diff --git a/acme/acme/testdata/cert-100sans.pem b/acme/acme/testdata/cert-100sans.pem deleted file mode 100644 index 3fdc9404f..000000000 --- a/acme/acme/testdata/cert-100sans.pem +++ /dev/null @@ -1,44 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV -BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv -bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X -DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU -BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp -ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B -AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 -rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T -BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t -ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt -cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j -b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN -ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh -bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs -ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx -LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv -bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN -ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh -bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs -ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 -LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv -bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN -ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh -bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs -ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz -LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv -bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN -ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh -bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs -ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 -LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv -bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN -ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh -bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs -ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 -LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv -bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN -ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh -bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs -ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN -AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ -XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert-idnsans.pem b/acme/acme/testdata/cert-idnsans.pem deleted file mode 100644 index 932649692..000000000 --- a/acme/acme/testdata/cert-idnsans.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV -BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv -bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X -DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU -BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp -ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B -AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 -rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T -BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I -z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g -z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z -z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM -2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf -2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 -2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi -2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi -2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs -aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN -247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p -bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 -27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt -4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh -oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh -oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh -oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm -4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 -LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT -TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert-nocn.der b/acme/acme/testdata/cert-nocn.der deleted file mode 100644 index 59da83ccc..000000000 Binary files a/acme/acme/testdata/cert-nocn.der and /dev/null differ diff --git a/acme/acme/testdata/cert-san.pem b/acme/acme/testdata/cert-san.pem deleted file mode 100644 index dcb835994..000000000 --- a/acme/acme/testdata/cert-san.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt -cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF -nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 -RDjyGMKy5ZgM2w== ------END CERTIFICATE----- diff --git a/acme/acme/testdata/cert.der b/acme/acme/testdata/cert.der deleted file mode 100644 index ab231982f..000000000 Binary files a/acme/acme/testdata/cert.der and /dev/null differ diff --git a/acme/acme/testdata/cert.pem b/acme/acme/testdata/cert.pem deleted file mode 100644 index 96c55cbf4..000000000 --- a/acme/acme/testdata/cert.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx -ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM -IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 -YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG -A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix -KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS -BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR -7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c -+pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll -vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn -B/o= ------END CERTIFICATE----- diff --git a/acme/acme/testdata/critical-san.pem b/acme/acme/testdata/critical-san.pem deleted file mode 100644 index 7aec8ab1c..000000000 --- a/acme/acme/testdata/critical-san.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIErTCCA5WgAwIBAgIKETb7VQAAAAAdGTANBgkqhkiG9w0BAQsFADCBkTELMAkG -A1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5 -MRUwEwYDVQQKEwxWZW5hZmksIEluYy4xHzAdBgNVBAsTFkRlbW9uc3RyYXRpb24g -U2VydmljZXMxIjAgBgNVBAMTGVZlbmFmaSBFeGFtcGxlIElzc3VpbmcgQ0EwHhcN -MTcwNzEwMjMxNjA1WhcNMTcwODA5MjMxNjA1WjAAMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEA7CU5qRIzCs9hCRiSUvLZ8r81l4zIYbx1V1vZz6x1cS4M -0keNfFJ1wB+zuvx80KaMYkWPYlg4Rsm9Ok3ZapakXDlaWtrfg78lxtHuPw1o7AYV -EXDwwPkNugLMJfYw5hWYSr8PCLcOJoY00YQ0fJ44L+kVsUyGjN4UTRRZmOh/yNVU -0W12dTCz4X7BAW01OuY6SxxwewnW3sBEep+APfr2jd/oIx7fgZmVB8aRCDPj4AFl -XINWIwxmptOwnKPbwLN/vhCvJRUkO6rA8lpYwQkedFf6fHhqi2Sq/NCEOg4RvMCF -fKbMpncOXxz+f4/i43SVLrPz/UyhjNbKGJZ+zFrQowIDAQABo4IBlTCCAZEwPgYD -VR0RAQH/BDQwMoIbY2hpY2Fnby1jdWJzLnZlbmFmaS5leGFtcGxlghNjdWJzLnZl -bmFmaS5leGFtcGxlMB0GA1UdDgQWBBTgKZXVSFNyPHHtO/phtIALPcCF5DAfBgNV -HSMEGDAWgBT/JJ6Wei/pzf+9DRHuv6Wgdk2HsjBSBgNVHR8ESzBJMEegRaBDhkFo -dHRwOi8vcGtpLnZlbmFmaS5leGFtcGxlL2NybC9WZW5hZmklMjBFeGFtcGxlJTIw -SXNzdWluZyUyMENBLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0 -dHA6Ly9wa2kudmVuYWZpLmV4YW1wbGUvb2NzcDAOBgNVHQ8BAf8EBAMCBaAwPQYJ -KwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhIDLGYTvsSSEnZ8ehvD5UofP4hMEgobv -DIGy4mcCAWQCAQIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGwYJKwYBBAGCNxUKBA4w -DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA3YW4t1AzxEn384OqdU6L -ny8XkMhWpRM0W0Z9ZC3gRZKbVUu49nG/KB5hbVn/de33zdX9HOZJKc0vXzkGZQUs -OUCCsKX4VKzV5naGXOuGRbvV4CJh5P0kPlDzyb5t312S49nJdcdBf0Y/uL5Qzhst -bXy8qNfFNG3SIKKRAUpqE9OVIl+F+JBwexa+v/4dFtUOqMipfXxB3TaxnDqvU1dS -yO34ZTvIMGXJIZ5nn/d/LNc3N3vBg2SHkMpladqw0Hr7mL0bFOe0b+lJgkDP06Be -n08fikhz1j2AW4/ZHa9w4DUz7J21+RtHMhh+Vd1On0EAeZ563svDe7Z+yrg6zOVv -KA== ------END CERTIFICATE----- \ No newline at end of file diff --git a/acme/acme/testdata/csr-100sans.pem b/acme/acme/testdata/csr-100sans.pem deleted file mode 100644 index 199814126..000000000 --- a/acme/acme/testdata/csr-100sans.pem +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz -Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG -A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt -H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 -lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud -EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv -bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh -bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu -Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C -DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 -YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w -bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy -MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j -b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C -DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 -YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w -bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz -Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j -b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C -DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 -YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w -bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 -My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j -b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C -DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 -YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w -bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 -OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j -b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C -DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 -YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w -bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 -NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j -b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C -DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 -YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w -bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 -DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo -duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-6sans.pem b/acme/acme/testdata/csr-6sans.pem deleted file mode 100644 index 8f6b52bd7..000000000 --- a/acme/acme/testdata/csr-6sans.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw -EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0 -9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG -9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL -ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t -ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd -k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv -IvzVBz/nD11drfz/RNuX ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-idnsans.pem b/acme/acme/testdata/csr-idnsans.pem deleted file mode 100644 index d6e91a420..000000000 --- a/acme/acme/testdata/csr-idnsans.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz -Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG -A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt -H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 -lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud -EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP -iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P -oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP -s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ -jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z -n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ -t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC -YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa -otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh -bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb -jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu -aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb -uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg -reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 -4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ -4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ -4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh -puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh -ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr -dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-nosans.pem b/acme/acme/testdata/csr-nosans.pem deleted file mode 100644 index 813db67b0..000000000 --- a/acme/acme/testdata/csr-nosans.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh -MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt -cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn -BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz -AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo -wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA= ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr-san.pem b/acme/acme/testdata/csr-san.pem deleted file mode 100644 index a7128e35c..000000000 --- a/acme/acme/testdata/csr-san.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN -AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t -MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy -tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/csr.der b/acme/acme/testdata/csr.der deleted file mode 100644 index d43ac85a1..000000000 Binary files a/acme/acme/testdata/csr.der and /dev/null differ diff --git a/acme/acme/testdata/csr.pem b/acme/acme/testdata/csr.pem deleted file mode 100644 index b6818e39d..000000000 --- a/acme/acme/testdata/csr.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw -EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy -c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG -9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f -p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN -AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB -AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G -n9XBE1N9W6HCIEut2d8wACg= ------END CERTIFICATE REQUEST----- diff --git a/acme/acme/testdata/dsa512_key.pem b/acme/acme/testdata/dsa512_key.pem deleted file mode 100644 index 78e164712..000000000 --- a/acme/acme/testdata/dsa512_key.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN DSA PARAMETERS----- -MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC -OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA -qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl -41pgNJpgu99YOYqPpS0g7A== ------END DSA PARAMETERS----- ------BEGIN DSA PRIVATE KEY----- -MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf -n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP -AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm -rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth -zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE -mNv063So6E+eYaIN ------END DSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa1024_key.pem b/acme/acme/testdata/rsa1024_key.pem deleted file mode 100644 index de5339d03..000000000 --- a/acme/acme/testdata/rsa1024_key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi -4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/ -w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB -AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB -Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc -TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB -CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X -UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak -Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt -73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa -HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU -6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ -c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc= ------END RSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa2048_cert.pem b/acme/acme/testdata/rsa2048_cert.pem deleted file mode 100644 index 3944cd1db..000000000 --- a/acme/acme/testdata/rsa2048_cert.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIJALVG/VbBb5U7MA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV -BAYTAkFVMQswCQYDVQQIDAJXQTEeMBwGA1UEBwwVVGhlIG1pZGRsZSBvZiBub3do -ZXJlMR8wHQYDVQQKDBZDZXJ0Ym90IFRlc3QgQ2VydHMgSW5jMCAXDTE2MTEyODIx -MzUzN1oYDzIyOTAwOTEzMjEzNTM3WjBbMQswCQYDVQQGEwJBVTELMAkGA1UECAwC -V0ExHjAcBgNVBAcMFVRoZSBtaWRkbGUgb2Ygbm93aGVyZTEfMB0GA1UECgwWQ2Vy -dGJvdCBUZXN0IENlcnRzIEluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBANoVT1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svN -PSa+oPTK7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm -7Gj6m2EzpSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFn -xvvOjBYop7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTD -g7P4UAuFkejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1 -RAeEeRTkh0WjUfltoem/5f8bIdsCAwEAAaNTMFEwHQYDVR0OBBYEFHy+bEYqwvFU -uQLTkIfQ36AM2DQiMB8GA1UdIwQYMBaAFHy+bEYqwvFUuQLTkIfQ36AM2DQiMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH3ANVzB59FcunZV/F8T -RiCD6/gV7Jc3CswU8N8tVjzMCg2jOdTFF9iYZzNNKQvG13o/n5LkQr/lkKRQkWTx -nkE5WZbR7vNqlzXgPa9NBiK5rPjgSt8azPW+Skct3Bj4B3PhTMSpoQ7PsUJ8UeV8 -kTNR5xrRLt6/mLfRJTXWXBM43GEZi8lL5q0nqz0tPGISADshHMo6ZlUu5Hvfp5v+ -aonpO4sVS9hGOVxjGNMXYApEUy4jid9jjAfEk6jeELJMbXGLy/botFgIJK/QPe6P -AfbdFgtg/qzG7Uy0A1iXXfWdgwmVrhCoGYYWCn4yWCAm894QKtdim87CHSDP0WUf -Esg= ------END CERTIFICATE----- diff --git a/acme/acme/testdata/rsa2048_key.pem b/acme/acme/testdata/rsa2048_key.pem deleted file mode 100644 index 5847aed55..000000000 --- a/acme/acme/testdata/rsa2048_key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaFU9aXb0VFATq -r5uzNnmyxB1e4YoFB+6gBlGRJHz+luGK4mFWPiB6x9bLzT0mvqD0yu8HgzZk9dXo -VpxshAiDPc9q9h4vclVdMnMYeIVkODbfAQCmcAsGl6clpuxo+pthM6UjeQ3eu4OW -AEAa1FIdfkUWxYvXNC7P+mThMTL34PJKreHNrFwoB9SBZ8b7zowWKKe8YdMdy9QJ -MmsdLcPKoEKf8+ZpLqZlQzSWDxZm8xTzDcg0etXnekFEw4Oz+FALhZHoyy8xb8+W -Gju7wvobmk7j4SJwIac6qa+MlPeyiI6LNv/NyLbvwr/FNUQHhHkU5IdFo1H5baHp -v+X/GyHbAgMBAAECggEAURFe4C68XRuGAF+rN2Fmt+djK6QXlGswb1gp9hRkSpd3 -3BLvMAoENOAYnsX6l26Bkr3lQRurmrgv/iBEIaqrJ25QrmgzLFwKE4zvcAdNPsYO -z7MltLktwBOb1MlKVHPkUqvKFXeoikWWUqphKhgHNmN7900UALmrNTDVU0jgs3fB -o35o8d5SjoC52K4wCTjhPyjt4cdbfbziRs2qFhfGdawidRO1xLlDM4tTTW+5yWGK -lt0SwyvDVC6XWeNoT3nXyKjXWP7hcYqm0iS7ffL9YzEC2RXNGQUqeR50i9Y0rDdH -Vqcr+Rqio2ww68zbDWBpC/jU133BSoHuSE1wstxIkQKBgQDxlEr42WJfgdajbZ1a -hUIeLEgvhezLmD1hcYwZuQCLgizmY2ovvmeAH74koCDEsUUQunPYHsRla7wT3q1/ -IkR1KgJPwESpkQaKuAqxeEAkv7Gn8Lzcn22jCoRCfGA68wKJz2ECFZDc0RDvRrT/ -9GhiiGUoO47jv9ezrSDO1eu5/QKBgQDnGfYVMNLiA0fy4AxSyY2vdo7vruOFGpRP -n94gwxZ+0dQDWHzn3J4rHivxtcyd/MOZv4I8PtYK7tmmjYv1ngQ6sGl4p8bpUtwj -9++/B1CyB1W5/VPqMkd+Sj0dbejycME55+F6/r4basPXxBFFCfknjAlVvyvbBhUy -ftNpHxZGtwKBgChJM4t2LPqCW3nbgL8ks9b2SX9rVQbKt4m1dsifWmDpb3VoJMAb -f4UVRg8ziONkMIFOppzm3JeRNMcXflVSMJpdTA9in9CrN60QbfAUfpXiRc0cz1H3 -YEAtM8smlKGf/s9efu3rDMJWNv3AC9UXPAUae8wOypBeYKk8+NilQe89AoGAXEA3 -xFO+CqyGnwQixzVf0qf//NuSRQLMK1DEyc02gJ9gA4niKmgd11Zu8kjBClvo9MnG -wifPJ4Qa6+pa8UwHoinjoF9Q/rit2cnSMS5JXxegd+MRCU7SzS3zYXkLYSPzbhsL -Hh7sYmNnFA1XW3jUtZ2n6EusxPyTn5mS6MaZDNcCgYBelFKFjNIQ50NbOnm8DewK -jUd5OFKowKodlQVcHiF9CVbjvpN8ZPRcBSmqDU4kpT/rmcybVoL6Zfa/zWkw8+Oh -QxKb3BYf5vRUMd/RA+/t5KG4ZOIIYB3qoltAYlhVaINukL6cGVG1qvV/ntcsfsn6 -kmf1UgGFcKrJuXgwEtTVxw== ------END PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa256_key.pem b/acme/acme/testdata/rsa256_key.pem deleted file mode 100644 index 659274d1d..000000000 --- a/acme/acme/testdata/rsa256_key.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh -AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N -E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 -rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt ------END RSA PRIVATE KEY----- diff --git a/acme/acme/testdata/rsa512_key.pem b/acme/acme/testdata/rsa512_key.pem deleted file mode 100644 index 610c8d315..000000000 --- a/acme/acme/testdata/rsa512_key.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 -vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn -elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc -mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp -Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj -8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq -6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ ------END RSA PRIVATE KEY----- diff --git a/acme/acme/util_test.py b/acme/acme/util_test.py deleted file mode 100644 index 00aa8b02d..000000000 --- a/acme/acme/util_test.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Tests for acme.util.""" -import unittest - - -class MapKeysTest(unittest.TestCase): - """Tests for acme.util.map_keys.""" - - def test_it(self): - from acme.util import map_keys - self.assertEqual({'a': 'b', 'c': 'd'}, - map_keys({'a': 'b', 'c': 'd'}, lambda key: key)) - self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1)) - - -if __name__ == '__main__': - unittest.main() # pragma: no cover diff --git a/acme/tests/challenges_test.py b/acme/tests/challenges_test.py new file mode 100644 index 000000000..819ba9261 --- /dev/null +++ b/acme/tests/challenges_test.py @@ -0,0 +1,419 @@ +"""Tests for acme.challenges.""" +import unittest + +import josepy as jose +import mock +import requests + +from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import + +import test_util + +CERT = test_util.load_comparable_cert('cert.pem') +KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) + + +class ChallengeTest(unittest.TestCase): + + def test_from_json_unrecognized(self): + from acme.challenges import Challenge + from acme.challenges import UnrecognizedChallenge + chall = UnrecognizedChallenge({"type": "foo"}) + # pylint: disable=no-member + self.assertEqual(chall, Challenge.from_json(chall.jobj)) + + +class UnrecognizedChallengeTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import UnrecognizedChallenge + self.jobj = {"type": "foo"} + self.chall = UnrecognizedChallenge(self.jobj) + + def test_to_partial_json(self): + self.assertEqual(self.jobj, self.chall.to_partial_json()) + + def test_from_json(self): + from acme.challenges import UnrecognizedChallenge + self.assertEqual( + self.chall, UnrecognizedChallenge.from_json(self.jobj)) + + +class KeyAuthorizationChallengeResponseTest(unittest.TestCase): + + def setUp(self): + def _encode(name): + assert name == "token" + return "foo" + self.chall = mock.Mock() + self.chall.encode.side_effect = _encode + + def test_verify_ok(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') + self.assertTrue(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_token(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_thumbprint(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + def test_verify_wrong_form(self): + from acme.challenges import KeyAuthorizationChallengeResponse + response = KeyAuthorizationChallengeResponse( + key_authorization='.foo.oKGqedy-b-acd5eoybm2f-' + 'NVFxvyOoET5CNy3xnv8WY') + self.assertFalse(response.verify(self.chall, KEY.public_key())) + + +class DNS01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS01Response + self.msg = DNS01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'dns-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import DNS01 + self.chall = DNS01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS01Response + self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS01Response + hash(DNS01Response.from_json(self.jmsg)) + + def test_simple_verify_failure(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + public_key = key2.public_key() + verified = self.response.simple_verify(self.chall, "local", public_key) + self.assertFalse(verified) + + def test_simple_verify_success(self): + public_key = KEY.public_key() + verified = self.response.simple_verify(self.chall, "local", public_key) + self.assertTrue(verified) + + +class DNS01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS01 + self.msg = DNS01(token=jose.decode_b64jose( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) + self.jmsg = { + 'type': 'dns-01', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_validation_domain_name(self): + self.assertEqual('_acme-challenge.www.example.com', + self.msg.validation_domain_name('www.example.com')) + + def test_validation(self): + self.assertEqual( + "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk", + self.msg.validation(KEY)) + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS01 + self.assertEqual(self.msg, DNS01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS01 + hash(DNS01.from_json(self.jmsg)) + + +class HTTP01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import HTTP01Response + self.msg = HTTP01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'http-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import HTTP01 + self.chall = HTTP01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import HTTP01Response + self.assertEqual( + self.msg, HTTP01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import HTTP01Response + hash(HTTP01Response.from_json(self.jmsg)) + + def test_simple_verify_bad_key_authorization(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + self.response.simple_verify(self.chall, "local", key2.public_key()) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_good_validation(self, mock_get): + validation = self.chall.validation(KEY) + mock_get.return_value = mock.MagicMock(text=validation) + self.assertTrue(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + mock_get.assert_called_once_with(self.chall.uri("local")) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_bad_validation(self, mock_get): + mock_get.return_value = mock.MagicMock(text="!") + self.assertFalse(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_whitespace_validation(self, mock_get): + from acme.challenges import HTTP01Response + mock_get.return_value = mock.MagicMock( + text=(self.chall.validation(KEY) + + HTTP01Response.WHITESPACE_CUTSET)) + self.assertTrue(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + mock_get.assert_called_once_with(self.chall.uri("local")) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_connection_error(self, mock_get): + mock_get.side_effect = requests.exceptions.RequestException + self.assertFalse(self.response.simple_verify( + self.chall, "local", KEY.public_key())) + + @mock.patch("acme.challenges.requests.get") + def test_simple_verify_port(self, mock_get): + self.response.simple_verify( + self.chall, domain="local", + account_public_key=KEY.public_key(), port=8080) + self.assertEqual("local:8080", urllib_parse.urlparse( + mock_get.mock_calls[0][1][0]).netloc) + + +class HTTP01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import HTTP01 + self.msg = HTTP01( + token=jose.decode_b64jose( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA')) + self.jmsg = { + 'type': 'http-01', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_path(self): + self.assertEqual(self.msg.path, '/.well-known/acme-challenge/' + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') + + def test_uri(self): + self.assertEqual( + 'http://example.com/.well-known/acme-challenge/' + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + self.msg.uri('example.com')) + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import HTTP01 + self.assertEqual(self.msg, HTTP01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import HTTP01 + hash(HTTP01.from_json(self.jmsg)) + + def test_good_token(self): + self.assertTrue(self.msg.good_token) + self.assertFalse( + self.msg.update(token=b'..').good_token) + + +class TLSALPN01ResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import TLSALPN01Response + self.msg = TLSALPN01Response(key_authorization=u'foo') + self.jmsg = { + 'resource': 'challenge', + 'type': 'tls-alpn-01', + 'keyAuthorization': u'foo', + } + + from acme.challenges import TLSALPN01 + self.chall = TLSALPN01(token=(b'x' * 16)) + self.response = self.chall.response(KEY) + + def test_to_partial_json(self): + self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, + self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import TLSALPN01Response + self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import TLSALPN01Response + hash(TLSALPN01Response.from_json(self.jmsg)) + + +class TLSALPN01Test(unittest.TestCase): + + def setUp(self): + from acme.challenges import TLSALPN01 + self.msg = TLSALPN01( + token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) + self.jmsg = { + 'type': 'tls-alpn-01', + 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import TLSALPN01 + self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import TLSALPN01 + hash(TLSALPN01.from_json(self.jmsg)) + + def test_from_json_invalid_token_length(self): + from acme.challenges import TLSALPN01 + self.jmsg['token'] = jose.encode_b64jose(b'abcd') + self.assertRaises( + jose.DeserializationError, TLSALPN01.from_json, self.jmsg) + + def test_validation(self): + self.assertRaises(NotImplementedError, self.msg.validation, KEY) + + +class DNSTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS + self.msg = DNS(token=jose.b64decode( + b'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) + self.jmsg = { + 'type': 'dns', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNS + self.assertEqual(self.msg, DNS.from_json(self.jmsg)) + + def test_from_json_hashable(self): + from acme.challenges import DNS + hash(DNS.from_json(self.jmsg)) + + def test_gen_check_validation(self): + self.assertTrue(self.msg.check_validation( + self.msg.gen_validation(KEY), KEY.public_key())) + + def test_gen_check_validation_wrong_key(self): + key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem')) + self.assertFalse(self.msg.check_validation( + self.msg.gen_validation(KEY), key2.public_key())) + + def test_check_validation_wrong_payload(self): + validations = tuple( + jose.JWS.sign(payload=payload, alg=jose.RS256, key=KEY) + for payload in (b'', b'{}') + ) + for validation in validations: + self.assertFalse(self.msg.check_validation( + validation, KEY.public_key())) + + def test_check_validation_wrong_fields(self): + bad_validation = jose.JWS.sign( + payload=self.msg.update( + token=b'x' * 20).json_dumps().encode('utf-8'), + alg=jose.RS256, key=KEY) + self.assertFalse(self.msg.check_validation( + bad_validation, KEY.public_key())) + + def test_gen_response(self): + with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen: + mock_gen.return_value = mock.sentinel.validation + response = self.msg.gen_response(KEY) + from acme.challenges import DNSResponse + self.assertTrue(isinstance(response, DNSResponse)) + self.assertEqual(response.validation, mock.sentinel.validation) + + def test_validation_domain_name(self): + self.assertEqual( + '_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf')) + + +class DNSResponseTest(unittest.TestCase): + + def setUp(self): + from acme.challenges import DNS + self.chall = DNS(token=jose.b64decode( + b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA")) + self.validation = jose.JWS.sign( + payload=self.chall.json_dumps(sort_keys=True).encode(), + key=KEY, alg=jose.RS256) + + from acme.challenges import DNSResponse + self.msg = DNSResponse(validation=self.validation) + self.jmsg_to = { + 'resource': 'challenge', + 'type': 'dns', + 'validation': self.validation, + } + self.jmsg_from = { + 'resource': 'challenge', + 'type': 'dns', + 'validation': self.validation.to_json(), + } + + def test_to_partial_json(self): + self.assertEqual(self.jmsg_to, self.msg.to_partial_json()) + + def test_from_json(self): + from acme.challenges import DNSResponse + self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from)) + + def test_from_json_hashable(self): + from acme.challenges import DNSResponse + hash(DNSResponse.from_json(self.jmsg_from)) + + def test_check_validation(self): + self.assertTrue( + self.msg.check_validation(self.chall, KEY.public_key())) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py new file mode 100644 index 000000000..22eb3fc45 --- /dev/null +++ b/acme/tests/client_test.py @@ -0,0 +1,1308 @@ +"""Tests for acme.client.""" +# pylint: disable=too-many-lines +import copy +import datetime +import json +import unittest + +from six.moves import http_client # pylint: disable=import-error + +import josepy as jose +import mock +import OpenSSL +import requests + +from acme import challenges +from acme import errors +from acme import jws as acme_jws +from acme import messages +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +import messages_test +import test_util + +CERT_DER = test_util.load_vector('cert.der') +CERT_SAN_PEM = test_util.load_vector('cert-san.pem') +CSR_SAN_PEM = test_util.load_vector('csr-san.pem') +KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) +KEY2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) + +DIRECTORY_V1 = messages.Directory({ + messages.NewRegistration: + 'https://www.letsencrypt-demo.org/acme/new-reg', + messages.Revocation: + 'https://www.letsencrypt-demo.org/acme/revoke-cert', + messages.NewAuthorization: + 'https://www.letsencrypt-demo.org/acme/new-authz', + messages.CertificateRequest: + 'https://www.letsencrypt-demo.org/acme/new-cert', +}) + +DIRECTORY_V2 = messages.Directory({ + 'newAccount': 'https://www.letsencrypt-demo.org/acme/new-account', + 'newNonce': 'https://www.letsencrypt-demo.org/acme/new-nonce', + 'newOrder': 'https://www.letsencrypt-demo.org/acme/new-order', + 'revokeCert': 'https://www.letsencrypt-demo.org/acme/revoke-cert', +}) + + +class ClientTestBase(unittest.TestCase): + """Base for tests in acme.client.""" + + def setUp(self): + self.response = mock.MagicMock( + ok=True, status_code=http_client.OK, headers={}, links={}) + self.net = mock.MagicMock() + self.net.post.return_value = self.response + self.net.get.return_value = self.response + + self.identifier = messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='example.com') + + # Registration + self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') + reg = messages.Registration( + contact=self.contact, key=KEY.public_key()) + the_arg = dict(reg) # type: Dict + self.new_reg = messages.NewRegistration(**the_arg) + self.regr = messages.RegistrationResource( + body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') + + # Authorization + authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1' + challb = messages.ChallengeBody( + uri=(authzr_uri + '/1'), status=messages.STATUS_VALID, + chall=challenges.DNS(token=jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'))) + self.challr = messages.ChallengeResource( + body=challb, authzr_uri=authzr_uri) + self.authz = messages.Authorization( + identifier=messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='example.com'), + challenges=(challb,), combinations=None) + self.authzr = messages.AuthorizationResource( + body=self.authz, uri=authzr_uri) + + # Reason code for revocation + self.rsn = 1 + + +class BackwardsCompatibleClientV2Test(ClientTestBase): + """Tests for acme.client.BackwardsCompatibleClientV2.""" + + def setUp(self): + super(BackwardsCompatibleClientV2Test, self).setUp() + # contains a loaded cert + self.certr = messages.CertificateResource( + body=messages_test.CERT) + + loaded = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, CERT_SAN_PEM) + wrapped = jose.ComparableX509(loaded) + self.chain = [wrapped, wrapped] + + self.cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode() + + single_chain = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, loaded).decode() + self.chain_pem = single_chain + single_chain + + self.fullchain_pem = self.cert_pem + self.chain_pem + + self.orderr = messages.OrderResource( + csr_pem=CSR_SAN_PEM) + + def _init(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import BackwardsCompatibleClientV2 + return BackwardsCompatibleClientV2(net=self.net, + key=KEY, server=uri) + + def test_init_downloads_directory(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import BackwardsCompatibleClientV2 + BackwardsCompatibleClientV2(net=self.net, + key=KEY, server=uri) + self.net.get.assert_called_once_with(uri) + + def test_init_acme_version(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + client = self._init() + self.assertEqual(client.acme_version, 1) + + self.response.json.return_value = DIRECTORY_V2.to_json() + client = self._init() + self.assertEqual(client.acme_version, 2) + + def test_query_registration_client_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + client = self._init() + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, client.query_registration(self.regr)) + + def test_forwarding(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + client = self._init() + self.assertEqual(client.directory, client.client.directory) + self.assertEqual(client.key, KEY) + self.assertEqual(client.deactivate_registration, client.client.deactivate_registration) + self.assertRaises(AttributeError, client.__getattr__, 'nonexistent') + self.assertRaises(AttributeError, client.__getattr__, 'new_account_and_tos') + self.assertRaises(AttributeError, client.__getattr__, 'new_account') + + def test_new_account_and_tos(self): + # v2 no tos + self.response.json.return_value = DIRECTORY_V2.to_json() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().new_account.assert_called_with(self.new_reg) + + # v2 tos good + with mock.patch('acme.client.ClientV2') as mock_client: + mock_client().directory.meta.__contains__.return_value = True + client = self._init() + client.new_account_and_tos(self.new_reg, lambda x: True) + mock_client().new_account.assert_called_with( + self.new_reg.update(terms_of_service_agreed=True)) + + # v2 tos bad + with mock.patch('acme.client.ClientV2') as mock_client: + mock_client().directory.meta.__contains__.return_value = True + client = self._init() + def _tos_cb(tos): + raise errors.Error + self.assertRaises(errors.Error, client.new_account_and_tos, + self.new_reg, _tos_cb) + mock_client().new_account.assert_not_called() + + # v1 yes tos + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + regr = mock.MagicMock(terms_of_service="TOS") + mock_client().register.return_value = regr + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().register.assert_called_once_with(self.new_reg) + mock_client().agree_to_tos.assert_called_once_with(regr) + + # v1 no tos + with mock.patch('acme.client.Client') as mock_client: + regr = mock.MagicMock(terms_of_service=None) + mock_client().register.return_value = regr + client = self._init() + client.new_account_and_tos(self.new_reg) + mock_client().register.assert_called_once_with(self.new_reg) + mock_client().agree_to_tos.assert_not_called() + + @mock.patch('OpenSSL.crypto.load_certificate_request') + @mock.patch('acme.crypto_util._pyopenssl_cert_or_req_all_names') + def test_new_order_v1(self, mock__pyopenssl_cert_or_req_all_names, + unused_mock_load_certificate_request): + self.response.json.return_value = DIRECTORY_V1.to_json() + mock__pyopenssl_cert_or_req_all_names.return_value = ['example.com', 'www.example.com'] + mock_csr_pem = mock.MagicMock() + with mock.patch('acme.client.Client') as mock_client: + mock_client().request_domain_challenges.return_value = mock.sentinel.auth + client = self._init() + orderr = client.new_order(mock_csr_pem) + self.assertEqual(orderr.authorizations, [mock.sentinel.auth, mock.sentinel.auth]) + + def test_new_order_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + mock_csr_pem = mock.MagicMock() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.new_order(mock_csr_pem) + mock_client().new_order.assert_called_once_with(mock_csr_pem) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_success(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + mock_client().fetch_chain.return_value = self.chain + + deadline = datetime.datetime(9999, 9, 9) + client = self._init() + result = client.finalize_order(self.orderr, deadline) + self.assertEqual(result.fullchain_pem, self.fullchain_pem) + mock_client().fetch_chain.assert_called_once_with(self.certr) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_fetch_chain_error(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + mock_client().fetch_chain.return_value = self.chain + mock_client().fetch_chain.side_effect = [errors.Error, self.chain] + + deadline = datetime.datetime(9999, 9, 9) + client = self._init() + result = client.finalize_order(self.orderr, deadline) + self.assertEqual(result.fullchain_pem, self.fullchain_pem) + self.assertEqual(mock_client().fetch_chain.call_count, 2) + + @mock.patch('acme.client.Client') + def test_finalize_order_v1_timeout(self, mock_client): + self.response.json.return_value = DIRECTORY_V1.to_json() + + mock_client().request_issuance.return_value = self.certr + + deadline = deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) + client = self._init() + self.assertRaises(errors.TimeoutError, client.finalize_order, + self.orderr, deadline) + + def test_finalize_order_v2(self): + self.response.json.return_value = DIRECTORY_V2.to_json() + mock_orderr = mock.MagicMock() + mock_deadline = mock.MagicMock() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.finalize_order(mock_orderr, mock_deadline) + mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline) + + def test_revoke(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + client = self._init() + client.revoke(messages_test.CERT, self.rsn) + mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) + + self.response.json.return_value = DIRECTORY_V2.to_json() + with mock.patch('acme.client.ClientV2') as mock_client: + client = self._init() + client.revoke(messages_test.CERT, self.rsn) + mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn) + + def test_update_registration(self): + self.response.json.return_value = DIRECTORY_V1.to_json() + with mock.patch('acme.client.Client') as mock_client: + client = self._init() + 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.""" + + def setUp(self): + super(ClientTest, self).setUp() + + self.directory = DIRECTORY_V1 + + # Registration + self.regr = self.regr.update( + terms_of_service='https://www.letsencrypt-demo.org/tos') + + # Request issuance + self.certr = messages.CertificateResource( + body=messages_test.CERT, authzrs=(self.authzr,), + uri='https://www.letsencrypt-demo.org/acme/cert/1', + cert_chain_uri='https://www.letsencrypt-demo.org/ca') + + from acme.client import Client + self.client = Client( + directory=self.directory, key=KEY, alg=jose.RS256, net=self.net) + + def test_init_downloads_directory(self): + uri = 'http://www.letsencrypt-demo.org/directory' + from acme.client import Client + self.client = Client( + directory=uri, key=KEY, alg=jose.RS256, net=self.net) + self.net.get.assert_called_once_with(uri) + + @mock.patch('acme.client.ClientNetwork') + def test_init_without_net(self, mock_net): + mock_net.return_value = mock.sentinel.net + alg = jose.RS256 + from acme.client import Client + self.client = Client( + directory=self.directory, key=KEY, alg=alg) + mock_net.called_once_with(KEY, alg=alg, verify_ssl=True) + self.assertEqual(self.client.net, mock.sentinel.net) + + def test_register(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.status_code = http_client.CREATED + self.response.json.return_value = self.regr.body.to_json() + self.response.headers['Location'] = self.regr.uri + self.response.links.update({ + 'terms-of-service': {'url': self.regr.terms_of_service}, + }) + + self.assertEqual(self.regr, self.client.register(self.new_reg)) + # TODO: test POST call arguments + + def test_update_registration(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.update_registration(self.regr)) + # TODO: test POST call arguments + + # TODO: split here and separate test + self.response.json.return_value = self.regr.body.update( + contact=()).to_json() + + def test_deactivate_account(self): + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, + self.client.deactivate_registration(self.regr)) + + def test_query_registration(self): + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.query_registration(self.regr)) + + def test_agree_to_tos(self): + self.client.update_registration = mock.Mock() + self.client.agree_to_tos(self.regr) + regr = self.client.update_registration.call_args[0][0] + self.assertEqual(self.regr.terms_of_service, regr.body.agreement) + + def _prepare_response_for_request_challenges(self): + self.response.status_code = http_client.CREATED + self.response.headers['Location'] = self.authzr.uri + self.response.json.return_value = self.authz.to_json() + + def test_request_challenges(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier) + self.net.post.assert_called_once_with( + self.directory.new_authz, + messages.NewAuthorization(identifier=self.identifier), + acme_version=1) + + def test_request_challenges_deprecated_arg(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier, new_authzr_uri="hi") + self.net.post.assert_called_once_with( + self.directory.new_authz, + messages.NewAuthorization(identifier=self.identifier), + acme_version=1) + + def test_request_challenges_custom_uri(self): + self._prepare_response_for_request_challenges() + self.client.request_challenges(self.identifier) + self.net.post.assert_called_once_with( + 'https://www.letsencrypt-demo.org/acme/new-authz', mock.ANY, + acme_version=1) + + def test_request_challenges_unexpected_update(self): + self._prepare_response_for_request_challenges() + self.response.json.return_value = self.authz.update( + identifier=self.identifier.update(value='foo')).to_json() + self.assertRaises( + errors.UnexpectedUpdate, self.client.request_challenges, + self.identifier) + + def test_request_challenges_wildcard(self): + wildcard_identifier = messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='*.example.org') + self.assertRaises( + errors.WildcardUnsupportedError, self.client.request_challenges, + wildcard_identifier) + + def test_request_domain_challenges(self): + self.client.request_challenges = mock.MagicMock() + self.assertEqual( + self.client.request_challenges(self.identifier), + self.client.request_domain_challenges('example.com')) + + def test_answer_challenge(self): + self.response.links['up'] = {'url': self.challr.authzr_uri} + self.response.json.return_value = self.challr.body.to_json() + + chall_response = challenges.DNSResponse(validation=None) + + self.client.answer_challenge(self.challr.body, chall_response) + + # TODO: split here and separate test + self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge, + self.challr.body.update(uri='foo'), chall_response) + + def test_answer_challenge_missing_next(self): + self.assertRaises( + errors.ClientError, self.client.answer_challenge, + self.challr.body, challenges.DNSResponse(validation=None)) + + def test_retry_after_date(self): + self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT' + self.assertEqual( + datetime.datetime(1999, 12, 31, 23, 59, 59), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_invalid(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.response.headers['Retry-After'] = 'foooo' + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_overflow(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + dt_mock.datetime.side_effect = datetime.datetime + + self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST" + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_seconds(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.response.headers['Retry-After'] = '50' + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 50), + self.client.retry_after(response=self.response, default=10)) + + @mock.patch('acme.client.datetime') + def test_retry_after_missing(self, dt_mock): + dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27) + dt_mock.timedelta = datetime.timedelta + + self.assertEqual( + datetime.datetime(2015, 3, 27, 0, 0, 10), + self.client.retry_after(response=self.response, default=10)) + + def test_poll(self): + self.response.json.return_value = self.authzr.body.to_json() + self.assertEqual((self.authzr, self.response), + self.client.poll(self.authzr)) + + # TODO: split here and separate test + self.response.json.return_value = self.authz.update( + identifier=self.identifier.update(value='foo')).to_json() + self.assertRaises( + errors.UnexpectedUpdate, self.client.poll, self.authzr) + + def test_request_issuance(self): + self.response.content = CERT_DER + self.response.headers['Location'] = self.certr.uri + self.response.links['up'] = {'url': self.certr.cert_chain_uri} + self.assertEqual(self.certr, self.client.request_issuance( + messages_test.CSR, (self.authzr,))) + # TODO: check POST args + + def test_request_issuance_missing_up(self): + self.response.content = CERT_DER + self.response.headers['Location'] = self.certr.uri + self.assertEqual( + self.certr.update(cert_chain_uri=None), + self.client.request_issuance(messages_test.CSR, (self.authzr,))) + + def test_request_issuance_missing_location(self): + self.assertRaises( + errors.ClientError, self.client.request_issuance, + messages_test.CSR, (self.authzr,)) + + @mock.patch('acme.client.datetime') + @mock.patch('acme.client.time') + def test_poll_and_request_issuance(self, time_mock, dt_mock): + # clock.dt | pylint: disable=no-member + clock = mock.MagicMock(dt=datetime.datetime(2015, 3, 27)) + + def sleep(seconds): + """increment clock""" + clock.dt += datetime.timedelta(seconds=seconds) + time_mock.sleep.side_effect = sleep + + def now(): + """return current clock value""" + return clock.dt + dt_mock.datetime.now.side_effect = now + dt_mock.timedelta = datetime.timedelta + + def poll(authzr): # pylint: disable=missing-docstring + # record poll start time based on the current clock value + authzr.times.append(clock.dt) + + # suppose it takes 2 seconds for server to produce the + # result, increment clock + clock.dt += datetime.timedelta(seconds=2) + + if len(authzr.retries) == 1: # no more retries + done = mock.MagicMock(uri=authzr.uri, times=authzr.times) + done.body.status = authzr.retries[0] + return done, [] + + # response (2nd result tuple element) is reduced to only + # Retry-After header contents represented as integer + # seconds; authzr.retries is a list of Retry-After + # headers, head(retries) is peeled of as a current + # Retry-After header, and tail(retries) is persisted for + # later poll() calls + return (mock.MagicMock(retries=authzr.retries[1:], + uri=authzr.uri + '.', times=authzr.times), + authzr.retries[0]) + self.client.poll = mock.MagicMock(side_effect=poll) + + mintime = 7 + + def retry_after(response, default): + # pylint: disable=missing-docstring + # check that poll_and_request_issuance correctly passes mintime + self.assertEqual(default, mintime) + return clock.dt + datetime.timedelta(seconds=response) + self.client.retry_after = mock.MagicMock(side_effect=retry_after) + + def request_issuance(csr, authzrs): # pylint: disable=missing-docstring + return csr, authzrs + self.client.request_issuance = mock.MagicMock( + side_effect=request_issuance) + + csr = mock.MagicMock() + authzrs = ( + mock.MagicMock(uri='a', times=[], retries=( + 8, 20, 30, messages.STATUS_VALID)), + mock.MagicMock(uri='b', times=[], retries=( + 5, messages.STATUS_VALID)), + ) + + cert, updated_authzrs = self.client.poll_and_request_issuance( + csr, authzrs, mintime=mintime, + # make sure that max_attempts is per-authorization, rather + # than global + max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries))) + self.assertTrue(cert[0] is csr) + self.assertTrue(cert[1] is updated_authzrs) + self.assertEqual(updated_authzrs[0].uri, 'a...') + self.assertEqual(updated_authzrs[1].uri, 'b.') + self.assertEqual(updated_authzrs[0].times, [ + datetime.datetime(2015, 3, 27), + # a is scheduled for 10, but b is polling [9..11), so it + # will be picked up as soon as b is finished, without + # additional sleeping + datetime.datetime(2015, 3, 27, 0, 0, 11), + datetime.datetime(2015, 3, 27, 0, 0, 33), + datetime.datetime(2015, 3, 27, 0, 1, 5), + ]) + self.assertEqual(updated_authzrs[1].times, [ + datetime.datetime(2015, 3, 27, 0, 0, 2), + datetime.datetime(2015, 3, 27, 0, 0, 9), + ]) + self.assertEqual(clock.dt, datetime.datetime(2015, 3, 27, 0, 1, 7)) + + # CA sets invalid | TODO: move to a separate test + invalid_authzr = mock.MagicMock( + times=[], retries=[messages.STATUS_INVALID]) + self.assertRaises( + errors.PollError, self.client.poll_and_request_issuance, + csr, authzrs=(invalid_authzr,), mintime=mintime) + + # exceeded max_attempts | TODO: move to a separate test + self.assertRaises( + errors.PollError, self.client.poll_and_request_issuance, + csr, authzrs, mintime=mintime, max_attempts=2) + + def test_deactivate_authorization(self): + authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) + self.response.json.return_value = authzb.to_json() + authzr = self.client.deactivate_authorization(self.authzr) + self.assertEqual(authzb, authzr.body) + self.assertEqual(self.client.net.post.call_count, 1) + self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) + + def test_check_cert(self): + self.response.headers['Location'] = self.certr.uri + self.response.content = CERT_DER + self.assertEqual(self.certr.update(body=messages_test.CERT), + self.client.check_cert(self.certr)) + + # TODO: split here and separate test + self.response.headers['Location'] = 'foo' + self.assertRaises( + errors.UnexpectedUpdate, self.client.check_cert, self.certr) + + def test_check_cert_missing_location(self): + self.response.content = CERT_DER + self.assertRaises( + errors.ClientError, self.client.check_cert, self.certr) + + def test_refresh(self): + self.client.check_cert = mock.MagicMock() + self.assertEqual( + self.client.check_cert(self.certr), self.client.refresh(self.certr)) + + def test_fetch_chain_no_up_link(self): + self.assertEqual([], self.client.fetch_chain(self.certr.update( + cert_chain_uri=None))) + + def test_fetch_chain_single(self): + # pylint: disable=protected-access + self.client._get_cert = mock.MagicMock() + self.client._get_cert.return_value = ( + mock.MagicMock(links={}), "certificate") + self.assertEqual([self.client._get_cert(self.certr.cert_chain_uri)[1]], + self.client.fetch_chain(self.certr)) + + def test_fetch_chain_max(self): + # pylint: disable=protected-access + up_response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) + noup_response = mock.MagicMock(links={}) + self.client._get_cert = mock.MagicMock() + self.client._get_cert.side_effect = [ + (up_response, "cert")] * 9 + [(noup_response, "last_cert")] + chain = self.client.fetch_chain(self.certr, max_length=10) + self.assertEqual(chain, ["cert"] * 9 + ["last_cert"]) + + def test_fetch_chain_too_many(self): # recursive + # pylint: disable=protected-access + response = mock.MagicMock(links={'up': {'url': 'http://cert'}}) + self.client._get_cert = mock.MagicMock() + self.client._get_cert.return_value = (response, "certificate") + self.assertRaises(errors.Error, self.client.fetch_chain, self.certr) + + def test_revoke(self): + self.client.revoke(self.certr.body, self.rsn) + self.net.post.assert_called_once_with( + self.directory[messages.Revocation], mock.ANY, acme_version=1) + + 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.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 + self.assertRaises( + errors.ClientError, + self.client.revoke, + self.certr, + self.rsn) + + +class ClientV2Test(ClientTestBase): + """Tests for acme.client.ClientV2.""" + + def setUp(self): + super(ClientV2Test, self).setUp() + + self.directory = DIRECTORY_V2 + + from acme.client import ClientV2 + self.client = ClientV2(self.directory, self.net) + + self.new_reg = self.new_reg.update(terms_of_service_agreed=True) + + self.authzr_uri2 = 'https://www.letsencrypt-demo.org/acme/authz/2' + self.authz2 = self.authz.update(identifier=messages.Identifier( + typ=messages.IDENTIFIER_FQDN, value='www.example.com'), + status=messages.STATUS_PENDING) + self.authzr2 = messages.AuthorizationResource( + body=self.authz2, uri=self.authzr_uri2) + + self.order = messages.Order( + identifiers=(self.authz.identifier, self.authz2.identifier), + status=messages.STATUS_PENDING, + authorizations=(self.authzr.uri, self.authzr_uri2), + finalize='https://www.letsencrypt-demo.org/acme/acct/1/order/1/finalize') + self.orderr = messages.OrderResource( + body=self.order, + uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1', + authorizations=[self.authzr, self.authzr2], csr_pem=CSR_SAN_PEM) + + def test_new_account(self): + self.response.status_code = http_client.CREATED + self.response.json.return_value = self.regr.body.to_json() + self.response.headers['Location'] = self.regr.uri + + self.assertEqual(self.regr, self.client.new_account(self.new_reg)) + + def test_new_account_conflict(self): + self.response.status_code = http_client.OK + self.response.headers['Location'] = self.regr.uri + self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg) + + def test_new_order(self): + order_response = copy.deepcopy(self.response) + order_response.status_code = http_client.CREATED + order_response.json.return_value = self.order.to_json() + order_response.headers['Location'] = self.orderr.uri + self.net.post.return_value = order_response + + authz_response = copy.deepcopy(self.response) + authz_response.json.return_value = self.authz.to_json() + authz_response.headers['Location'] = self.authzr.uri + authz_response2 = self.response + authz_response2.json.return_value = self.authz2.to_json() + authz_response2.headers['Location'] = self.authzr2.uri + + 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): + mock_datetime.datetime.now.return_value = datetime.datetime(2018, 2, 15) + mock_datetime.timedelta = datetime.timedelta + expected_deadline = mock_datetime.datetime.now() + datetime.timedelta(seconds=90) + + self.client.poll_authorizations = mock.Mock(return_value=self.orderr) + self.client.finalize_order = mock.Mock(return_value=self.orderr) + + self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr) + self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline) + self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline) + + @mock.patch('acme.client.datetime') + def test_poll_authorizations_timeout(self, mock_datetime): + now_side_effect = [datetime.datetime(2018, 2, 15), + datetime.datetime(2018, 2, 16), + datetime.datetime(2018, 2, 17)] + mock_datetime.datetime.now.side_effect = now_side_effect + self.response.json.side_effect = [ + self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()] + + self.assertRaises( + errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1]) + + def test_poll_authorizations_failure(self): + deadline = datetime.datetime(9999, 9, 9) + challb = self.challr.body.update(status=messages.STATUS_INVALID, + error=messages.Error.with_code('unauthorized')) + authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,)) + self.response.json.return_value = authz.to_json() + + self.assertRaises( + errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline) + + def test_poll_authorizations_success(self): + deadline = datetime.datetime(9999, 9, 9) + updated_authz2 = self.authz2.update(status=messages.STATUS_VALID) + updated_authzr2 = messages.AuthorizationResource( + body=updated_authz2, uri=self.authzr_uri2) + updated_orderr = self.orderr.update(authorizations=[self.authzr, updated_authzr2]) + + self.response.json.side_effect = ( + self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json()) + self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr) + + def test_finalize_order_success(self): + updated_order = self.order.update( + certificate='https://www.letsencrypt-demo.org/acme/cert/') + updated_orderr = self.orderr.update(body=updated_order, fullchain_pem=CERT_SAN_PEM) + + self.response.json.return_value = updated_order.to_json() + self.response.text = CERT_SAN_PEM + + deadline = datetime.datetime(9999, 9, 9) + self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr) + + def test_finalize_order_error(self): + updated_order = self.order.update(error=messages.Error.with_code('unauthorized')) + self.response.json.return_value = updated_order.to_json() + + deadline = datetime.datetime(9999, 9, 9) + self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline) + + def test_finalize_order_timeout(self): + deadline = datetime.datetime.now() - datetime.timedelta(seconds=60) + self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline) + + def test_revoke(self): + self.client.revoke(messages_test.CERT, self.rsn) + self.net.post.assert_called_once_with( + self.directory["revokeCert"], mock.ANY, acme_version=2, + new_nonce_url=DIRECTORY_V2['newNonce']) + + def test_update_registration(self): + # "Instance of 'Field' has no to_json/update member" bug: + self.response.headers['Location'] = self.regr.uri + self.response.json.return_value = self.regr.body.to_json() + self.assertEqual(self.regr, self.client.update_registration(self.regr)) + self.assertNotEqual(self.client.net.account, None) + self.assertEqual(self.client.net.post.call_count, 2) + self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0]) + + 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): + """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 + def __init__(self, value): + self.value = value + + def to_partial_json(self): + return {'foo': self.value} + + @classmethod + def from_json(cls, jobj): + pass # pragma: no cover + + +class ClientNetworkTest(unittest.TestCase): + """Tests for acme.client.ClientNetwork.""" + + def setUp(self): + self.verify_ssl = mock.MagicMock() + self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped) + + from acme.client import ClientNetwork + self.net = ClientNetwork( + key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl, + user_agent='acme-python-test') + + self.response = mock.MagicMock(ok=True, status_code=http_client.OK) + self.response.headers = {} + self.response.links = {} + + def test_init(self): + self.assertTrue(self.net.verify_ssl is self.verify_ssl) + + def test_wrap_in_jws(self): + # pylint: disable=protected-access + jws_dump = self.net._wrap_in_jws( + MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", + acme_version=1) + jws = acme_jws.JWS.json_loads(jws_dump) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(jws.signature.combined.nonce, b'Tg') + + def test_wrap_in_jws_v2(self): + self.net.account = {'uri': 'acct-uri'} + # pylint: disable=protected-access + jws_dump = self.net._wrap_in_jws( + MockJSONDeSerializable('foo'), nonce=b'Tg', url="url", + acme_version=2) + jws = acme_jws.JWS.json_loads(jws_dump) + self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'}) + self.assertEqual(jws.signature.combined.nonce, b'Tg') + 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 = {} + with mock.patch('acme.client.messages.Error.from_json') as from_json: + from_json.side_effect = jose.DeserializationError + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response) + + def test_check_response_not_ok_jobj_error(self): + self.response.ok = False + self.response.json.return_value = messages.Error( + detail='foo', typ='serverInternal', title='some title').to_json() + # pylint: disable=protected-access + self.assertRaises( + messages.Error, self.net._check_response, self.response) + + def test_check_response_not_ok_no_jobj(self): + self.response.ok = False + self.response.json.side_effect = ValueError + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response) + + def test_check_response_ok_no_jobj_ct_required(self): + self.response.json.side_effect = ValueError + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access + self.assertRaises( + errors.ClientError, self.net._check_response, self.response, + content_type=self.net.JSON_CONTENT_TYPE) + + def test_check_response_ok_no_jobj_no_ct(self): + self.response.json.side_effect = ValueError + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access,no-value-for-parameter + self.assertEqual( + self.response, self.net._check_response(self.response)) + + def test_check_response_conflict(self): + self.response.ok = False + self.response.status_code = 409 + # pylint: disable=protected-access + self.assertRaises(errors.ConflictError, self.net._check_response, self.response) + + def test_check_response_jobj(self): + self.response.json.return_value = {} + for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: + self.response.headers['Content-Type'] = response_ct + # pylint: disable=protected-access,no-value-for-parameter + self.assertEqual( + self.response, self.net._check_response(self.response)) + + def test_send_request(self): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._send_request( + 'HEAD', 'http://example.com/', 'foo', bar='baz')) + self.net.session.request.assert_called_once_with( + 'HEAD', 'http://example.com/', 'foo', + headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz') + + @mock.patch('acme.client.logger') + def test_send_request_get_der(self, mock_logger): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = mock.MagicMock( + ok=True, status_code=http_client.OK, + headers={"Content-Type": "application/pkix-cert"}, + content=b"hi") + # pylint: disable=protected-access + self.net._send_request('HEAD', 'http://example.com/', 'foo', + timeout=mock.ANY, bar='baz') + mock_logger.debug.assert_called_with( + 'Received response:\nHTTP %d\n%s\n\n%s', 200, + 'Content-Type: application/pkix-cert', b'aGk=') + + def test_send_request_post(self): + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + # pylint: disable=protected-access + self.assertEqual(self.response, self.net._send_request( + 'POST', 'http://example.com/', 'foo', data='qux', bar='baz')) + self.net.session.request.assert_called_once_with( + 'POST', 'http://example.com/', 'foo', + headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz') + + def test_send_request_verify_ssl(self): + # pylint: disable=protected-access + for verify in True, False: + self.net.session = mock.MagicMock() + self.net.session.request.return_value = self.response + self.net.verify_ssl = verify + # pylint: disable=protected-access + self.assertEqual( + self.response, + self.net._send_request('GET', 'http://example.com/')) + self.net.session.request.assert_called_once_with( + 'GET', 'http://example.com/', verify=verify, + timeout=mock.ANY, headers=mock.ANY) + + def test_send_request_user_agent(self): + self.net.session = mock.MagicMock() + # pylint: disable=protected-access + self.net._send_request('GET', 'http://example.com/', + headers={'bar': 'baz'}) + self.net.session.request.assert_called_once_with( + 'GET', 'http://example.com/', verify=mock.ANY, + timeout=mock.ANY, + headers={'User-Agent': 'acme-python-test', 'bar': 'baz'}) + + self.net._send_request('GET', 'http://example.com/', + headers={'User-Agent': 'foo2'}) + self.net.session.request.assert_called_with( + 'GET', 'http://example.com/', + verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'}) + + def test_send_request_timeout(self): + self.net.session = mock.MagicMock() + # pylint: disable=protected-access + self.net._send_request('GET', 'http://example.com/', + headers={'bar': 'baz'}) + self.net.session.request.assert_called_once_with( + mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY, + timeout=45) + + def test_del(self, close_exception=None): + sess = mock.MagicMock() + + if close_exception is not None: + sess.close.side_effect = close_exception + + self.net.session = sess + del self.net + sess.close.assert_called_once_with() + + def test_del_error(self): + self.test_del(ReferenceError) + + @mock.patch('acme.client.requests') + def test_requests_error_passthrough(self, mock_requests): + mock_requests.exceptions = requests.exceptions + mock_requests.request.side_effect = requests.exceptions.RequestException + # pylint: disable=protected-access + self.assertRaises(requests.exceptions.RequestException, + self.net._send_request, 'GET', 'uri') + + def test_urllib_error(self): + # Using a connection error to test a properly formatted error message + try: + # pylint: disable=protected-access + self.net._send_request('GET', "http://localhost:19123/nonexistent.txt") + + # Value Error Generated Exceptions + except ValueError as y: + self.assertEqual("Requesting localhost/nonexistent: " + "Connection refused", str(y)) + + # Requests Library Exceptions + except requests.exceptions.ConnectionError as z: #pragma: no cover + 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.""" + + def setUp(self): + from acme.client import ClientNetwork + self.net = ClientNetwork(key=None, alg=None) + + self.response = mock.MagicMock(ok=True, status_code=http_client.OK) + self.response.headers = {} + self.response.links = {} + self.response.checked = False + self.acmev1_nonce_response = mock.MagicMock(ok=False, + status_code=http_client.METHOD_NOT_ALLOWED) + self.acmev1_nonce_response.headers = {} + self.obj = mock.MagicMock() + self.wrapped_obj = mock.MagicMock() + self.content_type = mock.sentinel.content_type + + self.all_nonces = [ + jose.b64encode(b'Nonce'), + jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')] + self.available_nonces = self.all_nonces[:] + + def send_request(*args, **kwargs): + # pylint: disable=unused-argument,missing-docstring + self.assertFalse("new_nonce_url" in kwargs) + method = args[0] + uri = args[1] + if method == 'HEAD' and uri != "new_nonce_uri": + response = self.acmev1_nonce_response + else: + response = self.response + + if self.available_nonces: + response.headers = { + self.net.REPLAY_NONCE_HEADER: + self.available_nonces.pop().decode()} + else: + response.headers = {} + return response + + # pylint: disable=protected-access + self.net._send_request = self.send_request = mock.MagicMock( + side_effect=send_request) + self.net._check_response = self.check_response + self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj) + + def check_response(self, response, content_type): + # pylint: disable=missing-docstring + self.assertEqual(self.response, response) + self.assertEqual(self.content_type, content_type) + self.assertTrue(self.response.ok) + self.response.checked = True + return self.response + + def test_head(self): + self.assertEqual(self.acmev1_nonce_response, self.net.head( + 'http://example.com/', 'foo', bar='baz')) + self.send_request.assert_called_once_with( + 'HEAD', 'http://example.com/', 'foo', bar='baz') + + def test_head_v2(self): + self.assertEqual(self.response, self.net.head( + 'new_nonce_uri', 'foo', bar='baz')) + self.send_request.assert_called_once_with( + 'HEAD', 'new_nonce_uri', 'foo', bar='baz') + + def test_get(self): + self.assertEqual(self.response, self.net.get( + 'http://example.com/', content_type=self.content_type, bar='baz')) + self.assertTrue(self.response.checked) + self.send_request.assert_called_once_with( + 'GET', 'http://example.com/', bar='baz') + + def test_post_no_content_type(self): + self.content_type = self.net.JOSE_CONTENT_TYPE + self.assertEqual(self.response, self.net.post('uri', self.obj)) + self.assertTrue(self.response.checked) + + def test_post(self): + # pylint: disable=protected-access + self.assertEqual(self.response, self.net.post( + 'uri', self.obj, content_type=self.content_type)) + self.assertTrue(self.response.checked) + self.net._wrap_in_jws.assert_called_once_with( + self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) + + self.available_nonces = [] + self.assertRaises(errors.MissingNonce, self.net.post, + 'uri', self.obj, content_type=self.content_type) + self.net._wrap_in_jws.assert_called_with( + self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1) + + def test_post_wrong_initial_nonce(self): # HEAD + self.available_nonces = [b'f', jose.b64encode(b'good')] + self.assertRaises(errors.BadNonce, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_wrong_post_response_nonce(self): + self.available_nonces = [jose.b64encode(b'good'), b'f'] + self.assertRaises(errors.BadNonce, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_failed_retry(self): + check_response = mock.MagicMock() + check_response.side_effect = messages.Error.with_code('badNonce') + + # pylint: disable=protected-access + self.net._check_response = check_response + self.assertRaises(messages.Error, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_not_retried(self): + check_response = mock.MagicMock() + check_response.side_effect = [messages.Error.with_code('malformed'), + self.response] + + # pylint: disable=protected-access + self.net._check_response = check_response + self.assertRaises(messages.Error, self.net.post, 'uri', + self.obj, content_type=self.content_type) + + def test_post_successful_retry(self): + post_once = mock.MagicMock() + post_once.side_effect = [messages.Error.with_code('badNonce'), + self.response] + + # pylint: disable=protected-access + self.assertEqual(self.response, self.net.post( + 'uri', self.obj, content_type=self.content_type)) + + def test_head_get_post_error_passthrough(self): + self.send_request.side_effect = requests.exceptions.RequestException + for method in self.net.head, self.net.get: + self.assertRaises( + requests.exceptions.RequestException, method, 'GET', 'uri') + self.assertRaises(requests.exceptions.RequestException, + self.net.post, 'uri', obj=self.obj) + + def test_post_bad_nonce_head(self): + # pylint: disable=protected-access + # regression test for https://github.com/certbot/certbot/issues/6092 + bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE) + self.net._send_request = mock.MagicMock() + self.net._send_request.return_value = bad_response + self.content_type = None + check_response = mock.MagicMock() + self.net._check_response = check_response + self.assertRaises(errors.ClientError, self.net.post, 'uri', + self.obj, content_type=self.content_type, acme_version=2, + new_nonce_url='new_nonce_uri') + self.assertEqual(check_response.call_count, 1) + + def test_new_nonce_uri_removed(self): + self.content_type = None + self.net.post('uri', self.obj, content_type=None, + acme_version=2, new_nonce_url='new_nonce_uri') + + +class ClientNetworkSourceAddressBindingTest(unittest.TestCase): + """Tests that if ClientNetwork has a source IP set manually, the underlying library has + used the provided source address.""" + + def setUp(self): + self.source_address = "8.8.8.8" + + def test_source_address_set(self): + from acme.client import ClientNetwork + net = ClientNetwork(key=None, alg=None, source_address=self.source_address) + for adapter in net.session.adapters.values(): + self.assertTrue(self.source_address in adapter.source_address) + + def test_behavior_assumption(self): + """This is a test that guardrails the HTTPAdapter behavior so that if the default for + a Session() changes, the assumptions here aren't violated silently.""" + from acme.client import ClientNetwork + # Source address not specified, so the default adapter type should be bound -- this + # test should fail if the default adapter type is changed by requests + net = ClientNetwork(key=None, alg=None) + session = requests.Session() + for scheme in session.adapters.keys(): + client_network_adapter = net.session.adapters.get(scheme) + default_adapter = session.adapters.get(scheme) + self.assertEqual(client_network_adapter.__class__, default_adapter.__class__) + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/crypto_util_test.py b/acme/tests/crypto_util_test.py new file mode 100644 index 000000000..d351c1a3d --- /dev/null +++ b/acme/tests/crypto_util_test.py @@ -0,0 +1,267 @@ +"""Tests for acme.crypto_util.""" +import itertools +import socket +import threading +import time +import unittest + +import six +from six.moves import socketserver #type: ignore # pylint: disable=import-error + +import josepy as jose +import OpenSSL + +from acme import errors +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +import test_util + +class SSLSocketAndProbeSNITest(unittest.TestCase): + """Tests for acme.crypto_util.SSLSocket/probe_sni.""" + + + def setUp(self): + self.cert = test_util.load_comparable_cert('rsa2048_cert.pem') + key = test_util.load_pyopenssl_private_key('rsa2048_key.pem') + # pylint: disable=protected-access + certs = {b'foo': (key, self.cert.wrapped)} + + from acme.crypto_util import SSLSocket + + class _TestServer(socketserver.TCPServer): + + # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init + + def server_bind(self): # pylint: disable=missing-docstring + self.socket = SSLSocket(socket.socket(), certs=certs) + socketserver.TCPServer.server_bind(self) + + self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) + self.port = self.server.socket.getsockname()[1] + self.server_thread = threading.Thread( + # pylint: disable=no-member + target=self.server.handle_request) + + def tearDown(self): + if self.server_thread.is_alive(): + # The thread may have already terminated. + self.server_thread.join() # pragma: no cover + + def _probe(self, name): + from acme.crypto_util import probe_sni + return jose.ComparableX509(probe_sni( + name, host='127.0.0.1', port=self.port)) + + def _start_server(self): + self.server_thread.start() + time.sleep(1) # TODO: avoid race conditions in other way + + def test_probe_ok(self): + self._start_server() + self.assertEqual(self.cert, self._probe(b'foo')) + + def test_probe_not_recognized_name(self): + self._start_server() + self.assertRaises(errors.Error, self._probe, b'bar') + + def test_probe_connection_error(self): + # pylint has a hard time with six + self.server.server_close() # pylint: disable=no-member + original_timeout = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(1) + self.assertRaises(errors.Error, self._probe, b'bar') + finally: + socket.setdefaulttimeout(original_timeout) + + +class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase): + """Test for acme.crypto_util._pyopenssl_cert_or_req_all_names.""" + + @classmethod + def _call(cls, loader, name): + # pylint: disable=protected-access + from acme.crypto_util import _pyopenssl_cert_or_req_all_names + return _pyopenssl_cert_or_req_all_names(loader(name)) + + def _call_cert(self, name): + return self._call(test_util.load_cert, name) + + def test_cert_one_san_no_common(self): + self.assertEqual(self._call_cert('cert-nocn.der'), + ['no-common-name.badssl.com']) + + def test_cert_no_sans_yes_common(self): + self.assertEqual(self._call_cert('cert.pem'), ['example.com']) + + def test_cert_two_sans_yes_common(self): + self.assertEqual(self._call_cert('cert-san.pem'), + ['example.com', 'www.example.com']) + + +class PyOpenSSLCertOrReqSANTest(unittest.TestCase): + """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" + + + @classmethod + def _call(cls, loader, name): + # pylint: disable=protected-access + from acme.crypto_util import _pyopenssl_cert_or_req_san + return _pyopenssl_cert_or_req_san(loader(name)) + + @classmethod + def _get_idn_names(cls): + """Returns expected names from '{cert,csr}-idnsans.pem'.""" + chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), + range(0x641, 0x6fc), + range(0x1820, 0x1877))] + return [''.join(chars[i: i + 45]) + '.invalid' + for i in range(0, len(chars), 45)] + + def _call_cert(self, name): + return self._call(test_util.load_cert, name) + + def _call_csr(self, name): + return self._call(test_util.load_csr, name) + + def test_cert_no_sans(self): + self.assertEqual(self._call_cert('cert.pem'), []) + + def test_cert_two_sans(self): + self.assertEqual(self._call_cert('cert-san.pem'), + ['example.com', 'www.example.com']) + + def test_cert_hundred_sans(self): + self.assertEqual(self._call_cert('cert-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + + def test_cert_idn_sans(self): + self.assertEqual(self._call_cert('cert-idnsans.pem'), + self._get_idn_names()) + + def test_csr_no_sans(self): + self.assertEqual(self._call_csr('csr-nosans.pem'), []) + + def test_csr_one_san(self): + self.assertEqual(self._call_csr('csr.pem'), ['example.com']) + + def test_csr_two_sans(self): + self.assertEqual(self._call_csr('csr-san.pem'), + ['example.com', 'www.example.com']) + + def test_csr_six_sans(self): + self.assertEqual(self._call_csr('csr-6sans.pem'), + ['example.com', 'example.org', 'example.net', + 'example.info', 'subdomain.example.com', + 'other.subdomain.example.com']) + + def test_csr_hundred_sans(self): + self.assertEqual(self._call_csr('csr-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + + def test_csr_idn_sans(self): + self.assertEqual(self._call_csr('csr-idnsans.pem'), + self._get_idn_names()) + + def test_critical_san(self): + self.assertEqual(self._call_cert('critical-san.pem'), + ['chicago-cubs.venafi.example', 'cubs.venafi.example']) + + + +class RandomSnTest(unittest.TestCase): + """Test for random certificate serial numbers.""" + + + def setUp(self): + self.cert_count = 5 + self.serial_num = [] # type: List[int] + self.key = OpenSSL.crypto.PKey() + self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + + def test_sn_collisions(self): + from acme.crypto_util import gen_ss_cert + + for _ in range(self.cert_count): + cert = gen_ss_cert(self.key, ['dummy'], force_san=True) + self.serial_num.append(cert.get_serial_number()) + self.assertTrue(len(set(self.serial_num)) > 1) + +class MakeCSRTest(unittest.TestCase): + """Test for standalone functions.""" + + @classmethod + def _call_with_key(cls, *args, **kwargs): + privkey = OpenSSL.crypto.PKey() + privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) + from acme.crypto_util import make_csr + return make_csr(privkey_pem, *args, **kwargs) + + def test_make_csr(self): + csr_pem = self._call_with_key(["a.example", "b.example"]) + self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem) + self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem) + csr = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_pem) + # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't + # have a get_extensions() method, so we skip this test if the method + # isn't available. + if hasattr(csr, 'get_extensions'): + self.assertEqual(len(csr.get_extensions()), 1) + self.assertEqual(csr.get_extensions()[0].get_data(), + OpenSSL.crypto.X509Extension( + b'subjectAltName', + critical=False, + value=b'DNS:a.example, DNS:b.example', + ).get_data(), + ) + + def test_make_csr_must_staple(self): + csr_pem = self._call_with_key(["a.example"], must_staple=True) + csr = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr_pem) + + # In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't + # have a get_extensions() method, so we skip this test if the method + # isn't available. + if hasattr(csr, 'get_extensions'): + 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" + must_staple_exts = [e for e in csr.get_extensions() + if e.get_data() == b"0\x03\x02\x01\x05"] + self.assertEqual(len(must_staple_exts), 1, + "Expected exactly one Must Staple extension") + + +class DumpPyopensslChainTest(unittest.TestCase): + """Test for dump_pyopenssl_chain.""" + + @classmethod + def _call(cls, loaded): + # pylint: disable=protected-access + from acme.crypto_util import dump_pyopenssl_chain + return dump_pyopenssl_chain(loaded) + + def test_dump_pyopenssl_chain(self): + names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] + loaded = [test_util.load_cert(name) for name in names] + length = sum( + len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)) + for cert in loaded) + self.assertEqual(len(self._call(loaded)), length) + + def test_dump_pyopenssl_chain_wrapped(self): + names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem'] + loaded = [test_util.load_cert(name) for name in names] + wrap_func = jose.ComparableX509 + wrapped = [wrap_func(cert) for cert in loaded] + dump_func = OpenSSL.crypto.dump_certificate + length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded) + self.assertEqual(len(self._call(wrapped)), length) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/errors_test.py b/acme/tests/errors_test.py new file mode 100644 index 000000000..1e5f3d479 --- /dev/null +++ b/acme/tests/errors_test.py @@ -0,0 +1,53 @@ +"""Tests for acme.errors.""" +import unittest + +import mock + + +class BadNonceTest(unittest.TestCase): + """Tests for acme.errors.BadNonce.""" + + def setUp(self): + from acme.errors import BadNonce + self.error = BadNonce(nonce="xxx", error="error") + + def test_str(self): + self.assertEqual("Invalid nonce ('xxx'): error", str(self.error)) + + +class MissingNonceTest(unittest.TestCase): + """Tests for acme.errors.MissingNonce.""" + + def setUp(self): + from acme.errors import MissingNonce + self.response = mock.MagicMock(headers={}) + self.response.request.method = 'FOO' + self.error = MissingNonce(self.response) + + def test_str(self): + self.assertTrue("FOO" in str(self.error)) + self.assertTrue("{}" in str(self.error)) + + +class PollErrorTest(unittest.TestCase): + """Tests for acme.errors.PollError.""" + + def setUp(self): + from acme.errors import PollError + self.timeout = PollError( + exhausted=set([mock.sentinel.AR]), + updated={}) + self.invalid = PollError(exhausted=set(), updated={ + mock.sentinel.AR: mock.sentinel.AR2}) + + def test_timeout(self): + self.assertTrue(self.timeout.timeout) + self.assertFalse(self.invalid.timeout) + + def test_repr(self): + self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: ' + 'sentinel.AR2})' % repr(set()), repr(self.invalid)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/acme/tests/fields_test.py b/acme/tests/fields_test.py new file mode 100644 index 000000000..69dde8b89 --- /dev/null +++ b/acme/tests/fields_test.py @@ -0,0 +1,72 @@ +"""Tests for acme.fields.""" +import datetime +import unittest + +import josepy as jose +import pytz + + +class FixedTest(unittest.TestCase): + """Tests for acme.fields.Fixed.""" + + def setUp(self): + from acme.fields import Fixed + self.field = Fixed('name', 'x') + + def test_decode(self): + self.assertEqual('x', self.field.decode('x')) + + def test_decode_bad(self): + self.assertRaises(jose.DeserializationError, self.field.decode, 'y') + + def test_encode(self): + self.assertEqual('x', self.field.encode('x')) + + def test_encode_override(self): + self.assertEqual('y', self.field.encode('y')) + + +class RFC3339FieldTest(unittest.TestCase): + """Tests for acme.fields.RFC3339Field.""" + + def setUp(self): + self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc) + self.encoded = '2015-03-27T00:00:00Z' + + def test_default_encoder(self): + from acme.fields import RFC3339Field + self.assertEqual( + self.encoded, RFC3339Field.default_encoder(self.decoded)) + + def test_default_encoder_naive_fails(self): + from acme.fields import RFC3339Field + self.assertRaises( + ValueError, RFC3339Field.default_encoder, datetime.datetime.now()) + + def test_default_decoder(self): + from acme.fields import RFC3339Field + self.assertEqual( + self.decoded, RFC3339Field.default_decoder(self.encoded)) + + def test_default_decoder_raises_deserialization_error(self): + from acme.fields import RFC3339Field + self.assertRaises( + jose.DeserializationError, RFC3339Field.default_decoder, '') + + +class ResourceTest(unittest.TestCase): + """Tests for acme.fields.Resource.""" + + def setUp(self): + from acme.fields import Resource + self.field = Resource('x') + + def test_decode_good(self): + self.assertEqual('x', self.field.decode('x')) + + def test_decode_wrong(self): + self.assertRaises(jose.DeserializationError, self.field.decode, 'y') + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/jose_test.py b/acme/tests/jose_test.py new file mode 100644 index 000000000..340624a4f --- /dev/null +++ b/acme/tests/jose_test.py @@ -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 diff --git a/acme/tests/jws_test.py b/acme/tests/jws_test.py new file mode 100644 index 000000000..e43ed55e6 --- /dev/null +++ b/acme/tests/jws_test.py @@ -0,0 +1,67 @@ +"""Tests for acme.jws.""" +import unittest + +import josepy as jose + +import test_util + + +KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) + + +class HeaderTest(unittest.TestCase): + """Tests for acme.jws.Header.""" + + good_nonce = jose.encode_b64jose(b'foo') + wrong_nonce = u'F' + # Following just makes sure wrong_nonce is wrong + try: + jose.b64decode(wrong_nonce) + except (ValueError, TypeError): + assert True + else: + assert False # pragma: no cover + + def test_nonce_decoder(self): + from acme.jws import Header + nonce_field = Header._fields['nonce'] + + self.assertRaises( + jose.DeserializationError, nonce_field.decode, self.wrong_nonce) + self.assertEqual(b'foo', nonce_field.decode(self.good_nonce)) + + +class JWSTest(unittest.TestCase): + """Tests for acme.jws.JWS.""" + + def setUp(self): + self.privkey = KEY + self.pubkey = self.privkey.public_key() + self.nonce = jose.b64encode(b'Nonce') + self.url = 'hi' + self.kid = 'baaaaa' + + def test_kid_serialize(self): + from acme.jws import JWS + jws = JWS.sign(payload=b'foo', key=self.privkey, + alg=jose.RS256, nonce=self.nonce, + url=self.url, kid=self.kid) + self.assertEqual(jws.signature.combined.nonce, self.nonce) + self.assertEqual(jws.signature.combined.url, self.url) + self.assertEqual(jws.signature.combined.kid, self.kid) + self.assertEqual(jws.signature.combined.jwk, None) + # TODO: check that nonce is in protected header + + self.assertEqual(jws, JWS.from_json(jws.to_json())) + + def test_jwk_serialize(self): + from acme.jws import JWS + jws = JWS.sign(payload=b'foo', key=self.privkey, + alg=jose.RS256, nonce=self.nonce, + url=self.url) + self.assertEqual(jws.signature.combined.kid, None) + self.assertEqual(jws.signature.combined.jwk, self.pubkey) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/magic_typing_test.py b/acme/tests/magic_typing_test.py new file mode 100644 index 000000000..23dfe3367 --- /dev/null +++ b/acme/tests/magic_typing_test.py @@ -0,0 +1,41 @@ +"""Tests for acme.magic_typing.""" +import sys +import unittest + +import mock + + +class MagicTypingTest(unittest.TestCase): + """Tests for acme.magic_typing.""" + def test_import_success(self): + try: + import typing as temp_typing + except ImportError: # pragma: no cover + temp_typing = None # pragma: no cover + typing_class_mock = mock.MagicMock() + text_mock = mock.MagicMock() + typing_class_mock.Text = text_mock + sys.modules['typing'] = typing_class_mock + if 'acme.magic_typing' in sys.modules: + del sys.modules['acme.magic_typing'] # pragma: no cover + from acme.magic_typing import Text # pylint: disable=no-name-in-module + self.assertEqual(Text, text_mock) + del sys.modules['acme.magic_typing'] + sys.modules['typing'] = temp_typing + + def test_import_failure(self): + try: + import typing as temp_typing + except ImportError: # pragma: no cover + temp_typing = None # pragma: no cover + sys.modules['typing'] = None + if 'acme.magic_typing' in sys.modules: + del sys.modules['acme.magic_typing'] # pragma: no cover + from acme.magic_typing import Text # pylint: disable=no-name-in-module + self.assertTrue(Text is None) + del sys.modules['acme.magic_typing'] + sys.modules['typing'] = temp_typing + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py new file mode 100644 index 000000000..269970b1c --- /dev/null +++ b/acme/tests/messages_test.py @@ -0,0 +1,476 @@ +"""Tests for acme.messages.""" +import unittest + +import josepy as jose +import mock + +from acme import challenges +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + +import test_util + +CERT = test_util.load_comparable_cert('cert.der') +CSR = test_util.load_comparable_csr('csr.der') +KEY = test_util.load_rsa_private_key('rsa512_key.pem') + + +class ErrorTest(unittest.TestCase): + """Tests for acme.messages.Error.""" + + def setUp(self): + from acme.messages import Error, ERROR_PREFIX + self.error = Error( + detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') + self.jobj = { + 'detail': 'foo', + 'title': 'some title', + 'type': ERROR_PREFIX + 'malformed', + } + self.error_custom = Error(typ='custom', detail='bar') + self.empty_error = Error() + self.jobj_custom = {'type': 'custom', 'detail': 'bar'} + + def test_default_typ(self): + from acme.messages import Error + self.assertEqual(Error().typ, 'about:blank') + + def test_from_json_empty(self): + from acme.messages import Error + self.assertEqual(Error(), Error.from_json('{}')) + + def test_from_json_hashable(self): + from acme.messages import Error + hash(Error.from_json(self.error.to_json())) + + def test_description(self): + self.assertEqual( + 'The request message was malformed', self.error.description) + self.assertTrue(self.error_custom.description is None) + + def test_code(self): + from acme.messages import Error + self.assertEqual('malformed', self.error.code) + self.assertEqual(None, self.error_custom.code) + self.assertEqual(None, Error().code) + + def test_is_acme_error(self): + from acme.messages import is_acme_error + self.assertTrue(is_acme_error(self.error)) + self.assertFalse(is_acme_error(self.error_custom)) + self.assertFalse(is_acme_error(self.empty_error)) + self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) + + def test_unicode_error(self): + from acme.messages import Error, ERROR_PREFIX, is_acme_error + arabic_error = Error( + detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', + title='title') + self.assertTrue(is_acme_error(arabic_error)) + + def test_with_code(self): + from acme.messages import Error, is_acme_error + self.assertTrue(is_acme_error(Error.with_code('badCSR'))) + self.assertRaises(ValueError, Error.with_code, 'not an ACME error code') + + def test_str(self): + self.assertEqual( + str(self.error), + u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" + .format(self.error)) + + +class ConstantTest(unittest.TestCase): + """Tests for acme.messages._Constant.""" + + def setUp(self): + from acme.messages import _Constant + + class MockConstant(_Constant): # pylint: disable=missing-docstring + POSSIBLE_NAMES = {} # type: Dict + + self.MockConstant = MockConstant # pylint: disable=invalid-name + self.const_a = MockConstant('a') + self.const_b = MockConstant('b') + + def test_to_partial_json(self): + self.assertEqual('a', self.const_a.to_partial_json()) + self.assertEqual('b', self.const_b.to_partial_json()) + + def test_from_json(self): + self.assertEqual(self.const_a, self.MockConstant.from_json('a')) + self.assertRaises( + jose.DeserializationError, self.MockConstant.from_json, 'c') + + def test_from_json_hashable(self): + hash(self.MockConstant.from_json('a')) + + def test_repr(self): + self.assertEqual('MockConstant(a)', repr(self.const_a)) + self.assertEqual('MockConstant(b)', repr(self.const_b)) + + def test_equality(self): + const_a_prime = self.MockConstant('a') + self.assertFalse(self.const_a == self.const_b) + self.assertTrue(self.const_a == const_a_prime) + + self.assertTrue(self.const_a != self.const_b) + self.assertFalse(self.const_a != const_a_prime) + + +class DirectoryTest(unittest.TestCase): + """Tests for acme.messages.Directory.""" + + def setUp(self): + from acme.messages import Directory + self.dir = Directory({ + 'new-reg': 'reg', + mock.MagicMock(resource_type='new-cert'): 'cert', + 'meta': Directory.Meta( + terms_of_service='https://example.com/acme/terms', + website='https://www.example.com/', + caa_identities=['example.com'], + ), + }) + + def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use + from acme.messages import Directory + Directory({'foo': 'bar'}) + + def test_getitem(self): + self.assertEqual('reg', self.dir['new-reg']) + from acme.messages import NewRegistration + self.assertEqual('reg', self.dir[NewRegistration]) + self.assertEqual('reg', self.dir[NewRegistration()]) + + def test_getitem_fails_with_key_error(self): + self.assertRaises(KeyError, self.dir.__getitem__, 'foo') + + def test_getattr(self): + self.assertEqual('reg', self.dir.new_reg) + + def test_getattr_fails_with_attribute_error(self): + self.assertRaises(AttributeError, self.dir.__getattr__, 'foo') + + def test_to_json(self): + self.assertEqual(self.dir.to_json(), { + 'new-reg': 'reg', + 'new-cert': 'cert', + 'meta': { + 'terms-of-service': 'https://example.com/acme/terms', + 'website': 'https://www.example.com/', + 'caaIdentities': ['example.com'], + }, + }) + + def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use + from acme.messages import Directory + Directory.from_json({'foo': 'bar'}) + + def test_iter_meta(self): + result = False + for k in self.dir.meta: + if k == 'terms_of_service': + result = self.dir.meta[k] == 'https://example.com/acme/terms' + 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.""" + + def setUp(self): + key = jose.jwk.JWKRSA(key=KEY.public_key()) + contact = ( + 'mailto:admin@foo.com', + 'tel:1234', + ) + agreement = 'https://letsencrypt.org/terms' + + from acme.messages import Registration + self.reg = Registration(key=key, contact=contact, agreement=agreement) + self.reg_none = Registration() + + self.jobj_to = { + 'contact': contact, + 'agreement': agreement, + 'key': key, + } + self.jobj_from = self.jobj_to.copy() + self.jobj_from['key'] = key.to_json() + + def test_from_data(self): + from acme.messages import Registration + reg = Registration.from_data(phone='1234', email='admin@foo.com') + self.assertEqual(reg.contact, ( + 'tel:1234', + '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) + + def test_emails(self): + self.assertEqual(('admin@foo.com',), self.reg.emails) + + def test_to_partial_json(self): + self.assertEqual(self.jobj_to, self.reg.to_partial_json()) + + def test_from_json(self): + from acme.messages import Registration + self.assertEqual(self.reg, Registration.from_json(self.jobj_from)) + + def test_from_json_hashable(self): + from acme.messages import Registration + hash(Registration.from_json(self.jobj_from)) + + +class UpdateRegistrationTest(unittest.TestCase): + """Tests for acme.messages.UpdateRegistration.""" + + def test_empty(self): + from acme.messages import UpdateRegistration + jstring = '{"resource": "reg"}' + self.assertEqual(jstring, UpdateRegistration().json_dumps()) + self.assertEqual( + UpdateRegistration(), UpdateRegistration.json_loads(jstring)) + + +class RegistrationResourceTest(unittest.TestCase): + """Tests for acme.messages.RegistrationResource.""" + + def setUp(self): + from acme.messages import RegistrationResource + self.regr = RegistrationResource( + body=mock.sentinel.body, uri=mock.sentinel.uri, + terms_of_service=mock.sentinel.terms_of_service) + + def test_to_partial_json(self): + self.assertEqual(self.regr.to_json(), { + 'body': mock.sentinel.body, + 'uri': mock.sentinel.uri, + 'terms_of_service': mock.sentinel.terms_of_service, + }) + + +class ChallengeResourceTest(unittest.TestCase): + """Tests for acme.messages.ChallengeResource.""" + + def test_uri(self): + from acme.messages import ChallengeResource + self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock( + uri='http://challb'), authzr_uri='http://authz').uri) + + +class ChallengeBodyTest(unittest.TestCase): + """Tests for acme.messages.ChallengeBody.""" + + def setUp(self): + self.chall = challenges.DNS(token=jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')) + + from acme.messages import ChallengeBody + from acme.messages import Error + from acme.messages import STATUS_INVALID + self.status = STATUS_INVALID + error = Error(typ='urn:ietf:params:acme:error:serverInternal', + detail='Unable to communicate with DNS server') + self.challb = ChallengeBody( + uri='http://challb', chall=self.chall, status=self.status, + error=error) + + self.jobj_to = { + 'uri': 'http://challb', + 'status': self.status, + 'type': 'dns', + 'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA', + 'error': error, + } + self.jobj_from = self.jobj_to.copy() + self.jobj_from['status'] = 'invalid' + self.jobj_from['error'] = { + 'type': 'urn:ietf:params:acme:error:serverInternal', + 'detail': 'Unable to communicate with DNS server', + } + + def test_encode(self): + self.assertEqual(self.challb.encode('uri'), self.challb.uri) + + def test_to_partial_json(self): + self.assertEqual(self.jobj_to, self.challb.to_partial_json()) + + def test_from_json(self): + from acme.messages import ChallengeBody + self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from)) + + def test_from_json_hashable(self): + from acme.messages import ChallengeBody + hash(ChallengeBody.from_json(self.jobj_from)) + + def test_proxy(self): + self.assertEqual(jose.b64decode( + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token) + + +class AuthorizationTest(unittest.TestCase): + """Tests for acme.messages.Authorization.""" + + def setUp(self): + from acme.messages import ChallengeBody + from acme.messages import STATUS_VALID + + self.challbs = ( + ChallengeBody( + uri='http://challb1', status=STATUS_VALID, + chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')), + ChallengeBody(uri='http://challb2', status=STATUS_VALID, + chall=challenges.DNS( + token=b'DGyRejmCefe7v4NfDGDKfA')), + ) + combinations = ((0,), (1,)) + + from acme.messages import Authorization + from acme.messages import Identifier + from acme.messages import IDENTIFIER_FQDN + identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com') + self.authz = Authorization( + identifier=identifier, combinations=combinations, + challenges=self.challbs) + + self.jobj_from = { + 'identifier': identifier.to_json(), + 'challenges': [challb.to_json() for challb in self.challbs], + 'combinations': combinations, + } + + def test_from_json(self): + from acme.messages import Authorization + Authorization.from_json(self.jobj_from) + + def test_from_json_hashable(self): + from acme.messages import Authorization + hash(Authorization.from_json(self.jobj_from)) + + def test_resolved_combinations(self): + self.assertEqual(self.authz.resolved_combinations, ( + (self.challbs[0],), + (self.challbs[1],), + )) + + +class AuthorizationResourceTest(unittest.TestCase): + """Tests for acme.messages.AuthorizationResource.""" + + def test_json_de_serializable(self): + from acme.messages import AuthorizationResource + authzr = AuthorizationResource( + uri=mock.sentinel.uri, + body=mock.sentinel.body) + self.assertTrue(isinstance(authzr, jose.JSONDeSerializable)) + + +class CertificateRequestTest(unittest.TestCase): + """Tests for acme.messages.CertificateRequest.""" + + def setUp(self): + from acme.messages import CertificateRequest + self.req = CertificateRequest(csr=CSR) + + def test_json_de_serializable(self): + self.assertTrue(isinstance(self.req, jose.JSONDeSerializable)) + from acme.messages import CertificateRequest + self.assertEqual( + self.req, CertificateRequest.from_json(self.req.to_json())) + + +class CertificateResourceTest(unittest.TestCase): + """Tests for acme.messages.CertificateResourceTest.""" + + def setUp(self): + from acme.messages import CertificateResource + self.certr = CertificateResource( + body=CERT, uri=mock.sentinel.uri, authzrs=(), + cert_chain_uri=mock.sentinel.cert_chain_uri) + + def test_json_de_serializable(self): + self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable)) + from acme.messages import CertificateResource + self.assertEqual( + self.certr, CertificateResource.from_json(self.certr.to_json())) + + +class RevocationTest(unittest.TestCase): + """Tests for acme.messages.RevocationTest.""" + + def setUp(self): + from acme.messages import Revocation + self.rev = Revocation(certificate=CERT) + + def test_from_json_hashable(self): + from acme.messages import Revocation + hash(Revocation.from_json(self.rev.to_json())) + + +class OrderResourceTest(unittest.TestCase): + """Tests for acme.messages.OrderResource.""" + + def setUp(self): + from acme.messages import OrderResource + self.regr = OrderResource( + body=mock.sentinel.body, uri=mock.sentinel.uri) + + def test_to_partial_json(self): + self.assertEqual(self.regr.to_json(), { + 'body': mock.sentinel.body, + 'uri': mock.sentinel.uri, + 'authorizations': None, + }) + +class NewOrderTest(unittest.TestCase): + """Tests for acme.messages.NewOrder.""" + + def setUp(self): + from acme.messages import NewOrder + self.reg = NewOrder( + identifiers=mock.sentinel.identifiers) + + def test_to_partial_json(self): + self.assertEqual(self.reg.to_json(), { + 'identifiers': mock.sentinel.identifiers, + }) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover diff --git a/acme/tests/standalone_test.py b/acme/tests/standalone_test.py new file mode 100644 index 000000000..0be57bad4 --- /dev/null +++ b/acme/tests/standalone_test.py @@ -0,0 +1,190 @@ +"""Tests for acme.standalone.""" +import socket +import threading +import unittest + +from six.moves import http_client # pylint: disable=import-error +from six.moves import socketserver # type: ignore # pylint: disable=import-error + +import josepy as jose +import mock +import requests + +from acme import challenges +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module + +import test_util + +class TLSServerTest(unittest.TestCase): + """Tests for acme.standalone.TLSServer.""" + + + def test_bind(self): # pylint: disable=no-self-use + from acme.standalone import TLSServer + server = TLSServer( + ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True) + server.server_close() + + def test_ipv6(self): + if socket.has_ipv6: + from acme.standalone import TLSServer + server = TLSServer( + ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True, ipv6=True) + server.server_close() + + +class HTTP01ServerTest(unittest.TestCase): + """Tests for acme.standalone.HTTP01Server.""" + + + def setUp(self): + self.account_key = jose.JWK.load( + test_util.load_vector('rsa1024_key.pem')) + self.resources = set() # type: Set + + from acme.standalone import HTTP01Server + self.server = HTTP01Server(('', 0), resources=self.resources) + + self.port = self.server.socket.getsockname()[1] + self.thread = threading.Thread(target=self.server.serve_forever) + self.thread.start() + + def tearDown(self): + self.server.shutdown() + self.thread.join() + + def test_index(self): + response = requests.get( + 'http://localhost:{0}'.format(self.port), verify=False) + self.assertEqual( + response.text, 'ACME client standalone challenge solver') + self.assertTrue(response.ok) + + def test_404(self): + response = requests.get( + 'http://localhost:{0}/foo'.format(self.port), verify=False) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + + def _test_http01(self, add): + chall = challenges.HTTP01(token=(b'x' * 16)) + response, validation = chall.response_and_validation(self.account_key) + + from acme.standalone import HTTP01RequestHandler + resource = HTTP01RequestHandler.HTTP01Resource( + chall=chall, response=response, validation=validation) + if add: + self.resources.add(resource) + return resource.response.simple_verify( + resource.chall, 'localhost', self.account_key.public_key(), + port=self.port) + + def test_http01_found(self): + self.assertTrue(self._test_http01(add=True)) + + def test_http01_not_found(self): + self.assertFalse(self._test_http01(add=False)) + + +class BaseDualNetworkedServersTest(unittest.TestCase): + """Test for acme.standalone.BaseDualNetworkedServers.""" + + + class SingleProtocolServer(socketserver.TCPServer): + """Server that only serves on a single protocol. FreeBSD has this behavior for AF_INET6.""" + def __init__(self, *args, **kwargs): + ipv6 = kwargs.pop("ipv6", False) + if ipv6: + self.address_family = socket.AF_INET6 + kwargs["bind_and_activate"] = False + else: + self.address_family = socket.AF_INET + socketserver.TCPServer.__init__(self, *args, **kwargs) + if ipv6: + # NB: On Windows, socket.IPPROTO_IPV6 constant may be missing. + # We use the corresponding value (41) instead. + level = getattr(socket, "IPPROTO_IPV6", 41) + self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1) + try: + self.server_bind() + self.server_activate() + except: + self.server_close() + raise + + @mock.patch("socket.socket.bind") + def test_fail_to_bind(self, mock_bind): + mock_bind.side_effect = socket.error + from acme.standalone import BaseDualNetworkedServers + self.assertRaises(socket.error, BaseDualNetworkedServers, + BaseDualNetworkedServersTest.SingleProtocolServer, + ('', 0), + socketserver.BaseRequestHandler) + + def test_ports_equal(self): + from acme.standalone import BaseDualNetworkedServers + servers = BaseDualNetworkedServers( + BaseDualNetworkedServersTest.SingleProtocolServer, + ('', 0), + socketserver.BaseRequestHandler) + socknames = servers.getsocknames() + prev_port = None + # assert ports are equal + for sockname in socknames: + port = sockname[1] + if prev_port: + self.assertEqual(prev_port, port) + prev_port = port + + +class HTTP01DualNetworkedServersTest(unittest.TestCase): + """Tests for acme.standalone.HTTP01DualNetworkedServers.""" + + + def setUp(self): + self.account_key = jose.JWK.load( + test_util.load_vector('rsa1024_key.pem')) + self.resources = set() # type: Set + + from acme.standalone import HTTP01DualNetworkedServers + self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources) + + self.port = self.servers.getsocknames()[0][1] + self.servers.serve_forever() + + def tearDown(self): + self.servers.shutdown_and_server_close() + + def test_index(self): + response = requests.get( + 'http://localhost:{0}'.format(self.port), verify=False) + self.assertEqual( + response.text, 'ACME client standalone challenge solver') + self.assertTrue(response.ok) + + def test_404(self): + response = requests.get( + 'http://localhost:{0}/foo'.format(self.port), verify=False) + self.assertEqual(response.status_code, http_client.NOT_FOUND) + + def _test_http01(self, add): + chall = challenges.HTTP01(token=(b'x' * 16)) + response, validation = chall.response_and_validation(self.account_key) + + from acme.standalone import HTTP01RequestHandler + resource = HTTP01RequestHandler.HTTP01Resource( + chall=chall, response=response, validation=validation) + if add: + self.resources.add(resource) + return resource.response.simple_verify( + resource.chall, 'localhost', self.account_key.public_key(), + port=self.port) + + def test_http01_found(self): + self.assertTrue(self._test_http01(add=True)) + + def test_http01_not_found(self): + self.assertFalse(self._test_http01(add=False)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/acme/tests/test_util.py b/acme/tests/test_util.py new file mode 100644 index 000000000..6737bff4e --- /dev/null +++ b/acme/tests/test_util.py @@ -0,0 +1,68 @@ +"""Test utilities. + +.. warning:: This module is not part of the public API. + +""" +import os +import pkg_resources + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +import josepy as jose +from OpenSSL import crypto + + +def load_vector(*names): + """Load contents of a test vector.""" + # luckily, resource_string opens file in binary mode + return pkg_resources.resource_string( + __name__, os.path.join('testdata', *names)) + + +def _guess_loader(filename, loader_pem, loader_der): + _, ext = os.path.splitext(filename) + if ext.lower() == '.pem': + return loader_pem + elif ext.lower() == '.der': + return loader_der + else: # pragma: no cover + raise ValueError("Loader could not be recognized based on extension") + + +def load_cert(*names): + """Load certificate.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_certificate(loader, load_vector(*names)) + + +def load_comparable_cert(*names): + """Load ComparableX509 cert.""" + return jose.ComparableX509(load_cert(*names)) + + +def load_csr(*names): + """Load certificate request.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_certificate_request(loader, load_vector(*names)) + + +def load_comparable_csr(*names): + """Load ComparableX509 certificate request.""" + return jose.ComparableX509(load_csr(*names)) + + +def load_rsa_private_key(*names): + """Load RSA private key.""" + loader = _guess_loader(names[-1], serialization.load_pem_private_key, + serialization.load_der_private_key) + return jose.ComparableRSAKey(loader( + load_vector(*names), password=None, backend=default_backend())) + + +def load_pyopenssl_private_key(*names): + """Load pyOpenSSL private key.""" + loader = _guess_loader( + names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) + return crypto.load_privatekey(loader, load_vector(*names)) diff --git a/acme/tests/testdata/README b/acme/tests/testdata/README new file mode 100644 index 000000000..dfe3f5405 --- /dev/null +++ b/acme/tests/testdata/README @@ -0,0 +1,15 @@ +In order for acme.test_util._guess_loader to work properly, make sure +to use appropriate extension for vector filenames: .pem for PEM and +.der for DER. + +The following command has been used to generate test keys: + + for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done + +and for the CSR: + + openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der + +and for the certificate: + + openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der diff --git a/acme/tests/testdata/cert-100sans.pem b/acme/tests/testdata/cert-100sans.pem new file mode 100644 index 000000000..3fdc9404f --- /dev/null +++ b/acme/tests/testdata/cert-100sans.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t +ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt +cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j +b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN +ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh +bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs +ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx +LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv +bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN +ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh +bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs +ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 +LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv +bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN +ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh +bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs +ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz +LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv +bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN +ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh +bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs +ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 +LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv +bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN +ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh +bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs +ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 +LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv +bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN +ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh +bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs +ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN +AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ +XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert-idnsans.pem b/acme/tests/testdata/cert-idnsans.pem new file mode 100644 index 000000000..932649692 --- /dev/null +++ b/acme/tests/testdata/cert-idnsans.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I +z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g +z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z +z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM +2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf +2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 +2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi +2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi +2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs +aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN +247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p +bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 +27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt +4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh +oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh +oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh +oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm +4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 +LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT +TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert-nocn.der b/acme/tests/testdata/cert-nocn.der new file mode 100644 index 000000000..59da83ccc Binary files /dev/null and b/acme/tests/testdata/cert-nocn.der differ diff --git a/acme/tests/testdata/cert-san.pem b/acme/tests/testdata/cert-san.pem new file mode 100644 index 000000000..dcb835994 --- /dev/null +++ b/acme/tests/testdata/cert-san.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF +nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 +RDjyGMKy5ZgM2w== +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/cert.der b/acme/tests/testdata/cert.der new file mode 100644 index 000000000..ab231982f Binary files /dev/null and b/acme/tests/testdata/cert.der differ diff --git a/acme/tests/testdata/cert.pem b/acme/tests/testdata/cert.pem new file mode 100644 index 000000000..96c55cbf4 --- /dev/null +++ b/acme/tests/testdata/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll +vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn +B/o= +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/critical-san.pem b/acme/tests/testdata/critical-san.pem new file mode 100644 index 000000000..7aec8ab1c --- /dev/null +++ b/acme/tests/testdata/critical-san.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIErTCCA5WgAwIBAgIKETb7VQAAAAAdGTANBgkqhkiG9w0BAQsFADCBkTELMAkG +A1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5 +MRUwEwYDVQQKEwxWZW5hZmksIEluYy4xHzAdBgNVBAsTFkRlbW9uc3RyYXRpb24g +U2VydmljZXMxIjAgBgNVBAMTGVZlbmFmaSBFeGFtcGxlIElzc3VpbmcgQ0EwHhcN +MTcwNzEwMjMxNjA1WhcNMTcwODA5MjMxNjA1WjAAMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA7CU5qRIzCs9hCRiSUvLZ8r81l4zIYbx1V1vZz6x1cS4M +0keNfFJ1wB+zuvx80KaMYkWPYlg4Rsm9Ok3ZapakXDlaWtrfg78lxtHuPw1o7AYV +EXDwwPkNugLMJfYw5hWYSr8PCLcOJoY00YQ0fJ44L+kVsUyGjN4UTRRZmOh/yNVU +0W12dTCz4X7BAW01OuY6SxxwewnW3sBEep+APfr2jd/oIx7fgZmVB8aRCDPj4AFl +XINWIwxmptOwnKPbwLN/vhCvJRUkO6rA8lpYwQkedFf6fHhqi2Sq/NCEOg4RvMCF +fKbMpncOXxz+f4/i43SVLrPz/UyhjNbKGJZ+zFrQowIDAQABo4IBlTCCAZEwPgYD +VR0RAQH/BDQwMoIbY2hpY2Fnby1jdWJzLnZlbmFmaS5leGFtcGxlghNjdWJzLnZl +bmFmaS5leGFtcGxlMB0GA1UdDgQWBBTgKZXVSFNyPHHtO/phtIALPcCF5DAfBgNV +HSMEGDAWgBT/JJ6Wei/pzf+9DRHuv6Wgdk2HsjBSBgNVHR8ESzBJMEegRaBDhkFo +dHRwOi8vcGtpLnZlbmFmaS5leGFtcGxlL2NybC9WZW5hZmklMjBFeGFtcGxlJTIw +SXNzdWluZyUyMENBLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0 +dHA6Ly9wa2kudmVuYWZpLmV4YW1wbGUvb2NzcDAOBgNVHQ8BAf8EBAMCBaAwPQYJ +KwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhIDLGYTvsSSEnZ8ehvD5UofP4hMEgobv +DIGy4mcCAWQCAQIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGwYJKwYBBAGCNxUKBA4w +DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA3YW4t1AzxEn384OqdU6L +ny8XkMhWpRM0W0Z9ZC3gRZKbVUu49nG/KB5hbVn/de33zdX9HOZJKc0vXzkGZQUs +OUCCsKX4VKzV5naGXOuGRbvV4CJh5P0kPlDzyb5t312S49nJdcdBf0Y/uL5Qzhst +bXy8qNfFNG3SIKKRAUpqE9OVIl+F+JBwexa+v/4dFtUOqMipfXxB3TaxnDqvU1dS +yO34ZTvIMGXJIZ5nn/d/LNc3N3vBg2SHkMpladqw0Hr7mL0bFOe0b+lJgkDP06Be +n08fikhz1j2AW4/ZHa9w4DUz7J21+RtHMhh+Vd1On0EAeZ563svDe7Z+yrg6zOVv +KA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/acme/tests/testdata/csr-100sans.pem b/acme/tests/testdata/csr-100sans.pem new file mode 100644 index 000000000..199814126 --- /dev/null +++ b/acme/tests/testdata/csr-100sans.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv +bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh +bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu +Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C +DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 +YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w +bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy +MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j +b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C +DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 +YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w +bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz +Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j +b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C +DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 +YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w +bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 +My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j +b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C +DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 +YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w +bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 +OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j +b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C +DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 +YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w +bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 +NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j +b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C +DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 +YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w +bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 +DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo +duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-6sans.pem b/acme/tests/testdata/csr-6sans.pem new file mode 100644 index 000000000..8f6b52bd7 --- /dev/null +++ b/acme/tests/testdata/csr-6sans.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw +EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0 +9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG +9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL +ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t +ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd +k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv +IvzVBz/nD11drfz/RNuX +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-idnsans.pem b/acme/tests/testdata/csr-idnsans.pem new file mode 100644 index 000000000..d6e91a420 --- /dev/null +++ b/acme/tests/testdata/csr-idnsans.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP +iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P +oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP +s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ +jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z +n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ +t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC +YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa +otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh +bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb +jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu +aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb +uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg +reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 +4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ +4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ +4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh +puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh +ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr +dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-nosans.pem b/acme/tests/testdata/csr-nosans.pem new file mode 100644 index 000000000..813db67b0 --- /dev/null +++ b/acme/tests/testdata/csr-nosans.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt +cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn +BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz +AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo +wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA= +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr-san.pem b/acme/tests/testdata/csr-san.pem new file mode 100644 index 000000000..a7128e35c --- /dev/null +++ b/acme/tests/testdata/csr-san.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN +AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t +MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy +tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/csr.der b/acme/tests/testdata/csr.der new file mode 100644 index 000000000..d43ac85a1 Binary files /dev/null and b/acme/tests/testdata/csr.der differ diff --git a/acme/tests/testdata/csr.pem b/acme/tests/testdata/csr.pem new file mode 100644 index 000000000..b6818e39d --- /dev/null +++ b/acme/tests/testdata/csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw +EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy +c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG +9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f +p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN +AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB +AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G +n9XBE1N9W6HCIEut2d8wACg= +-----END CERTIFICATE REQUEST----- diff --git a/acme/tests/testdata/dsa512_key.pem b/acme/tests/testdata/dsa512_key.pem new file mode 100644 index 000000000..78e164712 --- /dev/null +++ b/acme/tests/testdata/dsa512_key.pem @@ -0,0 +1,14 @@ +-----BEGIN DSA PARAMETERS----- +MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC +OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA +qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl +41pgNJpgu99YOYqPpS0g7A== +-----END DSA PARAMETERS----- +-----BEGIN DSA PRIVATE KEY----- +MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf +n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP +AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm +rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth +zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE +mNv063So6E+eYaIN +-----END DSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa1024_key.pem b/acme/tests/testdata/rsa1024_key.pem new file mode 100644 index 000000000..de5339d03 --- /dev/null +++ b/acme/tests/testdata/rsa1024_key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi +4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/ +w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB +AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB +Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc +TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB +CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X +UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak +Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt +73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa +HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU +6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ +c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc= +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa2048_cert.pem b/acme/tests/testdata/rsa2048_cert.pem new file mode 100644 index 000000000..3944cd1db --- /dev/null +++ b/acme/tests/testdata/rsa2048_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIJALVG/VbBb5U7MA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV +BAYTAkFVMQswCQYDVQQIDAJXQTEeMBwGA1UEBwwVVGhlIG1pZGRsZSBvZiBub3do +ZXJlMR8wHQYDVQQKDBZDZXJ0Ym90IFRlc3QgQ2VydHMgSW5jMCAXDTE2MTEyODIx +MzUzN1oYDzIyOTAwOTEzMjEzNTM3WjBbMQswCQYDVQQGEwJBVTELMAkGA1UECAwC +V0ExHjAcBgNVBAcMFVRoZSBtaWRkbGUgb2Ygbm93aGVyZTEfMB0GA1UECgwWQ2Vy +dGJvdCBUZXN0IENlcnRzIEluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANoVT1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svN +PSa+oPTK7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm +7Gj6m2EzpSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFn +xvvOjBYop7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTD +g7P4UAuFkejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1 +RAeEeRTkh0WjUfltoem/5f8bIdsCAwEAAaNTMFEwHQYDVR0OBBYEFHy+bEYqwvFU +uQLTkIfQ36AM2DQiMB8GA1UdIwQYMBaAFHy+bEYqwvFUuQLTkIfQ36AM2DQiMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH3ANVzB59FcunZV/F8T +RiCD6/gV7Jc3CswU8N8tVjzMCg2jOdTFF9iYZzNNKQvG13o/n5LkQr/lkKRQkWTx +nkE5WZbR7vNqlzXgPa9NBiK5rPjgSt8azPW+Skct3Bj4B3PhTMSpoQ7PsUJ8UeV8 +kTNR5xrRLt6/mLfRJTXWXBM43GEZi8lL5q0nqz0tPGISADshHMo6ZlUu5Hvfp5v+ +aonpO4sVS9hGOVxjGNMXYApEUy4jid9jjAfEk6jeELJMbXGLy/botFgIJK/QPe6P +AfbdFgtg/qzG7Uy0A1iXXfWdgwmVrhCoGYYWCn4yWCAm894QKtdim87CHSDP0WUf +Esg= +-----END CERTIFICATE----- diff --git a/acme/tests/testdata/rsa2048_key.pem b/acme/tests/testdata/rsa2048_key.pem new file mode 100644 index 000000000..5847aed55 --- /dev/null +++ b/acme/tests/testdata/rsa2048_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaFU9aXb0VFATq +r5uzNnmyxB1e4YoFB+6gBlGRJHz+luGK4mFWPiB6x9bLzT0mvqD0yu8HgzZk9dXo +VpxshAiDPc9q9h4vclVdMnMYeIVkODbfAQCmcAsGl6clpuxo+pthM6UjeQ3eu4OW +AEAa1FIdfkUWxYvXNC7P+mThMTL34PJKreHNrFwoB9SBZ8b7zowWKKe8YdMdy9QJ +MmsdLcPKoEKf8+ZpLqZlQzSWDxZm8xTzDcg0etXnekFEw4Oz+FALhZHoyy8xb8+W +Gju7wvobmk7j4SJwIac6qa+MlPeyiI6LNv/NyLbvwr/FNUQHhHkU5IdFo1H5baHp +v+X/GyHbAgMBAAECggEAURFe4C68XRuGAF+rN2Fmt+djK6QXlGswb1gp9hRkSpd3 +3BLvMAoENOAYnsX6l26Bkr3lQRurmrgv/iBEIaqrJ25QrmgzLFwKE4zvcAdNPsYO +z7MltLktwBOb1MlKVHPkUqvKFXeoikWWUqphKhgHNmN7900UALmrNTDVU0jgs3fB +o35o8d5SjoC52K4wCTjhPyjt4cdbfbziRs2qFhfGdawidRO1xLlDM4tTTW+5yWGK +lt0SwyvDVC6XWeNoT3nXyKjXWP7hcYqm0iS7ffL9YzEC2RXNGQUqeR50i9Y0rDdH +Vqcr+Rqio2ww68zbDWBpC/jU133BSoHuSE1wstxIkQKBgQDxlEr42WJfgdajbZ1a +hUIeLEgvhezLmD1hcYwZuQCLgizmY2ovvmeAH74koCDEsUUQunPYHsRla7wT3q1/ +IkR1KgJPwESpkQaKuAqxeEAkv7Gn8Lzcn22jCoRCfGA68wKJz2ECFZDc0RDvRrT/ +9GhiiGUoO47jv9ezrSDO1eu5/QKBgQDnGfYVMNLiA0fy4AxSyY2vdo7vruOFGpRP +n94gwxZ+0dQDWHzn3J4rHivxtcyd/MOZv4I8PtYK7tmmjYv1ngQ6sGl4p8bpUtwj +9++/B1CyB1W5/VPqMkd+Sj0dbejycME55+F6/r4basPXxBFFCfknjAlVvyvbBhUy +ftNpHxZGtwKBgChJM4t2LPqCW3nbgL8ks9b2SX9rVQbKt4m1dsifWmDpb3VoJMAb +f4UVRg8ziONkMIFOppzm3JeRNMcXflVSMJpdTA9in9CrN60QbfAUfpXiRc0cz1H3 +YEAtM8smlKGf/s9efu3rDMJWNv3AC9UXPAUae8wOypBeYKk8+NilQe89AoGAXEA3 +xFO+CqyGnwQixzVf0qf//NuSRQLMK1DEyc02gJ9gA4niKmgd11Zu8kjBClvo9MnG +wifPJ4Qa6+pa8UwHoinjoF9Q/rit2cnSMS5JXxegd+MRCU7SzS3zYXkLYSPzbhsL +Hh7sYmNnFA1XW3jUtZ2n6EusxPyTn5mS6MaZDNcCgYBelFKFjNIQ50NbOnm8DewK +jUd5OFKowKodlQVcHiF9CVbjvpN8ZPRcBSmqDU4kpT/rmcybVoL6Zfa/zWkw8+Oh +QxKb3BYf5vRUMd/RA+/t5KG4ZOIIYB3qoltAYlhVaINukL6cGVG1qvV/ntcsfsn6 +kmf1UgGFcKrJuXgwEtTVxw== +-----END PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa256_key.pem b/acme/tests/testdata/rsa256_key.pem new file mode 100644 index 000000000..659274d1d --- /dev/null +++ b/acme/tests/testdata/rsa256_key.pem @@ -0,0 +1,6 @@ +-----BEGIN RSA PRIVATE KEY----- +MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh +AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N +E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 +rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/testdata/rsa512_key.pem b/acme/tests/testdata/rsa512_key.pem new file mode 100644 index 000000000..610c8d315 --- /dev/null +++ b/acme/tests/testdata/rsa512_key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 +vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn +elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc +mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp +Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj +8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq +6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ +-----END RSA PRIVATE KEY----- diff --git a/acme/tests/util_test.py b/acme/tests/util_test.py new file mode 100644 index 000000000..00aa8b02d --- /dev/null +++ b/acme/tests/util_test.py @@ -0,0 +1,16 @@ +"""Tests for acme.util.""" +import unittest + + +class MapKeysTest(unittest.TestCase): + """Tests for acme.util.map_keys.""" + + def test_it(self): + from acme.util import map_keys + self.assertEqual({'a': 'b', 'c': 'd'}, + map_keys({'a': 'b', 'c': 'd'}, lambda key: key)) + self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1)) + + +if __name__ == '__main__': + unittest.main() # pragma: no cover -- cgit v1.2.3 From f36b93267c675f39e50e6af0c699e61426a64461 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 26 Nov 2019 17:45:07 -0800 Subject: Exclude pycache and .py[cod] from certbot package (#7608) --- certbot/MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/certbot/MANIFEST.in b/certbot/MANIFEST.in index 3b743ee1b..ef91a3e7c 100644 --- a/certbot/MANIFEST.in +++ b/certbot/MANIFEST.in @@ -6,3 +6,5 @@ recursive-include examples * recursive-include certbot/tests/testdata * recursive-include tests *.py include certbot/ssl-dhparams.pem +global-exclude __pycache__ +global-exclude *.py[cod] -- cgit v1.2.3 From a8e711d281728c99e8a815db46a2e9aed86bb441 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Tue, 26 Nov 2019 17:45:18 -0800 Subject: Refactor tests out of packaged module for nginx plugin (#7606) * Refactor tests out of packaged module for nginx plugin * Exclude pycache and .py[cod] --- certbot-nginx/MANIFEST.in | 4 +- certbot-nginx/certbot_nginx/tests/__init__.py | 1 - .../certbot_nginx/tests/configurator_test.py | 1099 ------------------- .../certbot_nginx/tests/display_ops_test.py | 45 - certbot-nginx/certbot_nginx/tests/http_01_test.py | 146 --- .../certbot_nginx/tests/nginxparser_test.py | 440 -------- certbot-nginx/certbot_nginx/tests/obj_test.py | 228 ---- .../certbot_nginx/tests/parser_obj_test.py | 253 ----- certbot-nginx/certbot_nginx/tests/parser_test.py | 489 --------- .../tests/testdata/etc_nginx/broken.conf | 12 - .../tests/testdata/etc_nginx/comment_in_file.conf | 1 - .../tests/testdata/etc_nginx/edge_cases.conf | 27 - .../tests/testdata/etc_nginx/foo.conf | 25 - .../tests/testdata/etc_nginx/mime.types | 0 .../testdata/etc_nginx/minimalistic_comments.conf | 12 - .../tests/testdata/etc_nginx/multiline_quotes.conf | 16 - .../tests/testdata/etc_nginx/nginx.conf | 122 --- .../tests/testdata/etc_nginx/server.conf | 1 - .../tests/testdata/etc_nginx/sites-enabled/default | 10 - .../testdata/etc_nginx/sites-enabled/example.com | 6 - .../testdata/etc_nginx/sites-enabled/globalssl.com | 9 - .../testdata/etc_nginx/sites-enabled/headers.com | 4 - .../testdata/etc_nginx/sites-enabled/ipv6.com | 5 - .../testdata/etc_nginx/sites-enabled/ipv6ssl.com | 7 - .../testdata/etc_nginx/sites-enabled/migration.com | 19 - .../testdata/etc_nginx/sites-enabled/sslon.com | 6 - .../default_vhost/nginx/fastcgi_params | 25 - .../ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf | 108 -- .../ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win | 102 -- .../default_vhost/nginx/mime.types | 79 -- .../default_vhost/nginx/naxsi-ui.conf.1.4.1 | 16 - .../default_vhost/nginx/naxsi.rules | 13 - .../default_vhost/nginx/naxsi_core.rules | 75 -- .../default_vhost/nginx/nginx.conf | 95 -- .../default_vhost/nginx/proxy_params | 4 - .../default_vhost/nginx/scgi_params | 14 - .../default_vhost/nginx/sites-available/default | 112 -- .../default_vhost/nginx/sites-enabled/default | 1 - .../default_vhost/nginx/uwsgi_params | 15 - .../ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf | 125 --- certbot-nginx/certbot_nginx/tests/util.py | 131 --- certbot-nginx/tests/configurator_test.py | 1100 ++++++++++++++++++++ certbot-nginx/tests/display_ops_test.py | 46 + certbot-nginx/tests/http_01_test.py | 147 +++ certbot-nginx/tests/nginxparser_test.py | 441 ++++++++ certbot-nginx/tests/obj_test.py | 228 ++++ certbot-nginx/tests/parser_obj_test.py | 253 +++++ certbot-nginx/tests/parser_test.py | 490 +++++++++ certbot-nginx/tests/test_util.py | 131 +++ certbot-nginx/tests/testdata/etc_nginx/broken.conf | 12 + .../tests/testdata/etc_nginx/comment_in_file.conf | 1 + .../tests/testdata/etc_nginx/edge_cases.conf | 27 + certbot-nginx/tests/testdata/etc_nginx/foo.conf | 25 + certbot-nginx/tests/testdata/etc_nginx/mime.types | 0 .../testdata/etc_nginx/minimalistic_comments.conf | 12 + .../tests/testdata/etc_nginx/multiline_quotes.conf | 16 + certbot-nginx/tests/testdata/etc_nginx/nginx.conf | 122 +++ certbot-nginx/tests/testdata/etc_nginx/server.conf | 1 + .../tests/testdata/etc_nginx/sites-enabled/default | 10 + .../testdata/etc_nginx/sites-enabled/example.com | 6 + .../testdata/etc_nginx/sites-enabled/globalssl.com | 9 + .../testdata/etc_nginx/sites-enabled/headers.com | 4 + .../testdata/etc_nginx/sites-enabled/ipv6.com | 5 + .../testdata/etc_nginx/sites-enabled/ipv6ssl.com | 7 + .../testdata/etc_nginx/sites-enabled/migration.com | 19 + .../testdata/etc_nginx/sites-enabled/sslon.com | 6 + .../default_vhost/nginx/fastcgi_params | 25 + .../ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf | 108 ++ .../ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win | 102 ++ .../default_vhost/nginx/mime.types | 79 ++ .../default_vhost/nginx/naxsi-ui.conf.1.4.1 | 16 + .../default_vhost/nginx/naxsi.rules | 13 + .../default_vhost/nginx/naxsi_core.rules | 75 ++ .../default_vhost/nginx/nginx.conf | 95 ++ .../default_vhost/nginx/proxy_params | 4 + .../default_vhost/nginx/scgi_params | 14 + .../default_vhost/nginx/sites-available/default | 112 ++ .../default_vhost/nginx/sites-enabled/default | 1 + .../default_vhost/nginx/uwsgi_params | 15 + .../ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf | 125 +++ 80 files changed, 3905 insertions(+), 3899 deletions(-) delete mode 100644 certbot-nginx/certbot_nginx/tests/__init__.py delete mode 100644 certbot-nginx/certbot_nginx/tests/configurator_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/display_ops_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/http_01_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/nginxparser_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/obj_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/parser_obj_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/parser_test.py delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/mime.types delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default delete mode 120000 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params delete mode 100644 certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf delete mode 100644 certbot-nginx/certbot_nginx/tests/util.py create mode 100644 certbot-nginx/tests/configurator_test.py create mode 100644 certbot-nginx/tests/display_ops_test.py create mode 100644 certbot-nginx/tests/http_01_test.py create mode 100644 certbot-nginx/tests/nginxparser_test.py create mode 100644 certbot-nginx/tests/obj_test.py create mode 100644 certbot-nginx/tests/parser_obj_test.py create mode 100644 certbot-nginx/tests/parser_test.py create mode 100644 certbot-nginx/tests/test_util.py create mode 100644 certbot-nginx/tests/testdata/etc_nginx/broken.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/foo.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/mime.types create mode 100644 certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/nginx.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/server.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default create mode 120000 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params create mode 100644 certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index 39e956ced..65b27877e 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -1,4 +1,6 @@ include LICENSE.txt include README.rst -recursive-include certbot_nginx/tests/testdata * +recursive-include tests * recursive-include certbot_nginx/_internal/tls_configs *.conf +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-nginx/certbot_nginx/tests/__init__.py b/certbot-nginx/certbot_nginx/tests/__init__.py deleted file mode 100644 index 32ca193d9..000000000 --- a/certbot-nginx/certbot_nginx/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Nginx Tests""" diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py deleted file mode 100644 index a7327166f..000000000 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ /dev/null @@ -1,1099 +0,0 @@ -"""Test for certbot_nginx._internal.configurator.""" -import unittest - -import OpenSSL -import mock -from acme import challenges -from acme import messages - -from certbot import achallenges -from certbot import crypto_util -from certbot import errors -from certbot.compat import os -from certbot.tests import util as certbot_test_util - -from certbot_nginx._internal import obj -from certbot_nginx._internal import parser -from certbot_nginx._internal.configurator import _redirect_block_for_domain -from certbot_nginx._internal.nginxparser import UnspacedList -from certbot_nginx.tests import util - - -class NginxConfiguratorTest(util.NginxTest): - """Test a semi complex vhost configuration.""" - - - def setUp(self): - super(NginxConfiguratorTest, self).setUp() - - self.config = self.get_nginx_configurator( - self.config_path, self.config_dir, self.work_dir, self.logs_dir) - - @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") - def test_prepare_no_install(self, mock_exe_exists): - mock_exe_exists.return_value = False - self.assertRaises( - errors.NoInstallationError, self.config.prepare) - - def test_prepare(self): - self.assertEqual((1, 6, 2), self.config.version) - self.assertEqual(11, len(self.config.parser.parsed)) - - @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_prepare_initializes_version(self, mock_popen, mock_exe_exists): - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/1.6.2", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "TLS SNI support enabled", - "configure arguments: --prefix=/usr/local/Cellar/" - "nginx/1.6.2 --with-http_ssl_module"])) - - mock_exe_exists.return_value = True - - self.config.version = None - self.config.config_test = mock.Mock() - self.config.prepare() - self.assertEqual((1, 6, 2), self.config.version) - - def test_prepare_locked(self): - server_root = self.config.conf("server-root") - - from certbot import util as certbot_util - certbot_util._LOCKS[server_root].release() # pylint: disable=protected-access - - self.config.config_test = mock.Mock() - certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) - - @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") - def _test_prepare_locked(self, unused_exe_exists): - try: - self.config.prepare() - except errors.PluginError as err: - err_msg = str(err) - self.assertTrue("lock" in err_msg) - self.assertTrue(self.config.conf("server-root") in err_msg) - else: # pragma: no cover - self.fail("Exception wasn't raised!") - - @mock.patch("certbot_nginx._internal.configurator.socket.gethostbyaddr") - def test_get_all_names(self, mock_gethostbyaddr): - mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) - names = self.config.get_all_names() - self.assertEqual(names, { - "155.225.50.69.nephoscale.net", "www.example.org", "another.alias", - "migration.com", "summer.com", "geese.com", "sslon.com", - "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com", - "headers.com"}) - - def test_supported_enhancements(self): - self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], - self.config.supported_enhancements()) - - def test_enhance(self): - self.assertRaises( - errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') - - def test_get_chall_pref(self): - self.assertEqual([challenges.HTTP01], - self.config.get_chall_pref('myhost')) - - def test_save(self): - filep = self.config.parser.abs_path('sites-enabled/example.com') - mock_vhost = obj.VirtualHost(filep, - None, None, None, - set(['.example.com', 'example.*']), - None, [0]) - self.config.parser.add_server_directives( - mock_vhost, - [['listen', ' ', '5001', ' ', 'ssl']]) - self.config.save() - - # pylint: disable=protected-access - parsed = self.config.parser._parse_files(filep, override=True) - self.assertEqual([[['server'], - [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - ['listen', '5001', 'ssl'], - ['#', parser.COMMENT]]]], - parsed[0]) - - def test_choose_vhosts_alias(self): - self._test_choose_vhosts_common('alias', 'server_conf') - - def test_choose_vhosts_example_com(self): - self._test_choose_vhosts_common('example.com', 'example_conf') - - def test_choose_vhosts_localhost(self): - self._test_choose_vhosts_common('localhost', 'localhost_conf') - - def test_choose_vhosts_example_com_uk_test(self): - self._test_choose_vhosts_common('example.com.uk.test', 'example_conf') - - def test_choose_vhosts_www_example_com(self): - self._test_choose_vhosts_common('www.example.com', 'example_conf') - - def test_choose_vhosts_test_www_example_com(self): - self._test_choose_vhosts_common('test.www.example.com', 'foo_conf') - - def test_choose_vhosts_abc_www_foo_com(self): - self._test_choose_vhosts_common('abc.www.foo.com', 'foo_conf') - - def test_choose_vhosts_www_bar_co_uk(self): - self._test_choose_vhosts_common('www.bar.co.uk', 'localhost_conf') - - def test_choose_vhosts_ipv6_com(self): - self._test_choose_vhosts_common('ipv6.com', 'ipv6_conf') - - def _test_choose_vhosts_common(self, name, conf): - conf_names = {'localhost_conf': set(['localhost', r'~^(www\.)?(example|bar)\.']), - 'server_conf': set(['somename', 'another.alias', 'alias']), - 'example_conf': set(['.example.com', 'example.*']), - 'foo_conf': set(['*.www.foo.com', '*.www.example.com']), - 'ipv6_conf': set(['ipv6.com'])} - - conf_path = {'localhost': "etc_nginx/nginx.conf", - 'alias': "etc_nginx/nginx.conf", - 'example.com': "etc_nginx/sites-enabled/example.com", - 'example.com.uk.test': "etc_nginx/sites-enabled/example.com", - 'www.example.com': "etc_nginx/sites-enabled/example.com", - 'test.www.example.com': "etc_nginx/foo.conf", - 'abc.www.foo.com': "etc_nginx/foo.conf", - 'www.bar.co.uk': "etc_nginx/nginx.conf", - 'ipv6.com': "etc_nginx/sites-enabled/ipv6.com"} - conf_path = {key: os.path.normpath(value) for key, value in conf_path.items()} - - vhost = self.config.choose_vhosts(name)[0] - path = os.path.relpath(vhost.filep, self.temp_dir) - - self.assertEqual(conf_names[conf], vhost.names) - self.assertEqual(conf_path[name], path) - # IPv6 specific checks - if name == "ipv6.com": - self.assertTrue(vhost.ipv6_enabled()) - # Make sure that we have SSL enabled also for IPv6 addr - self.assertTrue( - any([True for x in vhost.addrs if x.ssl and x.ipv6])) - - def test_choose_vhosts_bad(self): - bad_results = ['www.foo.com', 'example', 't.www.bar.co', - '69.255.225.155'] - - for name in bad_results: - self.assertRaises(errors.MisconfigurationError, - self.config.choose_vhosts, name) - - def test_ipv6only(self): - # ipv6_info: (ipv6_active, ipv6only_present) - self.assertEqual((True, False), self.config.ipv6_info("80")) - # Port 443 has ipv6only=on because of ipv6ssl.com vhost - self.assertEqual((True, True), self.config.ipv6_info("443")) - - def test_ipv6only_detection(self): - self.config.version = (1, 3, 1) - - self.config.deploy_cert( - "ipv6.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - - for addr in self.config.choose_vhosts("ipv6.com")[0].addrs: - self.assertFalse(addr.ipv6only) - - def test_more_info(self): - self.assertTrue('nginx.conf' in self.config.more_info()) - - def test_deploy_cert_requires_fullchain_path(self): - self.config.version = (1, 3, 1) - self.assertRaises(errors.PluginError, self.config.deploy_cert, - "www.example.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - None) - - @mock.patch('certbot_nginx._internal.parser.NginxParser.update_or_add_server_directives') - def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): - mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() - self.assertRaises( - errors.PluginError, - self.config.deploy_cert, - "migration.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - - def test_deploy_cert(self): - server_conf = self.config.parser.abs_path('server.conf') - nginx_conf = self.config.parser.abs_path('nginx.conf') - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.version = (1, 3, 1) - - # Get the default SSL vhost - self.config.deploy_cert( - "www.example.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.deploy_cert( - "another.alias", - "/etc/nginx/cert.pem", - "/etc/nginx/key.pem", - "/etc/nginx/chain.pem", - "/etc/nginx/fullchain.pem") - self.config.save() - - self.config.parser.load() - - parsed_example_conf = util.filter_comments(self.config.parser.parsed[example_conf]) - parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf]) - parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf]) - - self.assertEqual([[['server'], - [ - ['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - - ['listen', '5001', 'ssl'], - ['ssl_certificate', 'example/fullchain.pem'], - ['ssl_certificate_key', 'example/key.pem'], - ['include', self.config.mod_ssl_conf], - ['ssl_dhparam', self.config.ssl_dhparams], - ]]], - parsed_example_conf) - self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], - parsed_server_conf) - self.assertTrue(util.contains_at_depth( - parsed_nginx_conf, - [['server'], - [ - ['listen', '8000'], - ['listen', 'somename:8080'], - ['include', 'server.conf'], - [['location', '/'], - [['root', 'html'], - ['index', 'index.html', 'index.htm']]], - ['listen', '5001', 'ssl'], - ['ssl_certificate', '/etc/nginx/fullchain.pem'], - ['ssl_certificate_key', '/etc/nginx/key.pem'], - ['include', self.config.mod_ssl_conf], - ['ssl_dhparam', self.config.ssl_dhparams], - ]], - 2)) - - def test_deploy_cert_add_explicit_listen(self): - migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') - self.config.deploy_cert( - "summer.com", - "summer/cert.pem", - "summer/key.pem", - "summer/chain.pem", - "summer/fullchain.pem") - self.config.save() - self.config.parser.load() - parsed_migration_conf = util.filter_comments(self.config.parser.parsed[migration_conf]) - self.assertEqual([['server'], - [ - ['server_name', 'migration.com'], - ['server_name', 'summer.com'], - - ['listen', '80'], - ['listen', '5001', 'ssl'], - ['ssl_certificate', 'summer/fullchain.pem'], - ['ssl_certificate_key', 'summer/key.pem'], - ['include', self.config.mod_ssl_conf], - ['ssl_dhparam', self.config.ssl_dhparams], - ]], - parsed_migration_conf[0]) - - @mock.patch("certbot_nginx._internal.configurator.http_01.NginxHttp01.perform") - @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.restart") - @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.revert_challenge_config") - def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): - # Only tests functionality specific to configurator.perform - # Note: As more challenges are offered this will have to be expanded - achall = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=messages.ChallengeBody( - chall=challenges.HTTP01(token=b"m8TdO1qik4JVFtgPPurJmg"), - uri="https://ca.org/chall1_uri", - status=messages.Status("pending"), - ), domain="example.com", account_key=self.rsa512jwk) - - expected = [ - achall.response(self.rsa512jwk), - ] - - mock_http_perform.return_value = expected[:] - responses = self.config.perform([achall]) - - self.assertEqual(mock_http_perform.call_count, 1) - self.assertEqual(responses, expected) - - self.config.cleanup([achall]) - self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access - self.assertEqual(mock_revert.call_count, 1) - self.assertEqual(mock_restart.call_count, 2) - - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_get_version(self, mock_popen): - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/1.4.2", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "TLS SNI support enabled", - "configure arguments: --prefix=/usr/local/Cellar/" - "nginx/1.6.2 --with-http_ssl_module"])) - self.assertEqual(self.config.get_version(), (1, 4, 2)) - - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/0.9", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "TLS SNI support enabled", - "configure arguments: --with-http_ssl_module"])) - self.assertEqual(self.config.get_version(), (0, 9)) - - mock_popen().communicate.return_value = ( - "", "\n".join(["blah 0.0.1", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "TLS SNI support enabled", - "configure arguments: --with-http_ssl_module"])) - self.assertRaises(errors.PluginError, self.config.get_version) - - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/1.4.2", - "TLS SNI support enabled"])) - self.assertRaises(errors.PluginError, self.config.get_version) - - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/1.4.2", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "configure arguments: --with-http_ssl_module"])) - self.assertRaises(errors.PluginError, self.config.get_version) - - mock_popen().communicate.return_value = ( - "", "\n".join(["nginx version: nginx/0.8.1", - "built by clang 6.0 (clang-600.0.56)" - " (based on LLVM 3.5svn)", - "TLS SNI support enabled", - "configure arguments: --with-http_ssl_module"])) - self.assertRaises(errors.NotSupportedError, self.config.get_version) - - mock_popen.side_effect = OSError("Can't find program") - self.assertRaises(errors.PluginError, self.config.get_version) - - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_get_openssl_version(self, mock_popen): - # pylint: disable=protected-access - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - built with OpenSSL 1.0.2g 1 Mar 2016 - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "1.0.2g") - - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - built with OpenSSL 1.0.2-beta1 1 Mar 2016 - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") - - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - built with OpenSSL 1.0.2 1 Mar 2016 - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "1.0.2") - - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - built with OpenSSL 1.0.2g 1 Mar 2016 (running with OpenSSL 1.0.2a 1 Mar 2016) - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "1.0.2a") - - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - built with LibreSSL 2.2.2 - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "") - - mock_popen().communicate.return_value = ( - "", """ - nginx version: nginx/1.15.5 - built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) - TLS SNI support enabled - configure arguments: - """) - self.assertEqual(self.config._get_openssl_version(), "") - - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_nginx_restart(self, mock_popen): - mocked = mock_popen() - mocked.communicate.return_value = ('', '') - mocked.returncode = 0 - self.config.restart() - - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_nginx_restart_fail(self, mock_popen): - mocked = mock_popen() - mocked.communicate.return_value = ('', '') - mocked.returncode = 1 - self.assertRaises(errors.MisconfigurationError, self.config.restart) - - @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") - def test_no_nginx_start(self, mock_popen): - mock_popen.side_effect = OSError("Can't find program") - self.assertRaises(errors.MisconfigurationError, self.config.restart) - - @mock.patch("certbot.util.run_script") - def test_config_test_bad_process(self, mock_run_script): - mock_run_script.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, self.config.config_test) - - @mock.patch("certbot.util.run_script") - def test_config_test(self, _): - self.config.config_test() - - @mock.patch("certbot.reverter.Reverter.recovery_routine") - def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine): - mock_recovery_routine.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.recovery_routine) - - @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") - def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): - mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) - - @mock.patch("certbot.reverter.Reverter.revert_temporary_config") - def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config): - mock_revert_temporary_config.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.revert_challenge_config) - - @mock.patch("certbot.reverter.Reverter.add_to_checkpoint") - def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint): - mock_add_to_checkpoint.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.save) - - def test_get_snakeoil_paths(self): - # pylint: disable=protected-access - cert, key = self.config._get_snakeoil_paths() - self.assertTrue(os.path.exists(cert)) - self.assertTrue(os.path.exists(key)) - with open(cert) as cert_file: - OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert_file.read()) - with open(key) as key_file: - OpenSSL.crypto.load_privatekey( - OpenSSL.crypto.FILETYPE_PEM, key_file.read()) - - def test_redirect_enhance(self): - # Test that we successfully add a redirect when there is - # a listen directive - expected = UnspacedList(_redirect_block_for_domain("www.example.com"))[0] - - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.enhance("www.example.com", "redirect") - - generated_conf = self.config.parser.parsed[example_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) - - # Test that we successfully add a redirect when there is - # no listen directive - migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') - self.config.enhance("migration.com", "redirect") - - expected = UnspacedList(_redirect_block_for_domain("migration.com"))[0] - - generated_conf = self.config.parser.parsed[migration_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) - - def test_split_for_redirect(self): - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.deploy_cert( - "example.org", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.enhance("www.example.com", "redirect") - generated_conf = self.config.parser.parsed[example_conf] - self.assertEqual( - [[['server'], [ - ['server_name', '.example.com'], - ['server_name', 'example.*'], [], - ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], - ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], - ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], - ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], - ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], - [], []]], - [['server'], [ - [['if', '($host', '=', 'www.example.com)'], [ - ['return', '301', 'https://$host$request_uri']]], - ['#', ' managed by Certbot'], [], - ['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - ['return', '404'], ['#', ' managed by Certbot'], [], [], []]]], - generated_conf) - - def test_split_for_headers(self): - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.deploy_cert( - "example.org", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") - generated_conf = self.config.parser.parsed[example_conf] - self.assertEqual( - [[['server'], [ - ['server_name', '.example.com'], - ['server_name', 'example.*'], [], - ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], - ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], - ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], - ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], - ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], - [], [], - ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'], - ['#', ' managed by Certbot'], - [], []]], - [['server'], [ - ['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - [], [], []]]], - generated_conf) - - def test_http_header_hsts(self): - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.enhance("www.example.com", "ensure-http-header", - "Strict-Transport-Security") - expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] - generated_conf = self.config.parser.parsed[example_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) - - def test_multiple_headers_hsts(self): - headers_conf = self.config.parser.abs_path('sites-enabled/headers.com') - self.config.enhance("headers.com", "ensure-http-header", - "Strict-Transport-Security") - expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] - generated_conf = self.config.parser.parsed[headers_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) - - def test_http_header_hsts_twice(self): - self.config.enhance("www.example.com", "ensure-http-header", - "Strict-Transport-Security") - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "www.example.com", - "ensure-http-header", "Strict-Transport-Security") - - - @mock.patch('certbot_nginx._internal.obj.VirtualHost.contains_list') - def test_certbot_redirect_exists(self, mock_contains_list): - # Test that we add no redirect statement if there is already a - # redirect in the block that is managed by certbot - # Has a certbot redirect - mock_contains_list.return_value = True - with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: - self.config.enhance("www.example.com", "redirect") - self.assertEqual(mock_logger.info.call_args[0][0], - "Traffic on port %s already redirecting to ssl in %s") - - def test_redirect_dont_enhance(self): - # Test that we don't accidentally add redirect to ssl-only block - with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: - self.config.enhance("geese.com", "redirect") - self.assertEqual(mock_logger.info.call_args[0][0], - 'No matching insecure server blocks listening on port %s found.') - - def test_double_redirect(self): - # Test that we add one redirect for each domain - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - self.config.enhance("example.com", "redirect") - self.config.enhance("example.org", "redirect") - - expected1 = UnspacedList(_redirect_block_for_domain("example.com"))[0] - expected2 = UnspacedList(_redirect_block_for_domain("example.org"))[0] - - generated_conf = self.config.parser.parsed[example_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected1, 2)) - self.assertTrue(util.contains_at_depth(generated_conf, expected2, 2)) - - def test_staple_ocsp_bad_version(self): - self.config.version = (1, 3, 1) - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", "chain_path") - - def test_staple_ocsp_no_chain_path(self): - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", None) - - def test_staple_ocsp_internal_error(self): - self.config.enhance("www.example.com", "staple-ocsp", "chain_path") - # error is raised because the server block has conflicting directives - self.assertRaises(errors.PluginError, self.config.enhance, - "www.example.com", "staple-ocsp", "different_path") - - def test_staple_ocsp(self): - chain_path = "example/chain.pem" - self.config.enhance("www.example.com", "staple-ocsp", chain_path) - - example_conf = self.config.parser.abs_path('sites-enabled/example.com') - generated_conf = self.config.parser.parsed[example_conf] - - self.assertTrue(util.contains_at_depth( - generated_conf, - ['ssl_trusted_certificate', 'example/chain.pem'], 2)) - self.assertTrue(util.contains_at_depth( - generated_conf, ['ssl_stapling', 'on'], 2)) - self.assertTrue(util.contains_at_depth( - generated_conf, ['ssl_stapling_verify', 'on'], 2)) - - def test_deploy_no_match_default_set(self): - default_conf = self.config.parser.abs_path('sites-enabled/default') - foo_conf = self.config.parser.abs_path('foo.conf') - del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server - self.config.version = (1, 3, 1) - - self.config.deploy_cert( - "www.nomatch.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.save() - - self.config.parser.load() - - parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) - - self.assertEqual([[['server'], - [['listen', 'myhost', 'default_server'], - ['listen', 'otherhost', 'default_server'], - ['server_name', '"www.example.org"'], - [['location', '/'], - [['root', 'html'], - ['index', 'index.html', 'index.htm']]]]], - [['server'], - [['listen', 'myhost'], - ['listen', 'otherhost'], - ['server_name', 'www.nomatch.com'], - [['location', '/'], - [['root', 'html'], - ['index', 'index.html', 'index.htm']]], - ['listen', '5001', 'ssl'], - ['ssl_certificate', 'example/fullchain.pem'], - ['ssl_certificate_key', 'example/key.pem'], - ['include', self.config.mod_ssl_conf], - ['ssl_dhparam', self.config.ssl_dhparams]]]], - parsed_default_conf) - - self.config.deploy_cert( - "nomatch.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.save() - - self.config.parser.load() - - parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) - - self.assertTrue(util.contains_at_depth(parsed_default_conf, "nomatch.com", 3)) - - def test_deploy_no_match_default_set_multi_level_path(self): - default_conf = self.config.parser.abs_path('sites-enabled/default') - foo_conf = self.config.parser.abs_path('foo.conf') - del self.config.parser.parsed[default_conf][0][1][0] - del self.config.parser.parsed[default_conf][0][1][0] - self.config.version = (1, 3, 1) - - self.config.deploy_cert( - "www.nomatch.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - self.config.save() - - self.config.parser.load() - - parsed_foo_conf = util.filter_comments(self.config.parser.parsed[foo_conf]) - - self.assertEqual([['server'], - [['listen', '*:80', 'ssl'], - ['server_name', 'www.nomatch.com'], - ['root', '/home/ubuntu/sites/foo/'], - [['location', '/status'], [[['types'], [['image/jpeg', 'jpg']]]]], - [['location', '~', 'case_sensitive\\.php$'], [['index', 'index.php'], - ['root', '/var/root']]], - [['location', '~*', 'case_insensitive\\.php$'], []], - [['location', '=', 'exact_match\\.php$'], []], - [['location', '^~', 'ignore_regex\\.php$'], []], - ['ssl_certificate', 'example/fullchain.pem'], - ['ssl_certificate_key', 'example/key.pem']]], - parsed_foo_conf[1][1][1]) - - def test_deploy_no_match_no_default_set(self): - default_conf = self.config.parser.abs_path('sites-enabled/default') - foo_conf = self.config.parser.abs_path('foo.conf') - del self.config.parser.parsed[default_conf][0][1][0] - del self.config.parser.parsed[default_conf][0][1][0] - del self.config.parser.parsed[foo_conf][2][1][0][1][0] - self.config.version = (1, 3, 1) - - self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, - "www.nomatch.com", "example/cert.pem", "example/key.pem", - "example/chain.pem", "example/fullchain.pem") - - def test_deploy_no_match_fail_multiple_defaults(self): - self.config.version = (1, 3, 1) - self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, - "www.nomatch.com", "example/cert.pem", "example/key.pem", - "example/chain.pem", "example/fullchain.pem") - - def test_deploy_no_match_multiple_defaults_ok(self): - foo_conf = self.config.parser.abs_path('foo.conf') - self.config.parser.parsed[foo_conf][2][1][0][1][0][1] = '*:5001' - self.config.version = (1, 3, 1) - self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", - "example/chain.pem", "example/fullchain.pem") - - def test_deploy_no_match_add_redirect(self): - default_conf = self.config.parser.abs_path('sites-enabled/default') - foo_conf = self.config.parser.abs_path('foo.conf') - del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server - self.config.version = (1, 3, 1) - - self.config.deploy_cert( - "www.nomatch.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - - self.config.deploy_cert( - "nomatch.com", - "example/cert.pem", - "example/key.pem", - "example/chain.pem", - "example/fullchain.pem") - - self.config.enhance("www.nomatch.com", "redirect") - - self.config.save() - - self.config.parser.load() - - expected = UnspacedList(_redirect_block_for_domain("www.nomatch.com"))[0] - - generated_conf = self.config.parser.parsed[default_conf] - self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) - - @mock.patch('certbot.reverter.logger') - @mock.patch('certbot_nginx._internal.parser.NginxParser.load') - def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger): - self.config.recovery_routine() - self.config.revert_challenge_config() - self.config.rollback_checkpoints() - self.assertTrue(mock_parser_load.call_count == 3) - - def test_choose_vhosts_wildcard(self): - # pylint: disable=protected-access - mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - vhost = [x for x in self.config.parser.get_vhosts() - if 'summer.com' in x.names][0] - mock_select_vhs.return_value = [vhost] - vhs = self.config._choose_vhosts_wildcard("*.com", - prefer_ssl=True) - # Check that the dialog was called with migration.com - self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) - - # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertEqual(vhs[0], vhost) - - def test_choose_vhosts_wildcard_redirect(self): - # pylint: disable=protected-access - mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - vhost = [x for x in self.config.parser.get_vhosts() - if 'summer.com' in x.names][0] - mock_select_vhs.return_value = [vhost] - vhs = self.config._choose_vhosts_wildcard("*.com", - prefer_ssl=False) - # Check that the dialog was called with migration.com - self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) - - # And the actual returned values - self.assertEqual(len(vhs), 1) - self.assertEqual(vhs[0], vhost) - - def test_deploy_cert_wildcard(self): - # pylint: disable=protected-access - mock_choose_vhosts = mock.MagicMock() - vhost = [x for x in self.config.parser.get_vhosts() - if 'geese.com' in x.names][0] - mock_choose_vhosts.return_value = [vhost] - self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_nginx._internal.configurator.NginxConfigurator._deploy_cert" - with mock.patch(mock_d) as mock_dep: - self.config.deploy_cert("*.com", "/tmp/path", - "/tmp/path", "/tmp/path", "/tmp/path") - self.assertTrue(mock_dep.called) - self.assertEqual(len(mock_dep.call_args_list), 1) - self.assertEqual(vhost, mock_dep.call_args_list[0][0][0]) - - @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") - def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): - # pylint: disable=protected-access - mock_dialog.return_value = [] - self.assertRaises(errors.PluginError, - self.config.deploy_cert, - "*.wild.cat", "/tmp/path", "/tmp/path", - "/tmp/path", "/tmp/path") - - @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") - def test_enhance_wildcard_ocsp_after_install(self, mock_dialog): - # pylint: disable=protected-access - vhost = [x for x in self.config.parser.get_vhosts() - if 'geese.com' in x.names][0] - self.config._wildcard_vhosts["*.com"] = [vhost] - self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") - self.assertFalse(mock_dialog.called) - - @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") - def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): - vhost = [x for x in self.config.parser.get_vhosts() - if 'summer.com' in x.names][0] - mock_dialog.return_value = [vhost] - self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") - self.assertTrue(mock_dialog.called) - - @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") - def test_enhance_wildcard_double_redirect(self, mock_dialog): - # pylint: disable=protected-access - vhost = [x for x in self.config.parser.get_vhosts() - if 'summer.com' in x.names][0] - self.config._wildcard_redirect_vhosts["*.com"] = [vhost] - self.config.enhance("*.com", "redirect") - self.assertFalse(mock_dialog.called) - - def test_choose_vhosts_wildcard_no_ssl_filter_port(self): - # pylint: disable=protected-access - mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - mock_select_vhs.return_value = [] - self.config._choose_vhosts_wildcard("*.com", - prefer_ssl=False, - no_ssl_filter_port='80') - # Check that the dialog was called with only port 80 vhosts - self.assertEqual(len(mock_select_vhs.call_args[0][0]), 5) - - -class InstallSslOptionsConfTest(util.NginxTest): - """Test that the options-ssl-nginx.conf file is installed and updated properly.""" - - def setUp(self): - super(InstallSslOptionsConfTest, self).setUp() - - self.config = self.get_nginx_configurator( - self.config_path, self.config_dir, self.work_dir, self.logs_dir) - - def _call(self): - self.config.install_ssl_options_conf(self.config.mod_ssl_conf, - self.config.updated_mod_ssl_conf_digest) - - def _current_ssl_options_hash(self): - return crypto_util.sha256sum(self.config.mod_ssl_conf_src) - - def _assert_current_file(self): - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) - - def test_no_file(self): - # prepare should have placed a file there - self._assert_current_file() - os.remove(self.config.mod_ssl_conf) - self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) - self._call() - self._assert_current_file() - - def test_current_file(self): - self._assert_current_file() - self._call() - self._assert_current_file() - - def _mock_hash_except_ssl_conf_src(self, fake_hash): - # Write a bad file in place so that update tests fail if no update occurs. - # We're going to pretend this file (the currently installed conf file) - # actually hashes to `fake_hash` for the update tests. - with open(self.config.mod_ssl_conf, "w") as f: - f.write("bogus") - sha256 = crypto_util.sha256sum - def _hash(filename): - return sha256(filename) if filename == self.config.mod_ssl_conf_src else fake_hash - return _hash - - def test_prev_file_updates_to_current(self): - from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - with mock.patch('certbot.crypto_util.sha256sum', - new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): - self._call() - self._assert_current_file() - - def test_prev_file_updates_to_current_old_nginx(self): - from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - self.config.version = (1, 5, 8) - with mock.patch('certbot.crypto_util.sha256sum', - new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): - self._call() - self._assert_current_file() - - def test_manually_modified_current_file_does_not_update(self): - with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), - self._current_ssl_options_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) - - def test_manually_modified_past_file_warns(self): - with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with open(self.config.updated_mod_ssl_conf_digest, "w") as f: - f.write("hashofanoldversion") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertEqual(mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), - self._current_ssl_options_hash()) - # only print warning once - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - - def test_current_file_hash_in_all_hashes(self): - from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended" - " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") - - def test_ssl_config_files_hash_in_all_hashes(self): - """ - It is really critical that all TLS Nginx config files have their SHA256 hash registered in - constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config - file has been manually edited by the user, and will refuse to update it. - This test ensures that all necessary hashes are present. - """ - from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - import pkg_resources - all_files = [ - pkg_resources.resource_filename("certbot_nginx", - os.path.join("_internal", "tls_configs", x)) - for x in ("options-ssl-nginx.conf", - "options-ssl-nginx-old.conf", - "options-ssl-nginx-tls12-only.conf") - ] - self.assertTrue(all_files) - for one_file in all_files: - file_hash = crypto_util.sha256sum(one_file) - self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " - "hash of {0} when it is updated.".format(one_file)) - - def test_nginx_version_uses_correct_config(self): - self.config.version = (1, 5, 8) - self.config.openssl_version = "1.0.2g" # shouldn't matter - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-old.conf") - self._call() - self._assert_current_file() - self.config.version = (1, 5, 9) - self.config.openssl_version = "1.0.2l" - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-tls12-only.conf") - self._call() - self._assert_current_file() - self.config.version = (1, 13, 0) - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx.conf") - self._call() - self._assert_current_file() - self.config.version = (1, 13, 0) - self.config.openssl_version = "1.0.2k" - self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), - "options-ssl-nginx-tls13-session-tix-on.conf") - - -class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): - """Tests for certbot_nginx._internal.configurator._determine_default_server_root.""" - - def _call(self): - from certbot_nginx._internal.configurator import _determine_default_server_root - return _determine_default_server_root() - - @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) - def test_docs_value(self): - self._test(expect_both_values=True) - - @mock.patch.dict(os.environ, {}) - def test_real_values(self): - self._test(expect_both_values=False) - - def _test(self, expect_both_values): - server_root = self._call() - - if expect_both_values: - self.assertIn("/usr/local/etc/nginx", server_root) - self.assertIn("/etc/nginx", server_root) - else: - self.assertTrue(server_root == "/etc/nginx" or server_root == "/usr/local/etc/nginx") - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-nginx/certbot_nginx/tests/display_ops_test.py b/certbot-nginx/certbot_nginx/tests/display_ops_test.py deleted file mode 100644 index a03c5d265..000000000 --- a/certbot-nginx/certbot_nginx/tests/display_ops_test.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Test certbot_nginx._internal.display_ops.""" -import unittest - -from certbot.display import util as display_util - -from certbot.tests import util as certbot_util - -from certbot_nginx._internal import parser - -from certbot_nginx._internal.display_ops import select_vhost_multiple -from certbot_nginx.tests import util - - -class SelectVhostMultiTest(util.NginxTest): - """Tests for certbot_nginx._internal.display_ops.select_vhost_multiple.""" - - def setUp(self): - super(SelectVhostMultiTest, self).setUp() - nparser = parser.NginxParser(self.config_path) - self.vhosts = nparser.get_vhosts() - - def test_select_no_input(self): - self.assertFalse(select_vhost_multiple([])) - - @certbot_util.patch_get_utility() - def test_select_correct(self, mock_util): - mock_util().checklist.return_value = ( - display_util.OK, [self.vhosts[3].display_repr(), - self.vhosts[2].display_repr()]) - vhs = select_vhost_multiple([self.vhosts[3], - self.vhosts[2], - self.vhosts[1]]) - self.assertTrue(self.vhosts[2] in vhs) - self.assertTrue(self.vhosts[3] in vhs) - self.assertFalse(self.vhosts[1] in vhs) - - @certbot_util.patch_get_utility() - def test_select_cancel(self, mock_util): - mock_util().checklist.return_value = (display_util.CANCEL, "whatever") - vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) - self.assertFalse(vhs) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/certbot_nginx/tests/http_01_test.py deleted file mode 100644 index 0335aab01..000000000 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Tests for certbot_nginx._internal.http_01""" -import unittest - -import josepy as jose -import mock -import six - -from acme import challenges - -from certbot import achallenges - -from certbot.tests import acme_util -from certbot.tests import util as test_util - -from certbot_nginx._internal.obj import Addr -from certbot_nginx.tests import util - -AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) - - -class HttpPerformTest(util.NginxTest): - """Test the NginxHttp01 challenge.""" - - account_key = AUTH_KEY - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=b"kNdwjwOeX0I_A8DXt9Msmg"), "pending"), - domain="www.example.com", account_key=account_key), - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01( - token=b"\xba\xa9\xda?= 1 and - x[0] == 2, - lambda x, y, pts=paths: pts.append(y)) - self.assertEqual(paths, result) - - def test_get_vhosts_global_ssl(self): - nparser = parser.NginxParser(self.config_path) - vhosts = nparser.get_vhosts() - - vhost = obj.VirtualHost(nparser.abs_path('sites-enabled/globalssl.com'), - [obj.Addr('4.8.2.6', '57', True, False, - False, False)], - True, True, set(['globalssl.com']), [], [0]) - - globalssl_com = [x for x in vhosts if 'globalssl.com' in x.filep][0] - self.assertEqual(vhost, globalssl_com) - - def test_get_vhosts(self): - nparser = parser.NginxParser(self.config_path) - vhosts = nparser.get_vhosts() - - vhost1 = obj.VirtualHost(nparser.abs_path('nginx.conf'), - [obj.Addr('', '8080', False, False, - False, False)], - False, True, - set(['localhost', - r'~^(www\.)?(example|bar)\.']), - [], [10, 1, 9]) - vhost2 = obj.VirtualHost(nparser.abs_path('nginx.conf'), - [obj.Addr('somename', '8080', False, False, - False, False), - obj.Addr('', '8000', False, False, - False, False)], - False, True, - set(['somename', 'another.alias', 'alias']), - [], [10, 1, 12]) - vhost3 = obj.VirtualHost(nparser.abs_path('sites-enabled/example.com'), - [obj.Addr('69.50.225.155', '9000', - False, False, False, False), - obj.Addr('127.0.0.1', '', False, False, - False, False)], - False, True, - set(['.example.com', 'example.*']), [], [0]) - vhost4 = obj.VirtualHost(nparser.abs_path('sites-enabled/default'), - [obj.Addr('myhost', '', False, True, - False, False), - obj.Addr('otherhost', '', False, True, - False, False)], - False, True, set(['www.example.org']), - [], [0]) - vhost5 = obj.VirtualHost(nparser.abs_path('foo.conf'), - [obj.Addr('*', '80', True, True, - False, False)], - True, True, set(['*.www.foo.com', - '*.www.example.com']), - [], [2, 1, 0]) - - self.assertEqual(13, len(vhosts)) - example_com = [x for x in vhosts if 'example.com' in x.filep][0] - self.assertEqual(vhost3, example_com) - default = [x for x in vhosts if 'default' in x.filep][0] - self.assertEqual(vhost4, default) - fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0] - self.assertEqual(vhost5, fooconf) - localhost = [x for x in vhosts if 'localhost' in x.names][0] - self.assertEqual(vhost1, localhost) - somename = [x for x in vhosts if 'somename' in x.names][0] - self.assertEqual(vhost2, somename) - - def test_has_ssl_on_directive(self): - nparser = parser.NginxParser(self.config_path) - mock_vhost = obj.VirtualHost(None, None, None, None, None, - [['listen', 'myhost default_server'], - ['server_name', 'www.example.org'], - [['location', '/'], [['root', 'html'], ['index', 'index.html index.htm']]] - ], None) - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) - mock_vhost.raw = [['listen', '*:80', 'default_server', 'ssl'], - ['server_name', '*.www.foo.com', '*.www.example.com'], - ['root', '/home/ubuntu/sites/foo/']] - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) - mock_vhost.raw = [['listen', '80 ssl'], - ['server_name', '*.www.foo.com', '*.www.example.com']] - self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) - mock_vhost.raw = [['listen', '80'], - ['ssl', 'on'], - ['server_name', '*.www.foo.com', '*.www.example.com']] - self.assertTrue(nparser.has_ssl_on_directive(mock_vhost)) - - - def test_remove_server_directives(self): - nparser = parser.NginxParser(self.config_path) - mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), - None, None, None, - set(['localhost', - r'~^(www\.)?(example|bar)\.']), - None, [10, 1, 9]) - example_com = nparser.abs_path('sites-enabled/example.com') - names = set(['.example.com', 'example.*']) - mock_vhost.filep = example_com - mock_vhost.names = names - mock_vhost.path = [0] - nparser.add_server_directives(mock_vhost, - [['foo', 'bar'], ['ssl_certificate', - '/etc/ssl/cert2.pem']]) - nparser.remove_server_directives(mock_vhost, 'foo') - nparser.remove_server_directives(mock_vhost, 'ssl_certificate') - self.assertEqual(nparser.parsed[example_com], - [[['server'], [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - []]]]) - - def test_add_server_directives(self): - nparser = parser.NginxParser(self.config_path) - mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), - None, None, None, - set(['localhost', - r'~^(www\.)?(example|bar)\.']), - None, [10, 1, 9]) - nparser.add_server_directives(mock_vhost, - [['foo', 'bar'], ['\n ', 'ssl_certificate', ' ', - '/etc/ssl/cert.pem']]) - ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem') - dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')]) - self.assertEqual(1, len(re.findall(ssl_re, dump))) - - example_com = nparser.abs_path('sites-enabled/example.com') - names = set(['.example.com', 'example.*']) - mock_vhost.filep = example_com - mock_vhost.names = names - mock_vhost.path = [0] - nparser.add_server_directives(mock_vhost, - [['foo', 'bar'], ['ssl_certificate', - '/etc/ssl/cert2.pem']]) - nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) - from certbot_nginx._internal.parser import COMMENT - self.assertEqual(nparser.parsed[example_com], - [[['server'], [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - ['foo', 'bar'], - ['#', COMMENT], - ['ssl_certificate', '/etc/ssl/cert2.pem'], - ['#', COMMENT], [], [] - ]]]) - - server_conf = nparser.abs_path('server.conf') - names = set(['alias', 'another.alias', 'somename']) - mock_vhost.filep = server_conf - mock_vhost.names = names - mock_vhost.path = [] - self.assertRaises(errors.MisconfigurationError, - nparser.add_server_directives, - mock_vhost, - [['foo', 'bar'], - ['ssl_certificate', '/etc/ssl/cert2.pem']]) - - def test_comment_is_repeatable(self): - nparser = parser.NginxParser(self.config_path) - example_com = nparser.abs_path('sites-enabled/example.com') - mock_vhost = obj.VirtualHost(example_com, - None, None, None, - set(['.example.com', 'example.*']), - None, [0]) - nparser.add_server_directives(mock_vhost, - [['\n ', '#', ' ', 'what a nice comment']]) - nparser.add_server_directives(mock_vhost, - [['\n ', 'include', ' ', - nparser.abs_path('comment_in_file.conf')]]) - from certbot_nginx._internal.parser import COMMENT - self.assertEqual(nparser.parsed[example_com], - [[['server'], [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', '.example.com'], - ['server_name', 'example.*'], - ['#', ' ', 'what a nice comment'], - [], - ['include', nparser.abs_path('comment_in_file.conf')], - ['#', COMMENT], - []]]] -) - - def test_replace_server_directives(self): - nparser = parser.NginxParser(self.config_path) - target = set(['.example.com', 'example.*']) - filep = nparser.abs_path('sites-enabled/example.com') - mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0]) - nparser.update_or_add_server_directives( - mock_vhost, [['server_name', 'foobar.com']]) - from certbot_nginx._internal.parser import COMMENT - self.assertEqual( - nparser.parsed[filep], - [[['server'], [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', 'foobar.com'], ['#', COMMENT], - ['server_name', 'example.*'], [] - ]]]) - mock_vhost.names = set(['foobar.com', 'example.*']) - nparser.update_or_add_server_directives( - mock_vhost, [['ssl_certificate', 'cert.pem']]) - self.assertEqual( - nparser.parsed[filep], - [[['server'], [['listen', '69.50.225.155:9000'], - ['listen', '127.0.0.1'], - ['server_name', 'foobar.com'], ['#', COMMENT], - ['server_name', 'example.*'], [], - ['ssl_certificate', 'cert.pem'], ['#', COMMENT], [], - ]]]) - - def test_get_best_match(self): - target_name = 'www.eff.org' - names = [set(['www.eff.org', 'irrelevant.long.name.eff.org', '*.org']), - set(['eff.org', 'ww2.eff.org', 'test.www.eff.org']), - set(['*.eff.org', '.www.eff.org']), - set(['.eff.org', '*.org']), - set(['www.eff.', 'www.eff.*', '*.www.eff.org']), - set(['example.com', r'~^(www\.)?(eff.+)', '*.eff.*']), - set(['*', r'~^(www\.)?(eff.+)']), - set(['www.*', r'~^(www\.)?(eff.+)', '.test.eff.org']), - set(['*.org', r'*.eff.org', 'www.eff.*']), - set(['*.www.eff.org', 'www.*']), - set(['*.org']), - set([]), - set(['example.com'])] - winners = [('exact', 'www.eff.org'), - (None, None), - ('exact', '.www.eff.org'), - ('wildcard_start', '.eff.org'), - ('wildcard_end', 'www.eff.*'), - ('regex', r'~^(www\.)?(eff.+)'), - ('wildcard_start', '*'), - ('wildcard_end', 'www.*'), - ('wildcard_start', '*.eff.org'), - ('wildcard_end', 'www.*'), - ('wildcard_start', '*.org'), - (None, None), - (None, None)] - - for i, winner in enumerate(winners): - self.assertEqual(winner, - parser.get_best_match(target_name, names[i])) - - def test_comment_directive(self): - # pylint: disable=protected-access - block = nginxparser.UnspacedList([ - ["\n", "a", " ", "b", "\n"], - ["c", " ", "d"], - ["\n", "e", " ", "f"]]) - from certbot_nginx._internal.parser import comment_directive, COMMENT_BLOCK - comment_directive(block, 1) - comment_directive(block, 0) - self.assertEqual(block.spaced, [ - ["\n", "a", " ", "b", "\n"], - COMMENT_BLOCK, - "\n", - ["c", " ", "d"], - COMMENT_BLOCK, - ["\n", "e", " ", "f"]]) - - def test_comment_out_directive(self): - server_block = nginxparser.loads(""" - server { - listen 80; - root /var/www/html; - index star.html; - - server_name *.functorkitten.xyz; - ssl_session_timeout 1440m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - - ssl_prefer_server_ciphers on; - }""") - block = server_block[0][1] - from certbot_nginx._internal.parser import _comment_out_directive - _comment_out_directive(block, 4, "blah1") - _comment_out_directive(block, 5, "blah2") - _comment_out_directive(block, 6, "blah3") - self.assertEqual(block.spaced, [ - ['\n ', 'listen', ' ', '80'], - ['\n ', 'root', ' ', '/var/www/html'], - ['\n ', 'index', ' ', 'star.html'], - ['\n\n ', 'server_name', ' ', '*.functorkitten.xyz'], - ['\n ', '#', ' ssl_session_timeout 1440m; # duplicated in blah1'], - [' ', '#', ' ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # duplicated in blah2'], - ['\n\n ', '#', ' ssl_prefer_server_ciphers on; # duplicated in blah3'], - '\n ']) - - def test_parse_server_raw_ssl(self): - server = parser._parse_server_raw([ #pylint: disable=protected-access - ['listen', '443'] - ]) - self.assertFalse(server['ssl']) - - server = parser._parse_server_raw([ #pylint: disable=protected-access - ['listen', '443', 'ssl'] - ]) - self.assertTrue(server['ssl']) - - server = parser._parse_server_raw([ #pylint: disable=protected-access - ['listen', '443'], ['ssl', 'off'] - ]) - self.assertFalse(server['ssl']) - - server = parser._parse_server_raw([ #pylint: disable=protected-access - ['listen', '443'], ['ssl', 'on'] - ]) - self.assertTrue(server['ssl']) - - def test_parse_server_raw_unix(self): - server = parser._parse_server_raw([ #pylint: disable=protected-access - ['listen', 'unix:/var/run/nginx.sock'] - ]) - self.assertEqual(len(server['addrs']), 0) - - def test_parse_server_global_ssl_applied(self): - nparser = parser.NginxParser(self.config_path) - server = nparser.parse_server([ - ['listen', '443'] - ]) - self.assertTrue(server['ssl']) - - def test_duplicate_vhost(self): - nparser = parser.NginxParser(self.config_path) - - vhosts = nparser.get_vhosts() - default = [x for x in vhosts if 'default' in x.filep][0] - new_vhost = nparser.duplicate_vhost(default, remove_singleton_listen_params=True) - nparser.filedump(ext='') - - # check properties of new vhost - self.assertFalse(next(iter(new_vhost.addrs)).default) - self.assertNotEqual(new_vhost.path, default.path) - - # check that things are written to file correctly - new_nparser = parser.NginxParser(self.config_path) - new_vhosts = new_nparser.get_vhosts() - new_defaults = [x for x in new_vhosts if 'default' in x.filep] - self.assertEqual(len(new_defaults), 2) - new_vhost_parsed = new_defaults[1] - self.assertFalse(next(iter(new_vhost_parsed.addrs)).default) - self.assertEqual(next(iter(default.names)), next(iter(new_vhost_parsed.names))) - self.assertEqual(len(default.raw), len(new_vhost_parsed.raw)) - self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs)))) - - def test_duplicate_vhost_remove_ipv6only(self): - nparser = parser.NginxParser(self.config_path) - - vhosts = nparser.get_vhosts() - ipv6ssl = [x for x in vhosts if 'ipv6ssl' in x.filep][0] - new_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=True) - nparser.filedump(ext='') - - for addr in new_vhost.addrs: - self.assertFalse(addr.ipv6only) - - identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False) - nparser.filedump(ext='') - - called = False - for addr in identical_vhost.addrs: - if addr.ipv6: - self.assertTrue(addr.ipv6only) - called = True - self.assertTrue(called) - - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf deleted file mode 100644 index 98aef55d6..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf +++ /dev/null @@ -1,12 +0,0 @@ -# A faulty configuration file - -pid logs/nginx.pid; - - -events { - worker_connections 1024; -} - -include foo.conf; - -@@@ diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf deleted file mode 100644 index f761079fa..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf +++ /dev/null @@ -1 +0,0 @@ -# a comment inside a file \ No newline at end of file diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf deleted file mode 100644 index 477cb1c45..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf +++ /dev/null @@ -1,27 +0,0 @@ -# This is not a valid nginx config file but it tests edge cases in valid nginx syntax - -server { - server_name simple; -} - -server { - server_name with.if; - location ~ ^/services/.+$ { - if ($request_filename ~* \.(ttf|woff)$) { - add_header Access-Control-Allow-Origin "*"; - } - } -} - -server { - server_name with.complicated.headers; - - location ~* \.(?:gif|jpe?g|png)$ { - - add_header Pragma public; - add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo; - blah "hello;world"; - - try_files $uri @rewrites; - } -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf deleted file mode 100644 index 574955398..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf +++ /dev/null @@ -1,25 +0,0 @@ -# a test nginx conf -user www-data; - -http { - server { - listen *:80 default_server ssl; - server_name *.www.foo.com *.www.example.com; - root /home/ubuntu/sites/foo/; - - location /status { - types { - image/jpeg jpg; - } - } - - location ~ case_sensitive\.php$ { - index index.php; - root /var/root; - } - location ~* case_insensitive\.php$ {} - location = exact_match\.php$ {} - location ^~ ignore_regex\.php$ {} - - } -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/mime.types b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/mime.types deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf deleted file mode 100644 index cf4648592..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf +++ /dev/null @@ -1,12 +0,0 @@ -# Use bar.conf when it's a full moon! -include foo.conf; # Kilroy was here -check_status; - -server { - # - # Don't forget to open up your firewall! - # - listen 1234; - # listen 80; -} - diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf deleted file mode 100644 index 74cd84bcd..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Test nginx configuration file with multiline quoted strings. -# Good example of usage for multilined quoted values is when -# using Openresty's Lua directives and you wish to keep the -# inline Lua code readable. -http { - server { - listen *:443; # because there should be no other port open. - - location / { - body_filter_by_lua 'ngx.ctx.buffered = (ngx.ctx.buffered or "") .. string.sub(ngx.arg[1], 1, 1000) - if ngx.arg[2] then - ngx.var.resp_body = ngx.ctx.buffered - end'; - } - } -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf deleted file mode 100644 index ccce4dc1b..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf +++ /dev/null @@ -1,122 +0,0 @@ -# standard default nginx config - -user nobody; -worker_processes 1; - -error_log logs/error.log; -error_log logs/error.log notice; -error_log logs/error.log info; - -pid logs/nginx.pid; - - -events { - worker_connections 1024; -} - -empty { -} - -include foo.conf; - -http { - include mime.types; - include sites-enabled/*; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log logs/access.log main; - - sendfile on; - tcp_nopush on; - - keepalive_timeout 0; - - gzip on; - - server { - listen 8080; - server_name localhost; - server_name ~^(www\.)?(example|bar)\.; - - charset koi8-r; - - access_log logs/host.access.log main; - - location / { - root html; - index index.html index.htm; - } - - error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - - # proxy the PHP scripts to Nginx listening on 127.0.0.1:80 - # - location ~ \.php$ { - proxy_pass http://127.0.0.1; - } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - location ~ \.php$ { - root html; - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; - } - - # deny access to .htaccess files, if Nginx's document root - # concurs with nginx's one - # - location ~ /\.ht { - deny all; - } - } - - - # another virtual host using mix of IP-, name-, and port-based configuration - # - server { - listen 8000; - listen somename:8080; - include server.conf; - - location / { - root html; - index index.html index.htm; - } - } - - - # HTTPS server - # - #server { - # listen 443 ssl; - # server_name localhost; - - # ssl_certificate cert.pem; - # ssl_certificate_key cert.key; - - # ssl_session_cache shared:SSL:1m; - # ssl_session_timeout 5m; - - # ssl_ciphers HIGH:!aNULL:!MD5; - # ssl_prefer_server_ciphers on; - - # location / { - # root html; - # index index.html index.htm; - # } - #} - - #include conf.d/test.conf; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf deleted file mode 100644 index 5fc4c8b24..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf +++ /dev/null @@ -1 +0,0 @@ -server_name somename alias another.alias; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default deleted file mode 100644 index e167761d1..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default +++ /dev/null @@ -1,10 +0,0 @@ -server { - listen myhost default_server; - listen otherhost default_server; - server_name "www.example.org"; - - location / { - root html; - index index.html index.htm; - } -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com deleted file mode 100644 index fd9117188..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com +++ /dev/null @@ -1,6 +0,0 @@ -server { - listen 69.50.225.155:9000; - listen 127.0.0.1; - server_name .example.com; - server_name example.*; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com deleted file mode 100644 index 969447d6e..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com +++ /dev/null @@ -1,9 +0,0 @@ -server { - server_name globalssl.com; - listen 4.8.2.6:57; -} - -server { - server_name globalsslsetssl.com; - listen 4.8.2.6:57 ssl; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com deleted file mode 100644 index 6c032928c..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com +++ /dev/null @@ -1,4 +0,0 @@ -server { - server_name headers.com; - add_header X-Content-Type-Options nosniff; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com deleted file mode 100644 index 7a7744b92..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com +++ /dev/null @@ -1,5 +0,0 @@ -server { - listen 80; - listen [::]:80; - server_name ipv6.com; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com deleted file mode 100644 index 875a9ee1b..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com +++ /dev/null @@ -1,7 +0,0 @@ -server { - listen 443 ssl; - listen [::]:443 ssl ipv6only=on; - listen 5001 ssl; - listen [::]:5001 ssl ipv6only=on; - server_name ipv6ssl.com; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com deleted file mode 100644 index 17bc6d0c3..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com +++ /dev/null @@ -1,19 +0,0 @@ -server { - server_name migration.com; - server_name summer.com; -} - -server { - listen 443 ssl; - server_name migration.com; - server_name geese.com; - - ssl_certificate cert.pem; - ssl_certificate_key cert.key; - - ssl_session_cache shared:SSL:1m; - ssl_session_timeout 5m; - - ssl_ciphers HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com deleted file mode 100644 index b93e6ba2d..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com +++ /dev/null @@ -1,6 +0,0 @@ -server { - server_name sslon.com; - ssl on; - ssl_certificate snakeoil.cert; - ssl_certificate_key snakeoil.key; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params deleted file mode 100644 index 4ee14e98d..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params +++ /dev/null @@ -1,25 +0,0 @@ -fastcgi_param QUERY_STRING $query_string; -fastcgi_param REQUEST_METHOD $request_method; -fastcgi_param CONTENT_TYPE $content_type; -fastcgi_param CONTENT_LENGTH $content_length; - -fastcgi_param SCRIPT_FILENAME $request_filename; -fastcgi_param SCRIPT_NAME $fastcgi_script_name; -fastcgi_param REQUEST_URI $request_uri; -fastcgi_param DOCUMENT_URI $document_uri; -fastcgi_param DOCUMENT_ROOT $document_root; -fastcgi_param SERVER_PROTOCOL $server_protocol; - -fastcgi_param GATEWAY_INTERFACE CGI/1.1; -fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; - -fastcgi_param REMOTE_ADDR $remote_addr; -fastcgi_param REMOTE_PORT $remote_port; -fastcgi_param SERVER_ADDR $server_addr; -fastcgi_param SERVER_PORT $server_port; -fastcgi_param SERVER_NAME $server_name; - -fastcgi_param HTTPS $https if_not_empty; - -# PHP only, required if PHP was built with --enable-force-cgi-redirect -fastcgi_param REDIRECT_STATUS 200; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf deleted file mode 100644 index 1edb9474f..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf +++ /dev/null @@ -1,108 +0,0 @@ -# This map is not a full koi8-r <> utf8 map: it does not contain -# box-drawing and some other characters. Besides this map contains -# several koi8-u and Byelorussian letters which are not in koi8-r. -# If you need a full and standard map, use contrib/unicode2nginx/koi-utf -# map instead. - -charset_map koi8-r utf-8 { - - 80 E282AC; # euro - - 95 E280A2; # bullet - - 9A C2A0; #   - - 9E C2B7; # · - - A3 D191; # small yo - A4 D194; # small Ukrainian ye - - A6 D196; # small Ukrainian i - A7 D197; # small Ukrainian yi - - AD D291; # small Ukrainian soft g - AE D19E; # small Byelorussian short u - - B0 C2B0; # ° - - B3 D081; # capital YO - B4 D084; # capital Ukrainian YE - - B6 D086; # capital Ukrainian I - B7 D087; # capital Ukrainian YI - - B9 E28496; # numero sign - - BD D290; # capital Ukrainian soft G - BE D18E; # capital Byelorussian short U - - BF C2A9; # (C) - - C0 D18E; # small yu - C1 D0B0; # small a - C2 D0B1; # small b - C3 D186; # small ts - C4 D0B4; # small d - C5 D0B5; # small ye - C6 D184; # small f - C7 D0B3; # small g - C8 D185; # small kh - C9 D0B8; # small i - CA D0B9; # small j - CB D0BA; # small k - CC D0BB; # small l - CD D0BC; # small m - CE D0BD; # small n - CF D0BE; # small o - - D0 D0BF; # small p - D1 D18F; # small ya - D2 D180; # small r - D3 D181; # small s - D4 D182; # small t - D5 D183; # small u - D6 D0B6; # small zh - D7 D0B2; # small v - D8 D18C; # small soft sign - D9 D18B; # small y - DA D0B7; # small z - DB D188; # small sh - DC D18D; # small e - DD D189; # small shch - DE D187; # small ch - DF D18A; # small hard sign - - E0 D0AE; # capital YU - E1 D090; # capital A - E2 D091; # capital B - E3 D0A6; # capital TS - E4 D094; # capital D - E5 D095; # capital YE - E6 D0A4; # capital F - E7 D093; # capital G - E8 D0A5; # capital KH - E9 D098; # capital I - EA D099; # capital J - EB D09A; # capital K - EC D09B; # capital L - ED D09C; # capital M - EE D09D; # capital N - EF D09E; # capital O - - F0 D09F; # capital P - F1 D0AF; # capital YA - F2 D0A0; # capital R - F3 D0A1; # capital S - F4 D0A2; # capital T - F5 D0A3; # capital U - F6 D096; # capital ZH - F7 D092; # capital V - F8 D0AC; # capital soft sign - F9 D0AB; # capital Y - FA D097; # capital Z - FB D0A8; # capital SH - FC D0AD; # capital E - FD D0A9; # capital SHCH - FE D0A7; # capital CH - FF D0AA; # capital hard sign -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win deleted file mode 100644 index c6930fc4f..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win +++ /dev/null @@ -1,102 +0,0 @@ -charset_map koi8-r windows-1251 { - - 80 88; # euro - - 95 95; # bullet - - 9A A0; #   - - 9E B7; # · - - A3 B8; # small yo - A4 BA; # small Ukrainian ye - - A6 B3; # small Ukrainian i - A7 BF; # small Ukrainian yi - - AD B4; # small Ukrainian soft g - AE A2; # small Byelorussian short u - - B0 B0; # ° - - B3 A8; # capital YO - B4 AA; # capital Ukrainian YE - - B6 B2; # capital Ukrainian I - B7 AF; # capital Ukrainian YI - - B9 B9; # numero sign - - BD A5; # capital Ukrainian soft G - BE A1; # capital Byelorussian short U - - BF A9; # (C) - - C0 FE; # small yu - C1 E0; # small a - C2 E1; # small b - C3 F6; # small ts - C4 E4; # small d - C5 E5; # small ye - C6 F4; # small f - C7 E3; # small g - C8 F5; # small kh - C9 E8; # small i - CA E9; # small j - CB EA; # small k - CC EB; # small l - CD EC; # small m - CE ED; # small n - CF EE; # small o - - D0 EF; # small p - D1 FF; # small ya - D2 F0; # small r - D3 F1; # small s - D4 F2; # small t - D5 F3; # small u - D6 E6; # small zh - D7 E2; # small v - D8 FC; # small soft sign - D9 FB; # small y - DA E7; # small z - DB F8; # small sh - DC FD; # small e - DD F9; # small shch - DE F7; # small ch - DF FA; # small hard sign - - E0 DE; # capital YU - E1 C0; # capital A - E2 C1; # capital B - E3 D6; # capital TS - E4 C4; # capital D - E5 C5; # capital YE - E6 D4; # capital F - E7 C3; # capital G - E8 D5; # capital KH - E9 C8; # capital I - EA C9; # capital J - EB CA; # capital K - EC CB; # capital L - ED CC; # capital M - EE CD; # capital N - EF CE; # capital O - - F0 CF; # capital P - F1 DF; # capital YA - F2 D0; # capital R - F3 D1; # capital S - F4 D2; # capital T - F5 D3; # capital U - F6 C6; # capital ZH - F7 C2; # capital V - F8 DC; # capital soft sign - F9 DB; # capital Y - FA C7; # capital Z - FB D8; # capital SH - FC DD; # capital E - FD D9; # capital SHCH - FE D7; # capital CH - FF DA; # capital hard sign -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types deleted file mode 100644 index fcce4a58d..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types +++ /dev/null @@ -1,79 +0,0 @@ -types { - text/html html htm shtml; - text/css css; - text/xml xml rss; - image/gif gif; - image/jpeg jpeg jpg; - application/x-javascript js; - application/atom+xml atom; - - text/mathml mml; - text/plain txt; - text/vnd.sun.j2me.app-descriptor jad; - text/vnd.wap.wml wml; - text/x-component htc; - - image/png png; - image/tiff tif tiff; - image/vnd.wap.wbmp wbmp; - image/x-icon ico; - image/x-jng jng; - image/x-ms-bmp bmp; - image/svg+xml svg svgz; - - application/java-archive jar war ear; - application/json json; - application/mac-binhex40 hqx; - application/msword doc; - application/pdf pdf; - application/postscript ps eps ai; - application/rtf rtf; - application/vnd.ms-excel xls; - application/vnd.ms-powerpoint ppt; - application/vnd.wap.wmlc wmlc; - application/vnd.google-earth.kml+xml kml; - application/vnd.google-earth.kmz kmz; - application/x-7z-compressed 7z; - application/x-cocoa cco; - application/x-java-archive-diff jardiff; - application/x-java-jnlp-file jnlp; - application/x-makeself run; - application/x-perl pl pm; - application/x-pilot prc pdb; - application/x-rar-compressed rar; - application/x-redhat-package-manager rpm; - application/x-sea sea; - application/x-shockwave-flash swf; - application/x-stuffit sit; - application/x-tcl tcl tk; - application/x-x509-ca-cert der pem crt; - application/x-xpinstall xpi; - application/xhtml+xml xhtml; - application/zip zip; - - application/octet-stream bin exe dll; - application/octet-stream deb; - application/octet-stream dmg; - application/octet-stream eot; - application/octet-stream iso img; - application/octet-stream msi msp msm; - application/ogg ogx; - - audio/midi mid midi kar; - audio/mpeg mpga mpega mp2 mp3 m4a; - audio/ogg oga ogg spx; - audio/x-realaudio ra; - audio/webm weba; - - video/3gpp 3gpp 3gp; - video/mp4 mp4; - video/mpeg mpeg mpg mpe; - video/ogg ogv; - video/quicktime mov; - video/webm webm; - video/x-flv flv; - video/x-mng mng; - video/x-ms-asf asx asf; - video/x-ms-wmv wmv; - video/x-msvideo avi; -} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 deleted file mode 100644 index f4eb9d49d..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 +++ /dev/null @@ -1,16 +0,0 @@ -[nx_extract] -username = naxsi_web -password = test -port = 8081 -rules_path = /etc/nginx/naxsi_core.rules - -[nx_intercept] -port = 8080 - -[sql] -dbtype = sqlite -username = root -password = -hostname = 127.0.0.1 -dbname = naxsi_sig - diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules deleted file mode 100644 index fec21ea4f..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules +++ /dev/null @@ -1,13 +0,0 @@ -# Sample rules file for default vhost. - -LearningMode; -SecRulesEnabled; -#SecRulesDisabled; -DeniedUrl "/RequestDenied"; - -## check rules -CheckRule "$SQL >= 8" BLOCK; -CheckRule "$RFI >= 8" BLOCK; -CheckRule "$TRAVERSAL >= 4" BLOCK; -CheckRule "$EVADE >= 4" BLOCK; -CheckRule "$XSS >= 8" BLOCK; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules deleted file mode 100644 index 9826e02cb..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules +++ /dev/null @@ -1,75 +0,0 @@ -################################## -## INTERNAL RULES IDS:1-10 ## -################################## -#weird_request : 1 -#big_body : 2 -#no_content_type : 3 - -#MainRule "str:yesone" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999; - -################################## -## SQL Injections IDs:1000-1099 ## -################################## -MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000; -MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1001; -MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002; -## Hardcore rules -MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003; -MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004; -MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005; -MainRule "rx:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006; -## end of hardcore rules -MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007; -MainRule "str:;" "msg:; in stuff" "mz:BODY|URL|ARGS" "s:$SQL:4" id:1008; -MainRule "str:=" "msg:equal in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009; -MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1010; -MainRule "str:)" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1011; -MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1013; -MainRule "str:\"" "msg:double quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1014; -MainRule "str:," "msg:, in stuff" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015; -MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016; - -############################### -## OBVIOUS RFI IDs:1100-1199 ## -############################### -MainRule "str:http://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100; -MainRule "str:https://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101; -MainRule "str:ftp://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102; -MainRule "str:php://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103; - -####################################### -## Directory traversal IDs:1200-1299 ## -####################################### -MainRule "str:.." "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200; -MainRule "str:/etc/passwd" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202; -MainRule "str:c:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203; -MainRule "str:cmd.exe" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204; -MainRule "str:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205; -#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206; -######################################## -## Cross Site Scripting IDs:1300-1399 ## -######################################## -MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302; -MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303; -MainRule "str:'" "msg:simple quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1306; -MainRule "str:\"" "msg:double quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1307; -MainRule "str:(" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1308; -MainRule "str:)" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1309; -MainRule "str:[" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310; -MainRule "str:]" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311; -MainRule "str:~" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312; -MainRule "str:;" "msg:semi coma" "mz:ARGS|URL|BODY" "s:$XSS:8" id:1313; -MainRule "str:`" "msg:grave accent !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314; -MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315; - -#################################### -## Evading tricks IDs: 1400-1500 ## -#################################### -MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400; -MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401; -MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402; - -############################# -## File uploads: 1500-1600 ## -############################# -MainRule "rx:.ph*|.asp*" "msg:asp/php file upload!" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf deleted file mode 100644 index 52219b940..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf +++ /dev/null @@ -1,95 +0,0 @@ -user www-data; -worker_processes 4; -pid /run/nginx.pid; - -events { - worker_connections 768; - # multi_accept on; -} - -http { - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - # server_tokens off; - - # server_names_hash_bucket_size 64; - # server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - ## - # Gzip Settings - ## - - gzip on; - gzip_disable "msie6"; - - # gzip_vary on; - # gzip_proxied any; - # gzip_comp_level 6; - # gzip_buffers 16 8k; - # gzip_http_version 1.1; - # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - ## - # nginx-naxsi config - ## - # Uncomment it if you installed nginx-naxsi - ## - - #include /etc/nginx/naxsi_core.rules; - - ## - # nginx-passenger config - ## - # Uncomment it if you installed nginx-passenger - ## - - #passenger_root /usr; - #passenger_ruby /usr/bin/ruby; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} - - -#mail { -# # See sample authentication script at: -# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript -# -# # auth_http localhost/auth.php; -# # pop3_capabilities "TOP" "USER"; -# # imap_capabilities "IMAP4rev1" "UIDPLUS"; -# -# server { -# listen localhost:110; -# protocol pop3; -# proxy on; -# } -# -# server { -# listen localhost:143; -# protocol imap; -# proxy on; -# } -#} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params deleted file mode 100644 index df75bc5d7..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params +++ /dev/null @@ -1,4 +0,0 @@ -proxy_set_header Host $http_host; -proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params deleted file mode 100644 index 76e858628..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params +++ /dev/null @@ -1,14 +0,0 @@ -scgi_param REQUEST_METHOD $request_method; -scgi_param REQUEST_URI $request_uri; -scgi_param QUERY_STRING $query_string; -scgi_param CONTENT_TYPE $content_type; - -scgi_param DOCUMENT_URI $document_uri; -scgi_param DOCUMENT_ROOT $document_root; -scgi_param SCGI 1; -scgi_param SERVER_PROTOCOL $server_protocol; - -scgi_param REMOTE_ADDR $remote_addr; -scgi_param REMOTE_PORT $remote_port; -scgi_param SERVER_PORT $server_port; -scgi_param SERVER_NAME $server_name; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default deleted file mode 100644 index 5d8f3ac15..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default +++ /dev/null @@ -1,112 +0,0 @@ -# You may add here your -# server { -# ... -# } -# statements for each of your virtual hosts to this file - -## -# You should look at the following URL's in order to grasp a solid understanding -# of Nginx configuration files in order to fully unleash the power of Nginx. -# http://wiki.nginx.org/Pitfalls -# http://wiki.nginx.org/QuickStart -# http://wiki.nginx.org/Configuration -# -# Generally, you will want to move this file somewhere, and start with a clean -# file but keep this around for reference. Or just disable in sites-enabled. -# -# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. -## - -server { - listen 80 default_server; - listen [::]:80 default_server ipv6only=on; - - root /usr/share/nginx/html; - index index.html index.htm; - - # Make site accessible from http://localhost/ - server_name localhost; - - location / { - # First attempt to serve request as file, then - # as directory, then fall back to displaying a 404. - try_files $uri $uri/ =404; - # Uncomment to enable naxsi on this location - # include /etc/nginx/naxsi.rules - } - - # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests - #location /RequestDenied { - # proxy_pass http://127.0.0.1:8080; - #} - - #error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - # - #error_page 500 502 503 504 /50x.html; - #location = /50x.html { - # root /usr/share/nginx/html; - #} - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - #location ~ \.php$ { - # fastcgi_split_path_info ^(.+\.php)(/.+)$; - # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini - # - # # With php5-cgi alone: - # fastcgi_pass 127.0.0.1:9000; - # # With php5-fpm: - # fastcgi_pass unix:/var/run/php5-fpm.sock; - # fastcgi_index index.php; - # include fastcgi_params; - #} - - # deny access to .htaccess files, if Apache's document root - # concurs with nginx's one - # - #location ~ /\.ht { - # deny all; - #} -} - - -# another virtual host using mix of IP-, name-, and port-based configuration -# -#server { -# listen 8000; -# listen somename:8080; -# server_name somename alias another.alias; -# root html; -# index index.html index.htm; -# -# location / { -# try_files $uri $uri/ =404; -# } -#} - - -# HTTPS server -# -#server { -# listen 443; -# server_name localhost; -# -# root html; -# index index.html index.htm; -# -# ssl on; -# ssl_certificate cert.pem; -# ssl_certificate_key cert.key; -# -# ssl_session_timeout 5m; -# -# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; -# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; -# ssl_prefer_server_ciphers on; -# -# location / { -# try_files $uri $uri/ =404; -# } -#} diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default deleted file mode 120000 index 6d9ba3371..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default +++ /dev/null @@ -1 +0,0 @@ -../sites-available/default \ No newline at end of file diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params deleted file mode 100644 index 3f72dbf0e..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params +++ /dev/null @@ -1,15 +0,0 @@ -uwsgi_param QUERY_STRING $query_string; -uwsgi_param REQUEST_METHOD $request_method; -uwsgi_param CONTENT_TYPE $content_type; -uwsgi_param CONTENT_LENGTH $content_length; - -uwsgi_param REQUEST_URI $request_uri; -uwsgi_param PATH_INFO $document_uri; -uwsgi_param DOCUMENT_ROOT $document_root; -uwsgi_param SERVER_PROTOCOL $server_protocol; -uwsgi_param UWSGI_SCHEME $scheme; - -uwsgi_param REMOTE_ADDR $remote_addr; -uwsgi_param REMOTE_PORT $remote_port; -uwsgi_param SERVER_PORT $server_port; -uwsgi_param SERVER_NAME $server_name; diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf b/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf deleted file mode 100644 index cd2885292..000000000 --- a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf +++ /dev/null @@ -1,125 +0,0 @@ -# This map is not a full windows-1251 <> utf8 map: it does not -# contain Serbian and Macedonian letters. If you need a full map, -# use contrib/unicode2nginx/win-utf map instead. - -charset_map windows-1251 utf-8 { - - 82 E2809A; # single low-9 quotation mark - - 84 E2809E; # double low-9 quotation mark - 85 E280A6; # ellipsis - 86 E280A0; # dagger - 87 E280A1; # double dagger - 88 E282AC; # euro - 89 E280B0; # per mille - - 91 E28098; # left single quotation mark - 92 E28099; # right single quotation mark - 93 E2809C; # left double quotation mark - 94 E2809D; # right double quotation mark - 95 E280A2; # bullet - 96 E28093; # en dash - 97 E28094; # em dash - - 99 E284A2; # trade mark sign - - A0 C2A0; #   - A1 D18E; # capital Byelorussian short U - A2 D19E; # small Byelorussian short u - - A4 C2A4; # currency sign - A5 D290; # capital Ukrainian soft G - A6 C2A6; # borken bar - A7 C2A7; # section sign - A8 D081; # capital YO - A9 C2A9; # (C) - AA D084; # capital Ukrainian YE - AB C2AB; # left-pointing double angle quotation mark - AC C2AC; # not sign - AD C2AD; # soft hyphen - AE C2AE; # (R) - AF D087; # capital Ukrainian YI - - B0 C2B0; # ° - B1 C2B1; # plus-minus sign - B2 D086; # capital Ukrainian I - B3 D196; # small Ukrainian i - B4 D291; # small Ukrainian soft g - B5 C2B5; # micro sign - B6 C2B6; # pilcrow sign - B7 C2B7; # · - B8 D191; # small yo - B9 E28496; # numero sign - BA D194; # small Ukrainian ye - BB C2BB; # right-pointing double angle quotation mark - - BF D197; # small Ukrainian yi - - C0 D090; # capital A - C1 D091; # capital B - C2 D092; # capital V - C3 D093; # capital G - C4 D094; # capital D - C5 D095; # capital YE - C6 D096; # capital ZH - C7 D097; # capital Z - C8 D098; # capital I - C9 D099; # capital J - CA D09A; # capital K - CB D09B; # capital L - CC D09C; # capital M - CD D09D; # capital N - CE D09E; # capital O - CF D09F; # capital P - - D0 D0A0; # capital R - D1 D0A1; # capital S - D2 D0A2; # capital T - D3 D0A3; # capital U - D4 D0A4; # capital F - D5 D0A5; # capital KH - D6 D0A6; # capital TS - D7 D0A7; # capital CH - D8 D0A8; # capital SH - D9 D0A9; # capital SHCH - DA D0AA; # capital hard sign - DB D0AB; # capital Y - DC D0AC; # capital soft sign - DD D0AD; # capital E - DE D0AE; # capital YU - DF D0AF; # capital YA - - E0 D0B0; # small a - E1 D0B1; # small b - E2 D0B2; # small v - E3 D0B3; # small g - E4 D0B4; # small d - E5 D0B5; # small ye - E6 D0B6; # small zh - E7 D0B7; # small z - E8 D0B8; # small i - E9 D0B9; # small j - EA D0BA; # small k - EB D0BB; # small l - EC D0BC; # small m - ED D0BD; # small n - EE D0BE; # small o - EF D0BF; # small p - - F0 D180; # small r - F1 D181; # small s - F2 D182; # small t - F3 D183; # small u - F4 D184; # small f - F5 D185; # small kh - F6 D186; # small ts - F7 D187; # small ch - F8 D188; # small sh - F9 D189; # small shch - FA D18A; # small hard sign - FB D18B; # small y - FC D18C; # small soft sign - FD D18D; # small e - FE D18E; # small yu - FF D18F; # small ya -} diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py deleted file mode 100644 index b238e6232..000000000 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Common utilities for certbot_nginx.""" -import copy -import shutil -import tempfile - -import josepy as jose -import mock -import pkg_resources -import zope.component - -from certbot import util -from certbot.compat import os -from certbot.plugins import common -from certbot.tests import util as test_util - -from certbot_nginx._internal import configurator -from certbot_nginx._internal import nginxparser - - -class NginxTest(test_util.ConfigTestCase): - - def setUp(self): - super(NginxTest, self).setUp() - - self.configuration = self.config - self.config = None - - self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( - "etc_nginx", "certbot_nginx.tests") - self.logs_dir = tempfile.mkdtemp('logs') - - self.config_path = os.path.join(self.temp_dir, "etc_nginx") - - self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( - "rsa512_key.pem")) - - def tearDown(self): - # Cleanup opened resources after a test. This is usually done through atexit handlers in - # Certbot, but during tests, atexit will not run registered functions before tearDown is - # called and instead will run them right before the entire test process exits. - # It is a problem on Windows, that does not accept to clean resources before closing them. - util._release_locks() # pylint: disable=protected-access - - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - shutil.rmtree(self.logs_dir) - - def get_nginx_configurator(self, config_path, config_dir, work_dir, logs_dir, - version=(1, 6, 2), openssl_version="1.0.2g"): - """Create an Nginx Configurator with the specified options.""" - - backups = os.path.join(work_dir, "backups") - - self.configuration.nginx_server_root = config_path - self.configuration.le_vhost_ext = "-le-ssl.conf" - self.configuration.config_dir = config_dir - self.configuration.work_dir = work_dir - self.configuration.logs_dir = logs_dir - self.configuration.backup_dir = backups - self.configuration.temp_checkpoint_dir = os.path.join(work_dir, "temp_checkpoints") - self.configuration.in_progress_dir = os.path.join(backups, "IN_PROGRESS") - self.configuration.server = "https://acme-server.org:443/new" - self.configuration.http01_port = 80 - self.configuration.https_port = 5001 - - with mock.patch("certbot_nginx._internal.configurator.NginxConfigurator." - "config_test"): - with mock.patch("certbot_nginx._internal.configurator.util." - "exe_exists") as mock_exe_exists: - mock_exe_exists.return_value = True - config = configurator.NginxConfigurator( - self.configuration, - name="nginx", - version=version, - openssl_version=openssl_version) - config.prepare() - - # Provide general config utility. - zope.component.provideUtility(self.configuration) - - return config - - -def get_data_filename(filename): - """Gets the filename of a test data file.""" - return pkg_resources.resource_filename( - "certbot_nginx.tests", os.path.join( - "testdata", "etc_nginx", filename)) - - -def filter_comments(tree): - """Filter comment nodes from parsed configurations.""" - - def traverse(tree): - """Generator dropping comment nodes""" - for entry in tree: - # key, values = entry - spaceless = [e for e in entry if not nginxparser.spacey(e)] - if spaceless: - key = spaceless[0] - values = spaceless[1] if len(spaceless) > 1 else None - else: - key = values = "" - if isinstance(key, list): - new = copy.deepcopy(entry) - new[1] = filter_comments(values) - yield new - else: - if key != '#' and spaceless: - yield spaceless - - return list(traverse(tree)) - - -def contains_at_depth(haystack, needle, n): - """Is the needle in haystack at depth n? - - Return true if the needle is present in one of the sub-iterables in haystack - at depth n. Haystack must be an iterable. - """ - # Specifically use hasattr rather than isinstance(..., collections.Iterable) - # because we want to include lists but reject strings. - if not hasattr(haystack, '__iter__') or hasattr(haystack, 'strip'): - return False - if n == 0: - return needle in haystack - for item in haystack: - if contains_at_depth(item, needle, n - 1): - return True - return False diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py new file mode 100644 index 000000000..9204d464d --- /dev/null +++ b/certbot-nginx/tests/configurator_test.py @@ -0,0 +1,1100 @@ +"""Test for certbot_nginx._internal.configurator.""" +import unittest + +import OpenSSL +import mock +from acme import challenges +from acme import messages + +from certbot import achallenges +from certbot import crypto_util +from certbot import errors +from certbot.compat import os +from certbot.tests import util as certbot_test_util + +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser +from certbot_nginx._internal.configurator import _redirect_block_for_domain +from certbot_nginx._internal.nginxparser import UnspacedList + +import test_util as util + + +class NginxConfiguratorTest(util.NginxTest): + """Test a semi complex vhost configuration.""" + + + def setUp(self): + super(NginxConfiguratorTest, self).setUp() + + self.config = self.get_nginx_configurator( + self.config_path, self.config_dir, self.work_dir, self.logs_dir) + + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") + def test_prepare_no_install(self, mock_exe_exists): + mock_exe_exists.return_value = False + self.assertRaises( + errors.NoInstallationError, self.config.prepare) + + def test_prepare(self): + self.assertEqual((1, 6, 2), self.config.version) + self.assertEqual(11, len(self.config.parser.parsed)) + + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_prepare_initializes_version(self, mock_popen, mock_exe_exists): + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/1.6.2", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "TLS SNI support enabled", + "configure arguments: --prefix=/usr/local/Cellar/" + "nginx/1.6.2 --with-http_ssl_module"])) + + mock_exe_exists.return_value = True + + self.config.version = None + self.config.config_test = mock.Mock() + self.config.prepare() + self.assertEqual((1, 6, 2), self.config.version) + + def test_prepare_locked(self): + server_root = self.config.conf("server-root") + + from certbot import util as certbot_util + certbot_util._LOCKS[server_root].release() # pylint: disable=protected-access + + self.config.config_test = mock.Mock() + certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) + + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") + def _test_prepare_locked(self, unused_exe_exists): + try: + self.config.prepare() + except errors.PluginError as err: + err_msg = str(err) + self.assertTrue("lock" in err_msg) + self.assertTrue(self.config.conf("server-root") in err_msg) + else: # pragma: no cover + self.fail("Exception wasn't raised!") + + @mock.patch("certbot_nginx._internal.configurator.socket.gethostbyaddr") + def test_get_all_names(self, mock_gethostbyaddr): + mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) + names = self.config.get_all_names() + self.assertEqual(names, { + "155.225.50.69.nephoscale.net", "www.example.org", "another.alias", + "migration.com", "summer.com", "geese.com", "sslon.com", + "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com", + "headers.com"}) + + def test_supported_enhancements(self): + self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'], + self.config.supported_enhancements()) + + def test_enhance(self): + self.assertRaises( + errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') + + def test_get_chall_pref(self): + self.assertEqual([challenges.HTTP01], + self.config.get_chall_pref('myhost')) + + def test_save(self): + filep = self.config.parser.abs_path('sites-enabled/example.com') + mock_vhost = obj.VirtualHost(filep, + None, None, None, + set(['.example.com', 'example.*']), + None, [0]) + self.config.parser.add_server_directives( + mock_vhost, + [['listen', ' ', '5001', ' ', 'ssl']]) + self.config.save() + + # pylint: disable=protected-access + parsed = self.config.parser._parse_files(filep, override=True) + self.assertEqual([[['server'], + [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + ['listen', '5001', 'ssl'], + ['#', parser.COMMENT]]]], + parsed[0]) + + def test_choose_vhosts_alias(self): + self._test_choose_vhosts_common('alias', 'server_conf') + + def test_choose_vhosts_example_com(self): + self._test_choose_vhosts_common('example.com', 'example_conf') + + def test_choose_vhosts_localhost(self): + self._test_choose_vhosts_common('localhost', 'localhost_conf') + + def test_choose_vhosts_example_com_uk_test(self): + self._test_choose_vhosts_common('example.com.uk.test', 'example_conf') + + def test_choose_vhosts_www_example_com(self): + self._test_choose_vhosts_common('www.example.com', 'example_conf') + + def test_choose_vhosts_test_www_example_com(self): + self._test_choose_vhosts_common('test.www.example.com', 'foo_conf') + + def test_choose_vhosts_abc_www_foo_com(self): + self._test_choose_vhosts_common('abc.www.foo.com', 'foo_conf') + + def test_choose_vhosts_www_bar_co_uk(self): + self._test_choose_vhosts_common('www.bar.co.uk', 'localhost_conf') + + def test_choose_vhosts_ipv6_com(self): + self._test_choose_vhosts_common('ipv6.com', 'ipv6_conf') + + def _test_choose_vhosts_common(self, name, conf): + conf_names = {'localhost_conf': set(['localhost', r'~^(www\.)?(example|bar)\.']), + 'server_conf': set(['somename', 'another.alias', 'alias']), + 'example_conf': set(['.example.com', 'example.*']), + 'foo_conf': set(['*.www.foo.com', '*.www.example.com']), + 'ipv6_conf': set(['ipv6.com'])} + + conf_path = {'localhost': "etc_nginx/nginx.conf", + 'alias': "etc_nginx/nginx.conf", + 'example.com': "etc_nginx/sites-enabled/example.com", + 'example.com.uk.test': "etc_nginx/sites-enabled/example.com", + 'www.example.com': "etc_nginx/sites-enabled/example.com", + 'test.www.example.com': "etc_nginx/foo.conf", + 'abc.www.foo.com': "etc_nginx/foo.conf", + 'www.bar.co.uk': "etc_nginx/nginx.conf", + 'ipv6.com': "etc_nginx/sites-enabled/ipv6.com"} + conf_path = {key: os.path.normpath(value) for key, value in conf_path.items()} + + vhost = self.config.choose_vhosts(name)[0] + path = os.path.relpath(vhost.filep, self.temp_dir) + + self.assertEqual(conf_names[conf], vhost.names) + self.assertEqual(conf_path[name], path) + # IPv6 specific checks + if name == "ipv6.com": + self.assertTrue(vhost.ipv6_enabled()) + # Make sure that we have SSL enabled also for IPv6 addr + self.assertTrue( + any([True for x in vhost.addrs if x.ssl and x.ipv6])) + + def test_choose_vhosts_bad(self): + bad_results = ['www.foo.com', 'example', 't.www.bar.co', + '69.255.225.155'] + + for name in bad_results: + self.assertRaises(errors.MisconfigurationError, + self.config.choose_vhosts, name) + + def test_ipv6only(self): + # ipv6_info: (ipv6_active, ipv6only_present) + self.assertEqual((True, False), self.config.ipv6_info("80")) + # Port 443 has ipv6only=on because of ipv6ssl.com vhost + self.assertEqual((True, True), self.config.ipv6_info("443")) + + def test_ipv6only_detection(self): + self.config.version = (1, 3, 1) + + self.config.deploy_cert( + "ipv6.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + + for addr in self.config.choose_vhosts("ipv6.com")[0].addrs: + self.assertFalse(addr.ipv6only) + + def test_more_info(self): + self.assertTrue('nginx.conf' in self.config.more_info()) + + def test_deploy_cert_requires_fullchain_path(self): + self.config.version = (1, 3, 1) + self.assertRaises(errors.PluginError, self.config.deploy_cert, + "www.example.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + None) + + @mock.patch('certbot_nginx._internal.parser.NginxParser.update_or_add_server_directives') + def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): + mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() + self.assertRaises( + errors.PluginError, + self.config.deploy_cert, + "migration.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + + def test_deploy_cert(self): + server_conf = self.config.parser.abs_path('server.conf') + nginx_conf = self.config.parser.abs_path('nginx.conf') + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.version = (1, 3, 1) + + # Get the default SSL vhost + self.config.deploy_cert( + "www.example.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.deploy_cert( + "another.alias", + "/etc/nginx/cert.pem", + "/etc/nginx/key.pem", + "/etc/nginx/chain.pem", + "/etc/nginx/fullchain.pem") + self.config.save() + + self.config.parser.load() + + parsed_example_conf = util.filter_comments(self.config.parser.parsed[example_conf]) + parsed_server_conf = util.filter_comments(self.config.parser.parsed[server_conf]) + parsed_nginx_conf = util.filter_comments(self.config.parser.parsed[nginx_conf]) + + self.assertEqual([[['server'], + [ + ['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + + ['listen', '5001', 'ssl'], + ['ssl_certificate', 'example/fullchain.pem'], + ['ssl_certificate_key', 'example/key.pem'], + ['include', self.config.mod_ssl_conf], + ['ssl_dhparam', self.config.ssl_dhparams], + ]]], + parsed_example_conf) + self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], + parsed_server_conf) + self.assertTrue(util.contains_at_depth( + parsed_nginx_conf, + [['server'], + [ + ['listen', '8000'], + ['listen', 'somename:8080'], + ['include', 'server.conf'], + [['location', '/'], + [['root', 'html'], + ['index', 'index.html', 'index.htm']]], + ['listen', '5001', 'ssl'], + ['ssl_certificate', '/etc/nginx/fullchain.pem'], + ['ssl_certificate_key', '/etc/nginx/key.pem'], + ['include', self.config.mod_ssl_conf], + ['ssl_dhparam', self.config.ssl_dhparams], + ]], + 2)) + + def test_deploy_cert_add_explicit_listen(self): + migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') + self.config.deploy_cert( + "summer.com", + "summer/cert.pem", + "summer/key.pem", + "summer/chain.pem", + "summer/fullchain.pem") + self.config.save() + self.config.parser.load() + parsed_migration_conf = util.filter_comments(self.config.parser.parsed[migration_conf]) + self.assertEqual([['server'], + [ + ['server_name', 'migration.com'], + ['server_name', 'summer.com'], + + ['listen', '80'], + ['listen', '5001', 'ssl'], + ['ssl_certificate', 'summer/fullchain.pem'], + ['ssl_certificate_key', 'summer/key.pem'], + ['include', self.config.mod_ssl_conf], + ['ssl_dhparam', self.config.ssl_dhparams], + ]], + parsed_migration_conf[0]) + + @mock.patch("certbot_nginx._internal.configurator.http_01.NginxHttp01.perform") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.restart") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.revert_challenge_config") + def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): + # Only tests functionality specific to configurator.perform + # Note: As more challenges are offered this will have to be expanded + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=messages.ChallengeBody( + chall=challenges.HTTP01(token=b"m8TdO1qik4JVFtgPPurJmg"), + uri="https://ca.org/chall1_uri", + status=messages.Status("pending"), + ), domain="example.com", account_key=self.rsa512jwk) + + expected = [ + achall.response(self.rsa512jwk), + ] + + mock_http_perform.return_value = expected[:] + responses = self.config.perform([achall]) + + self.assertEqual(mock_http_perform.call_count, 1) + self.assertEqual(responses, expected) + + self.config.cleanup([achall]) + self.assertEqual(0, self.config._chall_out) # pylint: disable=protected-access + self.assertEqual(mock_revert.call_count, 1) + self.assertEqual(mock_restart.call_count, 2) + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_get_version(self, mock_popen): + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/1.4.2", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "TLS SNI support enabled", + "configure arguments: --prefix=/usr/local/Cellar/" + "nginx/1.6.2 --with-http_ssl_module"])) + self.assertEqual(self.config.get_version(), (1, 4, 2)) + + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/0.9", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "TLS SNI support enabled", + "configure arguments: --with-http_ssl_module"])) + self.assertEqual(self.config.get_version(), (0, 9)) + + mock_popen().communicate.return_value = ( + "", "\n".join(["blah 0.0.1", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "TLS SNI support enabled", + "configure arguments: --with-http_ssl_module"])) + self.assertRaises(errors.PluginError, self.config.get_version) + + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/1.4.2", + "TLS SNI support enabled"])) + self.assertRaises(errors.PluginError, self.config.get_version) + + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/1.4.2", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "configure arguments: --with-http_ssl_module"])) + self.assertRaises(errors.PluginError, self.config.get_version) + + mock_popen().communicate.return_value = ( + "", "\n".join(["nginx version: nginx/0.8.1", + "built by clang 6.0 (clang-600.0.56)" + " (based on LLVM 3.5svn)", + "TLS SNI support enabled", + "configure arguments: --with-http_ssl_module"])) + self.assertRaises(errors.NotSupportedError, self.config.get_version) + + mock_popen.side_effect = OSError("Can't find program") + self.assertRaises(errors.PluginError, self.config.get_version) + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_get_openssl_version(self, mock_popen): + # pylint: disable=protected-access + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2g") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2-beta1 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 (running with OpenSSL 1.0.2a 1 Mar 2016) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2a") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with LibreSSL 2.2.2 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_nginx_restart(self, mock_popen): + mocked = mock_popen() + mocked.communicate.return_value = ('', '') + mocked.returncode = 0 + self.config.restart() + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_nginx_restart_fail(self, mock_popen): + mocked = mock_popen() + mocked.communicate.return_value = ('', '') + mocked.returncode = 1 + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_no_nginx_start(self, mock_popen): + mock_popen.side_effect = OSError("Can't find program") + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + @mock.patch("certbot.util.run_script") + def test_config_test_bad_process(self, mock_run_script): + mock_run_script.side_effect = errors.SubprocessError + self.assertRaises(errors.MisconfigurationError, self.config.config_test) + + @mock.patch("certbot.util.run_script") + def test_config_test(self, _): + self.config.config_test() + + @mock.patch("certbot.reverter.Reverter.recovery_routine") + def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine): + mock_recovery_routine.side_effect = errors.ReverterError("foo") + self.assertRaises(errors.PluginError, self.config.recovery_routine) + + @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") + def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): + mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") + self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) + + @mock.patch("certbot.reverter.Reverter.revert_temporary_config") + def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config): + mock_revert_temporary_config.side_effect = errors.ReverterError("foo") + self.assertRaises(errors.PluginError, self.config.revert_challenge_config) + + @mock.patch("certbot.reverter.Reverter.add_to_checkpoint") + def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint): + mock_add_to_checkpoint.side_effect = errors.ReverterError("foo") + self.assertRaises(errors.PluginError, self.config.save) + + def test_get_snakeoil_paths(self): + # pylint: disable=protected-access + cert, key = self.config._get_snakeoil_paths() + self.assertTrue(os.path.exists(cert)) + self.assertTrue(os.path.exists(key)) + with open(cert) as cert_file: + OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert_file.read()) + with open(key) as key_file: + OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, key_file.read()) + + def test_redirect_enhance(self): + # Test that we successfully add a redirect when there is + # a listen directive + expected = UnspacedList(_redirect_block_for_domain("www.example.com"))[0] + + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.enhance("www.example.com", "redirect") + + generated_conf = self.config.parser.parsed[example_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + + # Test that we successfully add a redirect when there is + # no listen directive + migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') + self.config.enhance("migration.com", "redirect") + + expected = UnspacedList(_redirect_block_for_domain("migration.com"))[0] + + generated_conf = self.config.parser.parsed[migration_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + + def test_split_for_redirect(self): + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.deploy_cert( + "example.org", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.enhance("www.example.com", "redirect") + generated_conf = self.config.parser.parsed[example_conf] + self.assertEqual( + [[['server'], [ + ['server_name', '.example.com'], + ['server_name', 'example.*'], [], + ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], + ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], + ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], + ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], + ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], + [], []]], + [['server'], [ + [['if', '($host', '=', 'www.example.com)'], [ + ['return', '301', 'https://$host$request_uri']]], + ['#', ' managed by Certbot'], [], + ['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + ['return', '404'], ['#', ' managed by Certbot'], [], [], []]]], + generated_conf) + + def test_split_for_headers(self): + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.deploy_cert( + "example.org", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security") + generated_conf = self.config.parser.parsed[example_conf] + self.assertEqual( + [[['server'], [ + ['server_name', '.example.com'], + ['server_name', 'example.*'], [], + ['listen', '5001', 'ssl'], ['#', ' managed by Certbot'], + ['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'], + ['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'], + ['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'], + ['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'], + [], [], + ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'], + ['#', ' managed by Certbot'], + [], []]], + [['server'], [ + ['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + [], [], []]]], + generated_conf) + + def test_http_header_hsts(self): + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.enhance("www.example.com", "ensure-http-header", + "Strict-Transport-Security") + expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] + generated_conf = self.config.parser.parsed[example_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + + def test_multiple_headers_hsts(self): + headers_conf = self.config.parser.abs_path('sites-enabled/headers.com') + self.config.enhance("headers.com", "ensure-http-header", + "Strict-Transport-Security") + expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'] + generated_conf = self.config.parser.parsed[headers_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + + def test_http_header_hsts_twice(self): + self.config.enhance("www.example.com", "ensure-http-header", + "Strict-Transport-Security") + self.assertRaises( + errors.PluginEnhancementAlreadyPresent, + self.config.enhance, "www.example.com", + "ensure-http-header", "Strict-Transport-Security") + + + @mock.patch('certbot_nginx._internal.obj.VirtualHost.contains_list') + def test_certbot_redirect_exists(self, mock_contains_list): + # Test that we add no redirect statement if there is already a + # redirect in the block that is managed by certbot + # Has a certbot redirect + mock_contains_list.return_value = True + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: + self.config.enhance("www.example.com", "redirect") + self.assertEqual(mock_logger.info.call_args[0][0], + "Traffic on port %s already redirecting to ssl in %s") + + def test_redirect_dont_enhance(self): + # Test that we don't accidentally add redirect to ssl-only block + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: + self.config.enhance("geese.com", "redirect") + self.assertEqual(mock_logger.info.call_args[0][0], + 'No matching insecure server blocks listening on port %s found.') + + def test_double_redirect(self): + # Test that we add one redirect for each domain + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + self.config.enhance("example.com", "redirect") + self.config.enhance("example.org", "redirect") + + expected1 = UnspacedList(_redirect_block_for_domain("example.com"))[0] + expected2 = UnspacedList(_redirect_block_for_domain("example.org"))[0] + + generated_conf = self.config.parser.parsed[example_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected1, 2)) + self.assertTrue(util.contains_at_depth(generated_conf, expected2, 2)) + + def test_staple_ocsp_bad_version(self): + self.config.version = (1, 3, 1) + self.assertRaises(errors.PluginError, self.config.enhance, + "www.example.com", "staple-ocsp", "chain_path") + + def test_staple_ocsp_no_chain_path(self): + self.assertRaises(errors.PluginError, self.config.enhance, + "www.example.com", "staple-ocsp", None) + + def test_staple_ocsp_internal_error(self): + self.config.enhance("www.example.com", "staple-ocsp", "chain_path") + # error is raised because the server block has conflicting directives + self.assertRaises(errors.PluginError, self.config.enhance, + "www.example.com", "staple-ocsp", "different_path") + + def test_staple_ocsp(self): + chain_path = "example/chain.pem" + self.config.enhance("www.example.com", "staple-ocsp", chain_path) + + example_conf = self.config.parser.abs_path('sites-enabled/example.com') + generated_conf = self.config.parser.parsed[example_conf] + + self.assertTrue(util.contains_at_depth( + generated_conf, + ['ssl_trusted_certificate', 'example/chain.pem'], 2)) + self.assertTrue(util.contains_at_depth( + generated_conf, ['ssl_stapling', 'on'], 2)) + self.assertTrue(util.contains_at_depth( + generated_conf, ['ssl_stapling_verify', 'on'], 2)) + + def test_deploy_no_match_default_set(self): + default_conf = self.config.parser.abs_path('sites-enabled/default') + foo_conf = self.config.parser.abs_path('foo.conf') + del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server + self.config.version = (1, 3, 1) + + self.config.deploy_cert( + "www.nomatch.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.save() + + self.config.parser.load() + + parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) + + self.assertEqual([[['server'], + [['listen', 'myhost', 'default_server'], + ['listen', 'otherhost', 'default_server'], + ['server_name', '"www.example.org"'], + [['location', '/'], + [['root', 'html'], + ['index', 'index.html', 'index.htm']]]]], + [['server'], + [['listen', 'myhost'], + ['listen', 'otherhost'], + ['server_name', 'www.nomatch.com'], + [['location', '/'], + [['root', 'html'], + ['index', 'index.html', 'index.htm']]], + ['listen', '5001', 'ssl'], + ['ssl_certificate', 'example/fullchain.pem'], + ['ssl_certificate_key', 'example/key.pem'], + ['include', self.config.mod_ssl_conf], + ['ssl_dhparam', self.config.ssl_dhparams]]]], + parsed_default_conf) + + self.config.deploy_cert( + "nomatch.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.save() + + self.config.parser.load() + + parsed_default_conf = util.filter_comments(self.config.parser.parsed[default_conf]) + + self.assertTrue(util.contains_at_depth(parsed_default_conf, "nomatch.com", 3)) + + def test_deploy_no_match_default_set_multi_level_path(self): + default_conf = self.config.parser.abs_path('sites-enabled/default') + foo_conf = self.config.parser.abs_path('foo.conf') + del self.config.parser.parsed[default_conf][0][1][0] + del self.config.parser.parsed[default_conf][0][1][0] + self.config.version = (1, 3, 1) + + self.config.deploy_cert( + "www.nomatch.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + self.config.save() + + self.config.parser.load() + + parsed_foo_conf = util.filter_comments(self.config.parser.parsed[foo_conf]) + + self.assertEqual([['server'], + [['listen', '*:80', 'ssl'], + ['server_name', 'www.nomatch.com'], + ['root', '/home/ubuntu/sites/foo/'], + [['location', '/status'], [[['types'], [['image/jpeg', 'jpg']]]]], + [['location', '~', 'case_sensitive\\.php$'], [['index', 'index.php'], + ['root', '/var/root']]], + [['location', '~*', 'case_insensitive\\.php$'], []], + [['location', '=', 'exact_match\\.php$'], []], + [['location', '^~', 'ignore_regex\\.php$'], []], + ['ssl_certificate', 'example/fullchain.pem'], + ['ssl_certificate_key', 'example/key.pem']]], + parsed_foo_conf[1][1][1]) + + def test_deploy_no_match_no_default_set(self): + default_conf = self.config.parser.abs_path('sites-enabled/default') + foo_conf = self.config.parser.abs_path('foo.conf') + del self.config.parser.parsed[default_conf][0][1][0] + del self.config.parser.parsed[default_conf][0][1][0] + del self.config.parser.parsed[foo_conf][2][1][0][1][0] + self.config.version = (1, 3, 1) + + self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, + "www.nomatch.com", "example/cert.pem", "example/key.pem", + "example/chain.pem", "example/fullchain.pem") + + def test_deploy_no_match_fail_multiple_defaults(self): + self.config.version = (1, 3, 1) + self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, + "www.nomatch.com", "example/cert.pem", "example/key.pem", + "example/chain.pem", "example/fullchain.pem") + + def test_deploy_no_match_multiple_defaults_ok(self): + foo_conf = self.config.parser.abs_path('foo.conf') + self.config.parser.parsed[foo_conf][2][1][0][1][0][1] = '*:5001' + self.config.version = (1, 3, 1) + self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", + "example/chain.pem", "example/fullchain.pem") + + def test_deploy_no_match_add_redirect(self): + default_conf = self.config.parser.abs_path('sites-enabled/default') + foo_conf = self.config.parser.abs_path('foo.conf') + del self.config.parser.parsed[foo_conf][2][1][0][1][0] # remove default_server + self.config.version = (1, 3, 1) + + self.config.deploy_cert( + "www.nomatch.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + + self.config.deploy_cert( + "nomatch.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + "example/fullchain.pem") + + self.config.enhance("www.nomatch.com", "redirect") + + self.config.save() + + self.config.parser.load() + + expected = UnspacedList(_redirect_block_for_domain("www.nomatch.com"))[0] + + generated_conf = self.config.parser.parsed[default_conf] + self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) + + @mock.patch('certbot.reverter.logger') + @mock.patch('certbot_nginx._internal.parser.NginxParser.load') + def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger): + self.config.recovery_routine() + self.config.revert_challenge_config() + self.config.rollback_checkpoints() + self.assertTrue(mock_parser_load.call_count == 3) + + def test_choose_vhosts_wildcard(self): + # pylint: disable=protected-access + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + vhost = [x for x in self.config.parser.get_vhosts() + if 'summer.com' in x.names][0] + mock_select_vhs.return_value = [vhost] + vhs = self.config._choose_vhosts_wildcard("*.com", + prefer_ssl=True) + # Check that the dialog was called with migration.com + self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) + + # And the actual returned values + self.assertEqual(len(vhs), 1) + self.assertEqual(vhs[0], vhost) + + def test_choose_vhosts_wildcard_redirect(self): + # pylint: disable=protected-access + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + vhost = [x for x in self.config.parser.get_vhosts() + if 'summer.com' in x.names][0] + mock_select_vhs.return_value = [vhost] + vhs = self.config._choose_vhosts_wildcard("*.com", + prefer_ssl=False) + # Check that the dialog was called with migration.com + self.assertTrue(vhost in mock_select_vhs.call_args[0][0]) + + # And the actual returned values + self.assertEqual(len(vhs), 1) + self.assertEqual(vhs[0], vhost) + + def test_deploy_cert_wildcard(self): + # pylint: disable=protected-access + mock_choose_vhosts = mock.MagicMock() + vhost = [x for x in self.config.parser.get_vhosts() + if 'geese.com' in x.names][0] + mock_choose_vhosts.return_value = [vhost] + self.config._choose_vhosts_wildcard = mock_choose_vhosts + mock_d = "certbot_nginx._internal.configurator.NginxConfigurator._deploy_cert" + with mock.patch(mock_d) as mock_dep: + self.config.deploy_cert("*.com", "/tmp/path", + "/tmp/path", "/tmp/path", "/tmp/path") + self.assertTrue(mock_dep.called) + self.assertEqual(len(mock_dep.call_args_list), 1) + self.assertEqual(vhost, mock_dep.call_args_list[0][0][0]) + + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") + def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): + # pylint: disable=protected-access + mock_dialog.return_value = [] + self.assertRaises(errors.PluginError, + self.config.deploy_cert, + "*.wild.cat", "/tmp/path", "/tmp/path", + "/tmp/path", "/tmp/path") + + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") + def test_enhance_wildcard_ocsp_after_install(self, mock_dialog): + # pylint: disable=protected-access + vhost = [x for x in self.config.parser.get_vhosts() + if 'geese.com' in x.names][0] + self.config._wildcard_vhosts["*.com"] = [vhost] + self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") + self.assertFalse(mock_dialog.called) + + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") + def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): + vhost = [x for x in self.config.parser.get_vhosts() + if 'summer.com' in x.names][0] + mock_dialog.return_value = [vhost] + self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") + self.assertTrue(mock_dialog.called) + + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") + def test_enhance_wildcard_double_redirect(self, mock_dialog): + # pylint: disable=protected-access + vhost = [x for x in self.config.parser.get_vhosts() + if 'summer.com' in x.names][0] + self.config._wildcard_redirect_vhosts["*.com"] = [vhost] + self.config.enhance("*.com", "redirect") + self.assertFalse(mock_dialog.called) + + def test_choose_vhosts_wildcard_no_ssl_filter_port(self): + # pylint: disable=protected-access + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + mock_select_vhs.return_value = [] + self.config._choose_vhosts_wildcard("*.com", + prefer_ssl=False, + no_ssl_filter_port='80') + # Check that the dialog was called with only port 80 vhosts + self.assertEqual(len(mock_select_vhs.call_args[0][0]), 5) + + +class InstallSslOptionsConfTest(util.NginxTest): + """Test that the options-ssl-nginx.conf file is installed and updated properly.""" + + def setUp(self): + super(InstallSslOptionsConfTest, self).setUp() + + self.config = self.get_nginx_configurator( + self.config_path, self.config_dir, self.work_dir, self.logs_dir) + + def _call(self): + self.config.install_ssl_options_conf(self.config.mod_ssl_conf, + self.config.updated_mod_ssl_conf_digest) + + def _current_ssl_options_hash(self): + return crypto_util.sha256sum(self.config.mod_ssl_conf_src) + + def _assert_current_file(self): + self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), + self._current_ssl_options_hash()) + + def test_no_file(self): + # prepare should have placed a file there + self._assert_current_file() + os.remove(self.config.mod_ssl_conf) + self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) + self._call() + self._assert_current_file() + + def test_current_file(self): + self._assert_current_file() + self._call() + self._assert_current_file() + + def _mock_hash_except_ssl_conf_src(self, fake_hash): + # Write a bad file in place so that update tests fail if no update occurs. + # We're going to pretend this file (the currently installed conf file) + # actually hashes to `fake_hash` for the update tests. + with open(self.config.mod_ssl_conf, "w") as f: + f.write("bogus") + sha256 = crypto_util.sha256sum + def _hash(filename): + return sha256(filename) if filename == self.config.mod_ssl_conf_src else fake_hash + return _hash + + def test_prev_file_updates_to_current(self): + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES + with mock.patch('certbot.crypto_util.sha256sum', + new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): + self._call() + self._assert_current_file() + + def test_prev_file_updates_to_current_old_nginx(self): + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES + self.config.version = (1, 5, 8) + with mock.patch('certbot.crypto_util.sha256sum', + new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): + self._call() + self._assert_current_file() + + def test_manually_modified_current_file_does_not_update(self): + with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), + self._current_ssl_options_hash()) + self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), + self._current_ssl_options_hash()) + + def test_manually_modified_past_file_warns(self): + with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with open(self.config.updated_mod_ssl_conf_digest, "w") as f: + f.write("hashofanoldversion") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertEqual(mock_logger.warning.call_args[0][0], + "%s has been manually modified; updated file " + "saved to %s. We recommend updating %s for security purposes.") + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src), + self._current_ssl_options_hash()) + # only print warning once + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + + def test_current_file_hash_in_all_hashes(self): + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES + self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended" + " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") + + def test_ssl_config_files_hash_in_all_hashes(self): + """ + It is really critical that all TLS Nginx config files have their SHA256 hash registered in + constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config + file has been manually edited by the user, and will refuse to update it. + This test ensures that all necessary hashes are present. + """ + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES + import pkg_resources + all_files = [ + pkg_resources.resource_filename("certbot_nginx", + os.path.join("_internal", "tls_configs", x)) + for x in ("options-ssl-nginx.conf", + "options-ssl-nginx-old.conf", + "options-ssl-nginx-tls12-only.conf") + ] + self.assertTrue(all_files) + for one_file in all_files: + file_hash = crypto_util.sha256sum(one_file) + self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + "hash of {0} when it is updated.".format(one_file)) + + def test_nginx_version_uses_correct_config(self): + self.config.version = (1, 5, 8) + self.config.openssl_version = "1.0.2g" # shouldn't matter + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-old.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 5, 9) + self.config.openssl_version = "1.0.2l" + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls12-only.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) + self.config.openssl_version = "1.0.2k" + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls13-session-tix-on.conf") + + +class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): + """Tests for certbot_nginx._internal.configurator._determine_default_server_root.""" + + def _call(self): + from certbot_nginx._internal.configurator import _determine_default_server_root + return _determine_default_server_root() + + @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) + def test_docs_value(self): + self._test(expect_both_values=True) + + @mock.patch.dict(os.environ, {}) + def test_real_values(self): + self._test(expect_both_values=False) + + def _test(self, expect_both_values): + server_root = self._call() + + if expect_both_values: + self.assertIn("/usr/local/etc/nginx", server_root) + self.assertIn("/etc/nginx", server_root) + else: + self.assertTrue(server_root == "/etc/nginx" or server_root == "/usr/local/etc/nginx") + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-nginx/tests/display_ops_test.py b/certbot-nginx/tests/display_ops_test.py new file mode 100644 index 000000000..e8a3c81d3 --- /dev/null +++ b/certbot-nginx/tests/display_ops_test.py @@ -0,0 +1,46 @@ +"""Test certbot_nginx._internal.display_ops.""" +import unittest + +from certbot.display import util as display_util + +from certbot.tests import util as certbot_util + +from certbot_nginx._internal import parser + +from certbot_nginx._internal.display_ops import select_vhost_multiple + +import test_util as util + + +class SelectVhostMultiTest(util.NginxTest): + """Tests for certbot_nginx._internal.display_ops.select_vhost_multiple.""" + + def setUp(self): + super(SelectVhostMultiTest, self).setUp() + nparser = parser.NginxParser(self.config_path) + self.vhosts = nparser.get_vhosts() + + def test_select_no_input(self): + self.assertFalse(select_vhost_multiple([])) + + @certbot_util.patch_get_utility() + def test_select_correct(self, mock_util): + mock_util().checklist.return_value = ( + display_util.OK, [self.vhosts[3].display_repr(), + self.vhosts[2].display_repr()]) + vhs = select_vhost_multiple([self.vhosts[3], + self.vhosts[2], + self.vhosts[1]]) + self.assertTrue(self.vhosts[2] in vhs) + self.assertTrue(self.vhosts[3] in vhs) + self.assertFalse(self.vhosts[1] in vhs) + + @certbot_util.patch_get_utility() + def test_select_cancel(self, mock_util): + mock_util().checklist.return_value = (display_util.CANCEL, "whatever") + vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) + self.assertFalse(vhs) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py new file mode 100644 index 000000000..8473d2038 --- /dev/null +++ b/certbot-nginx/tests/http_01_test.py @@ -0,0 +1,147 @@ +"""Tests for certbot_nginx._internal.http_01""" +import unittest + +import josepy as jose +import mock +import six + +from acme import challenges + +from certbot import achallenges + +from certbot.tests import acme_util +from certbot.tests import util as test_util + +from certbot_nginx._internal.obj import Addr + +import test_util as util + +AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) + + +class HttpPerformTest(util.NginxTest): + """Test the NginxHttp01 challenge.""" + + account_key = AUTH_KEY + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=b"kNdwjwOeX0I_A8DXt9Msmg"), "pending"), + domain="www.example.com", account_key=account_key), + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01( + token=b"\xba\xa9\xda?= 1 and + x[0] == 2, + lambda x, y, pts=paths: pts.append(y)) + self.assertEqual(paths, result) + + def test_get_vhosts_global_ssl(self): + nparser = parser.NginxParser(self.config_path) + vhosts = nparser.get_vhosts() + + vhost = obj.VirtualHost(nparser.abs_path('sites-enabled/globalssl.com'), + [obj.Addr('4.8.2.6', '57', True, False, + False, False)], + True, True, set(['globalssl.com']), [], [0]) + + globalssl_com = [x for x in vhosts if 'globalssl.com' in x.filep][0] + self.assertEqual(vhost, globalssl_com) + + def test_get_vhosts(self): + nparser = parser.NginxParser(self.config_path) + vhosts = nparser.get_vhosts() + + vhost1 = obj.VirtualHost(nparser.abs_path('nginx.conf'), + [obj.Addr('', '8080', False, False, + False, False)], + False, True, + set(['localhost', + r'~^(www\.)?(example|bar)\.']), + [], [10, 1, 9]) + vhost2 = obj.VirtualHost(nparser.abs_path('nginx.conf'), + [obj.Addr('somename', '8080', False, False, + False, False), + obj.Addr('', '8000', False, False, + False, False)], + False, True, + set(['somename', 'another.alias', 'alias']), + [], [10, 1, 12]) + vhost3 = obj.VirtualHost(nparser.abs_path('sites-enabled/example.com'), + [obj.Addr('69.50.225.155', '9000', + False, False, False, False), + obj.Addr('127.0.0.1', '', False, False, + False, False)], + False, True, + set(['.example.com', 'example.*']), [], [0]) + vhost4 = obj.VirtualHost(nparser.abs_path('sites-enabled/default'), + [obj.Addr('myhost', '', False, True, + False, False), + obj.Addr('otherhost', '', False, True, + False, False)], + False, True, set(['www.example.org']), + [], [0]) + vhost5 = obj.VirtualHost(nparser.abs_path('foo.conf'), + [obj.Addr('*', '80', True, True, + False, False)], + True, True, set(['*.www.foo.com', + '*.www.example.com']), + [], [2, 1, 0]) + + self.assertEqual(13, len(vhosts)) + example_com = [x for x in vhosts if 'example.com' in x.filep][0] + self.assertEqual(vhost3, example_com) + default = [x for x in vhosts if 'default' in x.filep][0] + self.assertEqual(vhost4, default) + fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0] + self.assertEqual(vhost5, fooconf) + localhost = [x for x in vhosts if 'localhost' in x.names][0] + self.assertEqual(vhost1, localhost) + somename = [x for x in vhosts if 'somename' in x.names][0] + self.assertEqual(vhost2, somename) + + def test_has_ssl_on_directive(self): + nparser = parser.NginxParser(self.config_path) + mock_vhost = obj.VirtualHost(None, None, None, None, None, + [['listen', 'myhost default_server'], + ['server_name', 'www.example.org'], + [['location', '/'], [['root', 'html'], ['index', 'index.html index.htm']]] + ], None) + self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + mock_vhost.raw = [['listen', '*:80', 'default_server', 'ssl'], + ['server_name', '*.www.foo.com', '*.www.example.com'], + ['root', '/home/ubuntu/sites/foo/']] + self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + mock_vhost.raw = [['listen', '80 ssl'], + ['server_name', '*.www.foo.com', '*.www.example.com']] + self.assertFalse(nparser.has_ssl_on_directive(mock_vhost)) + mock_vhost.raw = [['listen', '80'], + ['ssl', 'on'], + ['server_name', '*.www.foo.com', '*.www.example.com']] + self.assertTrue(nparser.has_ssl_on_directive(mock_vhost)) + + + def test_remove_server_directives(self): + nparser = parser.NginxParser(self.config_path) + mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), + None, None, None, + set(['localhost', + r'~^(www\.)?(example|bar)\.']), + None, [10, 1, 9]) + example_com = nparser.abs_path('sites-enabled/example.com') + names = set(['.example.com', 'example.*']) + mock_vhost.filep = example_com + mock_vhost.names = names + mock_vhost.path = [0] + nparser.add_server_directives(mock_vhost, + [['foo', 'bar'], ['ssl_certificate', + '/etc/ssl/cert2.pem']]) + nparser.remove_server_directives(mock_vhost, 'foo') + nparser.remove_server_directives(mock_vhost, 'ssl_certificate') + self.assertEqual(nparser.parsed[example_com], + [[['server'], [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + []]]]) + + def test_add_server_directives(self): + nparser = parser.NginxParser(self.config_path) + mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'), + None, None, None, + set(['localhost', + r'~^(www\.)?(example|bar)\.']), + None, [10, 1, 9]) + nparser.add_server_directives(mock_vhost, + [['foo', 'bar'], ['\n ', 'ssl_certificate', ' ', + '/etc/ssl/cert.pem']]) + ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem') + dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')]) + self.assertEqual(1, len(re.findall(ssl_re, dump))) + + example_com = nparser.abs_path('sites-enabled/example.com') + names = set(['.example.com', 'example.*']) + mock_vhost.filep = example_com + mock_vhost.names = names + mock_vhost.path = [0] + nparser.add_server_directives(mock_vhost, + [['foo', 'bar'], ['ssl_certificate', + '/etc/ssl/cert2.pem']]) + nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) + from certbot_nginx._internal.parser import COMMENT + self.assertEqual(nparser.parsed[example_com], + [[['server'], [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + ['foo', 'bar'], + ['#', COMMENT], + ['ssl_certificate', '/etc/ssl/cert2.pem'], + ['#', COMMENT], [], [] + ]]]) + + server_conf = nparser.abs_path('server.conf') + names = set(['alias', 'another.alias', 'somename']) + mock_vhost.filep = server_conf + mock_vhost.names = names + mock_vhost.path = [] + self.assertRaises(errors.MisconfigurationError, + nparser.add_server_directives, + mock_vhost, + [['foo', 'bar'], + ['ssl_certificate', '/etc/ssl/cert2.pem']]) + + def test_comment_is_repeatable(self): + nparser = parser.NginxParser(self.config_path) + example_com = nparser.abs_path('sites-enabled/example.com') + mock_vhost = obj.VirtualHost(example_com, + None, None, None, + set(['.example.com', 'example.*']), + None, [0]) + nparser.add_server_directives(mock_vhost, + [['\n ', '#', ' ', 'what a nice comment']]) + nparser.add_server_directives(mock_vhost, + [['\n ', 'include', ' ', + nparser.abs_path('comment_in_file.conf')]]) + from certbot_nginx._internal.parser import COMMENT + self.assertEqual(nparser.parsed[example_com], + [[['server'], [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', '.example.com'], + ['server_name', 'example.*'], + ['#', ' ', 'what a nice comment'], + [], + ['include', nparser.abs_path('comment_in_file.conf')], + ['#', COMMENT], + []]]] +) + + def test_replace_server_directives(self): + nparser = parser.NginxParser(self.config_path) + target = set(['.example.com', 'example.*']) + filep = nparser.abs_path('sites-enabled/example.com') + mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0]) + nparser.update_or_add_server_directives( + mock_vhost, [['server_name', 'foobar.com']]) + from certbot_nginx._internal.parser import COMMENT + self.assertEqual( + nparser.parsed[filep], + [[['server'], [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', 'foobar.com'], ['#', COMMENT], + ['server_name', 'example.*'], [] + ]]]) + mock_vhost.names = set(['foobar.com', 'example.*']) + nparser.update_or_add_server_directives( + mock_vhost, [['ssl_certificate', 'cert.pem']]) + self.assertEqual( + nparser.parsed[filep], + [[['server'], [['listen', '69.50.225.155:9000'], + ['listen', '127.0.0.1'], + ['server_name', 'foobar.com'], ['#', COMMENT], + ['server_name', 'example.*'], [], + ['ssl_certificate', 'cert.pem'], ['#', COMMENT], [], + ]]]) + + def test_get_best_match(self): + target_name = 'www.eff.org' + names = [set(['www.eff.org', 'irrelevant.long.name.eff.org', '*.org']), + set(['eff.org', 'ww2.eff.org', 'test.www.eff.org']), + set(['*.eff.org', '.www.eff.org']), + set(['.eff.org', '*.org']), + set(['www.eff.', 'www.eff.*', '*.www.eff.org']), + set(['example.com', r'~^(www\.)?(eff.+)', '*.eff.*']), + set(['*', r'~^(www\.)?(eff.+)']), + set(['www.*', r'~^(www\.)?(eff.+)', '.test.eff.org']), + set(['*.org', r'*.eff.org', 'www.eff.*']), + set(['*.www.eff.org', 'www.*']), + set(['*.org']), + set([]), + set(['example.com'])] + winners = [('exact', 'www.eff.org'), + (None, None), + ('exact', '.www.eff.org'), + ('wildcard_start', '.eff.org'), + ('wildcard_end', 'www.eff.*'), + ('regex', r'~^(www\.)?(eff.+)'), + ('wildcard_start', '*'), + ('wildcard_end', 'www.*'), + ('wildcard_start', '*.eff.org'), + ('wildcard_end', 'www.*'), + ('wildcard_start', '*.org'), + (None, None), + (None, None)] + + for i, winner in enumerate(winners): + self.assertEqual(winner, + parser.get_best_match(target_name, names[i])) + + def test_comment_directive(self): + # pylint: disable=protected-access + block = nginxparser.UnspacedList([ + ["\n", "a", " ", "b", "\n"], + ["c", " ", "d"], + ["\n", "e", " ", "f"]]) + from certbot_nginx._internal.parser import comment_directive, COMMENT_BLOCK + comment_directive(block, 1) + comment_directive(block, 0) + self.assertEqual(block.spaced, [ + ["\n", "a", " ", "b", "\n"], + COMMENT_BLOCK, + "\n", + ["c", " ", "d"], + COMMENT_BLOCK, + ["\n", "e", " ", "f"]]) + + def test_comment_out_directive(self): + server_block = nginxparser.loads(""" + server { + listen 80; + root /var/www/html; + index star.html; + + server_name *.functorkitten.xyz; + ssl_session_timeout 1440m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + + ssl_prefer_server_ciphers on; + }""") + block = server_block[0][1] + from certbot_nginx._internal.parser import _comment_out_directive + _comment_out_directive(block, 4, "blah1") + _comment_out_directive(block, 5, "blah2") + _comment_out_directive(block, 6, "blah3") + self.assertEqual(block.spaced, [ + ['\n ', 'listen', ' ', '80'], + ['\n ', 'root', ' ', '/var/www/html'], + ['\n ', 'index', ' ', 'star.html'], + ['\n\n ', 'server_name', ' ', '*.functorkitten.xyz'], + ['\n ', '#', ' ssl_session_timeout 1440m; # duplicated in blah1'], + [' ', '#', ' ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # duplicated in blah2'], + ['\n\n ', '#', ' ssl_prefer_server_ciphers on; # duplicated in blah3'], + '\n ']) + + def test_parse_server_raw_ssl(self): + server = parser._parse_server_raw([ #pylint: disable=protected-access + ['listen', '443'] + ]) + self.assertFalse(server['ssl']) + + server = parser._parse_server_raw([ #pylint: disable=protected-access + ['listen', '443', 'ssl'] + ]) + self.assertTrue(server['ssl']) + + server = parser._parse_server_raw([ #pylint: disable=protected-access + ['listen', '443'], ['ssl', 'off'] + ]) + self.assertFalse(server['ssl']) + + server = parser._parse_server_raw([ #pylint: disable=protected-access + ['listen', '443'], ['ssl', 'on'] + ]) + self.assertTrue(server['ssl']) + + def test_parse_server_raw_unix(self): + server = parser._parse_server_raw([ #pylint: disable=protected-access + ['listen', 'unix:/var/run/nginx.sock'] + ]) + self.assertEqual(len(server['addrs']), 0) + + def test_parse_server_global_ssl_applied(self): + nparser = parser.NginxParser(self.config_path) + server = nparser.parse_server([ + ['listen', '443'] + ]) + self.assertTrue(server['ssl']) + + def test_duplicate_vhost(self): + nparser = parser.NginxParser(self.config_path) + + vhosts = nparser.get_vhosts() + default = [x for x in vhosts if 'default' in x.filep][0] + new_vhost = nparser.duplicate_vhost(default, remove_singleton_listen_params=True) + nparser.filedump(ext='') + + # check properties of new vhost + self.assertFalse(next(iter(new_vhost.addrs)).default) + self.assertNotEqual(new_vhost.path, default.path) + + # check that things are written to file correctly + new_nparser = parser.NginxParser(self.config_path) + new_vhosts = new_nparser.get_vhosts() + new_defaults = [x for x in new_vhosts if 'default' in x.filep] + self.assertEqual(len(new_defaults), 2) + new_vhost_parsed = new_defaults[1] + self.assertFalse(next(iter(new_vhost_parsed.addrs)).default) + self.assertEqual(next(iter(default.names)), next(iter(new_vhost_parsed.names))) + self.assertEqual(len(default.raw), len(new_vhost_parsed.raw)) + self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs)))) + + def test_duplicate_vhost_remove_ipv6only(self): + nparser = parser.NginxParser(self.config_path) + + vhosts = nparser.get_vhosts() + ipv6ssl = [x for x in vhosts if 'ipv6ssl' in x.filep][0] + new_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=True) + nparser.filedump(ext='') + + for addr in new_vhost.addrs: + self.assertFalse(addr.ipv6only) + + identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False) + nparser.filedump(ext='') + + called = False + for addr in identical_vhost.addrs: + if addr.ipv6: + self.assertTrue(addr.ipv6only) + called = True + self.assertTrue(called) + + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-nginx/tests/test_util.py b/certbot-nginx/tests/test_util.py new file mode 100644 index 000000000..2ee0bb738 --- /dev/null +++ b/certbot-nginx/tests/test_util.py @@ -0,0 +1,131 @@ +"""Common utilities for certbot_nginx.""" +import copy +import shutil +import tempfile + +import josepy as jose +import mock +import pkg_resources +import zope.component + +from certbot import util +from certbot.compat import os +from certbot.plugins import common +from certbot.tests import util as test_util + +from certbot_nginx._internal import configurator +from certbot_nginx._internal import nginxparser + + +class NginxTest(test_util.ConfigTestCase): + + def setUp(self): + super(NginxTest, self).setUp() + + self.configuration = self.config + self.config = None + + self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( + "etc_nginx", __name__) + self.logs_dir = tempfile.mkdtemp('logs') + + self.config_path = os.path.join(self.temp_dir, "etc_nginx") + + self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( + "rsa512_key.pem")) + + def tearDown(self): + # Cleanup opened resources after a test. This is usually done through atexit handlers in + # Certbot, but during tests, atexit will not run registered functions before tearDown is + # called and instead will run them right before the entire test process exits. + # It is a problem on Windows, that does not accept to clean resources before closing them. + util._release_locks() # pylint: disable=protected-access + + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + shutil.rmtree(self.logs_dir) + + def get_nginx_configurator(self, config_path, config_dir, work_dir, logs_dir, + version=(1, 6, 2), openssl_version="1.0.2g"): + """Create an Nginx Configurator with the specified options.""" + + backups = os.path.join(work_dir, "backups") + + self.configuration.nginx_server_root = config_path + self.configuration.le_vhost_ext = "-le-ssl.conf" + self.configuration.config_dir = config_dir + self.configuration.work_dir = work_dir + self.configuration.logs_dir = logs_dir + self.configuration.backup_dir = backups + self.configuration.temp_checkpoint_dir = os.path.join(work_dir, "temp_checkpoints") + self.configuration.in_progress_dir = os.path.join(backups, "IN_PROGRESS") + self.configuration.server = "https://acme-server.org:443/new" + self.configuration.http01_port = 80 + self.configuration.https_port = 5001 + + with mock.patch("certbot_nginx._internal.configurator.NginxConfigurator." + "config_test"): + with mock.patch("certbot_nginx._internal.configurator.util." + "exe_exists") as mock_exe_exists: + mock_exe_exists.return_value = True + config = configurator.NginxConfigurator( + self.configuration, + name="nginx", + version=version, + openssl_version=openssl_version) + config.prepare() + + # Provide general config utility. + zope.component.provideUtility(self.configuration) + + return config + + +def get_data_filename(filename): + """Gets the filename of a test data file.""" + return pkg_resources.resource_filename( + __name__, os.path.join( + "testdata", "etc_nginx", filename)) + + +def filter_comments(tree): + """Filter comment nodes from parsed configurations.""" + + def traverse(tree): + """Generator dropping comment nodes""" + for entry in tree: + # key, values = entry + spaceless = [e for e in entry if not nginxparser.spacey(e)] + if spaceless: + key = spaceless[0] + values = spaceless[1] if len(spaceless) > 1 else None + else: + key = values = "" + if isinstance(key, list): + new = copy.deepcopy(entry) + new[1] = filter_comments(values) + yield new + else: + if key != '#' and spaceless: + yield spaceless + + return list(traverse(tree)) + + +def contains_at_depth(haystack, needle, n): + """Is the needle in haystack at depth n? + + Return true if the needle is present in one of the sub-iterables in haystack + at depth n. Haystack must be an iterable. + """ + # Specifically use hasattr rather than isinstance(..., collections.Iterable) + # because we want to include lists but reject strings. + if not hasattr(haystack, '__iter__') or hasattr(haystack, 'strip'): + return False + if n == 0: + return needle in haystack + for item in haystack: + if contains_at_depth(item, needle, n - 1): + return True + return False diff --git a/certbot-nginx/tests/testdata/etc_nginx/broken.conf b/certbot-nginx/tests/testdata/etc_nginx/broken.conf new file mode 100644 index 000000000..98aef55d6 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/broken.conf @@ -0,0 +1,12 @@ +# A faulty configuration file + +pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + +include foo.conf; + +@@@ diff --git a/certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf b/certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf new file mode 100644 index 000000000..f761079fa --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf @@ -0,0 +1 @@ +# a comment inside a file \ No newline at end of file diff --git a/certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf b/certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf new file mode 100644 index 000000000..477cb1c45 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf @@ -0,0 +1,27 @@ +# This is not a valid nginx config file but it tests edge cases in valid nginx syntax + +server { + server_name simple; +} + +server { + server_name with.if; + location ~ ^/services/.+$ { + if ($request_filename ~* \.(ttf|woff)$) { + add_header Access-Control-Allow-Origin "*"; + } + } +} + +server { + server_name with.complicated.headers; + + location ~* \.(?:gif|jpe?g|png)$ { + + add_header Pragma public; + add_header Cache-Control 'public, must-revalidate, proxy-revalidate' "test,;{}" foo; + blah "hello;world"; + + try_files $uri @rewrites; + } +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/foo.conf b/certbot-nginx/tests/testdata/etc_nginx/foo.conf new file mode 100644 index 000000000..574955398 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/foo.conf @@ -0,0 +1,25 @@ +# a test nginx conf +user www-data; + +http { + server { + listen *:80 default_server ssl; + server_name *.www.foo.com *.www.example.com; + root /home/ubuntu/sites/foo/; + + location /status { + types { + image/jpeg jpg; + } + } + + location ~ case_sensitive\.php$ { + index index.php; + root /var/root; + } + location ~* case_insensitive\.php$ {} + location = exact_match\.php$ {} + location ^~ ignore_regex\.php$ {} + + } +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/mime.types b/certbot-nginx/tests/testdata/etc_nginx/mime.types new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf b/certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf new file mode 100644 index 000000000..cf4648592 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf @@ -0,0 +1,12 @@ +# Use bar.conf when it's a full moon! +include foo.conf; # Kilroy was here +check_status; + +server { + # + # Don't forget to open up your firewall! + # + listen 1234; + # listen 80; +} + diff --git a/certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf b/certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf new file mode 100644 index 000000000..74cd84bcd --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf @@ -0,0 +1,16 @@ +# Test nginx configuration file with multiline quoted strings. +# Good example of usage for multilined quoted values is when +# using Openresty's Lua directives and you wish to keep the +# inline Lua code readable. +http { + server { + listen *:443; # because there should be no other port open. + + location / { + body_filter_by_lua 'ngx.ctx.buffered = (ngx.ctx.buffered or "") .. string.sub(ngx.arg[1], 1, 1000) + if ngx.arg[2] then + ngx.var.resp_body = ngx.ctx.buffered + end'; + } + } +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/nginx.conf b/certbot-nginx/tests/testdata/etc_nginx/nginx.conf new file mode 100644 index 000000000..ccce4dc1b --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/nginx.conf @@ -0,0 +1,122 @@ +# standard default nginx config + +user nobody; +worker_processes 1; + +error_log logs/error.log; +error_log logs/error.log notice; +error_log logs/error.log info; + +pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + +empty { +} + +include foo.conf; + +http { + include mime.types; + include sites-enabled/*; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log logs/access.log main; + + sendfile on; + tcp_nopush on; + + keepalive_timeout 0; + + gzip on; + + server { + listen 8080; + server_name localhost; + server_name ~^(www\.)?(example|bar)\.; + + charset koi8-r; + + access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # proxy the PHP scripts to Nginx listening on 127.0.0.1:80 + # + location ~ \.php$ { + proxy_pass http://127.0.0.1; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + location ~ \.php$ { + root html; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + } + + # deny access to .htaccess files, if Nginx's document root + # concurs with nginx's one + # + location ~ /\.ht { + deny all; + } + } + + + # another virtual host using mix of IP-, name-, and port-based configuration + # + server { + listen 8000; + listen somename:8080; + include server.conf; + + location / { + root html; + index index.html index.htm; + } + } + + + # HTTPS server + # + #server { + # listen 443 ssl; + # server_name localhost; + + # ssl_certificate cert.pem; + # ssl_certificate_key cert.key; + + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 5m; + + # ssl_ciphers HIGH:!aNULL:!MD5; + # ssl_prefer_server_ciphers on; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + #include conf.d/test.conf; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/server.conf b/certbot-nginx/tests/testdata/etc_nginx/server.conf new file mode 100644 index 000000000..5fc4c8b24 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/server.conf @@ -0,0 +1 @@ +server_name somename alias another.alias; diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default new file mode 100644 index 000000000..e167761d1 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default @@ -0,0 +1,10 @@ +server { + listen myhost default_server; + listen otherhost default_server; + server_name "www.example.org"; + + location / { + root html; + index index.html index.htm; + } +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com new file mode 100644 index 000000000..fd9117188 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com @@ -0,0 +1,6 @@ +server { + listen 69.50.225.155:9000; + listen 127.0.0.1; + server_name .example.com; + server_name example.*; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com new file mode 100644 index 000000000..969447d6e --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com @@ -0,0 +1,9 @@ +server { + server_name globalssl.com; + listen 4.8.2.6:57; +} + +server { + server_name globalsslsetssl.com; + listen 4.8.2.6:57 ssl; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com new file mode 100644 index 000000000..6c032928c --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com @@ -0,0 +1,4 @@ +server { + server_name headers.com; + add_header X-Content-Type-Options nosniff; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com new file mode 100644 index 000000000..7a7744b92 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com @@ -0,0 +1,5 @@ +server { + listen 80; + listen [::]:80; + server_name ipv6.com; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com new file mode 100644 index 000000000..875a9ee1b --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com @@ -0,0 +1,7 @@ +server { + listen 443 ssl; + listen [::]:443 ssl ipv6only=on; + listen 5001 ssl; + listen [::]:5001 ssl ipv6only=on; + server_name ipv6ssl.com; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com new file mode 100644 index 000000000..17bc6d0c3 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com @@ -0,0 +1,19 @@ +server { + server_name migration.com; + server_name summer.com; +} + +server { + listen 443 ssl; + server_name migration.com; + server_name geese.com; + + ssl_certificate cert.pem; + ssl_certificate_key cert.key; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com new file mode 100644 index 000000000..b93e6ba2d --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com @@ -0,0 +1,6 @@ +server { + server_name sslon.com; + ssl on; + ssl_certificate snakeoil.cert; + ssl_certificate_key snakeoil.key; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params new file mode 100644 index 000000000..4ee14e98d --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params @@ -0,0 +1,25 @@ +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_FILENAME $request_filename; +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +fastcgi_param HTTPS $https if_not_empty; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf new file mode 100644 index 000000000..1edb9474f --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf @@ -0,0 +1,108 @@ +# This map is not a full koi8-r <> utf8 map: it does not contain +# box-drawing and some other characters. Besides this map contains +# several koi8-u and Byelorussian letters which are not in koi8-r. +# If you need a full and standard map, use contrib/unicode2nginx/koi-utf +# map instead. + +charset_map koi8-r utf-8 { + + 80 E282AC; # euro + + 95 E280A2; # bullet + + 9A C2A0; #   + + 9E C2B7; # · + + A3 D191; # small yo + A4 D194; # small Ukrainian ye + + A6 D196; # small Ukrainian i + A7 D197; # small Ukrainian yi + + AD D291; # small Ukrainian soft g + AE D19E; # small Byelorussian short u + + B0 C2B0; # ° + + B3 D081; # capital YO + B4 D084; # capital Ukrainian YE + + B6 D086; # capital Ukrainian I + B7 D087; # capital Ukrainian YI + + B9 E28496; # numero sign + + BD D290; # capital Ukrainian soft G + BE D18E; # capital Byelorussian short U + + BF C2A9; # (C) + + C0 D18E; # small yu + C1 D0B0; # small a + C2 D0B1; # small b + C3 D186; # small ts + C4 D0B4; # small d + C5 D0B5; # small ye + C6 D184; # small f + C7 D0B3; # small g + C8 D185; # small kh + C9 D0B8; # small i + CA D0B9; # small j + CB D0BA; # small k + CC D0BB; # small l + CD D0BC; # small m + CE D0BD; # small n + CF D0BE; # small o + + D0 D0BF; # small p + D1 D18F; # small ya + D2 D180; # small r + D3 D181; # small s + D4 D182; # small t + D5 D183; # small u + D6 D0B6; # small zh + D7 D0B2; # small v + D8 D18C; # small soft sign + D9 D18B; # small y + DA D0B7; # small z + DB D188; # small sh + DC D18D; # small e + DD D189; # small shch + DE D187; # small ch + DF D18A; # small hard sign + + E0 D0AE; # capital YU + E1 D090; # capital A + E2 D091; # capital B + E3 D0A6; # capital TS + E4 D094; # capital D + E5 D095; # capital YE + E6 D0A4; # capital F + E7 D093; # capital G + E8 D0A5; # capital KH + E9 D098; # capital I + EA D099; # capital J + EB D09A; # capital K + EC D09B; # capital L + ED D09C; # capital M + EE D09D; # capital N + EF D09E; # capital O + + F0 D09F; # capital P + F1 D0AF; # capital YA + F2 D0A0; # capital R + F3 D0A1; # capital S + F4 D0A2; # capital T + F5 D0A3; # capital U + F6 D096; # capital ZH + F7 D092; # capital V + F8 D0AC; # capital soft sign + F9 D0AB; # capital Y + FA D097; # capital Z + FB D0A8; # capital SH + FC D0AD; # capital E + FD D0A9; # capital SHCH + FE D0A7; # capital CH + FF D0AA; # capital hard sign +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win new file mode 100644 index 000000000..c6930fc4f --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win @@ -0,0 +1,102 @@ +charset_map koi8-r windows-1251 { + + 80 88; # euro + + 95 95; # bullet + + 9A A0; #   + + 9E B7; # · + + A3 B8; # small yo + A4 BA; # small Ukrainian ye + + A6 B3; # small Ukrainian i + A7 BF; # small Ukrainian yi + + AD B4; # small Ukrainian soft g + AE A2; # small Byelorussian short u + + B0 B0; # ° + + B3 A8; # capital YO + B4 AA; # capital Ukrainian YE + + B6 B2; # capital Ukrainian I + B7 AF; # capital Ukrainian YI + + B9 B9; # numero sign + + BD A5; # capital Ukrainian soft G + BE A1; # capital Byelorussian short U + + BF A9; # (C) + + C0 FE; # small yu + C1 E0; # small a + C2 E1; # small b + C3 F6; # small ts + C4 E4; # small d + C5 E5; # small ye + C6 F4; # small f + C7 E3; # small g + C8 F5; # small kh + C9 E8; # small i + CA E9; # small j + CB EA; # small k + CC EB; # small l + CD EC; # small m + CE ED; # small n + CF EE; # small o + + D0 EF; # small p + D1 FF; # small ya + D2 F0; # small r + D3 F1; # small s + D4 F2; # small t + D5 F3; # small u + D6 E6; # small zh + D7 E2; # small v + D8 FC; # small soft sign + D9 FB; # small y + DA E7; # small z + DB F8; # small sh + DC FD; # small e + DD F9; # small shch + DE F7; # small ch + DF FA; # small hard sign + + E0 DE; # capital YU + E1 C0; # capital A + E2 C1; # capital B + E3 D6; # capital TS + E4 C4; # capital D + E5 C5; # capital YE + E6 D4; # capital F + E7 C3; # capital G + E8 D5; # capital KH + E9 C8; # capital I + EA C9; # capital J + EB CA; # capital K + EC CB; # capital L + ED CC; # capital M + EE CD; # capital N + EF CE; # capital O + + F0 CF; # capital P + F1 DF; # capital YA + F2 D0; # capital R + F3 D1; # capital S + F4 D2; # capital T + F5 D3; # capital U + F6 C6; # capital ZH + F7 C2; # capital V + F8 DC; # capital soft sign + F9 DB; # capital Y + FA C7; # capital Z + FB D8; # capital SH + FC DD; # capital E + FD D9; # capital SHCH + FE D7; # capital CH + FF DA; # capital hard sign +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types new file mode 100644 index 000000000..fcce4a58d --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types @@ -0,0 +1,79 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml rss; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + application/ogg ogx; + + audio/midi mid midi kar; + audio/mpeg mpga mpega mp2 mp3 m4a; + audio/ogg oga ogg spx; + audio/x-realaudio ra; + audio/webm weba; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg mpe; + video/ogg ogv; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 new file mode 100644 index 000000000..f4eb9d49d --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 @@ -0,0 +1,16 @@ +[nx_extract] +username = naxsi_web +password = test +port = 8081 +rules_path = /etc/nginx/naxsi_core.rules + +[nx_intercept] +port = 8080 + +[sql] +dbtype = sqlite +username = root +password = +hostname = 127.0.0.1 +dbname = naxsi_sig + diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules new file mode 100644 index 000000000..fec21ea4f --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules @@ -0,0 +1,13 @@ +# Sample rules file for default vhost. + +LearningMode; +SecRulesEnabled; +#SecRulesDisabled; +DeniedUrl "/RequestDenied"; + +## check rules +CheckRule "$SQL >= 8" BLOCK; +CheckRule "$RFI >= 8" BLOCK; +CheckRule "$TRAVERSAL >= 4" BLOCK; +CheckRule "$EVADE >= 4" BLOCK; +CheckRule "$XSS >= 8" BLOCK; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules new file mode 100644 index 000000000..9826e02cb --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules @@ -0,0 +1,75 @@ +################################## +## INTERNAL RULES IDS:1-10 ## +################################## +#weird_request : 1 +#big_body : 2 +#no_content_type : 3 + +#MainRule "str:yesone" "msg:foobar test pattern" "mz:ARGS" "s:$SQL:42" id:1999; + +################################## +## SQL Injections IDs:1000-1099 ## +################################## +MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000; +MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1001; +MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002; +## Hardcore rules +MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003; +MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004; +MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005; +MainRule "rx:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006; +## end of hardcore rules +MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007; +MainRule "str:;" "msg:; in stuff" "mz:BODY|URL|ARGS" "s:$SQL:4" id:1008; +MainRule "str:=" "msg:equal in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009; +MainRule "str:(" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1010; +MainRule "str:)" "msg:parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1011; +MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1013; +MainRule "str:\"" "msg:double quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1014; +MainRule "str:," "msg:, in stuff" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015; +MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016; + +############################### +## OBVIOUS RFI IDs:1100-1199 ## +############################### +MainRule "str:http://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100; +MainRule "str:https://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101; +MainRule "str:ftp://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102; +MainRule "str:php://" "msg:html comment tag" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103; + +####################################### +## Directory traversal IDs:1200-1299 ## +####################################### +MainRule "str:.." "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200; +MainRule "str:/etc/passwd" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202; +MainRule "str:c:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203; +MainRule "str:cmd.exe" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204; +MainRule "str:\\" "msg:html comment tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205; +#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206; +######################################## +## Cross Site Scripting IDs:1300-1399 ## +######################################## +MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302; +MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303; +MainRule "str:'" "msg:simple quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1306; +MainRule "str:\"" "msg:double quote" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1307; +MainRule "str:(" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1308; +MainRule "str:)" "msg:parenthesis" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1309; +MainRule "str:[" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310; +MainRule "str:]" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311; +MainRule "str:~" "msg:html close comment tag" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312; +MainRule "str:;" "msg:semi coma" "mz:ARGS|URL|BODY" "s:$XSS:8" id:1313; +MainRule "str:`" "msg:grave accent !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314; +MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315; + +#################################### +## Evading tricks IDs: 1400-1500 ## +#################################### +MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400; +MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401; +MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402; + +############################# +## File uploads: 1500-1600 ## +############################# +MainRule "rx:.ph*|.asp*" "msg:asp/php file upload!" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf new file mode 100644 index 000000000..52219b940 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf @@ -0,0 +1,95 @@ +user www-data; +worker_processes 4; +pid /run/nginx.pid; + +events { + worker_connections 768; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + gzip_disable "msie6"; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # nginx-naxsi config + ## + # Uncomment it if you installed nginx-naxsi + ## + + #include /etc/nginx/naxsi_core.rules; + + ## + # nginx-passenger config + ## + # Uncomment it if you installed nginx-passenger + ## + + #passenger_root /usr; + #passenger_ruby /usr/bin/ruby; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} + + +#mail { +# # See sample authentication script at: +# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript +# +# # auth_http localhost/auth.php; +# # pop3_capabilities "TOP" "USER"; +# # imap_capabilities "IMAP4rev1" "UIDPLUS"; +# +# server { +# listen localhost:110; +# protocol pop3; +# proxy on; +# } +# +# server { +# listen localhost:143; +# protocol imap; +# proxy on; +# } +#} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params new file mode 100644 index 000000000..df75bc5d7 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params @@ -0,0 +1,4 @@ +proxy_set_header Host $http_host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params new file mode 100644 index 000000000..76e858628 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params @@ -0,0 +1,14 @@ +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default new file mode 100644 index 000000000..5d8f3ac15 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default @@ -0,0 +1,112 @@ +# You may add here your +# server { +# ... +# } +# statements for each of your virtual hosts to this file + +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# http://wiki.nginx.org/Pitfalls +# http://wiki.nginx.org/QuickStart +# http://wiki.nginx.org/Configuration +# +# Generally, you will want to move this file somewhere, and start with a clean +# file but keep this around for reference. Or just disable in sites-enabled. +# +# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. +## + +server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + + root /usr/share/nginx/html; + index index.html index.htm; + + # Make site accessible from http://localhost/ + server_name localhost; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ =404; + # Uncomment to enable naxsi on this location + # include /etc/nginx/naxsi.rules + } + + # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests + #location /RequestDenied { + # proxy_pass http://127.0.0.1:8080; + #} + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + #error_page 500 502 503 504 /50x.html; + #location = /50x.html { + # root /usr/share/nginx/html; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # fastcgi_split_path_info ^(.+\.php)(/.+)$; + # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + # + # # With php5-cgi alone: + # fastcgi_pass 127.0.0.1:9000; + # # With php5-fpm: + # fastcgi_pass unix:/var/run/php5-fpm.sock; + # fastcgi_index index.php; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} + + +# another virtual host using mix of IP-, name-, and port-based configuration +# +#server { +# listen 8000; +# listen somename:8080; +# server_name somename alias another.alias; +# root html; +# index index.html index.htm; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} + + +# HTTPS server +# +#server { +# listen 443; +# server_name localhost; +# +# root html; +# index index.html index.htm; +# +# ssl on; +# ssl_certificate cert.pem; +# ssl_certificate_key cert.key; +# +# ssl_session_timeout 5m; +# +# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; +# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; +# ssl_prefer_server_ciphers on; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default new file mode 120000 index 000000000..6d9ba3371 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default @@ -0,0 +1 @@ +../sites-available/default \ No newline at end of file diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params new file mode 100644 index 000000000..3f72dbf0e --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params @@ -0,0 +1,15 @@ +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param UWSGI_SCHEME $scheme; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf new file mode 100644 index 000000000..cd2885292 --- /dev/null +++ b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf @@ -0,0 +1,125 @@ +# This map is not a full windows-1251 <> utf8 map: it does not +# contain Serbian and Macedonian letters. If you need a full map, +# use contrib/unicode2nginx/win-utf map instead. + +charset_map windows-1251 utf-8 { + + 82 E2809A; # single low-9 quotation mark + + 84 E2809E; # double low-9 quotation mark + 85 E280A6; # ellipsis + 86 E280A0; # dagger + 87 E280A1; # double dagger + 88 E282AC; # euro + 89 E280B0; # per mille + + 91 E28098; # left single quotation mark + 92 E28099; # right single quotation mark + 93 E2809C; # left double quotation mark + 94 E2809D; # right double quotation mark + 95 E280A2; # bullet + 96 E28093; # en dash + 97 E28094; # em dash + + 99 E284A2; # trade mark sign + + A0 C2A0; #   + A1 D18E; # capital Byelorussian short U + A2 D19E; # small Byelorussian short u + + A4 C2A4; # currency sign + A5 D290; # capital Ukrainian soft G + A6 C2A6; # borken bar + A7 C2A7; # section sign + A8 D081; # capital YO + A9 C2A9; # (C) + AA D084; # capital Ukrainian YE + AB C2AB; # left-pointing double angle quotation mark + AC C2AC; # not sign + AD C2AD; # soft hyphen + AE C2AE; # (R) + AF D087; # capital Ukrainian YI + + B0 C2B0; # ° + B1 C2B1; # plus-minus sign + B2 D086; # capital Ukrainian I + B3 D196; # small Ukrainian i + B4 D291; # small Ukrainian soft g + B5 C2B5; # micro sign + B6 C2B6; # pilcrow sign + B7 C2B7; # · + B8 D191; # small yo + B9 E28496; # numero sign + BA D194; # small Ukrainian ye + BB C2BB; # right-pointing double angle quotation mark + + BF D197; # small Ukrainian yi + + C0 D090; # capital A + C1 D091; # capital B + C2 D092; # capital V + C3 D093; # capital G + C4 D094; # capital D + C5 D095; # capital YE + C6 D096; # capital ZH + C7 D097; # capital Z + C8 D098; # capital I + C9 D099; # capital J + CA D09A; # capital K + CB D09B; # capital L + CC D09C; # capital M + CD D09D; # capital N + CE D09E; # capital O + CF D09F; # capital P + + D0 D0A0; # capital R + D1 D0A1; # capital S + D2 D0A2; # capital T + D3 D0A3; # capital U + D4 D0A4; # capital F + D5 D0A5; # capital KH + D6 D0A6; # capital TS + D7 D0A7; # capital CH + D8 D0A8; # capital SH + D9 D0A9; # capital SHCH + DA D0AA; # capital hard sign + DB D0AB; # capital Y + DC D0AC; # capital soft sign + DD D0AD; # capital E + DE D0AE; # capital YU + DF D0AF; # capital YA + + E0 D0B0; # small a + E1 D0B1; # small b + E2 D0B2; # small v + E3 D0B3; # small g + E4 D0B4; # small d + E5 D0B5; # small ye + E6 D0B6; # small zh + E7 D0B7; # small z + E8 D0B8; # small i + E9 D0B9; # small j + EA D0BA; # small k + EB D0BB; # small l + EC D0BC; # small m + ED D0BD; # small n + EE D0BE; # small o + EF D0BF; # small p + + F0 D180; # small r + F1 D181; # small s + F2 D182; # small t + F3 D183; # small u + F4 D184; # small f + F5 D185; # small kh + F6 D186; # small ts + F7 D187; # small ch + F8 D188; # small sh + F9 D189; # small shch + FA D18A; # small hard sign + FB D18B; # small y + FC D18C; # small soft sign + FD D18D; # small e + FE D18E; # small yu + FF D18F; # small ya +} -- cgit v1.2.3 From 6c1dfe43c76f3620a3cc4ab3ec71370660fd6255 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 27 Nov 2019 09:57:35 -0800 Subject: Refactor tests out of packaged module for apache plugin (#7607) Part of #7593. * Refactor tests out of packaged module for apache plugin * Exclude pycache and .py[cod] * Change tests path in tox.ini --- certbot-apache/MANIFEST.in | 4 +- certbot-apache/certbot_apache/tests/__init__.py | 1 - .../tests/apache-conf-files/NEEDED.txt | 6 - .../tests/apache-conf-files/apache-conf-test | 88 - .../apache-conf-files/apache-conf-test-pebble.py | 27 - .../failing/missing-double-quote-1724.conf | 52 - .../apache-conf-files/failing/multivhost-1093.conf | 295 ---- .../failing/multivhost-1093b.conf | 593 ------- .../tests/apache-conf-files/passing/1626-1531.conf | 37 - .../tests/apache-conf-files/passing/README.modules | 6 - .../apache-conf-files/passing/anarcat-1531.conf | 14 - .../passing/comment-continuations-2050.conf | 428 ----- .../passing/drupal-errordocument-arg-1724.conf | 116 -- .../passing/drupal-htaccess-1531.conf | 149 -- .../passing/escaped-space-arguments-2735.conf | 2 - .../apache-conf-files/passing/example-1755.conf | 36 - .../apache-conf-files/passing/example-ssl.conf | 136 -- .../tests/apache-conf-files/passing/example.conf | 32 - .../passing/finalize-1243.apache2.conf.txt | 222 --- .../apache-conf-files/passing/finalize-1243.conf | 67 - .../passing/graphite-quote-1934.conf | 21 - .../tests/apache-conf-files/passing/ipv6-1143.conf | 9 - .../apache-conf-files/passing/ipv6-1143b.conf | 18 - .../apache-conf-files/passing/ipv6-1143c.conf | 9 - .../apache-conf-files/passing/ipv6-1143d.conf | 18 - .../passing/missing-quote-1724.conf | 52 - .../apache-conf-files/passing/modmacro-1385.conf | 33 - .../apache-conf-files/passing/owncloud-1264.conf | 13 - .../passing/rewrite-quote-1960.conf | 7 - .../apache-conf-files/passing/roundcube-1222.conf | 61 - .../passing/section-continuations-2525.conf | 284 ---- .../passing/section-empty-continuations-2731.conf | 247 --- .../apache-conf-files/passing/semacode-1598.conf | 44 - .../passing/sslrequire-wordlist-1827.htaccess | 1 - .../passing/two-blocks-one-line-1693.conf | 28 - .../certbot_apache/tests/autohsts_test.py | 188 --- .../certbot_apache/tests/centos6_test.py | 222 --- certbot-apache/certbot_apache/tests/centos_test.py | 195 --- .../certbot_apache/tests/complex_parsing_test.py | 129 -- .../tests/configurator_reverter_test.py | 85 - .../certbot_apache/tests/configurator_test.py | 1772 ------------------- certbot-apache/certbot_apache/tests/debian_test.py | 213 --- .../certbot_apache/tests/display_ops_test.py | 108 -- .../certbot_apache/tests/entrypoint_test.py | 47 - certbot-apache/certbot_apache/tests/fedora_test.py | 195 --- certbot-apache/certbot_apache/tests/gentoo_test.py | 139 -- .../certbot_apache/tests/http_01_test.py | 228 --- certbot-apache/certbot_apache/tests/obj_test.py | 141 -- certbot-apache/certbot_apache/tests/parser_test.py | 402 ----- .../centos6_apache/apache/httpd/conf.d/README | 9 - .../centos6_apache/apache/httpd/conf.d/ssl.conf | 222 --- .../apache/httpd/conf.d/test.example.com.conf | 7 - .../apache/httpd/conf.d/welcome.conf | 11 - .../centos6_apache/apache/httpd/conf/httpd.conf | 1009 ----------- .../centos7_apache/apache/httpd/conf.d/README | 9 - .../apache/httpd/conf.d/autoindex.conf | 94 -- .../apache/httpd/conf.d/centos.example.com.conf | 7 - .../centos7_apache/apache/httpd/conf.d/ssl.conf | 211 --- .../apache/httpd/conf.d/userdir.conf | 36 - .../apache/httpd/conf.d/welcome.conf | 22 - .../apache/httpd/conf.modules.d/00-base.conf | 77 - .../apache/httpd/conf.modules.d/00-dav.conf | 3 - .../apache/httpd/conf.modules.d/00-lua.conf | 1 - .../apache/httpd/conf.modules.d/00-mpm.conf | 19 - .../apache/httpd/conf.modules.d/00-proxy.conf | 16 - .../apache/httpd/conf.modules.d/00-ssl.conf | 1 - .../apache/httpd/conf.modules.d/00-systemd.conf | 2 - .../apache/httpd/conf.modules.d/01-cgi.conf | 14 - .../centos7_apache/apache/httpd/conf/httpd.conf | 353 ---- .../centos7_apache/apache/httpd/conf/magic | 385 ----- .../tests/testdata/centos7_apache/apache/sites | 1 - .../testdata/centos7_apache/apache/sysconfig/httpd | 25 - .../tests/testdata/complex_parsing/apache2.conf | 55 - .../complex_parsing/conf-enabled/dummy.conf | 9 - .../testdata/complex_parsing/test_fnmatch.conf | 1 - .../testdata/complex_parsing/test_variables.conf | 66 - .../augeas_vhosts/apache2/apache2.conf | 196 --- .../apache2/conf-available/bad_conf_file.conf | 3 - .../conf-available/other-vhosts-access-log.conf | 4 - .../apache2/conf-available/security.conf | 35 - .../apache2/conf-available/serve-cgi-bin.conf | 20 - .../conf-enabled/other-vhosts-access-log.conf | 1 - .../apache2/conf-enabled/security.conf | 1 - .../apache2/conf-enabled/serve-cgi-bin.conf | 1 - .../augeas_vhosts/apache2/envvars | 29 - .../apache2/mods-available/authz_svn.load | 5 - .../augeas_vhosts/apache2/mods-available/dav.load | 3 - .../apache2/mods-available/dav_svn.conf | 56 - .../apache2/mods-available/dav_svn.load | 7 - .../apache2/mods-available/rewrite.load | 1 - .../augeas_vhosts/apache2/mods-available/ssl.conf | 89 - .../augeas_vhosts/apache2/mods-available/ssl.load | 2 - .../apache2/mods-enabled/authz_svn.load | 1 - .../augeas_vhosts/apache2/mods-enabled/dav.load | 1 - .../apache2/mods-enabled/dav_svn.conf | 1 - .../apache2/mods-enabled/dav_svn.load | 1 - .../augeas_vhosts/apache2/ports.conf | 15 - .../apache2/sites-available/another_wildcard.conf | 11 - .../apache2/sites-available/old-and-default.conf | 12 - .../apache2/sites-available/wildcard.conf | 11 - .../apache2/sites-enabled/another_wildcard.conf | 1 - .../apache2/sites-enabled/old-and-default.conf | 1 - .../apache2/sites-enabled/wildcard.conf | 1 - .../testdata/debian_apache_2_4/augeas_vhosts/sites | 3 - .../default_vhost/apache2/apache2.conf | 198 --- .../conf-available/other-vhosts-access-log.conf | 4 - .../apache2/conf-available/security.conf | 31 - .../apache2/conf-available/serve-cgi-bin.conf | 20 - .../conf-enabled/other-vhosts-access-log.conf | 1 - .../apache2/conf-enabled/security.conf | 1 - .../apache2/conf-enabled/serve-cgi-bin.conf | 1 - .../default_vhost/apache2/envvars | 28 - .../default_vhost/apache2/mods-available/ssl.conf | 89 - .../default_vhost/apache2/mods-available/ssl.load | 2 - .../default_vhost/apache2/ports.conf | 20 - .../apache2/sites-available/000-default.conf | 11 - .../apache2/sites-available/default-ssl.conf | 38 - .../apache2/sites-enabled/000-default.conf | 1 - .../testdata/debian_apache_2_4/default_vhost/sites | 1 - .../multi_vhosts/apache2/apache2.conf | 196 --- .../debian_apache_2_4/multi_vhosts/apache2/envvars | 29 - .../multi_vhosts/apache2/ports.conf | 15 - .../apache2/sites-available/default.conf | 22 - .../apache2/sites-available/multi-vhost.conf | 38 - .../apache2/sites-enabled/default.conf | 1 - .../apache2/sites-enabled/multi-vhost.conf | 1 - .../multiple_vhosts/apache2/apache2.conf | 207 --- .../apache2/conf-available/bad_conf_file.conf | 3 - .../conf-available/other-vhosts-access-log.conf | 4 - .../apache2/conf-available/security.conf | 35 - .../apache2/conf-available/serve-cgi-bin.conf | 20 - .../conf-enabled/other-vhosts-access-log.conf | 1 - .../apache2/conf-enabled/security.conf | 1 - .../apache2/conf-enabled/serve-cgi-bin.conf | 1 - .../multiple_vhosts/apache2/envvars | 29 - .../apache2/mods-available/authz_svn.load | 5 - .../apache2/mods-available/dav.load | 3 - .../apache2/mods-available/dav_svn.conf | 56 - .../apache2/mods-available/dav_svn.load | 7 - .../apache2/mods-available/rewrite.load | 1 - .../apache2/mods-available/ssl.conf | 89 - .../apache2/mods-available/ssl.load | 2 - .../apache2/mods-enabled/authz_svn.load | 1 - .../multiple_vhosts/apache2/mods-enabled/dav.load | 1 - .../apache2/mods-enabled/dav_svn.conf | 1 - .../apache2/mods-enabled/dav_svn.load | 1 - .../multiple_vhosts/apache2/ports.conf | 15 - .../apache2/sites-available/000-default.conf | 12 - .../apache2/sites-available/certbot.conf | 43 - .../sites-available/default-ssl-port-only.conf | 36 - .../apache2/sites-available/default-ssl.conf | 40 - .../apache2/sites-available/duplicatehttp.conf | 9 - .../apache2/sites-available/duplicatehttps.conf | 14 - .../sites-available/encryption-example.conf | 42 - .../apache2/sites-available/mod_macro-example.conf | 15 - .../apache2/sites-available/ocsp-ssl.conf | 36 - .../apache2/sites-available/wildcard.conf | 13 - .../apache2/sites-enabled/000-default.conf | 1 - .../apache2/sites-enabled/certbot.conf | 1 - .../sites-enabled/default-ssl-port-only.conf | 1 - .../apache2/sites-enabled/default-ssl.conf | 1 - .../apache2/sites-enabled/duplicatehttp.conf | 1 - .../apache2/sites-enabled/duplicatehttps.conf | 1 - .../apache2/sites-enabled/encryption-example.conf | 1 - .../apache2/sites-enabled/mod_macro-example.conf | 1 - .../apache2/sites-enabled/non-symlink.conf | 9 - .../apache2/sites-enabled/ocsp-ssl.conf | 1 - .../apache2/sites-enabled/wildcard.conf | 1 - .../debian_apache_2_4/multiple_vhosts/sites | 3 - .../gentoo_apache/apache/apache2/httpd.conf | 157 -- .../testdata/gentoo_apache/apache/apache2/magic | 385 ----- .../apache2/modules.d/00_default_settings.conf | 131 -- .../apache2/modules.d/00_error_documents.conf | 57 - .../apache/apache2/modules.d/00_languages.conf | 133 -- .../apache/apache2/modules.d/00_mod_autoindex.conf | 85 - .../apache/apache2/modules.d/00_mod_info.conf | 10 - .../apache2/modules.d/00_mod_log_config.conf | 35 - .../apache/apache2/modules.d/00_mod_mime.conf | 46 - .../apache/apache2/modules.d/00_mod_status.conf | 15 - .../apache/apache2/modules.d/00_mod_userdir.conf | 32 - .../apache/apache2/modules.d/00_mpm.conf | 99 -- .../apache/apache2/modules.d/10_mod_mem_cache.conf | 10 - .../apache/apache2/modules.d/40_mod_ssl.conf | 67 - .../apache/apache2/modules.d/41_mod_http2.conf | 9 - .../apache/apache2/modules.d/45_mod_dav.conf | 19 - .../apache/apache2/modules.d/46_mod_ldap.conf | 18 - .../apache2/vhosts.d/00_default_ssl_vhost.conf | 191 --- .../apache/apache2/vhosts.d/00_default_vhost.conf | 45 - .../apache/apache2/vhosts.d/default_vhost.include | 71 - .../apache2/vhosts.d/gentoo.example.com.conf | 7 - .../testdata/gentoo_apache/apache/conf.d/apache2 | 74 - .../tests/testdata/gentoo_apache/apache/sites | 3 - certbot-apache/certbot_apache/tests/util.py | 239 --- certbot-apache/tests/apache-conf-files/NEEDED.txt | 6 + .../tests/apache-conf-files/apache-conf-test | 88 + .../apache-conf-files/apache-conf-test-pebble.py | 27 + .../failing/missing-double-quote-1724.conf | 52 + .../apache-conf-files/failing/multivhost-1093.conf | 295 ++++ .../failing/multivhost-1093b.conf | 593 +++++++ .../tests/apache-conf-files/passing/1626-1531.conf | 37 + .../tests/apache-conf-files/passing/README.modules | 6 + .../apache-conf-files/passing/anarcat-1531.conf | 14 + .../passing/comment-continuations-2050.conf | 428 +++++ .../passing/drupal-errordocument-arg-1724.conf | 116 ++ .../passing/drupal-htaccess-1531.conf | 149 ++ .../passing/escaped-space-arguments-2735.conf | 2 + .../apache-conf-files/passing/example-1755.conf | 36 + .../apache-conf-files/passing/example-ssl.conf | 136 ++ .../tests/apache-conf-files/passing/example.conf | 32 + .../passing/finalize-1243.apache2.conf.txt | 222 +++ .../apache-conf-files/passing/finalize-1243.conf | 67 + .../passing/graphite-quote-1934.conf | 21 + .../tests/apache-conf-files/passing/ipv6-1143.conf | 9 + .../apache-conf-files/passing/ipv6-1143b.conf | 18 + .../apache-conf-files/passing/ipv6-1143c.conf | 9 + .../apache-conf-files/passing/ipv6-1143d.conf | 18 + .../passing/missing-quote-1724.conf | 52 + .../apache-conf-files/passing/modmacro-1385.conf | 33 + .../apache-conf-files/passing/owncloud-1264.conf | 13 + .../passing/rewrite-quote-1960.conf | 7 + .../apache-conf-files/passing/roundcube-1222.conf | 61 + .../passing/section-continuations-2525.conf | 284 ++++ .../passing/section-empty-continuations-2731.conf | 247 +++ .../apache-conf-files/passing/semacode-1598.conf | 44 + .../passing/sslrequire-wordlist-1827.htaccess | 1 + .../passing/two-blocks-one-line-1693.conf | 28 + certbot-apache/tests/autohsts_test.py | 189 +++ certbot-apache/tests/centos6_test.py | 223 +++ certbot-apache/tests/centos_test.py | 196 +++ certbot-apache/tests/complex_parsing_test.py | 129 ++ certbot-apache/tests/configurator_reverter_test.py | 85 + certbot-apache/tests/configurator_test.py | 1773 ++++++++++++++++++++ certbot-apache/tests/debian_test.py | 214 +++ certbot-apache/tests/display_ops_test.py | 109 ++ certbot-apache/tests/entrypoint_test.py | 47 + certbot-apache/tests/fedora_test.py | 196 +++ certbot-apache/tests/gentoo_test.py | 140 ++ certbot-apache/tests/http_01_test.py | 229 +++ certbot-apache/tests/obj_test.py | 141 ++ certbot-apache/tests/parser_test.py | 402 +++++ .../centos6_apache/apache/httpd/conf.d/README | 9 + .../centos6_apache/apache/httpd/conf.d/ssl.conf | 222 +++ .../apache/httpd/conf.d/test.example.com.conf | 7 + .../apache/httpd/conf.d/welcome.conf | 11 + .../centos6_apache/apache/httpd/conf/httpd.conf | 1009 +++++++++++ .../centos7_apache/apache/httpd/conf.d/README | 9 + .../apache/httpd/conf.d/autoindex.conf | 94 ++ .../apache/httpd/conf.d/centos.example.com.conf | 7 + .../centos7_apache/apache/httpd/conf.d/ssl.conf | 211 +++ .../apache/httpd/conf.d/userdir.conf | 36 + .../apache/httpd/conf.d/welcome.conf | 22 + .../apache/httpd/conf.modules.d/00-base.conf | 77 + .../apache/httpd/conf.modules.d/00-dav.conf | 3 + .../apache/httpd/conf.modules.d/00-lua.conf | 1 + .../apache/httpd/conf.modules.d/00-mpm.conf | 19 + .../apache/httpd/conf.modules.d/00-proxy.conf | 16 + .../apache/httpd/conf.modules.d/00-ssl.conf | 1 + .../apache/httpd/conf.modules.d/00-systemd.conf | 2 + .../apache/httpd/conf.modules.d/01-cgi.conf | 14 + .../centos7_apache/apache/httpd/conf/httpd.conf | 353 ++++ .../centos7_apache/apache/httpd/conf/magic | 385 +++++ .../tests/testdata/centos7_apache/apache/sites | 1 + .../testdata/centos7_apache/apache/sysconfig/httpd | 25 + .../tests/testdata/complex_parsing/apache2.conf | 55 + .../complex_parsing/conf-enabled/dummy.conf | 9 + .../testdata/complex_parsing/test_fnmatch.conf | 1 + .../testdata/complex_parsing/test_variables.conf | 66 + .../augeas_vhosts/apache2/apache2.conf | 196 +++ .../apache2/conf-available/bad_conf_file.conf | 3 + .../conf-available/other-vhosts-access-log.conf | 4 + .../apache2/conf-available/security.conf | 35 + .../apache2/conf-available/serve-cgi-bin.conf | 20 + .../conf-enabled/other-vhosts-access-log.conf | 1 + .../apache2/conf-enabled/security.conf | 1 + .../apache2/conf-enabled/serve-cgi-bin.conf | 1 + .../augeas_vhosts/apache2/envvars | 29 + .../apache2/mods-available/authz_svn.load | 5 + .../augeas_vhosts/apache2/mods-available/dav.load | 3 + .../apache2/mods-available/dav_svn.conf | 56 + .../apache2/mods-available/dav_svn.load | 7 + .../apache2/mods-available/rewrite.load | 1 + .../augeas_vhosts/apache2/mods-available/ssl.conf | 89 + .../augeas_vhosts/apache2/mods-available/ssl.load | 2 + .../apache2/mods-enabled/authz_svn.load | 1 + .../augeas_vhosts/apache2/mods-enabled/dav.load | 1 + .../apache2/mods-enabled/dav_svn.conf | 1 + .../apache2/mods-enabled/dav_svn.load | 1 + .../augeas_vhosts/apache2/ports.conf | 15 + .../apache2/sites-available/another_wildcard.conf | 11 + .../apache2/sites-available/old-and-default.conf | 12 + .../apache2/sites-available/wildcard.conf | 11 + .../apache2/sites-enabled/another_wildcard.conf | 1 + .../apache2/sites-enabled/old-and-default.conf | 1 + .../apache2/sites-enabled/wildcard.conf | 1 + .../testdata/debian_apache_2_4/augeas_vhosts/sites | 3 + .../default_vhost/apache2/apache2.conf | 198 +++ .../conf-available/other-vhosts-access-log.conf | 4 + .../apache2/conf-available/security.conf | 31 + .../apache2/conf-available/serve-cgi-bin.conf | 20 + .../conf-enabled/other-vhosts-access-log.conf | 1 + .../apache2/conf-enabled/security.conf | 1 + .../apache2/conf-enabled/serve-cgi-bin.conf | 1 + .../default_vhost/apache2/envvars | 28 + .../default_vhost/apache2/mods-available/ssl.conf | 89 + .../default_vhost/apache2/mods-available/ssl.load | 2 + .../default_vhost/apache2/ports.conf | 20 + .../apache2/sites-available/000-default.conf | 11 + .../apache2/sites-available/default-ssl.conf | 38 + .../apache2/sites-enabled/000-default.conf | 1 + .../testdata/debian_apache_2_4/default_vhost/sites | 1 + .../multi_vhosts/apache2/apache2.conf | 196 +++ .../debian_apache_2_4/multi_vhosts/apache2/envvars | 29 + .../multi_vhosts/apache2/ports.conf | 15 + .../apache2/sites-available/default.conf | 22 + .../apache2/sites-available/multi-vhost.conf | 38 + .../apache2/sites-enabled/default.conf | 1 + .../apache2/sites-enabled/multi-vhost.conf | 1 + .../multiple_vhosts/apache2/apache2.conf | 207 +++ .../apache2/conf-available/bad_conf_file.conf | 3 + .../conf-available/other-vhosts-access-log.conf | 4 + .../apache2/conf-available/security.conf | 35 + .../apache2/conf-available/serve-cgi-bin.conf | 20 + .../conf-enabled/other-vhosts-access-log.conf | 1 + .../apache2/conf-enabled/security.conf | 1 + .../apache2/conf-enabled/serve-cgi-bin.conf | 1 + .../multiple_vhosts/apache2/envvars | 29 + .../apache2/mods-available/authz_svn.load | 5 + .../apache2/mods-available/dav.load | 3 + .../apache2/mods-available/dav_svn.conf | 56 + .../apache2/mods-available/dav_svn.load | 7 + .../apache2/mods-available/rewrite.load | 1 + .../apache2/mods-available/ssl.conf | 89 + .../apache2/mods-available/ssl.load | 2 + .../apache2/mods-enabled/authz_svn.load | 1 + .../multiple_vhosts/apache2/mods-enabled/dav.load | 1 + .../apache2/mods-enabled/dav_svn.conf | 1 + .../apache2/mods-enabled/dav_svn.load | 1 + .../multiple_vhosts/apache2/ports.conf | 15 + .../apache2/sites-available/000-default.conf | 12 + .../apache2/sites-available/certbot.conf | 43 + .../sites-available/default-ssl-port-only.conf | 36 + .../apache2/sites-available/default-ssl.conf | 40 + .../apache2/sites-available/duplicatehttp.conf | 9 + .../apache2/sites-available/duplicatehttps.conf | 14 + .../sites-available/encryption-example.conf | 42 + .../apache2/sites-available/mod_macro-example.conf | 15 + .../apache2/sites-available/ocsp-ssl.conf | 36 + .../apache2/sites-available/wildcard.conf | 13 + .../apache2/sites-enabled/000-default.conf | 1 + .../apache2/sites-enabled/certbot.conf | 1 + .../sites-enabled/default-ssl-port-only.conf | 1 + .../apache2/sites-enabled/default-ssl.conf | 1 + .../apache2/sites-enabled/duplicatehttp.conf | 1 + .../apache2/sites-enabled/duplicatehttps.conf | 1 + .../apache2/sites-enabled/encryption-example.conf | 1 + .../apache2/sites-enabled/mod_macro-example.conf | 1 + .../apache2/sites-enabled/non-symlink.conf | 9 + .../apache2/sites-enabled/ocsp-ssl.conf | 1 + .../apache2/sites-enabled/wildcard.conf | 1 + .../debian_apache_2_4/multiple_vhosts/sites | 3 + .../gentoo_apache/apache/apache2/httpd.conf | 157 ++ .../testdata/gentoo_apache/apache/apache2/magic | 385 +++++ .../apache2/modules.d/00_default_settings.conf | 131 ++ .../apache2/modules.d/00_error_documents.conf | 57 + .../apache/apache2/modules.d/00_languages.conf | 133 ++ .../apache/apache2/modules.d/00_mod_autoindex.conf | 85 + .../apache/apache2/modules.d/00_mod_info.conf | 10 + .../apache2/modules.d/00_mod_log_config.conf | 35 + .../apache/apache2/modules.d/00_mod_mime.conf | 46 + .../apache/apache2/modules.d/00_mod_status.conf | 15 + .../apache/apache2/modules.d/00_mod_userdir.conf | 32 + .../apache/apache2/modules.d/00_mpm.conf | 99 ++ .../apache/apache2/modules.d/10_mod_mem_cache.conf | 10 + .../apache/apache2/modules.d/40_mod_ssl.conf | 67 + .../apache/apache2/modules.d/41_mod_http2.conf | 9 + .../apache/apache2/modules.d/45_mod_dav.conf | 19 + .../apache/apache2/modules.d/46_mod_ldap.conf | 18 + .../apache2/vhosts.d/00_default_ssl_vhost.conf | 191 +++ .../apache/apache2/vhosts.d/00_default_vhost.conf | 45 + .../apache/apache2/vhosts.d/default_vhost.include | 71 + .../apache2/vhosts.d/gentoo.example.com.conf | 7 + .../testdata/gentoo_apache/apache/conf.d/apache2 | 74 + .../tests/testdata/gentoo_apache/apache/sites | 3 + certbot-apache/tests/util.py | 239 +++ tox.ini | 4 +- 385 files changed, 13857 insertions(+), 13847 deletions(-) delete mode 100644 certbot-apache/certbot_apache/tests/__init__.py delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt delete mode 100755 certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test delete mode 100755 certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess delete mode 100644 certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf delete mode 100644 certbot-apache/certbot_apache/tests/autohsts_test.py delete mode 100644 certbot-apache/certbot_apache/tests/centos6_test.py delete mode 100644 certbot-apache/certbot_apache/tests/centos_test.py delete mode 100644 certbot-apache/certbot_apache/tests/complex_parsing_test.py delete mode 100644 certbot-apache/certbot_apache/tests/configurator_reverter_test.py delete mode 100644 certbot-apache/certbot_apache/tests/configurator_test.py delete mode 100644 certbot-apache/certbot_apache/tests/debian_test.py delete mode 100644 certbot-apache/certbot_apache/tests/display_ops_test.py delete mode 100644 certbot-apache/certbot_apache/tests/entrypoint_test.py delete mode 100644 certbot-apache/certbot_apache/tests/fedora_test.py delete mode 100644 certbot-apache/certbot_apache/tests/gentoo_test.py delete mode 100644 certbot-apache/certbot_apache/tests/http_01_test.py delete mode 100644 certbot-apache/certbot_apache/tests/obj_test.py delete mode 100644 certbot-apache/certbot_apache/tests/parser_test.py delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites delete mode 100644 certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd delete mode 100644 certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf delete mode 120000 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 delete mode 100644 certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites delete mode 100644 certbot-apache/certbot_apache/tests/util.py create mode 100644 certbot-apache/tests/apache-conf-files/NEEDED.txt create mode 100755 certbot-apache/tests/apache-conf-files/apache-conf-test create mode 100755 certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py create mode 100644 certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf create mode 100644 certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf create mode 100644 certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/1626-1531.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/README.modules create mode 100644 certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/example-1755.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/example-ssl.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/example.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt create mode 100644 certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf create mode 100644 certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess create mode 100644 certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf create mode 100644 certbot-apache/tests/autohsts_test.py create mode 100644 certbot-apache/tests/centos6_test.py create mode 100644 certbot-apache/tests/centos_test.py create mode 100644 certbot-apache/tests/complex_parsing_test.py create mode 100644 certbot-apache/tests/configurator_reverter_test.py create mode 100644 certbot-apache/tests/configurator_test.py create mode 100644 certbot-apache/tests/debian_test.py create mode 100644 certbot-apache/tests/display_ops_test.py create mode 100644 certbot-apache/tests/entrypoint_test.py create mode 100644 certbot-apache/tests/fedora_test.py create mode 100644 certbot-apache/tests/gentoo_test.py create mode 100644 certbot-apache/tests/http_01_test.py create mode 100644 certbot-apache/tests/obj_test.py create mode 100644 certbot-apache/tests/parser_test.py create mode 100644 certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README create mode 100644 certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf create mode 100644 certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf create mode 100644 certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf create mode 100644 certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/sites create mode 100644 certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd create mode 100644 certbot-apache/tests/testdata/complex_parsing/apache2.conf create mode 100644 certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf create mode 100644 certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf create mode 100644 certbot-apache/tests/testdata/complex_parsing/test_variables.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf create mode 120000 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf create mode 100644 certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 create mode 100644 certbot-apache/tests/testdata/gentoo_apache/apache/sites create mode 100644 certbot-apache/tests/util.py diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index 5f8396a8d..fa15504e7 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,6 +1,8 @@ include LICENSE.txt include README.rst -recursive-include certbot_apache/tests/testdata * +recursive-include tests * include certbot_apache/_internal/centos-options-ssl-apache.conf include certbot_apache/_internal/options-ssl-apache.conf recursive-include certbot_apache/_internal/augeas_lens *.aug +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-apache/certbot_apache/tests/__init__.py b/certbot-apache/certbot_apache/tests/__init__.py deleted file mode 100644 index 7e7d39fa4..000000000 --- a/certbot-apache/certbot_apache/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Apache Tests""" diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt b/certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt deleted file mode 100644 index c3606fefe..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt +++ /dev/null @@ -1,6 +0,0 @@ -Issues for which some kind of test case should be constructable, but we do not -currently have one: - -https://github.com/certbot/certbot/issues/1213 -https://github.com/certbot/certbot/issues/1602 - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test deleted file mode 100755 index 4838a6eee..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -# 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 - -function CleanupExit() { - echo control c, exiting tests... - if [ "$f" != "" ] ; then - Cleanup - fi - exit 1 -} - -function Setup() { - if [ "$APPEND_APACHECONF" = "" ] ; then - sudo cp "$f" "$EA"/sites-available/ - sudo ln -sf "$EA/sites-available/$f" "$EA/sites-enabled/$f" - echo " - - ServerName example.com - DocumentRoot /tmp/ - ErrorLog /tmp/error.log - CustomLog /tmp/requests.log combined -" | sudo tee $EA/sites-available/throwaway-example.conf >/dev/null - sudo ln -sf $EA/sites-available/throwaway-example.conf $EA/sites-enabled/throwaway-example.conf - else - TMP="/tmp/`basename \"$APPEND_APACHECONF\"`.$$" - sudo cp -a "$APPEND_APACHECONF" "$TMP" - sudo bash -c "cat \"$f\" >> \"$APPEND_APACHECONF\"" - fi -} - -function Cleanup() { - if [ "$APPEND_APACHECONF" = "" ] ; then - sudo rm /etc/apache2/sites-{enabled,available}/"$f" - sudo rm $EA/sites-available/throwaway-example.conf - sudo rm $EA/sites-enabled/throwaway-example.conf - else - sudo mv "$TMP" "$APPEND_APACHECONF" - fi -} - -# if our environment asks us to enable modules, do our best! -if [ "$1" = --debian-modules ] ; then - sudo apt-get install -y apache2 - sudo apt-get install -y libapache2-mod-wsgi - sudo apt-get install -y libapache2-mod-macro - - for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do - echo -n enabling $mod - sudo a2enmod $mod - 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 | $CERTBOT_CMD 2>&1` - if echo $RESULT | grep -Eq \("Which names would you like"\|"mod_macro is not yet"\) ; then - echo passed - else - echo failed - echo $RESULT - echo - echo - FAILS=`expr $FAILS + 1` - fi - Cleanup -done -if [ "$FAILS" -ne 0 ] ; then - exit 1 -fi -exit 0 diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py b/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py deleted file mode 100755 index 68bd6287d..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -""" -This executable script wraps the apache-conf-test bash script, in order to setup a pebble instance -before its execution. Directory URL is passed through the SERVER environment variable. -""" -import os -import subprocess -import sys - -from certbot_integration_tests.utils import acme_server - -SCRIPT_DIRNAME = os.path.dirname(__file__) - - -def main(args=None): - if not args: - args = sys.argv[1:] - with acme_server.ACMEServer('pebble', [], False) as acme_xdist: - environ = os.environ.copy() - environ['SERVER'] = acme_xdist['directory_url'] - command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')] - command.extend(args) - return subprocess.call(command, env=environ) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf deleted file mode 100644 index 7d97b23d0..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf +++ /dev/null @@ -1,52 +0,0 @@ - - ServerAdmin webmaster@localhost - ServerAlias www.example.com - ServerName example.com - DocumentRoot /var/www/example.com/www/ - SSLEngine on - - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$ - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - - - Options FollowSymLinks - AllowOverride All - - - Options Indexes FollowSymLinks MultiViews - AllowOverride All - Order allow,deny - allow from all - # This directive allows us to have apache2's default start page - # in /apache2-default/, but still have / go to the right place - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - - ErrorLog /var/log/apache2/error.log - - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog /var/log/apache2/access.log combined - ServerSignature On - - Alias /apache_doc/ "/usr/share/doc/" - - Options Indexes MultiViews FollowSymLinks - AllowOverride None - Order deny,allow - Deny from all - Allow from 127.0.0.0/255.0.0.0 ::1/128 - - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf deleted file mode 100644 index 444f0dade..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf +++ /dev/null @@ -1,295 +0,0 @@ - - AllowOverride None - Require all denied - - - - DocumentRoot /var/www/sjau.ch/web - - ServerName sjau.ch - ServerAlias www.sjau.ch - ServerAdmin webmaster@sjau.ch - - ErrorLog /var/log/ispconfig/httpd/sjau.ch/error.log - - Alias /error/ "/var/www/sjau.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client1/web2/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web2 client1 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web2 client1 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client1/web2/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - - - DocumentRoot /var/www/sjau.ch/web - - ServerName sjau.ch - ServerAlias www.sjau.ch - ServerAdmin webmaster@sjau.ch - - ErrorLog /var/log/ispconfig/httpd/sjau.ch/error.log - - Alias /error/ "/var/www/sjau.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client1/web2/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web2 client1 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web2 client1 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client1/web2/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf deleted file mode 100644 index 0388abc2c..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf +++ /dev/null @@ -1,593 +0,0 @@ - - AllowOverride None - Require all denied - - - - DocumentRoot /var/www/ensemen.ch/web - - ServerName ensemen.ch - ServerAlias www.ensemen.ch - ServerAdmin webmaster@ensemen.ch - - ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log - - Alias /error/ "/var/www/ensemen.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web17 client4 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web17 client4 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client4/web17/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - - - DocumentRoot /var/www/ensemen.ch/web - - ServerName ensemen.ch - ServerAlias www.ensemen.ch - ServerAdmin webmaster@ensemen.ch - - ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log - - Alias /error/ "/var/www/ensemen.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - SSLEngine on - SSLProtocol All -SSLv2 -SSLv3 - SSLCertificateFile /var/www/clients/client4/web17/ssl/ensemen.ch.crt - SSLCertificateKeyFile /var/www/clients/client4/web17/ssl/ensemen.ch.key - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web17 client4 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web17 client4 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client4/web17/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - - - DocumentRoot /var/www/ensemen.ch/web - - ServerName ensemen.ch - ServerAlias www.ensemen.ch - ServerAdmin webmaster@ensemen.ch - - ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log - - Alias /error/ "/var/www/ensemen.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web17 client4 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web17 client4 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client4/web17/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - - - DocumentRoot /var/www/ensemen.ch/web - - ServerName ensemen.ch - ServerAlias www.ensemen.ch - ServerAdmin webmaster@ensemen.ch - - ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log - - Alias /error/ "/var/www/ensemen.ch/web/error/" - ErrorDocument 400 /error/400.html - ErrorDocument 401 /error/401.html - ErrorDocument 403 /error/403.html - ErrorDocument 404 /error/404.html - ErrorDocument 405 /error/405.html - ErrorDocument 500 /error/500.html - ErrorDocument 502 /error/502.html - ErrorDocument 503 /error/503.html - - - SSLEngine on - SSLProtocol All -SSLv2 -SSLv3 - SSLCertificateFile /var/www/clients/client4/web17/ssl/ensemen.ch.crt - SSLCertificateKeyFile /var/www/clients/client4/web17/ssl/ensemen.ch.key - - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - # Clear PHP settings of this website - - SetHandler None - - Options +FollowSymLinks - AllowOverride All - Require all granted - - - - - Options +ExecCGI - - RubyRequire apache/ruby-run - #RubySafeLevel 0 - AddType text/html .rb - AddType text/html .rbx - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - SetHandler ruby-object - RubyHandler Apache::RubyRun.instance - - - - - - - - SetHandler mod_python - - PythonHandler mod_python.publisher - PythonDebug On - - - - # cgi enabled - - Require all granted - - ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ - - SetHandler cgi-script - - # suexec enabled - - SuexecUserGroup web17 client4 - - # php as fast-cgi enabled - # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html - - IdleTimeout 300 - ProcessLifeTime 3600 - # MaxProcessCount 1000 - DefaultMinClassProcessCount 0 - DefaultMaxClassProcessCount 100 - IPCConnectTimeout 3 - IPCCommTimeout 600 - BusyTimeout 3600 - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - SetHandler fcgid-script - - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 - FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 - Options +ExecCGI - AllowOverride All - Require all granted - - - - # add support for apache mpm_itk - - AssignUserId web17 client4 - - - - # Do not execute PHP files in webdav directory - - - SecRuleRemoveById 960015 - SecRuleRemoveById 960032 - - - SetHandler None - - - DavLockDB /var/www/clients/client4/web17/tmp/DavLock - # DO NOT REMOVE THE COMMENTS! - # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! - # WEBDAV BEGIN - # WEBDAV END - - - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf deleted file mode 100644 index 1622a57df..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf +++ /dev/null @@ -1,37 +0,0 @@ - - ServerAdmin denver@ossguy.com - ServerName c-beta.ossguy.com - - Alias /robots.txt /home/denver/www/c-beta.ossguy.com/static/robots.txt - Alias /favicon.ico /home/denver/www/c-beta.ossguy.com/static/favicon.ico - - AliasMatch /(.*\.css) /home/denver/www/c-beta.ossguy.com/static/$1 - AliasMatch /(.*\.js) /home/denver/www/c-beta.ossguy.com/static/$1 - AliasMatch /(.*\.png) /home/denver/www/c-beta.ossguy.com/static/$1 - AliasMatch /(.*\.gif) /home/denver/www/c-beta.ossguy.com/static/$1 - AliasMatch /(.*\.jpg) /home/denver/www/c-beta.ossguy.com/static/$1 - - WSGIScriptAlias / /home/denver/www/c-beta.ossguy.com/django.wsgi - WSGIDaemonProcess c-beta-ossguy user=www-data group=www-data home=/var/www processes=5 threads=10 maximum-requests=1000 umask=0007 display-name=c-beta-ossguy - WSGIProcessGroup c-beta-ossguy - WSGIApplicationGroup %{GLOBAL} - - DocumentRoot /home/denver/www/c-beta.ossguy.com/static - - - Options -Indexes +FollowSymLinks -MultiViews - Require all granted - AllowOverride None - - - - Options +Indexes +FollowSymLinks -MultiViews - Require all granted - AllowOverride None - - - # Custom log file locations - LogLevel warn - ErrorLog /tmp/error.log - CustomLog /tmp/access.log combined - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules deleted file mode 100644 index 32c3ef019..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules +++ /dev/null @@ -1,6 +0,0 @@ -# Modules required to parse these conf files: -ssl -rewrite -macro -wsgi -deflate diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf deleted file mode 100644 index 73a9b746c..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf +++ /dev/null @@ -1,14 +0,0 @@ - - ServerAdmin root@localhost - ServerName anarcat.wiki.orangeseeds.org:80 - - - UserDir disabled - - RewriteEngine On - RewriteRule ^/(.*) http\:\/\/anarc\.at\/$1 [L,R,NE] - - ErrorLog /var/log/apache2/1531error.log - LogLevel warn - CustomLog /var/log/apache2/1531access.log combined - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf deleted file mode 100644 index 4c3fa2af1..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf +++ /dev/null @@ -1,428 +0,0 @@ -# --------------------------------------------------------------- -# Core ModSecurity Rule Set ver.2.2.6 -# Copyright (C) 2006-2012 Trustwave All rights reserved. -# -# The OWASP ModSecurity Core Rule Set is distributed under -# Apache Software License (ASL) version 2 -# Please see the enclosed LICENCE file for full details. -# --------------------------------------------------------------- - - -# -# -- [[ Recommended Base Configuration ]] ------------------------------------------------- -# -# The configuration directives/settings in this file are used to control -# the OWASP ModSecurity CRS. These settings do **NOT** configure the main -# ModSecurity settings such as: -# -# - SecRuleEngine -# - SecRequestBodyAccess -# - SecAuditEngine -# - SecDebugLog -# -# You should use the modsecurity.conf-recommended file that comes with the -# ModSecurity source code archive. -# -# Ref: http://mod-security.svn.sourceforge.net/viewvc/mod-security/m2/trunk/modsecurity.conf-recommended -# - - -# -# -- [[ Rule Version ]] ------------------------------------------------------------------- -# -# Rule version data is added to the "Producer" line of Section H of the Audit log: -# -# - Producer: ModSecurity for Apache/2.7.0-rc1 (http://www.modsecurity.org/); OWASP_CRS/2.2.4. -# -# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecComponentSignature -# -#SecComponentSignature "OWASP_CRS/2.2.6" - - -# -# -- [[ Modes of Operation: Self-Contained vs. Collaborative Detection ]] ----------------- -# -# Each detection rule uses the "block" action which will inherit the SecDefaultAction -# specified below. Your settings here will determine which mode of operation you use. -# -# -- [[ Self-Contained Mode ]] -- -# Rules inherit the "deny" disruptive action. The first rule that matches will block. -# -# -- [[ Collaborative Detection Mode ]] -- -# This is a "delayed blocking" mode of operation where each matching rule will inherit -# the "pass" action and will only contribute to anomaly scores. Transactional blocking -# can be applied -# -# -- [[ Alert Logging Control ]] -- -# You have three options - -# -# - To log to both the Apache error_log and ModSecurity audit_log file use: "log" -# - To log *only* to the ModSecurity audit_log file use: "nolog,auditlog" -# - To log *only* to the Apache error_log file use: "log,noauditlog" -# -# Ref: http://blog.spiderlabs.com/2010/11/advanced-topic-of-the-week-traditional-vs-anomaly-scoring-detection-modes.html -# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecDefaultAction -# -#SecDefaultAction "phase:1,deny,log" - - -# -# -- [[ Collaborative Detection Severity Levels ]] ---------------------------------------- -# -# These are the default scoring points for each severity level. You may -# adjust these to you liking. These settings will be used in macro expansion -# in the rules to increment the anomaly scores when rules match. -# -# These are the default Severity ratings (with anomaly scores) of the individual rules - -# -# - 2: Critical - Anomaly Score of 5. -# Is the highest severity level possible without correlation. It is -# normally generated by the web attack rules (40 level files). -# - 3: Error - Anomaly Score of 4. -# Is generated mostly from outbound leakage rules (50 level files). -# - 4: Warning - Anomaly Score of 3. -# Is generated by malicious client rules (35 level files). -# - 5: Notice - Anomaly Score of 2. -# Is generated by the Protocol policy and anomaly files. -# -#SecAction \ - "id:'900001', \ - phase:1, \ - t:none, \ - setvar:tx.critical_anomaly_score=5, \ - setvar:tx.error_anomaly_score=4, \ - setvar:tx.warning_anomaly_score=3, \ - setvar:tx.notice_anomaly_score=2, \ - nolog, \ - pass" - - -# -# -- [[ Collaborative Detection Scoring Threshold Levels ]] ------------------------------ -# -# These variables are used in macro expansion in the 49 inbound blocking and 59 -# outbound blocking files. -# -# **MUST HAVE** ModSecurity v2.5.12 or higher to use macro expansion in numeric -# operators. If you have an earlier version, edit the 49/59 files directly to -# set the appropriate anomaly score levels. -# -# You should set the score to the proper threshold you would prefer. If set to "5" -# it will work similarly to previous Mod CRS rules and will create an event in the error_log -# file if there are any rules that match. If you would like to lessen the number of events -# generated in the error_log file, you should increase the anomaly score threshold to -# something like "20". This would only generate an event in the error_log file if -# there are multiple lower severity rule matches or if any 1 higher severity item matches. -# -#SecAction \ - "id:'900002', \ - phase:1, \ - t:none, \ - setvar:tx.inbound_anomaly_score_level=5, \ - nolog, \ - pass" - - -#SecAction \ - "id:'900003', \ - phase:1, \ - t:none, \ - setvar:tx.outbound_anomaly_score_level=4, \ - nolog, \ - pass" - - -# -# -- [[ Collaborative Detection Blocking ]] ----------------------------------------------- -# -# This is a collaborative detection mode where each rule will increment an overall -# anomaly score for the transaction. The scores are then evaluated in the following files: -# -# Inbound anomaly score - checked in the modsecurity_crs_49_inbound_blocking.conf file -# Outbound anomaly score - checked in the modsecurity_crs_59_outbound_blocking.conf file -# -# If you want to use anomaly scoring mode, then uncomment this line. -# -#SecAction \ - "id:'900004', \ - phase:1, \ - t:none, \ - setvar:tx.anomaly_score_blocking=on, \ - nolog, \ - pass" - - -# -# -- [[ GeoIP Database ]] ----------------------------------------------------------------- -# -# There are some rulesets that need to inspect the GEO data of the REMOTE_ADDR data. -# -# You must first download the MaxMind GeoIP Lite City DB - -# -# http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz -# -# You then need to define the proper path for the SecGeoLookupDb directive -# -# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html -# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html -# -#SecGeoLookupDb /opt/modsecurity/lib/GeoLiteCity.dat - -# -# -- [[ Regression Testing Mode ]] -------------------------------------------------------- -# -# If you are going to run the regression testing mode, you should uncomment the -# following rule. It will enable DetectionOnly mode for the SecRuleEngine and -# will enable Response Header tagging so that the client testing script can see -# which rule IDs have matched. -# -# You must specify the your source IP address where you will be running the tests -# from. -# -#SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" \ - "id:'900005', \ - phase:1, \ - t:none, \ - ctl:ruleEngine=DetectionOnly, \ - setvar:tx.regression_testing=1, \ - nolog, \ - pass" - - -# -# -- [[ HTTP Policy Settings ]] ---------------------------------------------------------- -# -# Set the following policy settings here and they will be propagated to the 23 rules -# file (modsecurity_common_23_request_limits.conf) by using macro expansion. -# If you run into false positives, you can adjust the settings here. -# -# Only the max number of args is uncommented by default as there are a high rate -# of false positives. Uncomment the items you wish to set. -# -# -# -- Maximum number of arguments in request limited -#SecAction \ - "id:'900006', \ - phase:1, \ - t:none, \ - setvar:tx.max_num_args=255, \ - nolog, \ - pass" - -# -# -- Limit argument name length -#SecAction \ - "id:'900007', \ - phase:1, \ - t:none, \ - setvar:tx.arg_name_length=100, \ - nolog, \ - pass" - -# -# -- Limit value name length -#SecAction \ - "id:'900008', \ - phase:1, \ - t:none, \ - setvar:tx.arg_length=400, \ - nolog, \ - pass" - -# -# -- Limit arguments total length -#SecAction \ - "id:'900009', \ - phase:1, \ - t:none, \ - setvar:tx.total_arg_length=64000, \ - nolog, \ - pass" - -# -# -- Individual file size is limited -#SecAction \ - "id:'900010', \ - phase:1, \ - t:none, \ - setvar:tx.max_file_size=1048576, \ - nolog, \ - pass" - -# -# -- Combined file size is limited -#SecAction \ - "id:'900011', \ - phase:1, \ - t:none, \ - setvar:tx.combined_file_sizes=1048576, \ - nolog, \ - pass" - - -# -# Set the following policy settings here and they will be propagated to the 30 rules -# file (modsecurity_crs_30_http_policy.conf) by using macro expansion. -# If you run into false positives, you can adjust the settings here. -# -#SecAction \ - "id:'900012', \ - phase:1, \ - t:none, \ - setvar:'tx.allowed_methods=GET HEAD POST OPTIONS', \ - setvar:'tx.allowed_request_content_type=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/x-amf', \ - setvar:'tx.allowed_http_versions=HTTP/0.9 HTTP/1.0 HTTP/1.1', \ - setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/', \ - setvar:'tx.restricted_headers=/Proxy-Connection/ /Lock-Token/ /Content-Range/ /Translate/ /via/ /if/', \ - nolog, \ - pass" - - -# -# -- [[ Content Security Policy (CSP) Settings ]] ----------------------------------------- -# -# The purpose of these settings is to send CSP response headers to -# Mozilla FireFox users so that you can enforce how dynamic content -# is used. CSP usage helps to prevent XSS attacks against your users. -# -# Reference Link: -# -# https://developer.mozilla.org/en/Security/CSP -# -# Uncomment this SecAction line if you want use CSP enforcement. -# You need to set the appropriate directives and settings for your site/domain and -# and activate the CSP file in the experimental_rules directory. -# -# Ref: http://blog.spiderlabs.com/2011/04/modsecurity-advanced-topic-of-the-week-integrating-content-security-policy-csp.html -# -#SecAction \ - "id:'900013', \ - phase:1, \ - t:none, \ - setvar:tx.csp_report_only=1, \ - setvar:tx.csp_report_uri=/csp_violation_report, \ - setenv:'csp_policy=allow \'self\'; img-src *.yoursite.com; media-src *.yoursite.com; style-src *.yoursite.com; frame-ancestors *.yoursite.com; script-src *.yoursite.com; report-uri %{tx.csp_report_uri}', \ - nolog, \ - pass" - - -# -# -- [[ Brute Force Protection ]] --------------------------------------------------------- -# -# If you are using the Brute Force Protection rule set, then uncomment the following -# lines and set the following variables: -# - Protected URLs: resources to protect (e.g. login pages) - set to your login page -# - Burst Time Slice Interval: time interval window to monitor for bursts -# - Request Threshold: request # threshold to trigger a burst -# - Block Period: temporary block timeout -# -#SecAction \ - "id:'900014', \ - phase:1, \ - t:none, \ - setvar:'tx.brute_force_protected_urls=/login.jsp /partner_login.php', \ - setvar:'tx.brute_force_burst_time_slice=60', \ - setvar:'tx.brute_force_counter_threshold=10', \ - setvar:'tx.brute_force_block_timeout=300', \ - nolog, \ - pass" - - -# -# -- [[ DoS Protection ]] ---------------------------------------------------------------- -# -# If you are using the DoS Protection rule set, then uncomment the following -# lines and set the following variables: -# - Burst Time Slice Interval: time interval window to monitor for bursts -# - Request Threshold: request # threshold to trigger a burst -# - Block Period: temporary block timeout -# -#SecAction \ - "id:'900015', \ - phase:1, \ - t:none, \ - setvar:'tx.dos_burst_time_slice=60', \ - setvar:'tx.dos_counter_threshold=100', \ - setvar:'tx.dos_block_timeout=600', \ - nolog, \ - pass" - - -# -# -- [[ Check UTF encoding ]] ----------------------------------------------------------- -# -# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise -# it will result in false positives. -# -# Uncomment this line if your site uses UTF8 encoding -#SecAction \ - "id:'900016', \ - phase:1, \ - t:none, \ - setvar:tx.crs_validate_utf8_encoding=1, \ - nolog, \ - pass" - - -# -# -- [[ Enable XML Body Parsing ]] ------------------------------------------------------- -# -# The rules in this file will trigger the XML parser upon an XML request -# -# Initiate XML Processor in case of xml content-type -# -#SecRule REQUEST_HEADERS:Content-Type "text/xml" \ - "id:'900017', \ - phase:1, \ - t:none,t:lowercase, \ - nolog, \ - pass, \ - chain" - #SecRule REQBODY_PROCESSOR "!@streq XML" \ - "ctl:requestBodyProcessor=XML" - - -# -# -- [[ Global and IP Collections ]] ----------------------------------------------------- -# -# Create both Global and IP collections for rules to use -# There are some CRS rules that assume that these two collections -# have already been initiated. -# -#SecRule REQUEST_HEADERS:User-Agent "^(.*)$" \ - "id:'900018', \ - phase:1, \ - t:none,t:sha1,t:hexEncode, \ - setvar:tx.ua_hash=%{matched_var}, \ - nolog, \ - pass" - - -#SecRule REQUEST_HEADERS:x-forwarded-for "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" \ - "id:'900019', \ - phase:1, \ - t:none, \ - capture, \ - setvar:tx.real_ip=%{tx.1}, \ - nolog, \ - pass" - - -#SecRule &TX:REAL_IP "!@eq 0" \ - "id:'900020', \ - phase:1, \ - t:none, \ - initcol:global=global, \ - initcol:ip=%{tx.real_ip}_%{tx.ua_hash}, \ - nolog, \ - pass" - - -#SecRule &TX:REAL_IP "@eq 0" \ - "id:'900021', \ - phase:1, \ - t:none, \ - initcol:global=global, \ - initcol:ip=%{remote_addr}_%{tx.ua_hash}, \ - nolog, \ - pass" diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf deleted file mode 100644 index 4733ffa4a..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf +++ /dev/null @@ -1,116 +0,0 @@ -# -# Apache/PHP/Drupal settings: -# - -# Protect files and directories from prying eyes. - - Order allow,deny - - -# Don't show directory listings for URLs which map to a directory. -Options -Indexes - -# Follow symbolic links in this directory. -Options +FollowSymLinks - -# Make Drupal handle any 404 errors. -ErrorDocument 404 /index.php - -# Force simple error message for requests for non-existent favicon.ico. - - # There is no end quote below, for compatibility with Apache 1.3. - ErrorDocument 404 "The requested file favicon.ico was not found. - - -# Set the default handler. -DirectoryIndex index.php - -# Override PHP settings. More in sites/default/settings.php -# but the following cannot be changed at runtime. - -# PHP 4, Apache 1. - - php_value magic_quotes_gpc 0 - php_value register_globals 0 - php_value session.auto_start 0 - php_value mbstring.http_input pass - php_value mbstring.http_output pass - php_value mbstring.encoding_translation 0 - - -# PHP 4, Apache 2. - - php_value magic_quotes_gpc 0 - php_value register_globals 0 - php_value session.auto_start 0 - php_value mbstring.http_input pass - php_value mbstring.http_output pass - php_value mbstring.encoding_translation 0 - - -# PHP 5, Apache 1 and 2. - - php_value magic_quotes_gpc 0 - php_value register_globals 0 - php_value session.auto_start 0 - php_value mbstring.http_input pass - php_value mbstring.http_output pass - php_value mbstring.encoding_translation 0 - - -# Requires mod_expires to be enabled. - - # Enable expirations. - ExpiresActive On - - # Cache all files for 2 weeks after access (A). - ExpiresDefault A1209600 - - - # Do not allow PHP scripts to be cached unless they explicitly send cache - # headers themselves. Otherwise all scripts would have to overwrite the - # headers set by mod_expires if they want another caching behavior. This may - # fail if an error occurs early in the bootstrap process, and it may cause - # problems if a non-Drupal PHP file is installed in a subdirectory. - ExpiresActive Off - - - -# Various rewrite rules. - - RewriteEngine on - - # If your site can be accessed both with and without the 'www.' prefix, you - # can use one of the following settings to redirect users to your preferred - # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: - # - # To redirect all users to access the site WITH the 'www.' prefix, - # (http://example.com/... will be redirected to http://www.example.com/...) - # adapt and uncomment the following: - # RewriteCond %{HTTP_HOST} ^example\.com$ [NC] - # RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301] - # - # To redirect all users to access the site WITHOUT the 'www.' prefix, - # (http://www.example.com/... will be redirected to http://example.com/...) - # uncomment and adapt the following: - # RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] - # RewriteRule ^(.*)$ http://example.com/$1 [L,R=301] - - # Modify the RewriteBase if you are using Drupal in a subdirectory or in a - # VirtualDocumentRoot and the rewrite rules are not working properly. - # For example if your site is at http://example.com/drupal uncomment and - # modify the following line: - # RewriteBase /drupal - # - # If your site is running in a VirtualDocumentRoot at http://example.com/, - # uncomment the following line: - # RewriteBase / - - # Rewrite URLs of the form 'x' to the form 'index.php?q=x'. - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] - - -# $Id$ diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf deleted file mode 100644 index a1aab7a39..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf +++ /dev/null @@ -1,149 +0,0 @@ -# -# Apache/PHP/Drupal settings: -# - -# Protect files and directories from prying eyes. - - Order allow,deny - - -# Don't show directory listings for URLs which map to a directory. -Options -Indexes - -# Follow symbolic links in this directory. -Options +FollowSymLinks - -# Make Drupal handle any 404 errors. -ErrorDocument 404 /index.php - -# Set the default handler. -DirectoryIndex index.php index.html index.htm - -# Override PHP settings that cannot be changed at runtime. See -# sites/default/default.settings.php and drupal_environment_initialize() in -# includes/bootstrap.inc for settings that can be changed at runtime. - -# PHP 5, Apache 1 and 2. - - php_flag magic_quotes_gpc off - php_flag magic_quotes_sybase off - php_flag register_globals off - php_flag session.auto_start off - php_value mbstring.http_input pass - php_value mbstring.http_output pass - php_flag mbstring.encoding_translation off - - -# Requires mod_expires to be enabled. - - # Enable expirations. - ExpiresActive On - - # Cache all files for 2 weeks after access (A). - ExpiresDefault A1209600 - - - # Do not allow PHP scripts to be cached unless they explicitly send cache - # headers themselves. Otherwise all scripts would have to overwrite the - # headers set by mod_expires if they want another caching behavior. This may - # fail if an error occurs early in the bootstrap process, and it may cause - # problems if a non-Drupal PHP file is installed in a subdirectory. - ExpiresActive Off - - - -# Various rewrite rules. - - RewriteEngine on - - # Set "protossl" to "s" if we were accessed via https://. This is used later - # if you enable "www." stripping or enforcement, in order to ensure that - # you don't bounce between http and https. - RewriteRule ^ - [E=protossl] - RewriteCond %{HTTPS} on - RewriteRule ^ - [E=protossl:s] - - # Make sure Authorization HTTP header is available to PHP - # even when running as CGI or FastCGI. - RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] - - # Block access to "hidden" directories whose names begin with a period. This - # includes directories used by version control systems such as Subversion or - # Git to store control files. Files whose names begin with a period, as well - # as the control files used by CVS, are protected by the FilesMatch directive - # above. - # - # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is - # not possible to block access to entire directories from .htaccess, because - # is not allowed here. - # - # If you do not have mod_rewrite installed, you should remove these - # directories from your webroot or otherwise protect them from being - # downloaded. - RewriteRule "(^|/)\." - [F] - - # If your site can be accessed both with and without the 'www.' prefix, you - # can use one of the following settings to redirect users to your preferred - # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: - # - # To redirect all users to access the site WITH the 'www.' prefix, - # (http://example.com/... will be redirected to http://www.example.com/...) - # uncomment the following: - # RewriteCond %{HTTP_HOST} . - # RewriteCond %{HTTP_HOST} !^www\. [NC] - # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] - # - # To redirect all users to access the site WITHOUT the 'www.' prefix, - # (http://www.example.com/... will be redirected to http://example.com/...) - # uncomment the following: - # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] - - # Modify the RewriteBase if you are using Drupal in a subdirectory or in a - # VirtualDocumentRoot and the rewrite rules are not working properly. - # For example if your site is at http://example.com/drupal uncomment and - # modify the following line: - # RewriteBase /drupal - # - # If your site is running in a VirtualDocumentRoot at http://example.com/, - # uncomment the following line: - # RewriteBase / - - # Pass all requests not referring directly to files in the filesystem to - # index.php. Clean URLs are handled in drupal_environment_initialize(). - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] - - # Rules to correctly serve gzip compressed CSS and JS files. - # Requires both mod_rewrite and mod_headers to be enabled. - - # Serve gzip compressed CSS files if they exist and the client accepts gzip. - RewriteCond %{HTTP:Accept-encoding} gzip - RewriteCond %{REQUEST_FILENAME}\.gz -s - RewriteRule ^(.*)\.css $1\.css\.gz [QSA] - - # Serve gzip compressed JS files if they exist and the client accepts gzip. - RewriteCond %{HTTP:Accept-encoding} gzip - RewriteCond %{REQUEST_FILENAME}\.gz -s - RewriteRule ^(.*)\.js $1\.js\.gz [QSA] - - # Serve correct content types, and prevent mod_deflate double gzip. - RewriteRule .css.gz$ - [T=text/css,E=no-gzip:1] - RewriteRule .js.gz$ - [T=text/javascript,E=no-gzip:1] - - - # Serve correct encoding type. - Header set Content-Encoding gzip - # Force proxies to cache gzipped & non-gzipped css/js files separately. - Header append Vary Accept-Encoding - - - - -# Add headers to all responses. - - # Disable content sniffing, since it's an attack vector. - Header always set X-Content-Type-Options nosniff - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf deleted file mode 100644 index 1ea53dfab..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf +++ /dev/null @@ -1,2 +0,0 @@ -RewriteCond %{HTTP:Content-Disposition} \.php [NC] -RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.+/trackback/?\ HTTP/ [NC] diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf deleted file mode 100644 index 260029576..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf +++ /dev/null @@ -1,36 +0,0 @@ - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - ServerName www.example.com - ServerAlias example.com -SetOutputFilter DEFLATE -# Do not attempt to compress the following extensions -SetEnvIfNoCase Request_URI \ -\.(?:gif|jpe?g|png|swf|flv|zip|gz|tar|mp3|mp4|m4v)$ no-gzip dont-vary - - ServerAdmin webmaster@localhost - DocumentRoot /var/www/proof - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf deleted file mode 100644 index 31deb7647..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf +++ /dev/null @@ -1,136 +0,0 @@ - - ServerName example.com - ServerAlias www.example.com - ServerAdmin webmaster@localhost - - DocumentRoot /var/www/html - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - # SSL Engine Switch: - # Enable/Disable SSL for this virtual host. - SSLEngine on - - # A self-signed (snakeoil) certificate can be created by installing - # the ssl-cert package. See - # /usr/share/doc/apache2/README.Debian.gz for more info. - # If both key and certificate are stored in the same file, only the - # SSLCertificateFile directive is needed. - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - - # Server Certificate Chain: - # Point SSLCertificateChainFile at a file containing the - # concatenation of PEM encoded CA certificates which form the - # certificate chain for the server certificate. Alternatively - # the referenced file can be the same as SSLCertificateFile - # when the CA certificates are directly appended to the server - # certificate for convenience. - #SSLCertificateChainFile /etc/apache2/ssl.crt/server-ca.crt - - # Certificate Authority (CA): - # Set the CA certificate verification path where to find CA - # certificates for client authentication or alternatively one - # huge file containing all of them (file must be PEM encoded) - # Note: Inside SSLCACertificatePath you need hash symlinks - # to point to the certificate files. Use the provided - # Makefile to update the hash symlinks after changes. - #SSLCACertificatePath /etc/ssl/certs/ - #SSLCACertificateFile /etc/apache2/ssl.crt/ca-bundle.crt - - # Certificate Revocation Lists (CRL): - # Set the CA revocation path where to find CA CRLs for client - # authentication or alternatively one huge file containing all - # of them (file must be PEM encoded) - # Note: Inside SSLCARevocationPath you need hash symlinks - # to point to the certificate files. Use the provided - # Makefile to update the hash symlinks after changes. - #SSLCARevocationPath /etc/apache2/ssl.crl/ - #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl - - # Client Authentication (Type): - # Client certificate verification type and depth. Types are - # none, optional, require and optional_no_ca. Depth is a - # number which specifies how deeply to verify the certificate - # issuer chain before deciding the certificate is not valid. - #SSLVerifyClient require - #SSLVerifyDepth 10 - - # SSL Engine Options: - # Set various options for the SSL engine. - # o FakeBasicAuth: - # Translate the client X.509 into a Basic Authorisation. This means that - # the standard Auth/DBMAuth methods can be used for access control. The - # user name is the `one line' version of the client's X.509 certificate. - # Note that no password is obtained from the user. Every entry in the user - # file needs this password: `xxj31ZMTZzkVA'. - # o ExportCertData: - # This exports two additional environment variables: SSL_CLIENT_CERT and - # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the - # server (always existing) and the client (only existing when client - # authentication is used). This can be used to import the certificates - # into CGI scripts. - # o StdEnvVars: - # This exports the standard SSL/TLS related `SSL_*' environment variables. - # Per default this exportation is switched off for performance reasons, - # because the extraction step is an expensive operation and is usually - # useless for serving static content. So one usually enables the - # exportation for CGI and SSI requests only. - # o OptRenegotiate: - # This enables optimized SSL connection renegotiation handling when SSL - # directives are used in per-directory context. - #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - - # SSL Protocol Adjustments: - # The safe and default but still SSL/TLS standard compliant shutdown - # approach is that mod_ssl sends the close notify alert but doesn't wait for - # the close notify alert from client. When you need a different shutdown - # approach you can use one of the following variables: - # o ssl-unclean-shutdown: - # This forces an unclean shutdown when the connection is closed, i.e. no - # SSL close notify alert is send or allowed to received. This violates - # the SSL/TLS standard but is needed for some brain-dead browsers. Use - # this when you receive I/O errors because of the standard approach where - # mod_ssl sends the close notify alert. - # o ssl-accurate-shutdown: - # This forces an accurate shutdown when the connection is closed, i.e. a - # SSL close notify alert is send and mod_ssl waits for the close notify - # alert of the client. This is 100% SSL/TLS standard compliant, but in - # practice often causes hanging connections with brain-dead browsers. Use - # this only for browsers where you know that their SSL implementation - # works correctly. - # Notice: Most problems of broken clients are also related to the HTTP - # keep-alive facility, so you usually additionally want to disable - # keep-alive for those clients, too. Use variable "nokeepalive" for this. - # Similarly, one has to force some clients to use HTTP/1.0 to workaround - # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and - # "force-response-1.0" for this. - BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - # MSIE 7 and newer should be able to use keepalive - BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf deleted file mode 100644 index 60bdeead6..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf +++ /dev/null @@ -1,32 +0,0 @@ - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - ServerName www.example.com - ServerAlias example.com - - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt deleted file mode 100644 index 73dc64223..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt +++ /dev/null @@ -1,222 +0,0 @@ -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See http://httpd.apache.org/docs/2.4/ for detailed information about -# the directives and /usr/share/doc/apache2/README.Debian about Debian specific -# hints. -# -# -# Summary of how the Apache 2 configuration works in Debian: -# The Apache 2 web server configuration in Debian is quite different to -# upstream's suggested way to configure the web server. This is because Debian's -# default Apache2 installation attempts to make adding and removing modules, -# virtual hosts, and extra configuration directives as flexible as possible, in -# order to make automating the changes and administering the server as easy as -# possible. - -# It is split into several files forming the configuration hierarchy outlined -# below, all located in the /etc/apache2/ directory: -# -# /etc/apache2/ -# |-- apache2.conf -# | `-- ports.conf -# |-- mods-enabled -# | |-- *.load -# | `-- *.conf -# |-- conf-enabled -# | `-- *.conf -# `-- sites-enabled -# `-- *.conf -# -# -# * apache2.conf is the main configuration file (this file). It puts the pieces -# together by including all remaining configuration files when starting up the -# web server. -# -# * ports.conf is always included from the main configuration file. It is -# supposed to determine listening ports for incoming connections which can be -# customized anytime. -# -# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ -# directories contain particular configuration snippets which manage modules, -# global configuration fragments, or virtual host configurations, -# respectively. -# -# They are activated by symlinking available configuration files from their -# respective *-available/ counterparts. These should be managed by using our -# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See -# their respective man pages for detailed information. -# -# * The binary is called apache2. Due to the use of environment variables, in -# the default configuration, apache2 needs to be started/stopped with -# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not -# work with the default configuration. - - -# Global configuration -# - -# -# ServerRoot: The top of the directory tree under which the server's -# configuration, error, and log files are kept. -# -# NOTE! If you intend to place this on an NFS (or otherwise network) -# mounted filesystem then please read the Mutex documentation (available -# at ); -# you will save yourself a lot of trouble. -# -# Do NOT add a slash at the end of the directory path. -# -#ServerRoot "/etc/apache2" - -# -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# -Mutex file:${APACHE_LOCK_DIR} default - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. -# This needs to be set in /etc/apache2/envvars -# -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 5 - - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog ${APACHE_LOG_DIR}/error.log - -# -# LogLevel: Control the severity of messages logged to the error_log. -# Available values: trace8, ..., trace1, debug, info, notice, warn, -# error, crit, alert, emerg. -# It is also possible to configure the log level for particular modules, e.g. -# "LogLevel info ssl:warn" -# -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - -# Include list of ports to listen on -Include ports.conf - - -# Sets the default security model of the Apache2 HTTPD server. It does -# not allow access to the root filesystem outside of /usr/share and /var/www. -# The former is used by web applications packaged in Debian, -# the latter may be used for local directories served by the web server. If -# your system is serving content from a sub-directory in /srv you must allow -# access here, or in any related virtual host. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - AllowOverride None - Require all granted - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# -# Options Indexes FollowSymLinks -# AllowOverride None -# Require all granted -# - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - - -# -# The following directives define some format nicknames for use with -# a CustomLog directive. -# -# These deviate from the Common Log Format definitions in that they use %O -# (the actual bytes sent including headers) instead of %b (the size of the -# requested file), because the latter makes it impossible to detect partial -# requests. -# -# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. -# Use mod_remoteip instead. -# -#LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined -LogFormat "%t \"%r\" %>s %O \"%{User-Agent}i\"" vhost_combined - -#LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined -#LogFormat "%h %l %u %t \"%r\" %>s %O" common -LogFormat "- %t \"%r\" %>s %b" noip - -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# Include of directories ignores editors' and dpkg's backup files, -# see README.Debian for details. - -# Include generic snippets of statements -IncludeOptional conf-enabled/*.conf - -# Include the virtual host configurations: -#IncludeOptional sites-enabled/*.conf - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf deleted file mode 100644 index dbfae3765..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf +++ /dev/null @@ -1,67 +0,0 @@ -#LoadModule ssl_module modules/mod_ssl.so - -Listen 4443 - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - ServerName www.eiserneketten.de - - SSLEngine on - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log noip - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - Options FollowSymLinks - AllowOverride None - Order Deny,Allow - #Deny from All - - - Alias / /eiserneketten/pages/eiserneketten.html -SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem -SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key -SSLCertificateChainFile /etc/ssl/certs/ssl-cert-snakeoil.pem -Include /etc/letsencrypt/options-ssl-apache.conf - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet - -# -# Directives to allow use of AWStats as a CGI -# -Alias /awstatsclasses "/usr/local/awstats/wwwroot/classes/" -Alias /awstatscss "/usr/local/awstats/wwwroot/css/" -Alias /awstatsicons "/usr/local/awstats/wwwroot/icon/" -ScriptAlias /awstats/ "/usr/local/awstats/wwwroot/cgi-bin/" - -# -# This is to permit URL access to scripts/files in AWStats directory. -# - - Options None - AllowOverride None - Order allow,deny - Allow from all - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf deleted file mode 100644 index f257dd9a8..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf +++ /dev/null @@ -1,21 +0,0 @@ - - - WSGIDaemonProcess _graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120 user=www-data group=www-data - WSGIProcessGroup _graphite - WSGIImportScript /usr/share/graphite-web/graphite.wsgi process-group=_graphite application-group=%{GLOBAL} - WSGIScriptAlias / /usr/share/graphite-web/graphite.wsgi - - Alias /content/ /usr/share/graphite-web/static/ - - SetHandler None - - - ErrorLog ${APACHE_LOG_DIR}/graphite-web_error.log - - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog ${APACHE_LOG_DIR}/graphite-web_access.log combined - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf deleted file mode 100644 index ad988dc05..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf +++ /dev/null @@ -1,9 +0,0 @@ - -DocumentRoot /tmp -ServerName example.com -ServerAlias www.example.com -CustomLog ${APACHE_LOG_DIR}/example.log combined - - AllowOverride All - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf deleted file mode 100644 index e2b4fd3da..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf +++ /dev/null @@ -1,18 +0,0 @@ - -DocumentRoot /tmp -ServerName example.com -ServerAlias www.example.com -CustomLog ${APACHE_LOG_DIR}/example.log combined - - AllowOverride All - - - SSLEngine on - - SSLHonorCipherOrder On - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" - - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf deleted file mode 100644 index f2d2ecbea..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf +++ /dev/null @@ -1,9 +0,0 @@ - -DocumentRoot /tmp -ServerName example.com -ServerAlias www.example.com -CustomLog ${APACHE_LOG_DIR}/example.log combined - - AllowOverride All - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf deleted file mode 100644 index f5b7a2b45..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf +++ /dev/null @@ -1,18 +0,0 @@ - -DocumentRoot /tmp -ServerName example.com -ServerAlias www.example.com -CustomLog ${APACHE_LOG_DIR}/example.log combined - - AllowOverride All - - - SSLEngine on - - SSLHonorCipherOrder On - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" - - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf deleted file mode 100644 index 7d97b23d0..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf +++ /dev/null @@ -1,52 +0,0 @@ - - ServerAdmin webmaster@localhost - ServerAlias www.example.com - ServerName example.com - DocumentRoot /var/www/example.com/www/ - SSLEngine on - - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$ - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - - - Options FollowSymLinks - AllowOverride All - - - Options Indexes FollowSymLinks MultiViews - AllowOverride All - Order allow,deny - allow from all - # This directive allows us to have apache2's default start page - # in /apache2-default/, but still have / go to the right place - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - - ErrorLog /var/log/apache2/error.log - - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog /var/log/apache2/access.log combined - ServerSignature On - - Alias /apache_doc/ "/usr/share/doc/" - - Options Indexes MultiViews FollowSymLinks - AllowOverride None - Order deny,allow - Deny from all - Allow from 127.0.0.0/255.0.0.0 ::1/128 - - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf deleted file mode 100644 index d327c9421..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf +++ /dev/null @@ -1,33 +0,0 @@ - - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - ServerName $host - - ServerAdmin webmaster@localhost - DocumentRoot $dir - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - -Use Vhost goxogle.com 80 /var/www/goxogle/ -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf deleted file mode 100644 index d0ac81fa3..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf +++ /dev/null @@ -1,13 +0,0 @@ -Alias /owncloud /usr/share/owncloud - - - Options +FollowSymLinks - AllowOverride All - - order allow,deny - allow from all - - = 2.3> - Require all granted - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf deleted file mode 100644 index 26214e7b0..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf +++ /dev/null @@ -1,7 +0,0 @@ - - RewriteEngine On - RewriteCond %{REQUEST_URI} ^.*(,|;|:|<|>|">|"<|/|\\\.\.\\).* [NC,OR] - RewriteCond %{REQUEST_URI} ^.*(\=|\@|\[|\]|\^|\`|\{|\}|\~).* [NC,OR] - RewriteCond %{REQUEST_URI} ^.*(\'|%0A|%0D|%27|%3C|%3E|%00).* [NC] - RewriteRule ^(.*)$ - [F,L] - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf deleted file mode 100644 index 72ced7fb3..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf +++ /dev/null @@ -1,61 +0,0 @@ -# Those aliases do not work properly with several hosts on your apache server -# Uncomment them to use it or adapt them to your configuration -# Alias /roundcube/program/js/tiny_mce/ /usr/share/tinymce/www/ -# Alias /roundcube /var/lib/roundcube - -# Access to tinymce files - - Options Indexes MultiViews FollowSymLinks - AllowOverride None - = 2.3> - Require all granted - - - Order allow,deny - Allow from all - - - - - Options +FollowSymLinks - # This is needed to parse /var/lib/roundcube/.htaccess. See its - # content before setting AllowOverride to None. - AllowOverride All - = 2.3> - Require all granted - - - Order allow,deny - Allow from all - - - -# Protecting basic directories: - - Options -FollowSymLinks - AllowOverride None - - - - Options -FollowSymLinks - AllowOverride None - = 2.3> - Require all denied - - - Order allow,deny - Deny from all - - - - - Options -FollowSymLinks - AllowOverride None - = 2.3> - Require all denied - - - Order allow,deny - Deny from all - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf deleted file mode 100644 index 8f65e4773..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf +++ /dev/null @@ -1,284 +0,0 @@ - -NameVirtualHost 0.0.0.0:7080 -NameVirtualHost [00000:000:000:000::0]:7080 -NameVirtualHost 0.0.0.0:7080 - -NameVirtualHost 127.0.0.1:7080 -NameVirtualHost 0.0.0.0:7081 -NameVirtualHost [0000:000:000:000::2]:7081 -NameVirtualHost 0.0.0.0:7081 - -NameVirtualHost 127.0.0.1:7081 - -ServerName "example.com" -ServerAdmin "srv@example.com" - -DocumentRoot /tmp - - - LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog - - - LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog - - -TraceEnable off - -ServerTokens ProductOnly - - - AllowOverride "All" - Options SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - - php_admin_flag engine off - - - - php_admin_flag engine off - - - - - - AllowOverride "All" - Options SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - php_admin_flag engine off - - - php_admin_flag engine off - - - - - Header add X-Powered-By PleskLin - - - - SecRuleEngine DetectionOnly - SecRequestBodyAccess On - SecRequestBodyLimit 134217728 - SecResponseBodyAccess Off - SecResponseBodyLimit 524288 - SecAuditEngine On - SecAuditLog "/var/log/modsec_audit.log" - SecAuditLogType serial - - -#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf" - - - ServerName "default" - UseCanonicalName Off - DocumentRoot /tmp - ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" - - - SSLEngine off - - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - - php_admin_flag engine on - - - - php_admin_flag engine on - - - - - - - - - - ServerName "default-0_0_0_0" - UseCanonicalName Off - DocumentRoot /tmp - ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" - - SSLEngine on - SSLVerifyClient none - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - - php_admin_flag engine on - - - - php_admin_flag engine on - - - - - - - ServerName "default-0000_000_000_00000__2" - UseCanonicalName Off - DocumentRoot /tmp - ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" - - SSLEngine on - SSLVerifyClient none - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - - php_admin_flag engine on - - - - php_admin_flag engine on - - - - - - - ServerName "default-0_0_0_0" - UseCanonicalName Off - DocumentRoot /tmp - ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" - - SSLEngine on - SSLVerifyClient none - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - - #SSLCACertificateFile "/usr/local/psa/var/certificates/cert-nLy6Z1" - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - - php_admin_flag engine on - - - - php_admin_flag engine on - - - - - - - - - - DocumentRoot /tmp - ServerName lists - ServerAlias lists.* - UseCanonicalName Off - - ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" - - Alias "/icons/" "/var/www/icons/" - Alias "/pipermail/" "/var/lib/mailman/archives/public/" - - - SSLEngine off - - - - Options FollowSymLinks - Order allow,deny - Allow from all - - - - - - - DocumentRoot /tmp - ServerName lists - ServerAlias lists.* - UseCanonicalName Off - - ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" - - Alias "/icons/" "/var/www/icons/" - Alias "/pipermail/" "/var/lib/mailman/archives/public/" - - SSLEngine on - SSLVerifyClient none - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - - - Options FollowSymLinks - Order allow,deny - Allow from all - - - - - - - RPAFproxy_ips 0.0.0.0 [00000:000:000:00000::2] 0.0.0.0 - - - RPAFproxy_ips 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0 - - - RemoteIPInternalProxy 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0 - RemoteIPHeader X-Forwarded-For - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf deleted file mode 100644 index 3f2f96965..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf +++ /dev/null @@ -1,247 +0,0 @@ -#ATTENTION! -# -#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY, -#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED. - -NameVirtualHost 192.168.100.218:80 -NameVirtualHost 10.128.178.192:80 - -NameVirtualHost 192.168.100.218:443 -NameVirtualHost 10.128.178.192:443 - - -ServerName "254020-web1.example.com" -ServerAdmin "name@example.com" - -DocumentRoot "/tmp" - - - LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog - - - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog - - - TraceEnable off - -ServerTokens ProductOnly - - - AllowOverride "All" - Options SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - -php_admin_flag engine off - - - -php_admin_flag engine off - - - - - - AllowOverride All - Options SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - php_admin_flag engine off - - - php_admin_flag engine off - - - - - Header add X-Powered-By PleskLin - - - - JkWorkersFile "/etc/httpd/conf/workers.properties" - JkLogFile /var/log/httpd/mod_jk.log - JkLogLevel info - - -#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf" - - - - ServerName "default" - UseCanonicalName Off - DocumentRoot "/tmp" - ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" - - - - SSLEngine off - - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - -php_admin_flag engine on - - - -php_admin_flag engine on - - - - - - - - - - ServerName "default-192_168_100_218" - UseCanonicalName Off - DocumentRoot "/tmp" - ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" - - - SSLEngine on - SSLVerifyClient none - #SSLCertificateFile "/usr/local/psa/var/certificates/cert-9MgutN" - - #SSLCACertificateFile "/usr/local/psa/var/certificates/cert-s6Wx3P" - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - -php_admin_flag engine on - - - -php_admin_flag engine on - - - - - - - ServerName "default-10_128_178_192" - UseCanonicalName Off - DocumentRoot "/tmp" - ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" - - - SSLEngine on - SSLVerifyClient none - #SSLCertificateFile "/usr/local/psa/var/certificates/certxfb6025" - - - - AllowOverride None - Options None - Order allow,deny - Allow from all - - - - - -php_admin_flag engine on - - - -php_admin_flag engine on - - - - - - - - - - - DocumentRoot "/tmp" - ServerName lists - ServerAlias lists.* - UseCanonicalName Off - - ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" - - Alias "/icons/" "/var/www/icons/" - Alias "/pipermail/" "/var/lib/mailman/archives/public/" - - - SSLEngine off - - - - - Options FollowSymLinks - Order allow,deny - Allow from all - - - - - - - DocumentRoot "/tmp" - ServerName lists - ServerAlias lists.* - UseCanonicalName Off - - ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" - - Alias "/icons/" "/var/www/icons/" - Alias "/pipermail/" "/var/lib/mailman/archives/public/" - - SSLEngine on - SSLVerifyClient none - #SSLCertificateFile "/usr/local/psa/var/certificates/certxfb6025" - - - - Options FollowSymLinks - Order allow,deny - Allow from all - - - - - - - RPAFproxy_ips 192.168.100.218 10.128.178.192 - - - RPAFproxy_ips 192.168.100.218 10.128.178.192 - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf deleted file mode 100644 index 89e2fb25c..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf +++ /dev/null @@ -1,44 +0,0 @@ - - ServerName semacode.com - ServerAlias www.semacode.com - DocumentRoot /tmp/ - TransferLog /tmp/access - ErrorLog /tmp/error - Redirect /posts/rss http://semacode.com/feed - Redirect permanent /weblog http://semacode.com/blog - -#ProxyPreserveHost On -# ProxyPass /past http://old.semacode.com - #ProxyPassReverse /past http://old.semacode.com -# - # Order allow,deny - #Allow from all -# - - Redirect /stylesheets/inside.css http://old.semacode.com/stylesheets/inside.css - RedirectMatch /images/portal/(.*) http://old.semacode.com/images/portal/$1 - Redirect /images/invisible.gif http://old.semacode.com/images/invisible.gif - RedirectMatch /javascripts/(.*) http://old.semacode.com/javascripts/$1 - - RewriteEngine on - RewriteRule ^/past/(.*) http://old.semacode.com/past/$1 [L,P] - RewriteCond %{HTTP_HOST} !^semacode\.com$ [NC] - RewriteCond %{HTTP_HOST} !^$ - RewriteRule ^/(.*) http://semacode.com/$1 [L,R] - - - - - - ServerName old.semacode.com - ServerAlias www.old.semacode.com - DocumentRoot /home/simon/semacode-server/semacode/website/trunk/public - TransferLog /tmp/access-old - ErrorLog /tmp/error-old - - Options FollowSymLinks - AllowOverride None - Order allow,deny - Allow from all - - diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess deleted file mode 100644 index 1c06d5497..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess +++ /dev/null @@ -1 +0,0 @@ -SSLRequire %{SSL_CLIENT_S_DN_CN} in {"foo@bar.com", "bar@foo.com"} diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf b/certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf deleted file mode 100644 index 5d3cef423..000000000 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf +++ /dev/null @@ -1,28 +0,0 @@ - - - ServerAdmin info@somethingnewentertainment.com - ServerName somethingnewentertainment.com - DocumentRoot /var/www/html - - ErrorLog /var/log/apache2/error.log - CustomLog /var/log/apache2/access.log combined - - SSLEngine on - SSLProtocol all -SSLv2 -SSLv3 - SSLHonorCipherOrder on - SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EEC DH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRS A RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4" - - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown - diff --git a/certbot-apache/certbot_apache/tests/autohsts_test.py b/certbot-apache/certbot_apache/tests/autohsts_test.py deleted file mode 100644 index 098bed019..000000000 --- a/certbot-apache/certbot_apache/tests/autohsts_test.py +++ /dev/null @@ -1,188 +0,0 @@ -# pylint: disable=too-many-lines -"""Test for certbot_apache._internal.configurator AutoHSTS functionality""" -import re -import unittest -import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import - -from certbot import errors -from certbot_apache._internal import constants -from certbot_apache.tests import util - - -class AutoHSTSTest(util.ApacheTest): - """Tests for AutoHSTS feature""" - # pylint: disable=protected-access - - def setUp(self): # pylint: disable=arguments-differ - super(AutoHSTSTest, self).setUp() - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) - self.config.parser.modules.add("headers_module") - self.config.parser.modules.add("mod_headers.c") - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - - self.vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multiple_vhosts") - - def get_autohsts_value(self, vh_path): - """ Get value from Strict-Transport-Security header """ - header_path = self.config.parser.find_dir("Header", None, vh_path) - if header_path: - pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' - for head in header_path: - if re.search(pat, self.config.parser.aug.get(head).lower()): - return self.config.parser.aug.get( - head.replace("arg[3]", "arg[4]")) - return None # pragma: no cover - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") - def test_autohsts_enable_headers_mod(self, mock_enable, _restart): - self.config.parser.modules.discard("headers_module") - self.config.parser.modules.discard("mod_header.c") - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - self.assertTrue(mock_enable.called) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - def test_autohsts_deploy_already_exists(self, _restart): - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - self.assertRaises(errors.PluginEnhancementAlreadyPresent, - self.config.enable_autohsts, - mock.MagicMock(), ["ocspvhost.com"]) - - @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.prepare") - def test_autohsts_increase(self, mock_prepare, _mock_restart): - self.config._prepared = False - maxage = "\"max-age={0}\"" - initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) - inc_val = maxage.format(constants.AUTOHSTS_STEPS[1]) - - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - # Verify initial value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - initial_val) - # Increase - self.config.update_autohsts(mock.MagicMock()) - # Verify increased value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - inc_val) - self.assertTrue(mock_prepare.called) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._autohsts_increase") - def test_autohsts_increase_noop(self, mock_increase, _restart): - maxage = "\"max-age={0}\"" - initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - # Verify initial value - self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - initial_val) - - self.config.update_autohsts(mock.MagicMock()) - # Freq not patched, so value shouldn't increase - self.assertFalse(mock_increase.called) - - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) - def test_autohsts_increase_no_header(self, _restart): - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - # Remove the header - dir_locs = self.config.parser.find_dir("Header", None, - self.vh_truth[7].path) - dir_loc = "/".join(dir_locs[0].split("/")[:-1]) - self.config.parser.aug.remove(dir_loc) - self.assertRaises(errors.PluginError, - self.config.update_autohsts, - mock.MagicMock()) - - @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - def test_autohsts_increase_and_make_permanent(self, _mock_restart): - maxage = "\"max-age={0}\"" - max_val = maxage.format(constants.AUTOHSTS_PERMANENT) - mock_lineage = mock.MagicMock() - mock_lineage.key_path = "/etc/apache2/ssl/key-certbot_15.pem" - self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) - for i in range(len(constants.AUTOHSTS_STEPS)-1): - # Ensure that value is not made permanent prematurely - self.config.deploy_autohsts(mock_lineage) - 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.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - cur_val) - # Ensure that the value is raised to max - 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.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), - max_val) - - def test_autohsts_update_noop(self): - with mock.patch("time.time") as mock_time: - # Time mock is used to make sure that the execution does not - # continue when no autohsts entries exist in pluginstorage - self.config.update_autohsts(mock.MagicMock()) - self.assertFalse(mock_time.called) - - def test_autohsts_make_permanent_noop(self): - self.config.storage.put = mock.MagicMock() - self.config.deploy_autohsts(mock.MagicMock()) - # Make sure that the execution does not continue when no entries in store - self.assertFalse(self.config.storage.put.called) - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - def test_autohsts_no_ssl_vhost(self, mock_select): - mock_select.return_value = self.vh_truth[0] - with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.assertRaises(errors.PluginError, - self.config.enable_autohsts, - mock.MagicMock(), "invalid.example.com") - self.assertTrue( - "Certbot was not able to find SSL" in mock_log.call_args[0][0]) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.add_vhost_id") - def test_autohsts_dont_enhance_twice(self, mock_id, _restart): - mock_id.return_value = "1234567" - self.config.enable_autohsts(mock.MagicMock(), - ["ocspvhost.com", "ocspvhost.com"]) - self.assertEqual(mock_id.call_count, 1) - - def test_autohsts_remove_orphaned(self): - # pylint: disable=protected-access - self.config._autohsts_fetch_state() - self.config._autohsts["orphan_id"] = {"laststep": 0, "timestamp": 0} - - self.config._autohsts_save_state() - self.config.update_autohsts(mock.MagicMock()) - self.assertFalse("orphan_id" in self.config._autohsts) - # Make sure it's removed from the pluginstorage file as well - self.config._autohsts = None - self.config._autohsts_fetch_state() - self.assertFalse(self.config._autohsts) - - def test_autohsts_make_permanent_vhost_not_found(self): - # pylint: disable=protected-access - self.config._autohsts_fetch_state() - self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0} - self.config._autohsts_save_state() - with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.config.deploy_autohsts(mock.MagicMock()) - self.assertTrue(mock_log.called) - self.assertTrue( - "VirtualHost with id orphan_id was not" in mock_log.call_args[0][0]) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/certbot_apache/tests/centos6_test.py deleted file mode 100644 index daee4cd8c..000000000 --- a/certbot-apache/certbot_apache/tests/centos6_test.py +++ /dev/null @@ -1,222 +0,0 @@ -"""Test for certbot_apache._internal.configurator for CentOS 6 overrides""" -import unittest - -from certbot.compat import os -from certbot.errors import MisconfigurationError - -from certbot_apache._internal import obj -from certbot_apache._internal import override_centos -from certbot_apache._internal import parser -from certbot_apache.tests import util - - -def get_vh_truth(temp_dir, config_name): - """Return the ground truth for the specified directory.""" - prefix = os.path.join( - temp_dir, config_name, "httpd/conf.d") - - aug_pre = "/files" + prefix - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "test.example.com.conf"), - os.path.join(aug_pre, "test.example.com.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "test.example.com"), - obj.VirtualHost( - os.path.join(prefix, "ssl.conf"), - os.path.join(aug_pre, "ssl.conf/VirtualHost"), - set([obj.Addr.fromstring("_default_:443")]), - True, True, None) - ] - return vh_truth - -class CentOS6Tests(util.ApacheTest): - """Tests for CentOS 6""" - - def setUp(self): # pylint: disable=arguments-differ - test_dir = "centos6_apache/apache" - config_root = "centos6_apache/apache/httpd" - vhost_root = "centos6_apache/apache/httpd/conf.d" - super(CentOS6Tests, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - version=(2, 2, 15), os_info="centos") - self.vh_truth = get_vh_truth( - self.temp_dir, "centos6_apache/apache") - - def test_get_parser(self): - self.assertTrue(isinstance(self.config.parser, - override_centos.CentOSParser)) - - def test_get_virtual_hosts(self): - """Make sure all vhosts are being properly found.""" - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) - found = 0 - - for vhost in vhs: - for centos_truth in self.vh_truth: - if vhost == centos_truth: - found += 1 - break - else: - raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 2) - - def test_loadmod_default(self): - ssl_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", exclude=False) - self.assertEqual(len(ssl_loadmods), 1) - # Make sure the LoadModule ssl_module is in ssl.conf (default) - self.assertTrue("ssl.conf" in ssl_loadmods[0]) - # ...and that it's not inside of - self.assertFalse("IfModule" in ssl_loadmods[0]) - - # Get the example vhost - self.config.assoc["test.example.com"] = self.vh_truth[0] - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.config.save() - - post_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", exclude=False) - - # We should now have LoadModule ssl_module in root conf and ssl.conf - self.assertEqual(len(post_loadmods), 2) - for lm in post_loadmods: - # lm[:-7] removes "/arg[#]" from the path - arguments = self.config.parser.get_all_args(lm[:-7]) - self.assertEqual(arguments, ["ssl_module", "modules/mod_ssl.so"]) - # ...and both of them should be wrapped in - # lm[:-17] strips off /directive/arg[1] from the path. - ifmod_args = self.config.parser.get_all_args(lm[:-17]) - self.assertTrue("!mod_ssl.c" in ifmod_args) - - def test_loadmod_multiple(self): - sslmod_args = ["ssl_module", "modules/mod_ssl.so"] - # Adds another LoadModule to main httpd.conf in addtition to ssl.conf - self.config.parser.add_dir(self.config.parser.loc["default"], "LoadModule", - sslmod_args) - self.config.save() - pre_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", exclude=False) - # LoadModules are not within IfModule blocks - self.assertFalse(any(["ifmodule" in m.lower() for m in pre_loadmods])) - self.config.assoc["test.example.com"] = self.vh_truth[0] - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - post_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", exclude=False) - - for mod in post_loadmods: - self.assertTrue(self.config.parser.not_modssl_ifmodule(mod)) #pylint: disable=no-member - - def test_loadmod_rootconf_exists(self): - sslmod_args = ["ssl_module", "modules/mod_ssl.so"] - rootconf_ifmod = self.config.parser.get_ifmod( - parser.get_aug_path(self.config.parser.loc["default"]), - "!mod_ssl.c", beginning=True) - self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) - self.config.save() - # Get the example vhost - self.config.assoc["test.example.com"] = self.vh_truth[0] - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.config.save() - - root_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", - start=parser.get_aug_path(self.config.parser.loc["default"]), - exclude=False) - - mods = [lm for lm in root_loadmods if self.config.parser.loc["default"] in lm] - - self.assertEqual(len(mods), 1) - # [:-7] removes "/arg[#]" from the path - self.assertEqual( - self.config.parser.get_all_args(mods[0][:-7]), - sslmod_args) - - def test_neg_loadmod_already_on_path(self): - loadmod_args = ["ssl_module", "modules/mod_ssl.so"] - ifmod = self.config.parser.get_ifmod( - self.vh_truth[1].path, "!mod_ssl.c", beginning=True) - self.config.parser.add_dir(ifmod[:-1], "LoadModule", loadmod_args) - self.config.parser.add_dir(self.vh_truth[1].path, "LoadModule", loadmod_args) - self.config.save() - pre_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) - self.assertEqual(len(pre_loadmods), 2) - # The ssl.conf now has two LoadModule directives, one inside of - # !mod_ssl.c IfModule - self.config.assoc["test.example.com"] = self.vh_truth[0] - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.config.save() - # Ensure that the additional LoadModule wasn't written into the IfModule - post_loadmods = self.config.parser.find_dir( - "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) - self.assertEqual(len(post_loadmods), 1) - - def test_loadmod_non_duplicate(self): - # the modules/mod_ssl.so exists in ssl.conf - sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"] - rootconf_ifmod = self.config.parser.get_ifmod( - parser.get_aug_path(self.config.parser.loc["default"]), - "!mod_ssl.c", beginning=True) - self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) - self.config.save() - self.config.assoc["test.example.com"] = self.vh_truth[0] - pre_matches = self.config.parser.find_dir("LoadModule", - "ssl_module", exclude=False) - - self.assertRaises(MisconfigurationError, self.config.deploy_cert, - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - - post_matches = self.config.parser.find_dir("LoadModule", - "ssl_module", exclude=False) - # Make sure that none was changed - self.assertEqual(pre_matches, post_matches) - - def test_loadmod_not_found(self): - # Remove all existing LoadModule ssl_module... directives - orig_loadmods = self.config.parser.find_dir("LoadModule", - "ssl_module", - exclude=False) - for mod in orig_loadmods: - noarg_path = mod.rpartition("/")[0] - self.config.parser.aug.remove(noarg_path) - self.config.save() - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - - post_loadmods = self.config.parser.find_dir("LoadModule", - "ssl_module", - exclude=False) - self.assertFalse(post_loadmods) - - def test_no_ifmod_search_false(self): - #pylint: disable=no-member - - self.assertFalse(self.config.parser.not_modssl_ifmodule( - "/path/does/not/include/ifmod" - )) - self.assertFalse(self.config.parser.not_modssl_ifmodule( - "" - )) - self.assertFalse(self.config.parser.not_modssl_ifmodule( - "/path/includes/IfModule/but/no/arguments" - )) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/certbot_apache/tests/centos_test.py deleted file mode 100644 index 4fa838976..000000000 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Test for certbot_apache._internal.configurator for Centos overrides""" -import unittest - -import mock - -from certbot import errors -from certbot.compat import filesystem -from certbot.compat import os - -from certbot_apache._internal import obj -from certbot_apache._internal import override_centos -from certbot_apache.tests import util - - -def get_vh_truth(temp_dir, config_name): - """Return the ground truth for the specified directory.""" - prefix = os.path.join( - temp_dir, config_name, "httpd/conf.d") - - aug_pre = "/files" + prefix - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "centos.example.com.conf"), - os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "centos.example.com"), - obj.VirtualHost( - os.path.join(prefix, "ssl.conf"), - os.path.join(aug_pre, "ssl.conf/VirtualHost"), - set([obj.Addr.fromstring("_default_:443")]), - True, True, None) - ] - return vh_truth - -class FedoraRestartTest(util.ApacheTest): - """Tests for Fedora specific self-signed certificate override""" - - def setUp(self): # pylint: disable=arguments-differ - test_dir = "centos7_apache/apache" - config_root = "centos7_apache/apache/httpd" - vhost_root = "centos7_apache/apache/httpd/conf.d" - super(FedoraRestartTest, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="fedora_old") - self.vh_truth = get_vh_truth( - self.temp_dir, "centos7_apache/apache") - - def _run_fedora_test(self): - self.assertIsInstance(self.config, override_centos.CentOSConfigurator) - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ["fedora", "28"] - self.config.config_test() - - def test_non_fedora_error(self): - c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" - with mock.patch(c_test) as mock_test: - mock_test.side_effect = errors.MisconfigurationError - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ["not_fedora"] - self.assertRaises(errors.MisconfigurationError, - self.config.config_test) - - def test_fedora_restart_error(self): - c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" - with mock.patch(c_test) as mock_test: - # First call raises error, second doesn't - mock_test.side_effect = [errors.MisconfigurationError, ''] - with mock.patch("certbot.util.run_script") as mock_run: - mock_run.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, - self._run_fedora_test) - - def test_fedora_restart(self): - c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" - with mock.patch(c_test) as mock_test: - with mock.patch("certbot.util.run_script") as mock_run: - # First call raises error, second doesn't - mock_test.side_effect = [errors.MisconfigurationError, ''] - self._run_fedora_test() - self.assertEqual(mock_test.call_count, 2) - self.assertEqual(mock_run.call_args[0][0], - ['systemctl', 'restart', 'httpd']) - - -class MultipleVhostsTestCentOS(util.ApacheTest): - """Multiple vhost tests for CentOS / RHEL family of distros""" - - _multiprocess_can_split_ = True - - def setUp(self): # pylint: disable=arguments-differ - test_dir = "centos7_apache/apache" - config_root = "centos7_apache/apache/httpd" - vhost_root = "centos7_apache/apache/httpd/conf.d" - super(MultipleVhostsTestCentOS, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="centos") - self.vh_truth = get_vh_truth( - self.temp_dir, "centos7_apache/apache") - - def test_get_parser(self): - self.assertIsInstance(self.config.parser, override_centos.CentOSParser) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_opportunistic_httpd_runtime_parsing(self, mock_get): - define_val = ( - 'Define: TEST1\n' - 'Define: TEST2\n' - 'Define: DUMP_RUN_CFG\n' - ) - mod_val = ( - 'Loaded Modules:\n' - ' mock_module (static)\n' - ' another_module (static)\n' - ) - def mock_get_cfg(command): - """Mock httpd process stdout""" - if command == ['apachectl', '-t', '-D', 'DUMP_RUN_CFG']: - return define_val - elif command == ['apachectl', '-t', '-D', 'DUMP_MODULES']: - return mod_val - return "" - mock_get.side_effect = mock_get_cfg - self.config.parser.modules = set() - self.config.parser.variables = {} - - with mock.patch("certbot.util.get_os_info") as mock_osi: - # Make sure we have the have the CentOS httpd constants - mock_osi.return_value = ("centos", "7") - self.config.parser.update_runtime_variables() - - 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) - - def test_get_virtual_hosts(self): - """Make sure all vhosts are being properly found.""" - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) - found = 0 - - for vhost in vhs: - for centos_truth in self.vh_truth: - if vhost == centos_truth: - found += 1 - break - else: - raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 2) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_get_sysconfig_vars(self, mock_cfg): - """Make sure we read the sysconfig OPTIONS variable correctly""" - # Return nothing for the process calls - mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = filesystem.realpath( - os.path.join(self.config.parser.root, "../sysconfig/httpd")) - self.config.parser.variables = {} - - with mock.patch("certbot.util.get_os_info") as mock_osi: - # Make sure we have the have the CentOS httpd constants - mock_osi.return_value = ("centos", "7") - self.config.parser.update_runtime_variables() - - self.assertTrue("mock_define" in self.config.parser.variables.keys()) - self.assertTrue("mock_define_too" in self.config.parser.variables.keys()) - self.assertTrue("mock_value" in self.config.parser.variables.keys()) - self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) - self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) - self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - - @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) - - @mock.patch("certbot_apache._internal.configurator.util.run_script") - def test_alt_restart_errors(self, mock_run_script): - mock_run_script.side_effect = [None, - errors.SubprocessError, - errors.SubprocessError] - self.assertRaises(errors.MisconfigurationError, self.config.restart) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/complex_parsing_test.py b/certbot-apache/certbot_apache/tests/complex_parsing_test.py deleted file mode 100644 index 7627b7a48..000000000 --- a/certbot-apache/certbot_apache/tests/complex_parsing_test.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Tests for certbot_apache._internal.parser.""" -import shutil -import unittest - -from certbot import errors -from certbot.compat import os - -from certbot_apache.tests import util - - -class ComplexParserTest(util.ParserTest): - """Apache Parser Test.""" - - def setUp(self): # pylint: disable=arguments-differ - super(ComplexParserTest, self).setUp( - "complex_parsing", "complex_parsing") - - self.setup_variables() - # This needs to happen after due to setup_variables not being run - # until after - self.parser.parse_modules() # pylint: disable=protected-access - - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - - def setup_variables(self): - """Set up variables for parser.""" - self.parser.variables.update( - { - "COMPLEX": "", - "tls_port": "1234", - "fnmatch_filename": "test_fnmatch.conf", - "tls_port_str": "1234" - } - ) - - def test_filter_args_num(self): - """Note: This may also fail do to Include conf-enabled/ syntax.""" - matches = self.parser.find_dir("TestArgsDirective") - - self.assertEqual(len(self.parser.filter_args_num(matches, 1)), 3) - self.assertEqual(len(self.parser.filter_args_num(matches, 2)), 2) - self.assertEqual(len(self.parser.filter_args_num(matches, 3)), 1) - - def test_basic_variable_parsing(self): - matches = self.parser.find_dir("TestVariablePort") - - self.assertEqual(len(matches), 1) - self.assertEqual(self.parser.get_arg(matches[0]), "1234") - - def test_basic_variable_parsing_quotes(self): - matches = self.parser.find_dir("TestVariablePortStr") - - self.assertEqual(len(matches), 1) - self.assertEqual(self.parser.get_arg(matches[0]), "1234") - - def test_invalid_variable_parsing(self): - del self.parser.variables["tls_port"] - - matches = self.parser.find_dir("TestVariablePort") - self.assertRaises( - errors.PluginError, self.parser.get_arg, matches[0]) - - def test_basic_ifdefine(self): - self.assertEqual(len(self.parser.find_dir("VAR_DIRECTIVE")), 2) - self.assertEqual(len(self.parser.find_dir("INVALID_VAR_DIRECTIVE")), 0) - - def test_basic_ifmodule(self): - self.assertEqual(len(self.parser.find_dir("MOD_DIRECTIVE")), 2) - self.assertEqual( - len(self.parser.find_dir("INVALID_MOD_DIRECTIVE")), 0) - - def test_nested(self): - self.assertEqual(len(self.parser.find_dir("NESTED_DIRECTIVE")), 3) - self.assertEqual( - len(self.parser.find_dir("INVALID_NESTED_DIRECTIVE")), 0) - - def test_load_modules(self): - """If only first is found, there is bad variable parsing.""" - self.assertTrue("status_module" in self.parser.modules) - self.assertTrue("mod_status.c" in self.parser.modules) - - # This is in an IfDefine - self.assertTrue("ssl_module" in self.parser.modules) - self.assertTrue("mod_ssl.c" in self.parser.modules) - - def verify_fnmatch(self, arg, hit=True): - """Test if Include was correctly parsed.""" - from certbot_apache._internal import parser - self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), - "Include", [arg]) - if hit: - self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE")) - else: - self.assertFalse(self.parser.find_dir("FNMATCH_DIRECTIVE")) - - # NOTE: Only run one test per function otherwise you will have - # inf recursion - def test_include(self): - self.verify_fnmatch("test_fnmatch.?onf") - - def test_include_complex(self): - self.verify_fnmatch("../complex_parsing/[te][te]st_*.?onf") - - def test_include_fullpath(self): - self.verify_fnmatch(os.path.join(self.config_path, - "test_fnmatch.conf")) - - def test_include_fullpath_trailing_slash(self): - self.verify_fnmatch(self.config_path + "//") - - def test_include_single_quotes(self): - self.verify_fnmatch("'" + self.config_path + "'") - - def test_include_double_quotes(self): - self.verify_fnmatch('"' + self.config_path + '"') - - def test_include_variable(self): - self.verify_fnmatch("../complex_parsing/${fnmatch_filename}") - - def test_include_missing(self): - # This should miss - self.verify_fnmatch("test_*.onf", False) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py b/certbot-apache/certbot_apache/tests/configurator_reverter_test.py deleted file mode 100644 index 045815e1f..000000000 --- a/certbot-apache/certbot_apache/tests/configurator_reverter_test.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Test for certbot_apache._internal.configurator implementations of reverter""" -import shutil -import unittest - -import mock - -from certbot import errors - -from certbot_apache.tests import util - - -class ConfiguratorReverterTest(util.ApacheTest): - """Test for ApacheConfigurator reverter methods""" - - - def setUp(self): # pylint: disable=arguments-differ - super(ConfiguratorReverterTest, self).setUp() - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) - - self.vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multiple_vhosts") - - def tearDown(self): - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - shutil.rmtree(self.temp_dir) - - def test_bad_save_checkpoint(self): - self.config.reverter.add_to_checkpoint = mock.Mock( - side_effect=errors.ReverterError) - self.config.parser.add_dir( - self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save) - - def test_bad_save_finalize_checkpoint(self): - self.config.reverter.finalize_checkpoint = mock.Mock( - side_effect=errors.ReverterError) - self.config.parser.add_dir( - self.vh_truth[0].path, "Test", "bad_save_ckpt") - self.assertRaises(errors.PluginError, self.config.save, "Title") - - def test_finalize_save(self): - mock_finalize = mock.Mock() - self.config.reverter = mock_finalize - self.config.save("Example Title") - - self.assertTrue(mock_finalize.is_called) - - def test_revert_challenge_config(self): - mock_load = mock.Mock() - self.config.parser.aug.load = mock_load - - self.config.revert_challenge_config() - self.assertEqual(mock_load.call_count, 1) - - def test_revert_challenge_config_error(self): - self.config.reverter.revert_temporary_config = mock.Mock( - side_effect=errors.ReverterError) - - self.assertRaises( - errors.PluginError, self.config.revert_challenge_config) - - def test_rollback_checkpoints(self): - mock_load = mock.Mock() - self.config.parser.aug.load = mock_load - - self.config.rollback_checkpoints() - self.assertEqual(mock_load.call_count, 1) - - def test_rollback_error(self): - self.config.reverter.rollback_checkpoints = mock.Mock( - side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) - - def test_recovery_routine_reload(self): - mock_load = mock.Mock() - self.config.parser.aug.load = mock_load - self.config.recovery_routine() - self.assertEqual(mock_load.call_count, 1) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py deleted file mode 100644 index 4321a1fa0..000000000 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ /dev/null @@ -1,1772 +0,0 @@ -# pylint: disable=too-many-lines -"""Test for certbot_apache._internal.configurator.""" -import copy -import shutil -import socket -import tempfile -import unittest - -import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import - -from acme import challenges - -from certbot import achallenges -from certbot import crypto_util -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem -from certbot.tests import acme_util -from certbot.tests import util as certbot_util - -from certbot_apache._internal import apache_util -from certbot_apache._internal import constants -from certbot_apache._internal import obj -from certbot_apache._internal import parser -from certbot_apache.tests import util - - -class MultipleVhostsTest(util.ApacheTest): - """Test two standard well-configured HTTP vhosts.""" - - def setUp(self): # pylint: disable=arguments-differ - super(MultipleVhostsTest, self).setUp() - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) - self.config = self.mock_deploy_cert(self.config) - self.vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multiple_vhosts") - - def mock_deploy_cert(self, config): - """A test for a mock deploy cert""" - config.real_deploy_cert = self.config.deploy_cert - - def mocked_deploy_cert(*args, **kwargs): - """a helper to mock a deployed cert""" - g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" - with mock.patch(g_mod): - config.real_deploy_cert(*args, **kwargs) - self.config.deploy_cert = mocked_deploy_cert - return self.config - - @mock.patch("certbot_apache._internal.configurator.path_surgery") - def test_prepare_no_install(self, mock_surgery): - silly_path = {"PATH": "/tmp/nothingness2342"} - mock_surgery.return_value = False - with mock.patch.dict('os.environ', silly_path): - self.assertRaises(errors.NoInstallationError, self.config.prepare) - self.assertEqual(mock_surgery.call_count, 1) - - @mock.patch("certbot_apache._internal.parser.ApacheParser") - @mock.patch("certbot_apache._internal.configurator.util.exe_exists") - def test_prepare_version(self, mock_exe_exists, _): - mock_exe_exists.return_value = True - self.config.version = None - self.config.config_test = mock.Mock() - self.config.get_version = mock.Mock(return_value=(1, 1)) - - self.assertRaises( - errors.NotSupportedError, self.config.prepare) - - def test_prepare_locked(self): - server_root = self.config.conf("server-root") - self.config.config_test = mock.Mock() - os.remove(os.path.join(server_root, ".certbot.lock")) - certbot_util.lock_and_call(self._test_prepare_locked, server_root) - - @mock.patch("certbot_apache._internal.parser.ApacheParser") - @mock.patch("certbot_apache._internal.configurator.util.exe_exists") - def _test_prepare_locked(self, unused_parser, unused_exe_exists): - try: - self.config.prepare() - except errors.PluginError as err: - err_msg = str(err) - self.assertTrue("lock" in err_msg) - self.assertTrue(self.config.conf("server-root") in err_msg) - else: # pragma: no cover - self.fail("Exception wasn't raised!") - - def test_add_parser_arguments(self): # pylint: disable=no-self-use - from certbot_apache._internal.configurator import ApacheConfigurator - # Weak test.. - ApacheConfigurator.add_parser_arguments(mock.MagicMock()) - - def test_docs_parser_arguments(self): - os.environ["CERTBOT_DOCS"] = "1" - from certbot_apache._internal.configurator import ApacheConfigurator - mock_add = mock.MagicMock() - ApacheConfigurator.add_parser_arguments(mock_add) - parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", - "vhost_root", "logs_root", "challenge_location", - "handle_modules", "handle_sites", "ctl"] - exp = dict() - - for k in ApacheConfigurator.OS_DEFAULTS: - if k in parserargs: - exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] - # Special cases - exp["vhost-root"] = None - - found = set() - for call in mock_add.call_args_list: - found.add(call[0][0]) - - # Make sure that all (and only) the expected values exist - self.assertEqual(len(mock_add.call_args_list), len(found)) - for e in exp: - self.assertTrue(e in found) - - del os.environ["CERTBOT_DOCS"] - - def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use - from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES - for cls in OVERRIDE_CLASSES.values(): - cls.add_parser_arguments(mock.MagicMock()) - - def test_all_configurators_defaults_defined(self): - from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES - from certbot_apache._internal.configurator import ApacheConfigurator - parameters = set(ApacheConfigurator.OS_DEFAULTS.keys()) - for cls in OVERRIDE_CLASSES.values(): - self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys()))) - - def test_constant(self): - self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in - self.config.option("server_root")) - self.assertEqual(self.config.option("nonexistent"), None) - - @certbot_util.patch_get_utility() - def test_get_all_names(self, mock_getutility): - mock_utility = mock_getutility() - mock_utility.notification = mock.MagicMock(return_value=True) - names = self.config.get_all_names() - self.assertEqual(names, set( - ["certbot.demo", "ocspvhost.com", "encryption-example.demo", - "nonsym.link", "vhost.in.rootconf", "www.certbot.demo", - "duplicate.example.com"] - )) - - @certbot_util.patch_get_utility() - @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") - def test_get_all_names_addrs(self, mock_gethost, mock_getutility): - mock_gethost.side_effect = [("google.com", "", ""), socket.error] - mock_utility = mock_getutility() - mock_utility.notification.return_value = True - vhost = obj.VirtualHost( - "fp", "ap", - set([obj.Addr(("8.8.8.8", "443")), - obj.Addr(("zombo.com",)), - obj.Addr(("192.168.1.2"))]), - True, False) - - self.config.vhosts.append(vhost) - - names = self.config.get_all_names() - self.assertEqual(len(names), 9) - self.assertTrue("zombo.com" in names) - self.assertTrue("google.com" in names) - self.assertTrue("certbot.demo" in names) - - def test_get_bad_path(self): - self.assertEqual(apache_util.get_file_path(None), None) - self.assertEqual(apache_util.get_file_path("nonexistent"), None) - self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access - - def test_get_aug_internal_path(self): - from certbot_apache._internal.apache_util import get_internal_aug_path - internal_paths = [ - "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost", - "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost", - "IfModule/VirtualHost"] - - for i, internal_path in enumerate(internal_paths): - self.assertEqual( - get_internal_aug_path(self.vh_truth[i].path), internal_path) - - def test_bad_servername_alias(self): - ssl_vh1 = obj.VirtualHost( - "fp1", "ap1", set([obj.Addr(("*", "443"))]), - True, False) - # pylint: disable=protected-access - self.config._add_servernames(ssl_vh1) - self.assertTrue( - self.config._add_servername_alias("oy_vey", ssl_vh1) is None) - - def test_add_servernames_alias(self): - self.config.parser.add_dir( - self.vh_truth[2].path, "ServerAlias", ["*.le.co"]) - # pylint: disable=protected-access - self.config._add_servernames(self.vh_truth[2]) - self.assertEqual( - self.vh_truth[2].get_names(), set(["*.le.co", "ip-172-30-0-17"])) - - def test_get_virtual_hosts(self): - """Make sure all vhosts are being properly found.""" - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 12) - found = 0 - - for vhost in vhs: - for truth in self.vh_truth: - if vhost == truth: - found += 1 - break - else: - raise Exception("Missed: %s" % vhost) # pragma: no cover - - self.assertEqual(found, 12) - - # Handle case of non-debian layout get_virtual_hosts - with mock.patch( - "certbot_apache._internal.configurator.ApacheConfigurator.conf" - ) as mock_conf: - mock_conf.return_value = False - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 12) - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - def test_choose_vhost_none_avail(self, mock_select): - mock_select.return_value = None - self.assertRaises( - errors.PluginError, self.config.choose_vhost, "none.com") - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - def test_choose_vhost_select_vhost_ssl(self, mock_select): - mock_select.return_value = self.vh_truth[1] - self.assertEqual( - self.vh_truth[1], self.config.choose_vhost("none.com")) - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") - def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select): - mock_select.return_value = self.vh_truth[0] - mock_conf.return_value = False - chosen_vhost = self.config.choose_vhost("none.com") - self.vh_truth[0].aliases.add("none.com") - self.assertEqual( - self.vh_truth[0].get_names(), chosen_vhost.get_names()) - - # Make sure we go from HTTP -> HTTPS - self.assertFalse(self.vh_truth[0].ssl) - self.assertTrue(chosen_vhost.ssl) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") - @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") - def test_choose_vhost_and_servername_addition(self, mock_add, mock_find): - ret_vh = self.vh_truth[8] - ret_vh.enabled = False - mock_find.return_value = self.vh_truth[8] - self.config.choose_vhost("whatever.com") - self.assertTrue(mock_add.called) - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - def test_choose_vhost_select_vhost_with_temp(self, mock_select): - mock_select.return_value = self.vh_truth[0] - chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) - self.assertEqual(self.vh_truth[0], chosen_vhost) - - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): - mock_select.return_value = self.vh_truth[3] - conflicting_vhost = obj.VirtualHost( - "path", "aug_path", set([obj.Addr.fromstring("*:443")]), - True, True) - self.config.vhosts.append(conflicting_vhost) - - self.assertRaises( - errors.PluginError, self.config.choose_vhost, "none.com") - - def test_find_best_http_vhost_default(self): - vh = obj.VirtualHost( - "fp", "ap", set([obj.Addr.fromstring("_default_:80")]), False, True) - self.config.vhosts = [vh] - self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh) - - def test_find_best_http_vhost_port(self): - port = "8080" - vh = obj.VirtualHost( - "fp", "ap", set([obj.Addr.fromstring("*:" + port)]), - False, True, "encryption-example.demo") - self.config.vhosts.append(vh) - self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh) - - def test_findbest_continues_on_short_domain(self): - # pylint: disable=protected-access - chosen_vhost = self.config._find_best_vhost("purple.com") - self.assertEqual(None, chosen_vhost) - - def test_findbest_continues_on_long_domain(self): - # pylint: disable=protected-access - chosen_vhost = self.config._find_best_vhost("green.red.purple.com") - self.assertEqual(None, chosen_vhost) - - def test_find_best_vhost(self): - # pylint: disable=protected-access - self.assertEqual( - self.vh_truth[3], self.config._find_best_vhost("certbot.demo")) - self.assertEqual( - self.vh_truth[0], - self.config._find_best_vhost("encryption-example.demo")) - self.assertEqual( - self.config._find_best_vhost("does-not-exist.com"), None) - - def test_find_best_vhost_variety(self): - # pylint: disable=protected-access - ssl_vh = obj.VirtualHost( - "fp", "ap", set([obj.Addr(("*", "443")), - obj.Addr(("zombo.com",))]), - True, False) - self.config.vhosts.append(ssl_vh) - self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) - - def test_find_best_vhost_default(self): - # pylint: disable=protected-access - # Assume only the two default vhosts. - self.config.vhosts = [ - vh for vh in self.config.vhosts - if vh.name not in ["certbot.demo", "nonsym.link", - "encryption-example.demo", "duplicate.example.com", - "ocspvhost.com", "vhost.in.rootconf"] - and "*.blue.purple.com" not in vh.aliases - ] - self.assertEqual( - self.config._find_best_vhost("encryption-example.demo"), - self.vh_truth[2]) - - def test_non_default_vhosts(self): - # pylint: disable=protected-access - vhosts = self.config._non_default_vhosts(self.config.vhosts) - self.assertEqual(len(vhosts), 10) - - def test_deploy_cert_enable_new_vhost(self): - # Create - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - - self.assertFalse(ssl_vhost.enabled) - self.config.deploy_cert( - "encryption-example.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.assertTrue(ssl_vhost.enabled) - - def test_no_duplicate_include(self): - def mock_find_dir(directive, argument, _): - """Mock method for parser.find_dir""" - if directive == "Include" and argument.endswith("options-ssl-apache.conf"): - return ["/path/to/whatever"] - return None # pragma: no cover - - mock_add = mock.MagicMock() - self.config.parser.add_dir = mock_add - self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access - tried_to_add = False - for a in mock_add.call_args_list: - if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: - tried_to_add = True - # Include should be added, find_dir is not patched, and returns falsy - self.assertTrue(tried_to_add) - - self.config.parser.find_dir = mock_find_dir - mock_add.reset_mock() - self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access - for a in mock_add.call_args_list: - if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: - self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \ - # pragma: no cover - - def test_deploy_cert(self): - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - # Patch _add_dummy_ssl_directives to make sure we write them correctly - # pylint: disable=protected-access - orig_add_dummy = self.config._add_dummy_ssl_directives - def mock_add_dummy_ssl(vhostpath): - """Mock method for _add_dummy_ssl_directives""" - def find_args(path, directive): - """Return list of arguments in requested directive at path""" - f_args = [] - dirs = self.config.parser.find_dir(directive, None, - path) - for d in dirs: - f_args.append(self.config.parser.get_arg(d)) - return f_args - # Verify that the dummy directives do not exist - self.assertFalse( - "insert_cert_file_path" in find_args(vhostpath, - "SSLCertificateFile")) - self.assertFalse( - "insert_key_file_path" in find_args(vhostpath, - "SSLCertificateKeyFile")) - orig_add_dummy(vhostpath) - # Verify that the dummy directives exist - self.assertTrue( - "insert_cert_file_path" in find_args(vhostpath, - "SSLCertificateFile")) - self.assertTrue( - "insert_key_file_path" in find_args(vhostpath, - "SSLCertificateKeyFile")) - # pylint: disable=protected-access - self.config._add_dummy_ssl_directives = mock_add_dummy_ssl - - # Get the default 443 vhost - self.config.assoc["random.demo"] = self.vh_truth[1] - self.config.deploy_cert( - "random.demo", - "example/cert.pem", "example/key.pem", "example/cert_chain.pem") - self.config.save() - - # Verify ssl_module was enabled. - self.assertTrue(self.vh_truth[1].enabled) - self.assertTrue("ssl_module" in self.config.parser.modules) - - loc_cert = self.config.parser.find_dir( - "sslcertificatefile", "example/cert.pem", self.vh_truth[1].path) - loc_key = self.config.parser.find_dir( - "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) - loc_chain = self.config.parser.find_dir( - "SSLCertificateChainFile", "example/cert_chain.pem", - self.vh_truth[1].path) - - # Verify one directive was found in the correct file - self.assertEqual(len(loc_cert), 1) - self.assertEqual( - apache_util.get_file_path(loc_cert[0]), - self.vh_truth[1].filep) - - self.assertEqual(len(loc_key), 1) - self.assertEqual( - apache_util.get_file_path(loc_key[0]), - self.vh_truth[1].filep) - - self.assertEqual(len(loc_chain), 1) - self.assertEqual( - apache_util.get_file_path(loc_chain[0]), - self.vh_truth[1].filep) - - # One more time for chain directive setting - self.config.deploy_cert( - "random.demo", - "two/cert.pem", "two/key.pem", "two/cert_chain.pem") - self.assertTrue(self.config.parser.find_dir( - "SSLCertificateChainFile", "two/cert_chain.pem", - self.vh_truth[1].path)) - - def test_deploy_cert_invalid_vhost(self): - """For test cases where the `ApacheConfigurator` class' `_deploy_cert` - method is called with an invalid vhost parameter. Currently this tests - that a PluginError is appropriately raised when important directives - are missing in an SSL module.""" - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - - def side_effect(*args): - """Mocks case where an SSLCertificateFile directive can be found - but an SSLCertificateKeyFile directive is missing.""" - if "SSLCertificateFile" in args: - return ["example/cert.pem"] - return [] - - mock_find_dir = mock.MagicMock(return_value=[]) - mock_find_dir.side_effect = side_effect - - self.config.parser.find_dir = mock_find_dir - - # Get the default 443 vhost - self.config.assoc["random.demo"] = self.vh_truth[1] - - self.assertRaises( - errors.PluginError, self.config.deploy_cert, "random.demo", - "example/cert.pem", "example/key.pem", "example/cert_chain.pem") - - # Remove side_effect to mock case where both SSLCertificateFile - # and SSLCertificateKeyFile directives are missing - self.config.parser.find_dir.side_effect = None - self.assertRaises( - errors.PluginError, self.config.deploy_cert, "random.demo", - "example/cert.pem", "example/key.pem", "example/cert_chain.pem") - - def test_is_name_vhost(self): - addr = obj.Addr.fromstring("*:80") - self.assertTrue(self.config.is_name_vhost(addr)) - self.config.version = (2, 2) - self.assertFalse(self.config.is_name_vhost(addr)) - - def test_add_name_vhost(self): - self.config.add_name_vhost(obj.Addr.fromstring("*:443")) - self.config.add_name_vhost(obj.Addr.fromstring("*:80")) - self.assertTrue(self.config.parser.find_dir( - "NameVirtualHost", "*:443", exclude=False)) - self.assertTrue(self.config.parser.find_dir( - "NameVirtualHost", "*:80")) - - def test_add_listen_80(self): - mock_find = mock.Mock() - mock_add_dir = mock.Mock() - mock_find.return_value = [] - self.config.parser.find_dir = mock_find - self.config.parser.add_dir = mock_add_dir - self.config.ensure_listen("80") - self.assertTrue(mock_add_dir.called) - self.assertTrue(mock_find.called) - self.assertEqual(mock_add_dir.call_args[0][1], "Listen") - self.assertEqual(mock_add_dir.call_args[0][2], "80") - - def test_add_listen_80_named(self): - mock_find = mock.Mock() - mock_find.return_value = ["test1", "test2", "test3"] - mock_get = mock.Mock() - mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] - mock_add_dir = mock.Mock() - - self.config.parser.find_dir = mock_find - self.config.parser.get_arg = mock_get - self.config.parser.add_dir = mock_add_dir - - self.config.ensure_listen("80") - self.assertEqual(mock_add_dir.call_count, 0) - - # Reset return lists and inputs - mock_add_dir.reset_mock() - mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] - - # Test - self.config.ensure_listen("8080") - self.assertEqual(mock_add_dir.call_count, 3) - self.assertTrue(mock_add_dir.called) - self.assertEqual(mock_add_dir.call_args[0][1], "Listen") - call_found = False - for mock_call in mock_add_dir.mock_calls: - if mock_call[1][2] == ['1.2.3.4:8080']: - call_found = True - self.assertTrue(call_found) - - def test_prepare_server_https(self): - mock_enable = mock.Mock() - self.config.enable_mod = mock_enable - - mock_find = mock.Mock() - mock_add_dir = mock.Mock() - mock_find.return_value = [] - - # This will test the Add listen - self.config.parser.find_dir = mock_find - self.config.parser.add_dir_to_ifmodssl = mock_add_dir - self.config.prepare_server_https("443") - # Changing the order these modules are enabled breaks the reverter - self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb") - self.assertEqual(mock_enable.call_args[0][0], "ssl") - self.assertEqual(mock_enable.call_args[1], {"temp": False}) - - self.config.prepare_server_https("8080", temp=True) - # Changing the order these modules are enabled breaks the reverter - self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb") - self.assertEqual(mock_enable.call_args[0][0], "ssl") - # Enable mod is temporary - self.assertEqual(mock_enable.call_args[1], {"temp": True}) - - self.assertEqual(mock_add_dir.call_count, 2) - - def test_prepare_server_https_named_listen(self): - mock_find = mock.Mock() - mock_find.return_value = ["test1", "test2", "test3"] - mock_get = mock.Mock() - mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] - mock_add_dir = mock.Mock() - mock_enable = mock.Mock() - - self.config.parser.find_dir = mock_find - self.config.parser.get_arg = mock_get - self.config.parser.add_dir_to_ifmodssl = mock_add_dir - self.config.enable_mod = mock_enable - - # Test Listen statements with specific ip listeed - self.config.prepare_server_https("443") - # Should be 0 as one interface already listens to 443 - self.assertEqual(mock_add_dir.call_count, 0) - - # Reset return lists and inputs - mock_add_dir.reset_mock() - mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] - - # Test - self.config.prepare_server_https("8080", temp=True) - self.assertEqual(mock_add_dir.call_count, 3) - call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)] - self.assertEqual( - sorted(call_args_list), - sorted([["1.2.3.4:8080", "https"], - ["[::1]:8080", "https"], - ["1.1.1.1:8080", "https"]])) - - # mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"] - # mock_find.return_value = ["test1", "test2", "test3"] - # self.config.parser.get_arg = mock_get - # self.config.prepare_server_https("8080", temp=True) - # self.assertEqual(self.listens, 0) - - def test_prepare_server_https_needed_listen(self): - mock_find = mock.Mock() - mock_find.return_value = ["test1", "test2"] - mock_get = mock.Mock() - mock_get.side_effect = ["1.2.3.4:8080", "80"] - mock_add_dir = mock.Mock() - mock_enable = mock.Mock() - - self.config.parser.find_dir = mock_find - self.config.parser.get_arg = mock_get - self.config.parser.add_dir_to_ifmodssl = mock_add_dir - self.config.enable_mod = mock_enable - - self.config.prepare_server_https("443") - self.assertEqual(mock_add_dir.call_count, 1) - - def test_prepare_server_https_mixed_listen(self): - - mock_find = mock.Mock() - mock_find.return_value = ["test1", "test2"] - mock_get = mock.Mock() - mock_get.side_effect = ["1.2.3.4:8080", "443"] - mock_add_dir = mock.Mock() - mock_enable = mock.Mock() - - self.config.parser.find_dir = mock_find - self.config.parser.get_arg = mock_get - self.config.parser.add_dir_to_ifmodssl = mock_add_dir - self.config.enable_mod = mock_enable - - # Test Listen statements with specific ip listeed - self.config.prepare_server_https("443") - # Should only be 2 here, as the third interface - # already listens to the correct port - self.assertEqual(mock_add_dir.call_count, 0) - - def test_make_vhost_ssl_with_mock_span(self): - # span excludes the closing tag in older versions - # of Augeas - return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142] - with mock.patch.object(self.config.parser.aug, 'span') as mock_span: - mock_span.return_value = return_value - self.test_make_vhost_ssl() - - def test_make_vhost_ssl_with_mock_span2(self): - # span includes the closing tag in newer versions - # of Augeas - return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157] - with mock.patch.object(self.config.parser.aug, 'span') as mock_span: - mock_span.return_value = return_value - self.test_make_vhost_ssl() - - def test_make_vhost_ssl_nonsymlink(self): - ssl_vhost_slink = self.config.make_vhost_ssl(self.vh_truth[8]) - self.assertTrue(ssl_vhost_slink.ssl) - self.assertTrue(ssl_vhost_slink.enabled) - self.assertEqual(ssl_vhost_slink.name, "nonsym.link") - - def test_make_vhost_ssl_nonexistent_vhost_path(self): - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) - self.assertEqual(os.path.dirname(ssl_vhost.filep), - os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) - - def test_make_vhost_ssl(self): - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) - - self.assertEqual( - ssl_vhost.filep, - os.path.join(self.config_path, "sites-available", - "encryption-example-le-ssl.conf")) - - self.assertEqual(ssl_vhost.path, - "/files" + ssl_vhost.filep + "/IfModule/Virtualhost") - self.assertEqual(len(ssl_vhost.addrs), 1) - self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs) - self.assertEqual(ssl_vhost.name, "encryption-example.demo") - self.assertTrue(ssl_vhost.ssl) - self.assertFalse(ssl_vhost.enabled) - - self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]), - self.config.is_name_vhost(ssl_vhost)) - - self.assertEqual(len(self.config.vhosts), 13) - - def test_clean_vhost_ssl(self): - # pylint: disable=protected-access - for directive in ["SSLCertificateFile", "SSLCertificateKeyFile", - "SSLCertificateChainFile", "SSLCACertificatePath"]: - for _ in range(10): - self.config.parser.add_dir(self.vh_truth[1].path, - directive, ["bogus"]) - self.config.save() - - self.config._clean_vhost(self.vh_truth[1]) - self.config.save() - - loc_cert = self.config.parser.find_dir( - 'SSLCertificateFile', None, self.vh_truth[1].path, False) - loc_key = self.config.parser.find_dir( - 'SSLCertificateKeyFile', None, self.vh_truth[1].path, False) - loc_chain = self.config.parser.find_dir( - 'SSLCertificateChainFile', None, self.vh_truth[1].path, False) - loc_cacert = self.config.parser.find_dir( - 'SSLCACertificatePath', None, self.vh_truth[1].path, False) - - self.assertEqual(len(loc_cert), 1) - self.assertEqual(len(loc_key), 1) - - self.assertEqual(len(loc_chain), 0) - - self.assertEqual(len(loc_cacert), 10) - - def test_deduplicate_directives(self): - # pylint: disable=protected-access - DIRECTIVE = "Foo" - for _ in range(10): - self.config.parser.add_dir(self.vh_truth[1].path, - DIRECTIVE, ["bar"]) - self.config.save() - - self.config._deduplicate_directives(self.vh_truth[1].path, [DIRECTIVE]) - self.config.save() - - self.assertEqual( - len(self.config.parser.find_dir( - DIRECTIVE, None, self.vh_truth[1].path, False)), 1) - - def test_remove_directives(self): - # pylint: disable=protected-access - DIRECTIVES = ["Foo", "Bar"] - for directive in DIRECTIVES: - for _ in range(10): - self.config.parser.add_dir(self.vh_truth[2].path, - directive, ["baz"]) - self.config.save() - - self.config._remove_directives(self.vh_truth[2].path, DIRECTIVES) - self.config.save() - - for directive in DIRECTIVES: - self.assertEqual( - len(self.config.parser.find_dir( - directive, None, self.vh_truth[2].path, False)), 0) - - def test_make_vhost_ssl_bad_write(self): - mock_open = mock.mock_open() - # This calls open - self.config.reverter.register_file_creation = mock.Mock() - mock_open.side_effect = IOError - with mock.patch("six.moves.builtins.open", mock_open): - self.assertRaises( - errors.PluginError, - self.config.make_vhost_ssl, self.vh_truth[0]) - - def test_get_ssl_vhost_path(self): - # pylint: disable=protected-access - self.assertTrue( - self.config._get_ssl_vhost_path("example_path").endswith(".conf")) - - def test_add_name_vhost_if_necessary(self): - # pylint: disable=protected-access - self.config.add_name_vhost = mock.Mock() - self.config.version = (2, 2) - self.config._add_name_vhost_if_necessary(self.vh_truth[0]) - self.assertTrue(self.config.add_name_vhost.called) - - new_addrs = set() - for addr in self.vh_truth[0].addrs: - new_addrs.add(obj.Addr(("_default_", addr.get_port(),))) - - self.vh_truth[0].addrs = new_addrs - self.config._add_name_vhost_if_necessary(self.vh_truth[0]) - self.assertEqual(self.config.add_name_vhost.call_count, 2) - - @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - def test_perform(self, mock_restart, mock_http_perform): - # Only tests functionality specific to configurator.perform - # Note: As more challenges are offered this will have to be expanded - account_key, achalls = self.get_key_and_achalls() - - expected = [achall.response(account_key) for achall in achalls] - mock_http_perform.return_value = expected - - responses = self.config.perform(achalls) - - self.assertEqual(mock_http_perform.call_count, 1) - self.assertEqual(responses, expected) - - self.assertEqual(mock_restart.call_count, 1) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_cleanup(self, mock_cfg, mock_restart): - mock_cfg.return_value = "" - _, achalls = self.get_key_and_achalls() - - for achall in achalls: - self.config._chall_out.add(achall) # pylint: disable=protected-access - - for i, achall in enumerate(achalls): - self.config.cleanup([achall]) - if i == len(achalls) - 1: - self.assertTrue(mock_restart.called) - else: - self.assertFalse(mock_restart.called) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_cleanup_no_errors(self, mock_cfg, mock_restart): - mock_cfg.return_value = "" - _, achalls = self.get_key_and_achalls() - self.config.http_doer = mock.MagicMock() - - for achall in achalls: - self.config._chall_out.add(achall) # pylint: disable=protected-access - - self.config.cleanup([achalls[-1]]) - self.assertFalse(mock_restart.called) - - self.config.cleanup(achalls) - self.assertTrue(mock_restart.called) - - @mock.patch("certbot.util.run_script") - def test_get_version(self, mock_script): - mock_script.return_value = ( - "Server Version: Apache/2.4.2 (Debian)", "") - self.assertEqual(self.config.get_version(), (2, 4, 2)) - - mock_script.return_value = ( - "Server Version: Apache/2 (Linux)", "") - self.assertEqual(self.config.get_version(), (2,)) - - mock_script.return_value = ( - "Server Version: Apache (Debian)", "") - self.assertRaises(errors.PluginError, self.config.get_version) - - mock_script.return_value = ( - "Server Version: Apache/2.3{0} Apache/2.4.7".format( - os.linesep), "") - self.assertRaises(errors.PluginError, self.config.get_version) - - mock_script.side_effect = errors.SubprocessError("Can't find program") - self.assertRaises(errors.PluginError, self.config.get_version) - - @mock.patch("certbot_apache._internal.configurator.util.run_script") - def test_restart(self, _): - self.config.restart() - - @mock.patch("certbot_apache._internal.configurator.util.run_script") - def test_restart_bad_process(self, mock_run_script): - mock_run_script.side_effect = [None, errors.SubprocessError] - - self.assertRaises(errors.MisconfigurationError, self.config.restart) - - @mock.patch("certbot.util.run_script") - def test_config_test(self, _): - self.config.config_test() - - @mock.patch("certbot.util.run_script") - def test_config_test_bad_process(self, mock_run_script): - mock_run_script.side_effect = errors.SubprocessError - - self.assertRaises(errors.MisconfigurationError, - self.config.config_test) - - def test_more_info(self): - self.assertTrue(self.config.more_info()) - - def test_get_chall_pref(self): - self.assertTrue(isinstance(self.config.get_chall_pref(""), list)) - - def test_install_ssl_options_conf(self): - path = os.path.join(self.work_dir, "test_it") - other_path = os.path.join(self.work_dir, "other_test_it") - self.config.install_ssl_options_conf(path, other_path) - self.assertTrue(os.path.isfile(path)) - self.assertTrue(os.path.isfile(other_path)) - - # TEST ENHANCEMENTS - def test_supported_enhancements(self): - self.assertTrue(isinstance(self.config.supported_enhancements(), list)) - - def test_find_http_vhost_without_ancestor(self): - # pylint: disable=protected-access - vhost = self.vh_truth[0] - vhost.ssl = True - vhost.ancestor = None - res = self.config._get_http_vhost(vhost) - self.assertEqual(self.vh_truth[0].name, res.name) - self.assertEqual(self.vh_truth[0].aliases, res.aliases) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") - @mock.patch("certbot_apache._internal.display_ops.select_vhost") - @mock.patch("certbot.util.exe_exists") - def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get): - self.config.parser.modules.add("rewrite_module") - mock_exe.return_value = True - ssl_vh1 = obj.VirtualHost( - "fp1", "ap1", set([obj.Addr(("*", "443"))]), - True, False) - ssl_vh1.name = "satoshi.com" - self.config.vhosts.append(ssl_vh1) - mock_sel_vhost.return_value = None - mock_get.return_value = None - - self.assertRaises( - errors.PluginError, - self.config.enhance, "satoshi.com", "redirect") - - def test_enhance_unknown_enhancement(self): - self.assertRaises( - errors.PluginError, - self.config.enhance, "certbot.demo", "unknown_enhancement") - - def test_enhance_no_ssl_vhost(self): - with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: - self.assertRaises(errors.PluginError, self.config.enhance, - "certbot.demo", "redirect") - # Check that correct logger.warning was printed - self.assertTrue("not able to find" in mock_log.call_args[0][0]) - self.assertTrue("\"redirect\"" in mock_log.call_args[0][0]) - - mock_log.reset_mock() - - self.assertRaises(errors.PluginError, self.config.enhance, - "certbot.demo", "ensure-http-header", "Test") - # Check that correct logger.warning was printed - self.assertTrue("not able to find" in mock_log.call_args[0][0]) - self.assertTrue("Test" in mock_log.call_args[0][0]) - - @mock.patch("certbot.util.exe_exists") - def test_ocsp_stapling(self, mock_exe): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - self.config.get_version = mock.Mock(return_value=(2, 4, 7)) - mock_exe.return_value = True - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "staple-ocsp") - - # Get the ssl vhost for certbot.demo - ssl_vhost = self.config.assoc["certbot.demo"] - - ssl_use_stapling_aug_path = self.config.parser.find_dir( - "SSLUseStapling", "on", ssl_vhost.path) - - self.assertEqual(len(ssl_use_stapling_aug_path), 1) - - ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) - stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', - "shmcb:/var/run/apache2/stapling_cache(128000)", - ssl_vhost_aug_path) - - self.assertEqual(len(stapling_cache_aug_path), 1) - - @mock.patch("certbot.util.exe_exists") - def test_ocsp_stapling_twice(self, mock_exe): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - self.config.get_version = mock.Mock(return_value=(2, 4, 7)) - mock_exe.return_value = True - - # Checking the case with already enabled ocsp stapling configuration - self.config.choose_vhost("ocspvhost.com") - self.config.enhance("ocspvhost.com", "staple-ocsp") - - # Get the ssl vhost for letsencrypt.demo - ssl_vhost = self.config.assoc["ocspvhost.com"] - - ssl_use_stapling_aug_path = self.config.parser.find_dir( - "SSLUseStapling", "on", ssl_vhost.path) - - self.assertEqual(len(ssl_use_stapling_aug_path), 1) - ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) - stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', - "shmcb:/var/run/apache2/stapling_cache(128000)", - ssl_vhost_aug_path) - - self.assertEqual(len(stapling_cache_aug_path), 1) - - - @mock.patch("certbot.util.exe_exists") - def test_ocsp_unsupported_apache_version(self, mock_exe): - mock_exe.return_value = True - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - self.config.get_version = mock.Mock(return_value=(2, 2, 0)) - self.config.choose_vhost("certbot.demo") - - self.assertRaises(errors.PluginError, - self.config.enhance, "certbot.demo", "staple-ocsp") - - - def test_get_http_vhost_third_filter(self): - ssl_vh = obj.VirtualHost( - "fp", "ap", set([obj.Addr(("*", "443"))]), - True, False) - ssl_vh.name = "satoshi.com" - self.config.vhosts.append(ssl_vh) - - # pylint: disable=protected-access - http_vh = self.config._get_http_vhost(ssl_vh) - self.assertFalse(http_vh.ssl) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_http_header_hsts(self, mock_exe, _): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("headers_module") - mock_exe.return_value = True - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "ensure-http-header", - "Strict-Transport-Security") - - # Get the ssl vhost for certbot.demo - ssl_vhost = self.config.assoc["certbot.demo"] - - # These are not immediately available in find_dir even with save() and - # load(). They must be found in sites-available - hsts_header = self.config.parser.find_dir( - "Header", None, ssl_vhost.path) - - # four args to HSTS header - self.assertEqual(len(hsts_header), 4) - - def test_http_header_hsts_twice(self): - self.config.parser.modules.add("mod_ssl.c") - # skip the enable mod - self.config.parser.modules.add("headers_module") - - # This will create an ssl vhost for encryption-example.demo - self.config.choose_vhost("encryption-example.demo") - self.config.enhance("encryption-example.demo", "ensure-http-header", - "Strict-Transport-Security") - - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "encryption-example.demo", - "ensure-http-header", "Strict-Transport-Security") - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_http_header_uir(self, mock_exe, _): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("headers_module") - - mock_exe.return_value = True - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "ensure-http-header", - "Upgrade-Insecure-Requests") - - self.assertTrue("headers_module" in self.config.parser.modules) - - # Get the ssl vhost for certbot.demo - ssl_vhost = self.config.assoc["certbot.demo"] - - # These are not immediately available in find_dir even with save() and - # load(). They must be found in sites-available - uir_header = self.config.parser.find_dir( - "Header", None, ssl_vhost.path) - - # four args to HSTS header - self.assertEqual(len(uir_header), 4) - - def test_http_header_uir_twice(self): - self.config.parser.modules.add("mod_ssl.c") - # skip the enable mod - self.config.parser.modules.add("headers_module") - - # This will create an ssl vhost for encryption-example.demo - self.config.choose_vhost("encryption-example.demo") - self.config.enhance("encryption-example.demo", "ensure-http-header", - "Upgrade-Insecure-Requests") - - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "encryption-example.demo", - "ensure-http-header", "Upgrade-Insecure-Requests") - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_redirect_well_formed_http(self, mock_exe, _): - self.config.parser.modules.add("rewrite_module") - self.config.parser.update_runtime_variables = mock.Mock() - mock_exe.return_value = True - self.config.get_version = mock.Mock(return_value=(2, 2)) - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "redirect") - - # These are not immediately available in find_dir even with save() and - # load(). They must be found in sites-available - rw_engine = self.config.parser.find_dir( - "RewriteEngine", "on", self.vh_truth[3].path) - rw_rule = self.config.parser.find_dir( - "RewriteRule", None, self.vh_truth[3].path) - - self.assertEqual(len(rw_engine), 1) - # three args to rw_rule - self.assertEqual(len(rw_rule), 3) - - # [:-3] to remove the vhost index number - self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) - self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) - - def test_rewrite_rule_exists(self): - # Skip the enable mod - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 3, 9)) - self.config.parser.add_dir( - self.vh_truth[3].path, "RewriteRule", ["Unknown"]) - # pylint: disable=protected-access - self.assertTrue(self.config._is_rewrite_exists(self.vh_truth[3])) - - def test_rewrite_engine_exists(self): - # Skip the enable mod - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 3, 9)) - self.config.parser.add_dir( - self.vh_truth[3].path, "RewriteEngine", "on") - # pylint: disable=protected-access - self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3])) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_redirect_with_existing_rewrite(self, mock_exe, _): - self.config.parser.modules.add("rewrite_module") - self.config.parser.update_runtime_variables = mock.Mock() - mock_exe.return_value = True - self.config.get_version = mock.Mock(return_value=(2, 2, 0)) - - # Create a preexisting rewrite rule - self.config.parser.add_dir( - self.vh_truth[3].path, "RewriteRule", ["UnknownPattern", - "UnknownTarget"]) - self.config.save() - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "redirect") - - # These are not immediately available in find_dir even with save() and - # load(). They must be found in sites-available - rw_engine = self.config.parser.find_dir( - "RewriteEngine", "on", self.vh_truth[3].path) - rw_rule = self.config.parser.find_dir( - "RewriteRule", None, self.vh_truth[3].path) - - self.assertEqual(len(rw_engine), 1) - # three args to rw_rule + 1 arg for the pre existing rewrite - self.assertEqual(len(rw_rule), 5) - # [:-3] to remove the vhost index number - self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) - self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) - - self.assertTrue("rewrite_module" in self.config.parser.modules) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_redirect_with_old_https_redirection(self, mock_exe, _): - self.config.parser.modules.add("rewrite_module") - self.config.parser.update_runtime_variables = mock.Mock() - mock_exe.return_value = True - self.config.get_version = mock.Mock(return_value=(2, 2, 0)) - - ssl_vhost = self.config.choose_vhost("certbot.demo") - - # pylint: disable=protected-access - http_vhost = self.config._get_http_vhost(ssl_vhost) - - # Create an old (previously suppoorted) https redirectoin rewrite rule - self.config.parser.add_dir( - http_vhost.path, "RewriteRule", - ["^", - "https://%{SERVER_NAME}%{REQUEST_URI}", - "[L,QSA,R=permanent]"]) - - self.config.save() - - try: - self.config.enhance("certbot.demo", "redirect") - except errors.PluginEnhancementAlreadyPresent: - args_paths = self.config.parser.find_dir( - "RewriteRule", None, http_vhost.path, False) - arg_vals = [self.config.parser.aug.get(x) for x in args_paths] - self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) - - - def test_redirect_with_conflict(self): - self.config.parser.modules.add("rewrite_module") - ssl_vh = obj.VirtualHost( - "fp", "ap", set([obj.Addr(("*", "443")), - obj.Addr(("zombo.com",))]), - True, False) - # No names ^ this guy should conflict. - - # pylint: disable=protected-access - self.assertRaises( - errors.PluginError, self.config._enable_redirect, ssl_vh, "") - - def test_redirect_two_domains_one_vhost(self): - # Skip the enable mod - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 3, 9)) - - # Creates ssl vhost for the domain - self.config.choose_vhost("red.blue.purple.com") - - self.config.enhance("red.blue.purple.com", "redirect") - verify_no_redirect = ("certbot_apache._internal.configurator." - "ApacheConfigurator._verify_no_certbot_redirect") - with mock.patch(verify_no_redirect) as mock_verify: - self.config.enhance("green.blue.purple.com", "redirect") - self.assertFalse(mock_verify.called) - - def test_redirect_from_previous_run(self): - # Skip the enable mod - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 3, 9)) - self.config.choose_vhost("red.blue.purple.com") - self.config.enhance("red.blue.purple.com", "redirect") - # Clear state about enabling redirect on this run - # pylint: disable=protected-access - self.config._enhanced_vhosts["redirect"].clear() - - self.assertRaises( - errors.PluginEnhancementAlreadyPresent, - self.config.enhance, "green.blue.purple.com", "redirect") - - def test_create_own_redirect(self): - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 3, 9)) - # For full testing... give names... - self.vh_truth[1].name = "default.com" - self.vh_truth[1].aliases = set(["yes.default.com"]) - - # pylint: disable=protected-access - self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 13) - - def test_create_own_redirect_for_old_apache_version(self): - self.config.parser.modules.add("rewrite_module") - self.config.get_version = mock.Mock(return_value=(2, 2)) - # For full testing... give names... - self.vh_truth[1].name = "default.com" - self.vh_truth[1].aliases = set(["yes.default.com"]) - - # pylint: disable=protected-access - self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 13) - - def test_sift_rewrite_rule(self): - # pylint: disable=protected-access - small_quoted_target = "RewriteRule ^ \"http://\"" - self.assertFalse(self.config._sift_rewrite_rule(small_quoted_target)) - - https_target = "RewriteRule ^ https://satoshi" - self.assertTrue(self.config._sift_rewrite_rule(https_target)) - - normal_target = "RewriteRule ^/(.*) http://www.a.com:1234/$1 [L,R]" - self.assertFalse(self.config._sift_rewrite_rule(normal_target)) - - not_rewriterule = "NotRewriteRule ^ ..." - self.assertFalse(self.config._sift_rewrite_rule(not_rewriterule)) - - def get_key_and_achalls(self): - """Return testing achallenges.""" - account_key = self.rsa512jwk - achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01( - token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"), - "pending"), - domain="encryption-example.demo", account_key=account_key) - achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01( - token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"), - "pending"), - domain="certbot.demo", account_key=account_key) - achall3 = achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=(b'x' * 16)), "pending"), - domain="example.org", account_key=account_key) - - return account_key, (achall1, achall2, achall3) - - def test_enable_site_nondebian(self): - inc_path = "/path/to/wherever" - vhost = self.vh_truth[0] - vhost.enabled = False - vhost.filep = inc_path - self.assertFalse(self.config.parser.find_dir("Include", inc_path)) - self.assertFalse( - os.path.dirname(inc_path) in self.config.parser.existing_paths) - self.config.enable_site(vhost) - self.assertTrue(self.config.parser.find_dir("Include", inc_path)) - self.assertTrue( - os.path.dirname(inc_path) in self.config.parser.existing_paths) - self.assertTrue( - os.path.basename(inc_path) in self.config.parser.existing_paths[ - os.path.dirname(inc_path)]) - - def test_deploy_cert_not_parsed_path(self): - # Make sure that we add include to root config for vhosts when - # handle-sites is false - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("socache_shmcb_module") - tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) - filesystem.chmod(tmp_path, 0o755) - mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path" - mock_a = "certbot_apache._internal.parser.ApacheParser.add_include" - - with mock.patch(mock_p) as mock_path: - mock_path.return_value = os.path.join(tmp_path, "whatever.conf") - with mock.patch(mock_a) as mock_add: - self.config.deploy_cert( - "encryption-example.demo", - "example/cert.pem", "example/key.pem", - "example/cert_chain.pem") - # Test that we actually called add_include - self.assertTrue(mock_add.called) - shutil.rmtree(tmp_path) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") - def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed): - ret_vh = self.vh_truth[8] - ret_vh.enabled = True - self.config.enable_site(ret_vh) - # Make sure that we return early - self.assertFalse(mock_parsed.called) - - def test_enable_mod_unsupported(self): - self.assertRaises(errors.MisconfigurationError, - self.config.enable_mod, - "whatever") - - def test_wildcard_domain(self): - # pylint: disable=protected-access - cases = {u"*.example.org": True, b"*.x.example.org": True, - u"a.example.org": False, b"a.x.example.org": False} - for key in cases: - self.assertEqual(self.config._wildcard_domain(key), cases[key]) - - def test_choose_vhosts_wildcard(self): - # pylint: disable=protected-access - mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - mock_select_vhs.return_value = [self.vh_truth[3]] - vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", - create_ssl=True) - # Check that the dialog was called with one vh: certbot.demo - 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.assertEqual(len(vhs), 1) - self.assertTrue(vhs[0].name == "certbot.demo") - self.assertTrue(vhs[0].ssl) - - self.assertFalse(vhs[0] == self.vh_truth[3]) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") - def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): - # pylint: disable=protected-access - mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - mock_select_vhs.return_value = [self.vh_truth[1]] - vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", - create_ssl=False) - self.assertFalse(mock_makessl.called) - self.assertEqual(vhs[0], self.vh_truth[1]) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") - def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w): - # pylint: disable=protected-access - # Already SSL vhost - mock_vh_for_w.return_value = [self.vh_truth[7]] - mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" - with mock.patch(mock_path) as mock_select_vhs: - mock_select_vhs.return_value = [self.vh_truth[7]] - vhs = self.config._choose_vhosts_wildcard("whatever", - create_ssl=True) - 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.assertEqual(len(vhs), 1) - self.assertTrue(vhs[0].ssl) - self.assertEqual(vhs[0], self.vh_truth[7]) - - - def test_deploy_cert_wildcard(self): - # pylint: disable=protected-access - mock_choose_vhosts = mock.MagicMock() - mock_choose_vhosts.return_value = [self.vh_truth[7]] - self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert" - with mock.patch(mock_d) as mock_dep: - self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", - "/tmp/path", "/tmp/path", "/tmp/path") - self.assertTrue(mock_dep.called) - 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._internal.display_ops.select_vhost_multiple") - def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): - # pylint: disable=protected-access - mock_dialog.return_value = [] - self.assertRaises(errors.PluginError, - self.config.deploy_cert, - "*.wild.cat", "/tmp/path", "/tmp/path", - "/tmp/path", "/tmp/path") - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") - def test_enhance_wildcard_after_install(self, mock_choose): - # pylint: disable=protected-access - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("headers_module") - self.vh_truth[3].ssl = True - self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]] - self.config.enhance("*.certbot.demo", "ensure-http-header", - "Upgrade-Insecure-Requests") - self.assertFalse(mock_choose.called) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") - def test_enhance_wildcard_no_install(self, mock_choose): - self.vh_truth[3].ssl = True - mock_choose.return_value = [self.vh_truth[3]] - self.config.parser.modules.add("mod_ssl.c") - self.config.parser.modules.add("headers_module") - self.config.enhance("*.certbot.demo", "ensure-http-header", - "Upgrade-Insecure-Requests") - self.assertTrue(mock_choose.called) - - def test_add_vhost_id(self): - for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]: - vh_id = self.config.add_vhost_id(vh) - self.assertEqual(vh, self.config.find_vhost_by_id(vh_id)) - - def test_find_vhost_by_id_404(self): - self.assertRaises(errors.PluginError, - self.config.find_vhost_by_id, - "nonexistent") - - def test_add_vhost_id_already_exists(self): - first_id = self.config.add_vhost_id(self.vh_truth[0]) - second_id = self.config.add_vhost_id(self.vh_truth[0]) - self.assertEqual(first_id, second_id) - - def test_realpath_replaces_symlink(self): - orig_match = self.config.parser.aug.match - mock_vhost = copy.deepcopy(self.vh_truth[0]) - mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') - mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') - mock_vhost.enabled = False - self.config.parser.parse_file(mock_vhost.filep) - - def mock_match(aug_expr): - """Return a mocked match list of VirtualHosts""" - if "/mocked/path" in aug_expr: - return [self.vh_truth[1].path, self.vh_truth[0].path, mock_vhost.path] - return orig_match(aug_expr) - - self.config.parser.parser_paths = ["/mocked/path"] - self.config.parser.aug.match = mock_match - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) - self.assertTrue(vhs[0] == self.vh_truth[1]) - # mock_vhost should have replaced the vh_truth[0], because its filepath - # isn't a symlink - self.assertTrue(vhs[1] == mock_vhost) - - -class AugeasVhostsTest(util.ApacheTest): - """Test vhosts with illegal names dependent on augeas version.""" - # pylint: disable=protected-access - - def setUp(self): # pylint: disable=arguments-differ - td = "debian_apache_2_4/augeas_vhosts" - cr = "debian_apache_2_4/augeas_vhosts/apache2" - vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available" - super(AugeasVhostsTest, self).setUp(test_dir=td, - config_root=cr, - vhost_root=vr) - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, - self.work_dir) - - def test_choosevhost_with_illegal_name(self): - self.config.parser.aug = mock.MagicMock() - self.config.parser.aug.match.side_effect = RuntimeError - path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" - chosen_vhost = self.config._create_vhost(path) - self.assertEqual(None, chosen_vhost) - - def test_choosevhost_works(self): - path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" - chosen_vhost = self.config._create_vhost(path) - self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") - def test_get_vhost_continue(self, mock_vhost): - mock_vhost.return_value = None - vhs = self.config.get_virtual_hosts() - self.assertEqual([], vhs) - - def test_choose_vhost_with_matching_wildcard(self): - names = ( - "an.example.net", "another.example.net", "an.other.example.net") - for name in names: - self.assertFalse(name in self.config.choose_vhost(name).aliases) - - @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") - def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): - mock_conflicts.return_value = False - mock_path = "certbot_apache._internal.display_ops.select_vhost" - with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): - for name in ("a.example.net", "other.example.net"): - self.assertTrue(name in self.config.choose_vhost(name).aliases) - - @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") - def test_choose_vhost_wildcard_not_found(self, mock_conflicts): - mock_conflicts.return_value = False - mock_path = "certbot_apache._internal.display_ops.select_vhost" - names = ( - "abc.example.net", "not.there.tld", "aa.wildcard.tld" - ) - with mock.patch(mock_path) as mock_select: - mock_select.return_value = self.config.vhosts[0] - for name in names: - orig_cc = mock_select.call_count - self.config.choose_vhost(name) - self.assertEqual(mock_select.call_count - orig_cc, 1) - - def test_choose_vhost_wildcard_found(self): - mock_path = "certbot_apache._internal.display_ops.select_vhost" - names = ( - "ab.example.net", "a.wildcard.tld", "yetanother.example.net" - ) - with mock.patch(mock_path) as mock_select: - mock_select.return_value = self.config.vhosts[0] - for name in names: - self.config.choose_vhost(name) - self.assertEqual(mock_select.call_count, 0) - - def test_augeas_span_error(self): - broken_vhost = self.config.vhosts[0] - broken_vhost.path = broken_vhost.path + "/nonexistent" - self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, - broken_vhost) - -class MultiVhostsTest(util.ApacheTest): - """Test configuration with multiple virtualhosts in a single file.""" - # pylint: disable=protected-access - - def setUp(self): # pylint: disable=arguments-differ - td = "debian_apache_2_4/multi_vhosts" - cr = "debian_apache_2_4/multi_vhosts/apache2" - vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available" - super(MultiVhostsTest, self).setUp(test_dir=td, - config_root=cr, - vhost_root=vr) - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, - self.config_dir, self.work_dir, conf_vhost_path=self.vhost_path) - self.vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multi_vhosts") - - def test_make_vhost_ssl(self): - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) - - self.assertEqual( - ssl_vhost.filep, - os.path.join(self.config_path, "sites-available", - "default-le-ssl.conf")) - - self.assertEqual(ssl_vhost.path, - "/files" + ssl_vhost.filep + "/IfModule/VirtualHost") - self.assertEqual(len(ssl_vhost.addrs), 1) - self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs) - self.assertEqual(ssl_vhost.name, "banana.vomit.com") - self.assertTrue(ssl_vhost.ssl) - self.assertFalse(ssl_vhost.enabled) - - - self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]), - self.config.is_name_vhost(ssl_vhost)) - - mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" - with mock.patch(mock_path) as mock_getpath: - mock_getpath.return_value = None - self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, - self.vh_truth[1]) - - def test_get_new_path(self): - with_index_1 = ["/path[1]/section[1]"] - without_index = ["/path/section"] - with_index_2 = ["/path[2]/section[2]"] - self.assertEqual(self.config._get_new_vh_path(without_index, - with_index_1), - None) - self.assertEqual(self.config._get_new_vh_path(without_index, - with_index_2), - with_index_2[0]) - - both = with_index_1 + with_index_2 - self.assertEqual(self.config._get_new_vh_path(without_index, both), - with_index_2[0]) - - @certbot_util.patch_get_utility() - def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_get_utility): - self.config.parser.modules.add("rewrite_module") - - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4]) - - self.assertTrue(self.config.parser.find_dir( - "RewriteEngine", "on", ssl_vhost.path, False)) - - 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/(.+)\" " - "\"http://new.example.com/docs/$1\" [R,L]") - self.assertTrue(commented_rewrite_rule in conf_text) - self.assertTrue(uncommented_rewrite_rule in conf_text) - mock_get_utility().add_message.assert_called_once_with(mock.ANY, - mock.ANY) - - @certbot_util.patch_get_utility() - def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_get_utility): - self.config.parser.modules.add("rewrite_module") - - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3]) - - 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") - not_commented_rewrite_rule = ("RewriteRule " - "^(.*)$ b://u%{REQUEST_URI} [P,NE,L]") - - commented_cond1 = "# RewriteCond %{HTTPS} !=on" - commented_cond2 = "# RewriteCond %{HTTPS} !^$" - commented_rewrite_rule = ("# RewriteRule ^ " - "https://%{SERVER_NAME}%{REQUEST_URI} " - "[L,NE,R=permanent]") - - self.assertTrue(not_commented_cond1 in conf_line_set) - self.assertTrue(not_commented_rewrite_rule in conf_line_set) - - self.assertTrue(commented_cond1 in conf_line_set) - self.assertTrue(commented_cond2 in conf_line_set) - self.assertTrue(commented_rewrite_rule in conf_line_set) - mock_get_utility().add_message.assert_called_once_with(mock.ANY, - mock.ANY) - - -class InstallSslOptionsConfTest(util.ApacheTest): - """Test that the options-ssl-nginx.conf file is installed and updated properly.""" - - def setUp(self): # pylint: disable=arguments-differ - super(InstallSslOptionsConfTest, self).setUp() - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir) - - def _call(self): - self.config.install_ssl_options_conf(self.config.mod_ssl_conf, - self.config.updated_mod_ssl_conf_digest) - - def _current_ssl_options_hash(self): - return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC")) - - def _assert_current_file(self): - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) - - def test_no_file(self): - # prepare should have placed a file there - self._assert_current_file() - os.remove(self.config.mod_ssl_conf) - self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) - self._call() - self._assert_current_file() - - def test_current_file(self): - self._assert_current_file() - self._call() - self._assert_current_file() - - def test_prev_file_updates_to_current(self): - from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES - ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match") - with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: - mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] - self._call() - self._assert_current_file() - - def test_manually_modified_current_file_does_not_update(self): - with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) - self.assertEqual(crypto_util.sha256sum( - self.config.option("MOD_SSL_CONF_SRC")), - self._current_ssl_options_hash()) - self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), - self._current_ssl_options_hash()) - - def test_manually_modified_past_file_warns(self): - with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: - mod_ssl_conf.write("a new line for the wrong hash\n") - with open(self.config.updated_mod_ssl_conf_digest, "w") as f: - f.write("hashofanoldversion") - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertEqual(mock_logger.warning.call_args[0][0], - "%s has been manually modified; updated file " - "saved to %s. We recommend updating %s for security purposes.") - self.assertEqual(crypto_util.sha256sum( - self.config.option("MOD_SSL_CONF_SRC")), - self._current_ssl_options_hash()) - # only print warning once - with mock.patch("certbot.plugins.common.logger") as mock_logger: - self._call() - self.assertFalse(mock_logger.warning.called) - - def test_current_file_hash_in_all_hashes(self): - from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES - self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, - "Constants.ALL_SSL_OPTIONS_HASHES must be appended" - " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/certbot_apache/tests/debian_test.py deleted file mode 100644 index d52c5b3c1..000000000 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ /dev/null @@ -1,213 +0,0 @@ -"""Test for certbot_apache._internal.configurator for Debian overrides""" -import shutil -import unittest - -import mock - -from certbot import errors -from certbot.compat import os - -from certbot_apache._internal import apache_util -from certbot_apache._internal import obj -from certbot_apache.tests import util - - -class MultipleVhostsTestDebian(util.ApacheTest): - """Multiple vhost tests for Debian family of distros""" - - _multiprocess_can_split_ = True - - def setUp(self): # pylint: disable=arguments-differ - super(MultipleVhostsTestDebian, self).setUp() - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="debian") - self.config = self.mock_deploy_cert(self.config) - self.vh_truth = util.get_vh_truth(self.temp_dir, - "debian_apache_2_4/multiple_vhosts") - - def mock_deploy_cert(self, config): - """A test for a mock deploy cert""" - config.real_deploy_cert = self.config.deploy_cert - - def mocked_deploy_cert(*args, **kwargs): - """a helper to mock a deployed cert""" - g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" - d_mod = "certbot_apache._internal.override_debian.DebianConfigurator.enable_mod" - with mock.patch(g_mod): - with mock.patch(d_mod): - config.real_deploy_cert(*args, **kwargs) - self.config.deploy_cert = mocked_deploy_cert - return self.config - - def test_enable_mod_unsupported_dirs(self): - shutil.rmtree(os.path.join(self.config.parser.root, "mods-enabled")) - self.assertRaises( - errors.NotSupportedError, self.config.enable_mod, "ssl") - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") - def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): - mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") - mock_popen().returncode = 0 - mock_exe_exists.return_value = True - - self.config.enable_mod("ssl") - self.assertTrue("ssl_module" in self.config.parser.modules) - self.assertTrue("mod_ssl.c" in self.config.parser.modules) - - self.assertTrue(mock_run_script.called) - - def test_deploy_cert_enable_new_vhost(self): - # Create - ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - self.assertFalse(ssl_vhost.enabled) - self.config.deploy_cert( - "encryption-example.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.assertTrue(ssl_vhost.enabled) - # Make sure that we don't error out if symlink already exists - ssl_vhost.enabled = False - self.assertFalse(ssl_vhost.enabled) - self.config.deploy_cert( - "encryption-example.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.assertTrue(ssl_vhost.enabled) - - def test_enable_site_failure(self): - self.config.parser.root = "/tmp/nonexistent" - with mock.patch("certbot.compat.os.path.isdir") as mock_dir: - mock_dir.return_value = True - with mock.patch("certbot.compat.os.path.islink") as mock_link: - mock_link.return_value = False - self.assertRaises( - errors.NotSupportedError, - self.config.enable_site, - obj.VirtualHost("asdf", "afsaf", set(), False, False)) - - def test_deploy_cert_newssl(self): - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, - self.work_dir, version=(2, 4, 16)) - self.config = self.mock_deploy_cert(self.config) - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - - # Get the default 443 vhost - self.config.assoc["random.demo"] = self.vh_truth[1] - self.config.deploy_cert( - "random.demo", "example/cert.pem", "example/key.pem", - "example/cert_chain.pem", "example/fullchain.pem") - self.config.save() - - # Verify ssl_module was enabled. - self.assertTrue(self.vh_truth[1].enabled) - self.assertTrue("ssl_module" in self.config.parser.modules) - - loc_cert = self.config.parser.find_dir( - "sslcertificatefile", "example/fullchain.pem", - self.vh_truth[1].path) - loc_key = self.config.parser.find_dir( - "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) - - # Verify one directive was found in the correct file - self.assertEqual(len(loc_cert), 1) - self.assertEqual( - apache_util.get_file_path(loc_cert[0]), - self.vh_truth[1].filep) - - self.assertEqual(len(loc_key), 1) - self.assertEqual( - apache_util.get_file_path(loc_key[0]), - self.vh_truth[1].filep) - - def test_deploy_cert_newssl_no_fullchain(self): - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, - self.work_dir, version=(2, 4, 16)) - self.config = self.mock_deploy_cert(self.config) - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - - # Get the default 443 vhost - self.config.assoc["random.demo"] = self.vh_truth[1] - self.assertRaises(errors.PluginError, - lambda: self.config.deploy_cert( - "random.demo", "example/cert.pem", - "example/key.pem")) - - def test_deploy_cert_old_apache_no_chain(self): - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, - self.work_dir, version=(2, 4, 7)) - self.config = self.mock_deploy_cert(self.config) - self.config.parser.modules.add("ssl_module") - self.config.parser.modules.add("mod_ssl.c") - - # Get the default 443 vhost - self.config.assoc["random.demo"] = self.vh_truth[1] - self.assertRaises(errors.PluginError, - lambda: self.config.deploy_cert( - "random.demo", "example/cert.pem", - "example/key.pem")) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_ocsp_stapling_enable_mod(self, mock_exe, _): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - self.config.get_version = mock.Mock(return_value=(2, 4, 7)) - mock_exe.return_value = True - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "staple-ocsp") - self.assertTrue("socache_shmcb_module" in self.config.parser.modules) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_ensure_http_header_enable_mod(self, mock_exe, _): - self.config.parser.update_runtime_variables = mock.Mock() - self.config.parser.modules.add("mod_ssl.c") - mock_exe.return_value = True - - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "ensure-http-header", - "Strict-Transport-Security") - self.assertTrue("headers_module" in self.config.parser.modules) - - @mock.patch("certbot.util.run_script") - @mock.patch("certbot.util.exe_exists") - def test_redirect_enable_mod(self, mock_exe, _): - self.config.parser.update_runtime_variables = mock.Mock() - mock_exe.return_value = True - self.config.get_version = mock.Mock(return_value=(2, 2)) - # This will create an ssl vhost for certbot.demo - self.config.choose_vhost("certbot.demo") - self.config.enhance("certbot.demo", "redirect") - self.assertTrue("rewrite_module" in self.config.parser.modules) - - def test_enable_site_already_enabled(self): - self.assertTrue(self.vh_truth[1].enabled) - self.config.enable_site(self.vh_truth[1]) - - def test_enable_site_call_parent(self): - with mock.patch( - "certbot_apache._internal.configurator.ApacheConfigurator.enable_site") as e_s: - self.config.parser.root = "/tmp/nonexistent" - vh = self.vh_truth[0] - vh.enabled = False - self.config.enable_site(vh) - self.assertTrue(e_s.called) - - @mock.patch("certbot.util.exe_exists") - def test_enable_mod_no_disable(self, mock_exe_exists): - mock_exe_exists.return_value = False - self.assertRaises( - errors.MisconfigurationError, self.config.enable_mod, "ssl") - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/display_ops_test.py b/certbot-apache/certbot_apache/tests/display_ops_test.py deleted file mode 100644 index 6202bf4b0..000000000 --- a/certbot-apache/certbot_apache/tests/display_ops_test.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Test certbot_apache._internal.display_ops.""" -import unittest - -import mock - -from certbot import errors - -from certbot.display import util as display_util - -from certbot.tests import util as certbot_util - -from certbot_apache._internal import obj - -from certbot_apache._internal.display_ops import select_vhost_multiple -from certbot_apache.tests import util - - -class SelectVhostMultiTest(unittest.TestCase): - """Tests for certbot_apache._internal.display_ops.select_vhost_multiple.""" - - def setUp(self): - self.base_dir = "/example_path" - self.vhosts = util.get_vh_truth( - self.base_dir, "debian_apache_2_4/multiple_vhosts") - - def test_select_no_input(self): - self.assertFalse(select_vhost_multiple([])) - - @certbot_util.patch_get_utility() - def test_select_correct(self, mock_util): - mock_util().checklist.return_value = ( - display_util.OK, [self.vhosts[3].display_repr(), - self.vhosts[2].display_repr()]) - vhs = select_vhost_multiple([self.vhosts[3], - self.vhosts[2], - self.vhosts[1]]) - self.assertTrue(self.vhosts[2] in vhs) - self.assertTrue(self.vhosts[3] in vhs) - self.assertFalse(self.vhosts[1] in vhs) - - @certbot_util.patch_get_utility() - def test_select_cancel(self, mock_util): - mock_util().checklist.return_value = (display_util.CANCEL, "whatever") - vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) - self.assertFalse(vhs) - -class SelectVhostTest(unittest.TestCase): - """Tests for certbot_apache._internal.display_ops.select_vhost.""" - - def setUp(self): - self.base_dir = "/example_path" - self.vhosts = util.get_vh_truth( - self.base_dir, "debian_apache_2_4/multiple_vhosts") - - @classmethod - def _call(cls, vhosts): - from certbot_apache._internal.display_ops import select_vhost - return select_vhost("example.com", vhosts) - - @certbot_util.patch_get_utility() - def test_successful_choice(self, mock_util): - mock_util().menu.return_value = (display_util.OK, 3) - self.assertEqual(self.vhosts[3], self._call(self.vhosts)) - - @certbot_util.patch_get_utility() - def test_noninteractive(self, mock_util): - mock_util().menu.side_effect = errors.MissingCommandlineFlag("no vhost default") - try: - self._call(self.vhosts) - except errors.MissingCommandlineFlag as e: - self.assertTrue("vhost ambiguity" in str(e)) - - @certbot_util.patch_get_utility() - def test_more_info_cancel(self, mock_util): - mock_util().menu.side_effect = [ - (display_util.CANCEL, -1), - ] - - self.assertEqual(None, self._call(self.vhosts)) - - def test_no_vhosts(self): - self.assertEqual(self._call([]), None) - - @mock.patch("certbot_apache._internal.display_ops.display_util") - @certbot_util.patch_get_utility() - @mock.patch("certbot_apache._internal.display_ops.logger") - def test_small_display(self, mock_logger, mock_util, mock_display_util): - mock_display_util.WIDTH = 20 - mock_util().menu.return_value = (display_util.OK, 0) - self._call(self.vhosts) - - self.assertEqual(mock_logger.debug.call_count, 1) - - @certbot_util.patch_get_utility() - def test_multiple_names(self, mock_util): - mock_util().menu.return_value = (display_util.OK, 5) - - self.vhosts.append( - obj.VirtualHost( - "path", "aug_path", set([obj.Addr.fromstring("*:80")]), - False, False, - "wildcard.com", set(["*.wildcard.com"]))) - - self.assertEqual(self.vhosts[5], self._call(self.vhosts)) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/certbot_apache/tests/entrypoint_test.py deleted file mode 100644 index 04c393bdf..000000000 --- a/certbot-apache/certbot_apache/tests/entrypoint_test.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Test for certbot_apache._internal.entrypoint for override class resolution""" -import unittest - -import mock - -from certbot_apache._internal import configurator -from certbot_apache._internal import entrypoint - - -class EntryPointTest(unittest.TestCase): - """Entrypoint tests""" - - _multiprocess_can_split_ = True - - def test_get_configurator(self): - - with mock.patch("certbot.util.get_os_info") as mock_info: - for distro in entrypoint.OVERRIDE_CLASSES: - return_value = (distro, "whatever") - if distro == 'fedora_old': - return_value = ('fedora', '28') - elif distro == 'fedora': - return_value = ('fedora', '29') - mock_info.return_value = return_value - self.assertEqual(entrypoint.get_configurator(), - entrypoint.OVERRIDE_CLASSES[distro]) - - def test_nonexistent_like(self): - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ("nonexistent", "irrelevant") - with mock.patch("certbot.util.get_systemd_os_like") as mock_like: - for like in entrypoint.OVERRIDE_CLASSES: - mock_like.return_value = [like] - self.assertEqual(entrypoint.get_configurator(), - entrypoint.OVERRIDE_CLASSES[like]) - - def test_nonexistent_generic(self): - with mock.patch("certbot.util.get_os_info") as mock_info: - mock_info.return_value = ("nonexistent", "irrelevant") - with mock.patch("certbot.util.get_systemd_os_like") as mock_like: - mock_like.return_value = ["unknonwn"] - self.assertEqual(entrypoint.get_configurator(), - configurator.ApacheConfigurator) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/certbot_apache/tests/fedora_test.py deleted file mode 100644 index 0db6eb60a..000000000 --- a/certbot-apache/certbot_apache/tests/fedora_test.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides""" -import unittest - -import mock - -from certbot import errors -from certbot.compat import filesystem -from certbot.compat import os - -from certbot_apache._internal import obj -from certbot_apache._internal import override_fedora -from certbot_apache.tests import util - - -def get_vh_truth(temp_dir, config_name): - """Return the ground truth for the specified directory.""" - prefix = os.path.join( - temp_dir, config_name, "httpd/conf.d") - - aug_pre = "/files" + prefix - # TODO: eventually, these tests should have a dedicated configuration instead - # of reusing the ones from centos_test - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "centos.example.com.conf"), - os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"), - {obj.Addr.fromstring("*:80")}, - False, True, "centos.example.com"), - obj.VirtualHost( - os.path.join(prefix, "ssl.conf"), - os.path.join(aug_pre, "ssl.conf/VirtualHost"), - {obj.Addr.fromstring("_default_:443")}, - True, True, None) - ] - return vh_truth - - -class FedoraRestartTest(util.ApacheTest): - """Tests for Fedora specific self-signed certificate override""" - - # TODO: eventually, these tests should have a dedicated configuration instead - # of reusing the ones from centos_test - def setUp(self): # pylint: disable=arguments-differ - test_dir = "centos7_apache/apache" - config_root = "centos7_apache/apache/httpd" - vhost_root = "centos7_apache/apache/httpd/conf.d" - super(FedoraRestartTest, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="fedora") - self.vh_truth = get_vh_truth( - self.temp_dir, "centos7_apache/apache") - - def _run_fedora_test(self): - self.assertIsInstance(self.config, override_fedora.FedoraConfigurator) - self.config.config_test() - - def test_fedora_restart_error(self): - c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" - with mock.patch(c_test) as mock_test: - # First call raises error, second doesn't - mock_test.side_effect = [errors.MisconfigurationError, ''] - with mock.patch("certbot.util.run_script") as mock_run: - mock_run.side_effect = errors.SubprocessError - self.assertRaises(errors.MisconfigurationError, - self._run_fedora_test) - - def test_fedora_restart(self): - c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" - with mock.patch(c_test) as mock_test: - with mock.patch("certbot.util.run_script") as mock_run: - # First call raises error, second doesn't - mock_test.side_effect = [errors.MisconfigurationError, ''] - self._run_fedora_test() - self.assertEqual(mock_test.call_count, 2) - self.assertEqual(mock_run.call_args[0][0], - ['systemctl', 'restart', 'httpd']) - - -class MultipleVhostsTestFedora(util.ApacheTest): - """Multiple vhost tests for CentOS / RHEL family of distros""" - - _multiprocess_can_split_ = True - - def setUp(self): # pylint: disable=arguments-differ - test_dir = "centos7_apache/apache" - config_root = "centos7_apache/apache/httpd" - vhost_root = "centos7_apache/apache/httpd/conf.d" - super(MultipleVhostsTestFedora, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="fedora") - self.vh_truth = get_vh_truth( - self.temp_dir, "centos7_apache/apache") - - def test_get_parser(self): - self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_opportunistic_httpd_runtime_parsing(self, mock_get): - define_val = ( - 'Define: TEST1\n' - 'Define: TEST2\n' - 'Define: DUMP_RUN_CFG\n' - ) - mod_val = ( - 'Loaded Modules:\n' - ' mock_module (static)\n' - ' another_module (static)\n' - ) - def mock_get_cfg(command): - """Mock httpd process stdout""" - if command == ['httpd', '-t', '-D', 'DUMP_RUN_CFG']: - return define_val - elif command == ['httpd', '-t', '-D', 'DUMP_MODULES']: - return mod_val - return "" - mock_get.side_effect = mock_get_cfg - self.config.parser.modules = set() - self.config.parser.variables = {} - - with mock.patch("certbot.util.get_os_info") as mock_osi: - # Make sure we have the have the CentOS httpd constants - mock_osi.return_value = ("fedora", "29") - self.config.parser.update_runtime_variables() - - 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) - - @mock.patch("certbot_apache._internal.configurator.util.run_script") - def test_get_version(self, mock_run_script): - mock_run_script.return_value = ('', None) - self.assertRaises(errors.PluginError, self.config.get_version) - self.assertEqual(mock_run_script.call_args[0][0][0], 'httpd') - - def test_get_virtual_hosts(self): - """Make sure all vhosts are being properly found.""" - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 2) - found = 0 - - for vhost in vhs: - for centos_truth in self.vh_truth: - if vhost == centos_truth: - found += 1 - break - else: - raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 2) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_get_sysconfig_vars(self, mock_cfg): - """Make sure we read the sysconfig OPTIONS variable correctly""" - # Return nothing for the process calls - mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = filesystem.realpath( - os.path.join(self.config.parser.root, "../sysconfig/httpd")) - self.config.parser.variables = {} - - with mock.patch("certbot.util.get_os_info") as mock_osi: - # Make sure we have the have the CentOS httpd constants - mock_osi.return_value = ("fedora", "29") - self.config.parser.update_runtime_variables() - - self.assertTrue("mock_define" in self.config.parser.variables.keys()) - self.assertTrue("mock_define_too" in self.config.parser.variables.keys()) - self.assertTrue("mock_value" in self.config.parser.variables.keys()) - self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) - self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) - self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - - @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) - - @mock.patch("certbot_apache._internal.configurator.util.run_script") - def test_alt_restart_errors(self, mock_run_script): - mock_run_script.side_effect = [None, - errors.SubprocessError, - errors.SubprocessError] - self.assertRaises(errors.MisconfigurationError, self.config.restart) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py deleted file mode 100644 index 2eb6335b4..000000000 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Test for certbot_apache._internal.configurator for Gentoo overrides""" -import unittest - -import mock - -from certbot import errors -from certbot.compat import filesystem -from certbot.compat import os - -from certbot_apache._internal import obj -from certbot_apache._internal import override_gentoo -from certbot_apache.tests import util - - -def get_vh_truth(temp_dir, config_name): - """Return the ground truth for the specified directory.""" - prefix = os.path.join( - temp_dir, config_name, "apache2/vhosts.d") - - aug_pre = "/files" + prefix - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "gentoo.example.com.conf"), - os.path.join(aug_pre, "gentoo.example.com.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "gentoo.example.com"), - obj.VirtualHost( - os.path.join(prefix, "00_default_vhost.conf"), - os.path.join(aug_pre, "00_default_vhost.conf/IfDefine/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "localhost"), - obj.VirtualHost( - os.path.join(prefix, "00_default_ssl_vhost.conf"), - os.path.join(aug_pre, - "00_default_ssl_vhost.conf" + - "/IfDefine/IfDefine/IfModule/VirtualHost"), - set([obj.Addr.fromstring("_default_:443")]), - True, True, "localhost") - ] - return vh_truth - -class MultipleVhostsTestGentoo(util.ApacheTest): - """Multiple vhost tests for non-debian distro""" - - _multiprocess_can_split_ = True - - def setUp(self): # pylint: disable=arguments-differ - test_dir = "gentoo_apache/apache" - config_root = "gentoo_apache/apache/apache2" - vhost_root = "gentoo_apache/apache/apache2/vhosts.d" - super(MultipleVhostsTestGentoo, self).setUp(test_dir=test_dir, - config_root=config_root, - vhost_root=vhost_root) - - # pylint: disable=line-too-long - with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"): - self.config = util.get_apache_configurator( - self.config_path, self.vhost_path, self.config_dir, self.work_dir, - os_info="gentoo") - self.vh_truth = get_vh_truth( - self.temp_dir, "gentoo_apache/apache") - - def test_get_parser(self): - self.assertTrue(isinstance(self.config.parser, - override_gentoo.GentooParser)) - - def test_get_virtual_hosts(self): - """Make sure all vhosts are being properly found.""" - vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 3) - found = 0 - - for vhost in vhs: - for gentoo_truth in self.vh_truth: - if vhost == gentoo_truth: - found += 1 - break - else: - raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 3) - - def test_get_sysconfig_vars(self): - """Make sure we read the Gentoo APACHE2_OPTS variable correctly""" - defines = ['DEFAULT_VHOST', 'INFO', - 'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE'] - self.config.parser.apacheconfig_filep = filesystem.realpath( - os.path.join(self.config.parser.root, "../conf.d/apache2")) - self.config.parser.variables = {} - with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): - self.config.parser.update_runtime_variables() - for define in defines: - self.assertTrue(define in self.config.parser.variables.keys()) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.parse_from_subprocess") - def test_no_binary_configdump(self, mock_subprocess): - """Make sure we don't call binary dumps other than modules from Apache - as this is not supported in Gentoo currently""" - - with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): - self.config.parser.update_runtime_variables() - self.config.parser.reset_modules() - self.assertFalse(mock_subprocess.called) - - self.config.parser.update_runtime_variables() - self.config.parser.reset_modules() - self.assertTrue(mock_subprocess.called) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_opportunistic_httpd_runtime_parsing(self, mock_get): - mod_val = ( - 'Loaded Modules:\n' - ' mock_module (static)\n' - ' another_module (static)\n' - ) - def mock_get_cfg(command): - """Mock httpd process stdout""" - if command == ['apache2ctl', 'modules']: - return mod_val - return None # pragma: no cover - mock_get.side_effect = mock_get_cfg - self.config.parser.modules = set() - - with mock.patch("certbot.util.get_os_info") as mock_osi: - # Make sure we have the have the Gentoo httpd constants - mock_osi.return_value = ("gentoo", "123") - self.config.parser.update_runtime_variables() - - 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._internal.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.assertEqual(mock_run_script.call_count, 3) - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/certbot_apache/tests/http_01_test.py deleted file mode 100644 index ab83bfb8b..000000000 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ /dev/null @@ -1,228 +0,0 @@ -"""Test for certbot_apache._internal.http_01.""" -import unittest -import mock - -from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import achallenges -from certbot import errors -from certbot.compat import filesystem -from certbot.compat import os -from certbot.tests import acme_util - -from certbot_apache._internal.parser import get_aug_path -from certbot_apache.tests import util - - -NUM_ACHALLS = 3 - - -class ApacheHttp01Test(util.ApacheTest): - """Test for certbot_apache._internal.http_01.ApacheHttp01.""" - - def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ - super(ApacheHttp01Test, self).setUp(*args, **kwargs) - - self.account_key = self.rsa512jwk - self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge] - vh_truth = util.get_vh_truth( - self.temp_dir, "debian_apache_2_4/multiple_vhosts") - # Takes the vhosts for encryption-example.demo, certbot.demo - # and vhost.in.rootconf - self.vhosts = [vh_truth[0], vh_truth[3], vh_truth[10]] - - for i in range(NUM_ACHALLS): - self.achalls.append( - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((chr(ord('a') + i).encode() * 16))), - "pending"), - domain=self.vhosts[i].name, account_key=self.account_key)) - - modules = ["ssl", "rewrite", "authz_core", "authz_host"] - for mod in modules: - self.config.parser.modules.add("mod_{0}.c".format(mod)) - self.config.parser.modules.add(mod + "_module") - - from certbot_apache._internal.http_01 import ApacheHttp01 - self.http = ApacheHttp01(self.config) - - def test_empty_perform(self): - self.assertFalse(self.http.perform()) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") - def test_enable_modules_apache_2_2(self, mock_enmod): - self.config.version = (2, 2) - self.config.parser.modules.remove("authz_host_module") - self.config.parser.modules.remove("mod_authz_host.c") - - enmod_calls = self.common_enable_modules_test(mock_enmod) - self.assertEqual(enmod_calls[0][0][0], "authz_host") - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") - def test_enable_modules_apache_2_4(self, mock_enmod): - self.config.parser.modules.remove("authz_core_module") - self.config.parser.modules.remove("mod_authz_core.c") - - enmod_calls = self.common_enable_modules_test(mock_enmod) - self.assertEqual(enmod_calls[0][0][0], "authz_core") - - def common_enable_modules_test(self, mock_enmod): - """Tests enabling mod_rewrite and other modules.""" - self.config.parser.modules.remove("rewrite_module") - self.config.parser.modules.remove("mod_rewrite.c") - - self.http.prepare_http01_modules() - - self.assertTrue(mock_enmod.called) - calls = mock_enmod.call_args_list - other_calls = [] - for call in calls: - if call[0][0] != "rewrite": - other_calls.append(call) - - # If these lists are equal, we never enabled mod_rewrite - self.assertNotEqual(calls, other_calls) - return other_calls - - def test_same_vhost(self): - vhost = next(v for v in self.config.vhosts if v.name == "certbot.demo") - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((b'a' * 16))), - "pending"), - domain=vhost.name, account_key=self.account_key), - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((b'b' * 16))), - "pending"), - domain=next(iter(vhost.aliases)), account_key=self.account_key) - ] - self.common_perform_test(achalls, [vhost]) - - def test_anonymous_vhost(self): - vhosts = [v for v in self.config.vhosts if not v.ssl] - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((b'a' * 16))), - "pending"), - domain="something.nonexistent", account_key=self.account_key)] - self.common_perform_test(achalls, vhosts) - - def test_configure_multiple_vhosts(self): - vhosts = [v for v in self.config.vhosts if "duplicate.example.com" in v.get_names()] - self.assertEqual(len(vhosts), 2) - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((b'a' * 16))), - "pending"), - domain="duplicate.example.com", account_key=self.account_key)] - self.common_perform_test(achalls, vhosts) - - def test_no_vhost(self): - for achall in self.achalls: - self.http.add_chall(achall) - self.config.config.http01_port = 12345 - self.assertRaises(errors.PluginError, self.http.perform) - - def test_perform_1_achall_apache_2_2(self): - self.combinations_perform_test(num_achalls=1, minor_version=2) - - def test_perform_1_achall_apache_2_4(self): - self.combinations_perform_test(num_achalls=1, minor_version=4) - - def test_perform_2_achall_apache_2_2(self): - self.combinations_perform_test(num_achalls=2, minor_version=2) - - def test_perform_2_achall_apache_2_4(self): - self.combinations_perform_test(num_achalls=2, minor_version=4) - - def test_perform_3_achall_apache_2_2(self): - self.combinations_perform_test(num_achalls=3, minor_version=2) - - def test_perform_3_achall_apache_2_4(self): - self.combinations_perform_test(num_achalls=3, minor_version=4) - - def test_activate_disabled_vhost(self): - vhosts = [v for v in self.config.vhosts if v.name == "certbot.demo"] - achalls = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.HTTP01(token=((b'a' * 16))), - "pending"), - domain="certbot.demo", account_key=self.account_key)] - vhosts[0].enabled = False - self.common_perform_test(achalls, vhosts) - matches = self.config.parser.find_dir( - "Include", vhosts[0].filep, - get_aug_path(self.config.parser.loc["default"])) - self.assertEqual(len(matches), 1) - - def combinations_perform_test(self, num_achalls, minor_version): - """Test perform with the given achall count and Apache version.""" - achalls = self.achalls[:num_achalls] - vhosts = self.vhosts[:num_achalls] - self.config.version = (2, minor_version) - self.common_perform_test(achalls, vhosts) - - def common_perform_test(self, achalls, vhosts): - """Tests perform with the given achalls.""" - challenge_dir = self.http.challenge_dir - self.assertFalse(os.path.exists(challenge_dir)) - for achall in achalls: - self.http.add_chall(achall) - - expected_response = [ - achall.response(self.account_key) for achall in achalls] - self.assertEqual(self.http.perform(), expected_response) - - self.assertTrue(os.path.isdir(self.http.challenge_dir)) - self.assertTrue(filesystem.has_min_permissions(self.http.challenge_dir, 0o755)) - self._test_challenge_conf() - - for achall in achalls: - self._test_challenge_file(achall) - - for vhost in vhosts: - matches = self.config.parser.find_dir("Include", - self.http.challenge_conf_pre, - vhost.path) - self.assertEqual(len(matches), 1) - matches = self.config.parser.find_dir("Include", - self.http.challenge_conf_post, - vhost.path) - self.assertEqual(len(matches), 1) - - self.assertTrue(os.path.exists(challenge_dir)) - - def _test_challenge_conf(self): - with open(self.http.challenge_conf_pre) as f: - pre_conf_contents = f.read() - - with open(self.http.challenge_conf_post) as f: - post_conf_contents = f.read() - - self.assertTrue("RewriteEngine on" in pre_conf_contents) - self.assertTrue("RewriteRule" in pre_conf_contents) - - self.assertTrue(self.http.challenge_dir in post_conf_contents) - if self.config.version < (2, 4): - self.assertTrue("Allow from all" in post_conf_contents) - else: - self.assertTrue("Require all granted" in post_conf_contents) - - def _test_challenge_file(self, achall): - name = os.path.join(self.http.challenge_dir, achall.chall.encode("token")) - validation = achall.validation(self.account_key) - - self.assertTrue(filesystem.has_min_permissions(name, 0o644)) - with open(name, 'rb') as f: - self.assertEqual(f.read(), validation.encode()) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/obj_test.py b/certbot-apache/certbot_apache/tests/obj_test.py deleted file mode 100644 index 1761b9c94..000000000 --- a/certbot-apache/certbot_apache/tests/obj_test.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Tests for certbot_apache._internal.obj.""" -import unittest - - -class VirtualHostTest(unittest.TestCase): - """Test the VirtualHost class.""" - - def setUp(self): - from certbot_apache._internal.obj import Addr - from certbot_apache._internal.obj import VirtualHost - - self.addr1 = Addr.fromstring("127.0.0.1") - self.addr2 = Addr.fromstring("127.0.0.1:443") - self.addr_default = Addr.fromstring("_default_:443") - - self.vhost1 = VirtualHost( - "filep", "vh_path", set([self.addr1]), False, False, "localhost") - - self.vhost1b = VirtualHost( - "filep", "vh_path", set([self.addr1]), False, False, "localhost") - - self.vhost2 = VirtualHost( - "fp", "vhp", set([self.addr2]), False, False, "localhost") - - def test_repr(self): - self.assertEqual(repr(self.addr2), - "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))") - - def test_eq(self): - self.assertTrue(self.vhost1b == self.vhost1) - self.assertFalse(self.vhost1 == self.vhost2) - self.assertEqual(str(self.vhost1b), str(self.vhost1)) - self.assertFalse(self.vhost1b == 1234) - - def test_ne(self): - self.assertTrue(self.vhost1 != self.vhost2) - self.assertFalse(self.vhost1 != self.vhost1b) - - def test_conflicts(self): - from certbot_apache._internal.obj import Addr - from certbot_apache._internal.obj import VirtualHost - - complex_vh = VirtualHost( - "fp", "vhp", - set([Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")]), - False, False) - self.assertTrue(complex_vh.conflicts([self.addr1])) - self.assertTrue(complex_vh.conflicts([self.addr2])) - self.assertFalse(complex_vh.conflicts([self.addr_default])) - - self.assertTrue(self.vhost1.conflicts([self.addr2])) - self.assertFalse(self.vhost1.conflicts([self.addr_default])) - - self.assertFalse(self.vhost2.conflicts([self.addr1, - self.addr_default])) - - def test_same_server(self): - from certbot_apache._internal.obj import VirtualHost - no_name1 = VirtualHost( - "fp", "vhp", set([self.addr1]), False, False, None) - no_name2 = VirtualHost( - "fp", "vhp", set([self.addr2]), False, False, None) - no_name3 = VirtualHost( - "fp", "vhp", set([self.addr_default]), - False, False, None) - no_name4 = VirtualHost( - "fp", "vhp", set([self.addr2, self.addr_default]), - False, False, None) - - self.assertTrue(self.vhost1.same_server(self.vhost2)) - self.assertTrue(no_name1.same_server(no_name2)) - - self.assertFalse(self.vhost1.same_server(no_name1)) - self.assertFalse(no_name1.same_server(no_name3)) - self.assertFalse(no_name1.same_server(no_name4)) - - -class AddrTest(unittest.TestCase): - """Test obj.Addr.""" - def setUp(self): - from certbot_apache._internal.obj import Addr - self.addr = Addr.fromstring("*:443") - - self.addr1 = Addr.fromstring("127.0.0.1") - self.addr2 = Addr.fromstring("127.0.0.1:*") - - self.addr_defined = Addr.fromstring("127.0.0.1:443") - self.addr_default = Addr.fromstring("_default_:443") - - def test_wildcard(self): - self.assertFalse(self.addr.is_wildcard()) - self.assertTrue(self.addr1.is_wildcard()) - self.assertTrue(self.addr2.is_wildcard()) - - def test_get_sni_addr(self): - from certbot_apache._internal.obj import Addr - self.assertEqual( - self.addr.get_sni_addr("443"), Addr.fromstring("*:443")) - self.assertEqual( - self.addr.get_sni_addr("225"), Addr.fromstring("*:225")) - self.assertEqual( - self.addr1.get_sni_addr("443"), Addr.fromstring("127.0.0.1")) - - def test_conflicts(self): - # Note: Defined IP is more important than defined port in match - self.assertTrue(self.addr.conflicts(self.addr1)) - self.assertTrue(self.addr.conflicts(self.addr2)) - self.assertTrue(self.addr.conflicts(self.addr_defined)) - self.assertFalse(self.addr.conflicts(self.addr_default)) - - self.assertFalse(self.addr1.conflicts(self.addr)) - self.assertTrue(self.addr1.conflicts(self.addr_defined)) - self.assertFalse(self.addr1.conflicts(self.addr_default)) - - self.assertFalse(self.addr_defined.conflicts(self.addr1)) - self.assertFalse(self.addr_defined.conflicts(self.addr2)) - self.assertFalse(self.addr_defined.conflicts(self.addr)) - self.assertFalse(self.addr_defined.conflicts(self.addr_default)) - - self.assertTrue(self.addr_default.conflicts(self.addr)) - self.assertTrue(self.addr_default.conflicts(self.addr1)) - self.assertTrue(self.addr_default.conflicts(self.addr_defined)) - - # Self test - self.assertTrue(self.addr.conflicts(self.addr)) - self.assertTrue(self.addr1.conflicts(self.addr1)) - # This is a tricky one... - self.assertTrue(self.addr1.conflicts(self.addr2)) - - def test_equal(self): - self.assertTrue(self.addr1 == self.addr2) - self.assertFalse(self.addr == self.addr1) - self.assertFalse(self.addr == 123) - - def test_not_equal(self): - self.assertFalse(self.addr1 != self.addr2) - self.assertTrue(self.addr != self.addr1) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/certbot_apache/tests/parser_test.py deleted file mode 100644 index 9c650eeda..000000000 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ /dev/null @@ -1,402 +0,0 @@ -"""Tests for certbot_apache._internal.parser.""" -import shutil -import unittest - -import mock - -from certbot import errors -from certbot.compat import os - -from certbot_apache.tests import util - - -class BasicParserTest(util.ParserTest): - """Apache Parser Test.""" - - def setUp(self): # pylint: disable=arguments-differ - super(BasicParserTest, self).setUp() - - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - - def test_bad_parse(self): - self.parser.parse_file(os.path.join(self.parser.root, - "conf-available", "bad_conf_file.conf")) - self.assertRaises( - errors.PluginError, self.parser.check_parsing_errors, "httpd.aug") - - def test_bad_save(self): - mock_save = mock.Mock() - mock_save.side_effect = IOError - self.parser.aug.save = mock_save - self.assertRaises(errors.PluginError, self.parser.unsaved_files) - - def test_aug_version(self): - mock_match = mock.Mock(return_value=["something"]) - self.parser.aug.match = mock_match - # pylint: disable=protected-access - self.assertEqual(self.parser.check_aug_version(), - ["something"]) - self.parser.aug.match.side_effect = RuntimeError - self.assertFalse(self.parser.check_aug_version()) - - def test_find_config_root_no_root(self): - # pylint: disable=protected-access - os.remove(self.parser.loc["root"]) - self.assertRaises( - errors.NoInstallationError, self.parser._find_config_root) - - def test_parse_file(self): - """Test parse_file. - - certbot.conf is chosen as the test file as it will not be - included during the normal course of execution. - - """ - file_path = os.path.join( - self.config_path, "not-parsed-by-default", "certbot.conf") - - self.parser.parse_file(file_path) # pylint: disable=protected-access - - # search for the httpd incl - matches = self.parser.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % file_path) - - self.assertTrue(matches) - - def test_find_dir(self): - test = self.parser.find_dir("Listen", "80") - # This will only look in enabled hosts - test2 = self.parser.find_dir("documentroot") - - self.assertEqual(len(test), 1) - self.assertEqual(len(test2), 8) - - def test_add_dir(self): - aug_default = "/files" + self.parser.loc["default"] - self.parser.add_dir(aug_default, "AddDirective", "test") - - self.assertTrue( - self.parser.find_dir("AddDirective", "test", aug_default)) - - self.parser.add_dir(aug_default, "AddList", ["1", "2", "3", "4"]) - matches = self.parser.find_dir("AddList", None, aug_default) - for i, match in enumerate(matches): - self.assertEqual(self.parser.aug.get(match), str(i + 1)) - - def test_add_dir_beginning(self): - aug_default = "/files" + self.parser.loc["default"] - self.parser.add_dir_beginning(aug_default, - "AddDirectiveBeginning", - "testBegin") - - self.assertTrue( - self.parser.find_dir("AddDirectiveBeginning", "testBegin", aug_default)) - - self.assertEqual( - self.parser.aug.get(aug_default+"/directive[1]"), - "AddDirectiveBeginning") - self.parser.add_dir_beginning(aug_default, "AddList", ["1", "2", "3", "4"]) - matches = self.parser.find_dir("AddList", None, aug_default) - for i, match in enumerate(matches): - self.assertEqual(self.parser.aug.get(match), str(i + 1)) - - def test_empty_arg(self): - self.assertEqual(None, - self.parser.get_arg("/files/whatever/nonexistent")) - - def test_add_dir_to_ifmodssl(self): - """test add_dir_to_ifmodssl. - - Path must be valid before attempting to add to augeas - - """ - from certbot_apache._internal.parser import get_aug_path - # This makes sure that find_dir will work - self.parser.modules.add("mod_ssl.c") - - self.parser.add_dir_to_ifmodssl( - get_aug_path(self.parser.loc["default"]), - "FakeDirective", ["123"]) - - matches = self.parser.find_dir("FakeDirective", "123") - - self.assertEqual(len(matches), 1) - self.assertTrue("IfModule" in matches[0]) - - def test_add_dir_to_ifmodssl_multiple(self): - from certbot_apache._internal.parser import get_aug_path - # This makes sure that find_dir will work - self.parser.modules.add("mod_ssl.c") - - self.parser.add_dir_to_ifmodssl( - get_aug_path(self.parser.loc["default"]), - "FakeDirective", ["123", "456", "789"]) - - matches = self.parser.find_dir("FakeDirective") - - self.assertEqual(len(matches), 3) - self.assertTrue("IfModule" in matches[0]) - - def test_get_aug_path(self): - from certbot_apache._internal.parser import get_aug_path - self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) - - def test_set_locations(self): - with mock.patch("certbot_apache._internal.parser.os.path") as mock_path: - - mock_path.isfile.side_effect = [False, False] - - # pylint: disable=protected-access - results = self.parser._set_locations() - - self.assertEqual(results["default"], results["listen"]) - self.assertEqual(results["default"], results["name"]) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache._internal.parser.ApacheParser.get_arg") - def test_parse_modules_bad_syntax(self, mock_arg, mock_find): - mock_find.return_value = ["1", "2", "3", "4", "5", "6", "7", "8"] - mock_arg.return_value = None - with mock.patch("certbot_apache._internal.parser.logger") as mock_logger: - self.parser.parse_modules() - # Make sure that we got None return value and logged the file - self.assertTrue(mock_logger.debug.called) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_update_runtime_variables(self, mock_cfg, _): - define_val = ( - 'ServerRoot: "/etc/apache2"\n' - 'Main DocumentRoot: "/var/www"\n' - 'Main ErrorLog: "/var/log/apache2/error.log"\n' - 'Mutex ssl-stapling: using_defaults\n' - 'Mutex ssl-cache: using_defaults\n' - 'Mutex default: dir="/var/lock/apache2" mechanism=fcntl\n' - 'Mutex watchdog-callback: using_defaults\n' - 'PidFile: "/var/run/apache2/apache2.pid"\n' - 'Define: TEST\n' - 'Define: DUMP_RUN_CFG\n' - 'Define: U_MICH\n' - 'Define: TLS=443\n' - 'Define: example_path=Documents/path\n' - 'User: name="www-data" id=33 not_used\n' - 'Group: name="www-data" id=33 not_used\n' - ) - inc_val = ( - 'Included configuration files:\n' - ' (*) /etc/apache2/apache2.conf\n' - ' (146) /etc/apache2/mods-enabled/access_compat.load\n' - ' (146) /etc/apache2/mods-enabled/alias.load\n' - ' (146) /etc/apache2/mods-enabled/auth_basic.load\n' - ' (146) /etc/apache2/mods-enabled/authn_core.load\n' - ' (146) /etc/apache2/mods-enabled/authn_file.load\n' - ' (146) /etc/apache2/mods-enabled/authz_core.load\n' - ' (146) /etc/apache2/mods-enabled/authz_host.load\n' - ' (146) /etc/apache2/mods-enabled/authz_user.load\n' - ' (146) /etc/apache2/mods-enabled/autoindex.load\n' - ' (146) /etc/apache2/mods-enabled/deflate.load\n' - ' (146) /etc/apache2/mods-enabled/dir.load\n' - ' (146) /etc/apache2/mods-enabled/env.load\n' - ' (146) /etc/apache2/mods-enabled/filter.load\n' - ' (146) /etc/apache2/mods-enabled/mime.load\n' - ' (146) /etc/apache2/mods-enabled/mpm_event.load\n' - ' (146) /etc/apache2/mods-enabled/negotiation.load\n' - ' (146) /etc/apache2/mods-enabled/reqtimeout.load\n' - ' (146) /etc/apache2/mods-enabled/setenvif.load\n' - ' (146) /etc/apache2/mods-enabled/socache_shmcb.load\n' - ' (146) /etc/apache2/mods-enabled/ssl.load\n' - ' (146) /etc/apache2/mods-enabled/status.load\n' - ' (147) /etc/apache2/mods-enabled/alias.conf\n' - ' (147) /etc/apache2/mods-enabled/autoindex.conf\n' - ' (147) /etc/apache2/mods-enabled/deflate.conf\n' - ) - mod_val = ( - 'Loaded Modules:\n' - ' core_module (static)\n' - ' so_module (static)\n' - ' watchdog_module (static)\n' - ' http_module (static)\n' - ' log_config_module (static)\n' - ' logio_module (static)\n' - ' version_module (static)\n' - ' unixd_module (static)\n' - ' access_compat_module (shared)\n' - ' alias_module (shared)\n' - ' auth_basic_module (shared)\n' - ' authn_core_module (shared)\n' - ' authn_file_module (shared)\n' - ' authz_core_module (shared)\n' - ' authz_host_module (shared)\n' - ' authz_user_module (shared)\n' - ' autoindex_module (shared)\n' - ' deflate_module (shared)\n' - ' dir_module (shared)\n' - ' env_module (shared)\n' - ' filter_module (shared)\n' - ' mime_module (shared)\n' - ' mpm_event_module (shared)\n' - ' negotiation_module (shared)\n' - ' reqtimeout_module (shared)\n' - ' setenvif_module (shared)\n' - ' socache_shmcb_module (shared)\n' - ' ssl_module (shared)\n' - ' status_module (shared)\n' - ) - - def mock_get_vars(cmd): - """Mock command output""" - if cmd[-1] == "DUMP_RUN_CFG": - return define_val - elif cmd[-1] == "DUMP_INCLUDES": - return inc_val - elif cmd[-1] == "DUMP_MODULES": - return mod_val - return None # pragma: no cover - - mock_cfg.side_effect = mock_get_vars - - expected_vars = {"TEST": "", "U_MICH": "", "TLS": "443", - "example_path": "Documents/path"} - - self.parser.modules = set() - with mock.patch( - "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: - self.parser.update_runtime_variables() - self.assertEqual(self.parser.variables, expected_vars) - self.assertEqual(len(self.parser.modules), 58) - # None of the includes in inc_val should be in parsed paths. - # Make sure we tried to include them all. - self.assertEqual(mock_parse.call_count, 25) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_update_runtime_variables_alt_values(self, mock_cfg, _): - inc_val = ( - 'Included configuration files:\n' - ' (*) {0}\n' - ' (146) /etc/apache2/mods-enabled/access_compat.load\n' - ' (146) {1}/mods-enabled/alias.load\n' - ).format(self.parser.loc["root"], - os.path.dirname(self.parser.loc["root"])) - - mock_cfg.return_value = inc_val - self.parser.modules = set() - - with mock.patch( - "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: - self.parser.update_runtime_variables() - # No matching modules should have been found - self.assertEqual(len(self.parser.modules), 0) - # Only one of the three includes do not exist in already parsed - # path derived from root configuration Include statements - self.assertEqual(mock_parse.call_count, 1) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_update_runtime_vars_bad_output(self, mock_cfg): - mock_cfg.return_value = "Define: TLS=443=24" - self.parser.update_runtime_variables() - - mock_cfg.return_value = "Define: DUMP_RUN_CFG\nDefine: TLS=443=24" - self.assertRaises( - errors.PluginError, self.parser.update_runtime_variables) - - @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") - def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): - mock_popen.side_effect = OSError - mock_opt.return_value = "nonexistent" - self.assertRaises( - errors.MisconfigurationError, - self.parser.update_runtime_variables) - - @mock.patch("certbot_apache._internal.parser.subprocess.Popen") - def test_update_runtime_vars_bad_exit(self, mock_popen): - mock_popen().communicate.return_value = ("", "") - mock_popen.returncode = -1 - self.assertRaises( - errors.MisconfigurationError, - self.parser.update_runtime_variables) - - def test_add_comment(self): - from certbot_apache._internal.parser import get_aug_path - self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456") - comm = self.parser.find_comments("123456") - self.assertEqual(len(comm), 1) - self.assertTrue(self.parser.loc["name"] in comm[0]) - - -class ParserInitTest(util.ApacheTest): - def setUp(self): # pylint: disable=arguments-differ - super(ParserInitTest, self).setUp() - - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - - @mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas") - def test_prepare_no_augeas(self, mock_init_augeas): - from certbot_apache._internal.parser import ApacheParser - mock_init_augeas.side_effect = errors.NoInstallationError - self.config.config_test = mock.Mock() - self.assertRaises( - errors.NoInstallationError, ApacheParser, - os.path.relpath(self.config_path), "/dummy/vhostpath", - version=(2, 4, 22), configurator=self.config) - - def test_init_old_aug(self): - from certbot_apache._internal.parser import ApacheParser - with mock.patch("certbot_apache._internal.parser.ApacheParser.check_aug_version") as mock_c: - mock_c.return_value = False - self.assertRaises( - errors.NotSupportedError, - ApacheParser, os.path.relpath(self.config_path), - "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) - - @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") - def test_unparseable(self, mock_cfg): - from certbot_apache._internal.parser import ApacheParser - mock_cfg.return_value = ('Define: TEST') - self.assertRaises( - errors.PluginError, - ApacheParser, os.path.relpath(self.config_path), - "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config) - - def test_root_normalized(self): - from certbot_apache._internal.parser import ApacheParser - - with mock.patch("certbot_apache._internal.parser.ApacheParser." - "update_runtime_variables"): - path = os.path.join( - self.temp_dir, - "debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2") - - parser = ApacheParser(path, "/dummy/vhostpath", configurator=self.config) - - self.assertEqual(parser.root, self.config_path) - - def test_root_absolute(self): - from certbot_apache._internal.parser import ApacheParser - with mock.patch("certbot_apache._internal.parser.ApacheParser." - "update_runtime_variables"): - parser = ApacheParser( - os.path.relpath(self.config_path), - "/dummy/vhostpath", configurator=self.config) - - self.assertEqual(parser.root, self.config_path) - - def test_root_no_trailing_slash(self): - from certbot_apache._internal.parser import ApacheParser - with mock.patch("certbot_apache._internal.parser.ApacheParser." - "update_runtime_variables"): - parser = ApacheParser( - self.config_path + os.path.sep, - "/dummy/vhostpath", configurator=self.config) - self.assertEqual(parser.root, self.config_path) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README deleted file mode 100644 index c12e149f2..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README +++ /dev/null @@ -1,9 +0,0 @@ - -This directory holds Apache 2.0 module-specific configuration files; -any files in this directory which have the ".conf" extension will be -processed as Apache configuration files. - -Files are processed in alphabetical order, so if using configuration -directives which depend on, say, mod_perl being loaded, ensure that -these are placed in a filename later in the sort order than "perl.conf". - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf deleted file mode 100644 index fb2174af1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf +++ /dev/null @@ -1,222 +0,0 @@ -# -# This is the Apache server configuration file providing SSL support. -# It contains the configuration directives to instruct the server how to -# serve pages over an https connection. For detailing information about these -# directives see -# -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. -# - -LoadModule ssl_module modules/mod_ssl.so - -# -# When we also provide SSL we have to listen to the -# the HTTPS port in addition. -# -Listen 443 - -## -## SSL Global Context -## -## All SSL configuration in this context applies both to -## the main server and all SSL-enabled virtual hosts. -## - -# Pass Phrase Dialog: -# Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal -# terminal dialog) has to provide the pass phrase on stdout. -SSLPassPhraseDialog builtin - -# Inter-Process Session Cache: -# Configure the SSL Session Cache: First the mechanism -# to use and second the expiring timeout (in seconds). -SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) -SSLSessionCacheTimeout 300 - -# Semaphore: -# Configure the path to the mutual exclusion semaphore the -# SSL engine uses internally for inter-process synchronization. -SSLMutex default - -# Pseudo Random Number Generator (PRNG): -# Configure one or more sources to seed the PRNG of the -# SSL library. The seed data should be of good random quality. -# WARNING! On some platforms /dev/random blocks if not enough entropy -# is available. This means you then cannot use the /dev/random device -# because it would lead to very long connection times (as long as -# it requires to make more entropy available). But usually those -# platforms additionally provide a /dev/urandom device which doesn't -# block. So, if available, use this one instead. Read the mod_ssl User -# Manual for more details. -SSLRandomSeed startup file:/dev/urandom 256 -SSLRandomSeed connect builtin -#SSLRandomSeed startup file:/dev/random 512 -#SSLRandomSeed connect file:/dev/random 512 -#SSLRandomSeed connect file:/dev/urandom 512 - -# -# Use "SSLCryptoDevice" to enable any supported hardware -# accelerators. Use "openssl engine -v" to list supported -# engine names. NOTE: If you enable an accelerator and the -# server does not start, consult the error logs and ensure -# your accelerator is functioning properly. -# -SSLCryptoDevice builtin -#SSLCryptoDevice ubsec - -## -## SSL Virtual Host Context -## - - - -# General setup for the virtual host, inherited from global configuration -#DocumentRoot "/var/www/html" -#ServerName www.example.com:443 - -# Use separate log files for the SSL virtual host; note that LogLevel -# is not inherited from httpd.conf. -ErrorLog logs/ssl_error_log -TransferLog logs/ssl_access_log -LogLevel warn - -# SSL Engine Switch: -# Enable/Disable SSL for this virtual host. -SSLEngine on - -# SSL Protocol support: -# List the enable protocol levels with which clients will be able to -# connect. Disable SSLv2 access by default: -SSLProtocol all -SSLv2 - -# SSL Cipher Suite: -# List the ciphers that the client is permitted to negotiate. -# See the mod_ssl documentation for a complete list. -SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:+3DES - -# Server Certificate: -# Point SSLCertificateFile at a PEM encoded certificate. If -# the certificate is encrypted, then you will be prompted for a -# pass phrase. Note that a kill -HUP will prompt again. A new -# certificate can be generated using the genkey(1) command. -SSLCertificateFile /etc/pki/tls/certs/localhost.crt - -# Server Private Key: -# If the key is not combined with the certificate, use this -# directive to point at the key file. Keep in mind that if -# you've both a RSA and a DSA private key you can configure -# both in parallel (to also allow the use of DSA ciphers, etc.) -SSLCertificateKeyFile /etc/pki/tls/private/localhost.key - -# Server Certificate Chain: -# Point SSLCertificateChainFile at a file containing the -# concatenation of PEM encoded CA certificates which form the -# certificate chain for the server certificate. Alternatively -# the referenced file can be the same as SSLCertificateFile -# when the CA certificates are directly appended to the server -# certificate for convinience. -#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt - -# Certificate Authority (CA): -# Set the CA certificate verification path where to find CA -# certificates for client authentication or alternatively one -# huge file containing all of them (file must be PEM encoded) -#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt - -# Client Authentication (Type): -# Client certificate verification type and depth. Types are -# none, optional, require and optional_no_ca. Depth is a -# number which specifies how deeply to verify the certificate -# issuer chain before deciding the certificate is not valid. -#SSLVerifyClient require -#SSLVerifyDepth 10 - -# Access Control: -# With SSLRequire you can do per-directory access control based -# on arbitrary complex boolean expressions containing server -# variable checks and other lookup directives. The syntax is a -# mixture between C and Perl. See the mod_ssl documentation -# for more details. -# -#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ -# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ -# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ -# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ -# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ -# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ -# - -# SSL Engine Options: -# Set various options for the SSL engine. -# o FakeBasicAuth: -# Translate the client X.509 into a Basic Authorisation. This means that -# the standard Auth/DBMAuth methods can be used for access control. The -# user name is the `one line' version of the client's X.509 certificate. -# Note that no password is obtained from the user. Every entry in the user -# file needs this password: `xxj31ZMTZzkVA'. -# o ExportCertData: -# This exports two additional environment variables: SSL_CLIENT_CERT and -# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the -# server (always existing) and the client (only existing when client -# authentication is used). This can be used to import the certificates -# into CGI scripts. -# o StdEnvVars: -# This exports the standard SSL/TLS related `SSL_*' environment variables. -# Per default this exportation is switched off for performance reasons, -# because the extraction step is an expensive operation and is usually -# useless for serving static content. So one usually enables the -# exportation for CGI and SSI requests only. -# o StrictRequire: -# This denies access when "SSLRequireSSL" or "SSLRequire" applied even -# under a "Satisfy any" situation, i.e. when it applies access is denied -# and no other module can change it. -# o OptRenegotiate: -# This enables optimized SSL connection renegotiation handling when SSL -# directives are used in per-directory context. -#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - -# SSL Protocol Adjustments: -# The safe and default but still SSL/TLS standard compliant shutdown -# approach is that mod_ssl sends the close notify alert but doesn't wait for -# the close notify alert from client. When you need a different shutdown -# approach you can use one of the following variables: -# o ssl-unclean-shutdown: -# This forces an unclean shutdown when the connection is closed, i.e. no -# SSL close notify alert is send or allowed to received. This violates -# the SSL/TLS standard but is needed for some brain-dead browsers. Use -# this when you receive I/O errors because of the standard approach where -# mod_ssl sends the close notify alert. -# o ssl-accurate-shutdown: -# This forces an accurate shutdown when the connection is closed, i.e. a -# SSL close notify alert is send and mod_ssl waits for the close notify -# alert of the client. This is 100% SSL/TLS standard compliant, but in -# practice often causes hanging connections with brain-dead browsers. Use -# this only for browsers where you know that their SSL implementation -# works correctly. -# Notice: Most problems of broken clients are also related to the HTTP -# keep-alive facility, so you usually additionally want to disable -# keep-alive for those clients, too. Use variable "nokeepalive" for this. -# Similarly, one has to force some clients to use HTTP/1.0 to workaround -# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and -# "force-response-1.0" for this. -SetEnvIf User-Agent ".*MSIE.*" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - -# Per-Server Logging: -# The home of a custom SSL log file. Use this when you want a -# compact non-error SSL logfile on a virtual host basis. -CustomLog logs/ssl_request_log \ - "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" - - - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf deleted file mode 100644 index 3dd7b18f1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf +++ /dev/null @@ -1,7 +0,0 @@ - - ServerName test.example.com - ServerAdmin webmaster@dummy-host.example.com - DocumentRoot /var/www/htdocs - ErrorLog logs/dummy-host.example.com-error_log - CustomLog logs/dummy-host.example.com-access_log common - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf deleted file mode 100644 index c1d23c512..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf +++ /dev/null @@ -1,11 +0,0 @@ -# -# This configuration file enables the default "Welcome" -# page if there is no default index page present for -# the root URL. To disable the Welcome page, comment -# out all the lines below. -# - - Options -Indexes - ErrorDocument 403 /error/noindex.html - - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf deleted file mode 100644 index 579d194ce..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf +++ /dev/null @@ -1,1009 +0,0 @@ -# -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See for detailed information. -# In particular, see -# -# for a discussion of each configuration directive. -# -# -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. -# -# The configuration directives are grouped into three basic sections: -# 1. Directives that control the operation of the Apache server process as a -# whole (the 'global environment'). -# 2. Directives that define the parameters of the 'main' or 'default' server, -# which responds to requests that aren't handled by a virtual host. -# These directives also provide default values for the settings -# of all virtual hosts. -# 3. Settings for virtual hosts, which allow Web requests to be sent to -# different IP addresses or hostnames and have them handled by the -# same Apache server process. -# -# Configuration and logfile names: If the filenames you specify for many -# of the server's control files begin with "/" (or "drive:/" for Win32), the -# server will use that explicit path. If the filenames do *not* begin -# with "/", the value of ServerRoot is prepended -- so "logs/foo.log" -# with ServerRoot set to "/etc/httpd" will be interpreted by the -# server as "/etc/httpd/logs/foo.log". -# - -### Section 1: Global Environment -# -# The directives in this section affect the overall operation of Apache, -# such as the number of concurrent requests it can handle or where it -# can find its configuration files. -# - -# -# Don't give away too much information about all the subcomponents -# we are running. Comment out this line if you don't mind remote sites -# finding out what major optional modules you are running -ServerTokens OS - -# -# ServerRoot: The top of the directory tree under which the server's -# configuration, error, and log files are kept. -# -# NOTE! If you intend to place this on an NFS (or otherwise network) -# mounted filesystem then please read the LockFile documentation -# (available at ); -# you will save yourself a lot of trouble. -# -# Do NOT add a slash at the end of the directory path. -# -ServerRoot "/etc/httpd" - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. Note the PIDFILE variable in -# /etc/sysconfig/httpd must be set appropriately if this location is -# changed. -# -PidFile run/httpd.pid - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 60 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive Off - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 15 - -## -## Server-Pool Size Regulation (MPM specific) -## - -# prefork MPM -# StartServers: number of server processes to start -# MinSpareServers: minimum number of server processes which are kept spare -# MaxSpareServers: maximum number of server processes which are kept spare -# ServerLimit: maximum value for MaxClients for the lifetime of the server -# MaxClients: maximum number of server processes allowed to start -# MaxRequestsPerChild: maximum number of requests a server process serves - -StartServers 8 -MinSpareServers 5 -MaxSpareServers 20 -ServerLimit 256 -MaxClients 256 -MaxRequestsPerChild 4000 - - -# worker MPM -# StartServers: initial number of server processes to start -# MaxClients: maximum number of simultaneous client connections -# MinSpareThreads: minimum number of worker threads which are kept spare -# MaxSpareThreads: maximum number of worker threads which are kept spare -# ThreadsPerChild: constant number of worker threads in each server process -# MaxRequestsPerChild: maximum number of requests a server process serves - -StartServers 4 -MaxClients 300 -MinSpareThreads 25 -MaxSpareThreads 75 -ThreadsPerChild 25 -MaxRequestsPerChild 0 - - -# -# Listen: Allows you to bind Apache to specific IP addresses and/or -# ports, in addition to the default. See also the -# directive. -# -# Change this to Listen on specific IP addresses as shown below to -# prevent Apache from glomming onto all bound IP addresses (0.0.0.0) -# -#Listen 12.34.56.78:80 -Listen 80 - -# -# Dynamic Shared Object (DSO) Support -# -# To be able to use the functionality of a module which was built as a DSO you -# have to place corresponding `LoadModule' lines at this location so the -# directives contained in it are actually available _before_ they are used. -# Statically compiled modules (those listed by `httpd -l') do not need -# to be loaded here. -# -# Example: -# LoadModule foo_module modules/mod_foo.so -# -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule auth_digest_module modules/mod_auth_digest.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authn_alias_module modules/mod_authn_alias.so -LoadModule authn_anon_module modules/mod_authn_anon.so -LoadModule authn_dbm_module modules/mod_authn_dbm.so -LoadModule authn_default_module modules/mod_authn_default.so -LoadModule authz_host_module modules/mod_authz_host.so -LoadModule authz_user_module modules/mod_authz_user.so -LoadModule authz_owner_module modules/mod_authz_owner.so -LoadModule authz_groupfile_module modules/mod_authz_groupfile.so -LoadModule authz_dbm_module modules/mod_authz_dbm.so -LoadModule authz_default_module modules/mod_authz_default.so -LoadModule ldap_module modules/mod_ldap.so -LoadModule authnz_ldap_module modules/mod_authnz_ldap.so -LoadModule include_module modules/mod_include.so -LoadModule log_config_module modules/mod_log_config.so -LoadModule logio_module modules/mod_logio.so -LoadModule env_module modules/mod_env.so -LoadModule ext_filter_module modules/mod_ext_filter.so -LoadModule mime_magic_module modules/mod_mime_magic.so -LoadModule expires_module modules/mod_expires.so -LoadModule deflate_module modules/mod_deflate.so -LoadModule headers_module modules/mod_headers.so -LoadModule usertrack_module modules/mod_usertrack.so -LoadModule setenvif_module modules/mod_setenvif.so -LoadModule mime_module modules/mod_mime.so -LoadModule dav_module modules/mod_dav.so -LoadModule status_module modules/mod_status.so -LoadModule autoindex_module modules/mod_autoindex.so -LoadModule info_module modules/mod_info.so -LoadModule dav_fs_module modules/mod_dav_fs.so -LoadModule vhost_alias_module modules/mod_vhost_alias.so -LoadModule negotiation_module modules/mod_negotiation.so -LoadModule dir_module modules/mod_dir.so -LoadModule actions_module modules/mod_actions.so -LoadModule speling_module modules/mod_speling.so -LoadModule userdir_module modules/mod_userdir.so -LoadModule alias_module modules/mod_alias.so -LoadModule substitute_module modules/mod_substitute.so -LoadModule rewrite_module modules/mod_rewrite.so -LoadModule proxy_module modules/mod_proxy.so -LoadModule proxy_balancer_module modules/mod_proxy_balancer.so -LoadModule proxy_ftp_module modules/mod_proxy_ftp.so -LoadModule proxy_http_module modules/mod_proxy_http.so -LoadModule proxy_ajp_module modules/mod_proxy_ajp.so -LoadModule proxy_connect_module modules/mod_proxy_connect.so -LoadModule cache_module modules/mod_cache.so -LoadModule suexec_module modules/mod_suexec.so -LoadModule disk_cache_module modules/mod_disk_cache.so -LoadModule cgi_module modules/mod_cgi.so -LoadModule version_module modules/mod_version.so - -# -# The following modules are not loaded by default: -# -#LoadModule asis_module modules/mod_asis.so -#LoadModule authn_dbd_module modules/mod_authn_dbd.so -#LoadModule cern_meta_module modules/mod_cern_meta.so -#LoadModule cgid_module modules/mod_cgid.so -#LoadModule dbd_module modules/mod_dbd.so -#LoadModule dumpio_module modules/mod_dumpio.so -#LoadModule filter_module modules/mod_filter.so -#LoadModule ident_module modules/mod_ident.so -#LoadModule log_forensic_module modules/mod_log_forensic.so -#LoadModule unique_id_module modules/mod_unique_id.so -# - -# -# Load config files from the config directory "/etc/httpd/conf.d". -# -Include conf.d/*.conf - -# -# ExtendedStatus controls whether Apache will generate "full" status -# information (ExtendedStatus On) or just basic information (ExtendedStatus -# Off) when the "server-status" handler is called. The default is Off. -# -#ExtendedStatus On - -# -# If you wish httpd to run as a different user or group, you must run -# httpd as root initially and it will switch. -# -# User/Group: The name (or #number) of the user/group to run httpd as. -# . On SCO (ODT 3) use "User nouser" and "Group nogroup". -# . On HPUX you may not be able to use shared memory as nobody, and the -# suggested workaround is to create a user www and use that user. -# NOTE that some kernels refuse to setgid(Group) or semctl(IPC_SET) -# when the value of (unsigned)Group is above 60000; -# don't use Group #-1 on these systems! -# -User apache -Group apache - -### Section 2: 'Main' server configuration -# -# The directives in this section set up the values used by the 'main' -# server, which responds to any requests that aren't handled by a -# definition. These values also provide defaults for -# any containers you may define later in the file. -# -# All of these directives may appear inside containers, -# in which case these default settings will be overridden for the -# virtual host being defined. -# - -# -# ServerAdmin: Your address, where problems with the server should be -# e-mailed. This address appears on some server-generated pages, such -# as error documents. e.g. admin@your-domain.com -# -ServerAdmin root@localhost - -# -# ServerName gives the name and port that the server uses to identify itself. -# This can often be determined automatically, but we recommend you specify -# it explicitly to prevent problems during startup. -# -# If this is not set to valid DNS name for your host, server-generated -# redirections will not work. See also the UseCanonicalName directive. -# -# If your host doesn't have a registered DNS name, enter its IP address here. -# You will have to access it by its address anyway, and this will make -# redirections work in a sensible way. -# -#ServerName www.example.com:80 - -# -# UseCanonicalName: Determines how Apache constructs self-referencing -# URLs and the SERVER_NAME and SERVER_PORT variables. -# When set "Off", Apache will use the Hostname and Port supplied -# by the client. When set "On", Apache will use the value of the -# ServerName directive. -# -UseCanonicalName Off - -# -# DocumentRoot: The directory out of which you will serve your -# documents. By default, all requests are taken from this directory, but -# symbolic links and aliases may be used to point to other locations. -# -DocumentRoot "/var/www/html" - -# -# Each directory to which Apache has access can be configured with respect -# to which services and features are allowed and/or disabled in that -# directory (and its subdirectories). -# -# First, we configure the "default" to be a very restrictive set of -# features. -# - - Options FollowSymLinks - AllowOverride None - - -# -# Note that from this point forward you must specifically allow -# particular features to be enabled - so if something's not working as -# you might expect, make sure that you have specifically enabled it -# below. -# - -# -# This should be changed to whatever you set DocumentRoot to. -# - - -# -# Possible values for the Options directive are "None", "All", -# or any combination of: -# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews -# -# Note that "MultiViews" must be named *explicitly* --- "Options All" -# doesn't give it to you. -# -# The Options directive is both complicated and important. Please see -# http://httpd.apache.org/docs/2.2/mod/core.html#options -# for more information. -# - Options Indexes FollowSymLinks - -# -# AllowOverride controls what directives may be placed in .htaccess files. -# It can be "All", "None", or any combination of the keywords: -# Options FileInfo AuthConfig Limit -# - AllowOverride None - -# -# Controls who can get stuff from this server. -# - Order allow,deny - Allow from all - - - -# -# UserDir: The name of the directory that is appended onto a user's home -# directory if a ~user request is received. -# -# The path to the end user account 'public_html' directory must be -# accessible to the webserver userid. This usually means that ~userid -# must have permissions of 711, ~userid/public_html must have permissions -# of 755, and documents contained therein must be world-readable. -# Otherwise, the client will only receive a "403 Forbidden" message. -# -# See also: http://httpd.apache.org/docs/misc/FAQ.html#forbidden -# - - # - # UserDir is disabled by default since it can confirm the presence - # of a username on the system (depending on home directory - # permissions). - # - UserDir disabled - - # - # To enable requests to /~user/ to serve the user's public_html - # directory, remove the "UserDir disabled" line above, and uncomment - # the following line instead: - # - #UserDir public_html - - - -# -# Control access to UserDir directories. The following is an example -# for a site where these directories are restricted to read-only. -# -# -# AllowOverride FileInfo AuthConfig Limit -# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec -# -# Order allow,deny -# Allow from all -# -# -# Order deny,allow -# Deny from all -# -# - -# -# DirectoryIndex: sets the file that Apache will serve if a directory -# is requested. -# -# The index.html.var file (a type-map) is used to deliver content- -# negotiated documents. The MultiViews Option can be used for the -# same purpose, but it is much slower. -# -DirectoryIndex index.html index.html.var - -# -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Order allow,deny - Deny from all - Satisfy All - - -# -# TypesConfig describes where the mime.types file (or equivalent) is -# to be found. -# -TypesConfig /etc/mime.types - -# -# DefaultType is the default MIME type the server will use for a document -# if it cannot otherwise determine one, such as from filename extensions. -# If your server contains mostly text or HTML documents, "text/plain" is -# a good value. If most of your content is binary, such as applications -# or images, you may want to use "application/octet-stream" instead to -# keep browsers from trying to display binary files as though they are -# text. -# -DefaultType text/plain - -# -# The mod_mime_magic module allows the server to use various hints from the -# contents of the file itself to determine its type. The MIMEMagicFile -# directive tells the module where the hint definitions are located. -# - -# MIMEMagicFile /usr/share/magic.mime - MIMEMagicFile conf/magic - - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# -# EnableMMAP: Control whether memory-mapping is used to deliver -# files (assuming that the underlying OS supports it). -# The default is on; turn this off if you serve from NFS-mounted -# filesystems. On some systems, turning it off (regardless of -# filesystem) can improve performance; for details, please see -# http://httpd.apache.org/docs/2.2/mod/core.html#enablemmap -# -#EnableMMAP off - -# -# EnableSendfile: Control whether the sendfile kernel support is -# used to deliver files (assuming that the OS supports it). -# The default is on; turn this off if you serve from NFS-mounted -# filesystems. Please see -# http://httpd.apache.org/docs/2.2/mod/core.html#enablesendfile -# -#EnableSendfile off - -# -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog logs/error_log - -# -# LogLevel: Control the number of messages logged to the error_log. -# Possible values include: debug, info, notice, warn, error, crit, -# alert, emerg. -# -LogLevel warn - -# -# The following directives define some format nicknames for use with -# a CustomLog directive (see below). -# -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %b" common -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# "combinedio" includes actual counts of actual bytes received (%I) and sent (%O); this -# requires the mod_logio module to be loaded. -#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio - -# -# The location and format of the access logfile (Common Logfile Format). -# If you do not define any access logfiles within a -# container, they will be logged here. Contrariwise, if you *do* -# define per- access logfiles, transactions will be -# logged therein and *not* in this file. -# -#CustomLog logs/access_log common - -# -# If you would like to have separate agent and referer logfiles, uncomment -# the following directives. -# -#CustomLog logs/referer_log referer -#CustomLog logs/agent_log agent - -# -# For a single logfile with access, agent, and referer information -# (Combined Logfile Format), use the following directive: -# -CustomLog logs/access_log combined - -# -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -# -ServerSignature On - -# -# Aliases: Add here as many aliases as you need (with no limit). The format is -# Alias fakename realname -# -# Note that if you include a trailing / on fakename then the server will -# require it to be present in the URL. So "/icons" isn't aliased in this -# example, only "/icons/". If the fakename is slash-terminated, then the -# realname must also be slash terminated, and if the fakename omits the -# trailing slash, the realname must also omit it. -# -# We include the /icons/ alias for FancyIndexed directory listings. If you -# do not use FancyIndexing, you may comment this out. -# -Alias /icons/ "/var/www/icons/" - - - Options Indexes MultiViews FollowSymLinks - AllowOverride None - Order allow,deny - Allow from all - - -# -# WebDAV module configuration section. -# - - # Location of the WebDAV lock database. - DAVLockDB /var/lib/dav/lockdb - - -# -# ScriptAlias: This controls which directories contain server scripts. -# ScriptAliases are essentially the same as Aliases, except that -# documents in the realname directory are treated as applications and -# run by the server when requested rather than as documents sent to the client. -# The same rules about trailing "/" apply to ScriptAlias directives as to -# Alias. -# -ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" - -# -# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased -# CGI directory exists, if you have that configured. -# - - AllowOverride None - Options None - Order allow,deny - Allow from all - - -# -# Redirect allows you to tell clients about documents which used to exist in -# your server's namespace, but do not anymore. This allows you to tell the -# clients where to look for the relocated document. -# Example: -# Redirect permanent /foo http://www.example.com/bar - -# -# Directives controlling the display of server-generated directory listings. -# - -# -# IndexOptions: Controls the appearance of server-generated directory -# listings. -# -IndexOptions FancyIndexing VersionSort NameWidth=* HTMLTable Charset=UTF-8 - -# -# AddIcon* directives tell the server which icon to show for different -# files or filename extensions. These are only displayed for -# FancyIndexed directories. -# -AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip - -AddIconByType (TXT,/icons/text.gif) text/* -AddIconByType (IMG,/icons/image2.gif) image/* -AddIconByType (SND,/icons/sound2.gif) audio/* -AddIconByType (VID,/icons/movie.gif) video/* - -AddIcon /icons/binary.gif .bin .exe -AddIcon /icons/binhex.gif .hqx -AddIcon /icons/tar.gif .tar -AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv -AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip -AddIcon /icons/a.gif .ps .ai .eps -AddIcon /icons/layout.gif .html .shtml .htm .pdf -AddIcon /icons/text.gif .txt -AddIcon /icons/c.gif .c -AddIcon /icons/p.gif .pl .py -AddIcon /icons/f.gif .for -AddIcon /icons/dvi.gif .dvi -AddIcon /icons/uuencoded.gif .uu -AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl -AddIcon /icons/tex.gif .tex -AddIcon /icons/bomb.gif /core - -AddIcon /icons/back.gif .. -AddIcon /icons/hand.right.gif README -AddIcon /icons/folder.gif ^^DIRECTORY^^ -AddIcon /icons/blank.gif ^^BLANKICON^^ - -# -# DefaultIcon is which icon to show for files which do not have an icon -# explicitly set. -# -DefaultIcon /icons/unknown.gif - -# -# AddDescription allows you to place a short description after a file in -# server-generated indexes. These are only displayed for FancyIndexed -# directories. -# Format: AddDescription "description" filename -# -#AddDescription "GZIP compressed document" .gz -#AddDescription "tar archive" .tar -#AddDescription "GZIP compressed tar archive" .tgz - -# -# ReadmeName is the name of the README file the server will look for by -# default, and append to directory listings. -# -# HeaderName is the name of a file which should be prepended to -# directory indexes. -ReadmeName README.html -HeaderName HEADER.html - -# -# IndexIgnore is a set of filenames which directory indexing should ignore -# and not include in the listing. Shell-style wildcarding is permitted. -# -IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t - -# -# DefaultLanguage and AddLanguage allows you to specify the language of -# a document. You can then use content negotiation to give a browser a -# file in a language the user can understand. -# -# Specify a default language. This means that all data -# going out without a specific language tag (see below) will -# be marked with this one. You probably do NOT want to set -# this unless you are sure it is correct for all cases. -# -# * It is generally better to not mark a page as -# * being a certain language than marking it with the wrong -# * language! -# -# DefaultLanguage nl -# -# Note 1: The suffix does not have to be the same as the language -# keyword --- those with documents in Polish (whose net-standard -# language code is pl) may wish to use "AddLanguage pl .po" to -# avoid the ambiguity with the common suffix for perl scripts. -# -# Note 2: The example entries below illustrate that in some cases -# the two character 'Language' abbreviation is not identical to -# the two character 'Country' code for its country, -# E.g. 'Danmark/dk' versus 'Danish/da'. -# -# Note 3: In the case of 'ltz' we violate the RFC by using a three char -# specifier. There is 'work in progress' to fix this and get -# the reference data for rfc1766 cleaned up. -# -# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) -# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) -# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) -# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) -# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) -# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) -# -AddLanguage ca .ca -AddLanguage cs .cz .cs -AddLanguage da .dk -AddLanguage de .de -AddLanguage el .el -AddLanguage en .en -AddLanguage eo .eo -AddLanguage es .es -AddLanguage et .et -AddLanguage fr .fr -AddLanguage he .he -AddLanguage hr .hr -AddLanguage it .it -AddLanguage ja .ja -AddLanguage ko .ko -AddLanguage ltz .ltz -AddLanguage nl .nl -AddLanguage nn .nn -AddLanguage no .no -AddLanguage pl .po -AddLanguage pt .pt -AddLanguage pt-BR .pt-br -AddLanguage ru .ru -AddLanguage sv .sv -AddLanguage zh-CN .zh-cn -AddLanguage zh-TW .zh-tw - -# -# LanguagePriority allows you to give precedence to some languages -# in case of a tie during content negotiation. -# -# Just list the languages in decreasing order of preference. We have -# more or less alphabetized them here. You probably want to change this. -# -LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW - -# -# ForceLanguagePriority allows you to serve a result page rather than -# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) -# [in case no accepted languages matched the available variants] -# -ForceLanguagePriority Prefer Fallback - -# -# Specify a default charset for all content served; this enables -# interpretation of all content as UTF-8 by default. To use the -# default browser choice (ISO-8859-1), or to allow the META tags -# in HTML content to override this choice, comment out this -# directive: -# -AddDefaultCharset UTF-8 - -# -# AddType allows you to add to or override the MIME configuration -# file mime.types for specific file types. -# -#AddType application/x-tar .tgz - -# -# AddEncoding allows you to have certain browsers uncompress -# information on the fly. Note: Not all browsers support this. -# Despite the name similarity, the following Add* directives have nothing -# to do with the FancyIndexing customization directives above. -# -#AddEncoding x-compress .Z -#AddEncoding x-gzip .gz .tgz - -# If the AddEncoding directives above are commented-out, then you -# probably should define those extensions to indicate media types: -# -AddType application/x-compress .Z -AddType application/x-gzip .gz .tgz - -# -# MIME-types for downloading Certificates and CRLs -# -AddType application/x-x509-ca-cert .crt -AddType application/x-pkcs7-crl .crl - -# -# AddHandler allows you to map certain file extensions to "handlers": -# actions unrelated to filetype. These can be either built into the server -# or added with the Action directive (see below) -# -# To use CGI scripts outside of ScriptAliased directories: -# (You will also need to add "ExecCGI" to the "Options" directive.) -# -#AddHandler cgi-script .cgi - -# -# For files that include their own HTTP headers: -# -#AddHandler send-as-is asis - -# -# For type maps (negotiated resources): -# (This is enabled by default to allow the Apache "It Worked" page -# to be distributed in multiple languages.) -# -AddHandler type-map var - -# -# Filters allow you to process content before it is sent to the client. -# -# To parse .shtml files for server-side includes (SSI): -# (You will also need to add "Includes" to the "Options" directive.) -# -AddType text/html .shtml -AddOutputFilter INCLUDES .shtml - -# -# Action lets you define media types that will execute a script whenever -# a matching file is called. This eliminates the need for repeated URL -# pathnames for oft-used CGI file processors. -# Format: Action media/type /cgi-script/location -# Format: Action handler-name /cgi-script/location -# - -# -# Customizable error responses come in three flavors: -# 1) plain text 2) local redirects 3) external redirects -# -# Some examples: -#ErrorDocument 500 "The server made a boo boo." -#ErrorDocument 404 /missing.html -#ErrorDocument 404 "/cgi-bin/missing_handler.pl" -#ErrorDocument 402 http://www.example.com/subscription_info.html -# - -# -# Putting this all together, we can internationalize error responses. -# -# We use Alias to redirect any /error/HTTP_.html.var response to -# our collection of by-error message multi-language collections. We use -# includes to substitute the appropriate text. -# -# You can modify the messages' appearance without changing any of the -# default HTTP_.html.var files by adding the line: -# -# Alias /error/include/ "/your/include/path/" -# -# which allows you to create your own set of files by starting with the -# /var/www/error/include/ files and -# copying them to /your/include/path/, even on a per-VirtualHost basis. -# - -Alias /error/ "/var/www/error/" - - - - - AllowOverride None - Options IncludesNoExec - AddOutputFilter Includes html - AddHandler type-map var - Order allow,deny - Allow from all - LanguagePriority en es de fr - ForceLanguagePriority Prefer Fallback - - -# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var -# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var -# ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var -# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var -# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var -# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var -# ErrorDocument 410 /error/HTTP_GONE.html.var -# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var -# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var -# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var -# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var -# ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var -# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var -# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var -# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var -# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var -# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var - - - - -# -# The following directives modify normal HTTP response behavior to -# handle known problems with browser implementations. -# -BrowserMatch "Mozilla/2" nokeepalive -BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 -BrowserMatch "RealPlayer 4\.0" force-response-1.0 -BrowserMatch "Java/1\.0" force-response-1.0 -BrowserMatch "JDK/1\.0" force-response-1.0 - -# -# The following directive disables redirects on non-GET requests for -# a directory that does not include the trailing slash. This fixes a -# problem with Microsoft WebFolders which does not appropriately handle -# redirects for folders with DAV methods. -# Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. -# -BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully -BrowserMatch "MS FrontPage" redirect-carefully -BrowserMatch "^WebDrive" redirect-carefully -BrowserMatch "^WebDAVFS/1.[0123]" redirect-carefully -BrowserMatch "^gnome-vfs/1.0" redirect-carefully -BrowserMatch "^XML Spy" redirect-carefully -BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully - -# -# Allow server status reports generated by mod_status, -# with the URL of http://servername/server-status -# Change the ".example.com" to match your domain to enable. -# -# -# SetHandler server-status -# Order deny,allow -# Deny from all -# Allow from .example.com -# - -# -# Allow remote server configuration reports, with the URL of -# http://servername/server-info (requires that mod_info.c be loaded). -# Change the ".example.com" to match your domain to enable. -# -# -# SetHandler server-info -# Order deny,allow -# Deny from all -# Allow from .example.com -# - -# -# Proxy Server directives. Uncomment the following lines to -# enable the proxy server: -# -# -#ProxyRequests On -# -# -# Order deny,allow -# Deny from all -# Allow from .example.com -# - -# -# Enable/disable the handling of HTTP/1.1 "Via:" headers. -# ("Full" adds the server version; "Block" removes all outgoing Via: headers) -# Set to one of: Off | On | Full | Block -# -#ProxyVia On - -# -# To enable a cache of proxied content, uncomment the following lines. -# See http://httpd.apache.org/docs/2.2/mod/mod_cache.html for more details. -# -# -# CacheEnable disk / -# CacheRoot "/var/cache/mod_proxy" -# -# - -# -# End of proxy directives. - -### Section 3: Virtual Hosts -# -# VirtualHost: If you want to maintain multiple domains/hostnames on your -# machine you can setup VirtualHost containers for them. Most configurations -# use only name-based virtual hosts so the server doesn't need to worry about -# IP addresses. This is indicated by the asterisks in the directives below. -# -# Please see the documentation at -# -# for further details before you try to setup virtual hosts. -# -# You may use the command line option '-S' to verify your virtual host -# configuration. - -# -# Use name-based virtual hosting. -# -#NameVirtualHost *:80 -# -# NOTE: NameVirtualHost cannot be used without a port specifier -# (e.g. :80) if mod_ssl is being used, due to the nature of the -# SSL protocol. -# - -# -# VirtualHost example: -# Almost any Apache directive may go into a VirtualHost container. -# The first VirtualHost section is used for requests without a known -# server name. -# -# -# ServerAdmin webmaster@dummy-host.example.com -# DocumentRoot /www/docs/dummy-host.example.com -# ServerName dummy-host.example.com -# ErrorLog logs/dummy-host.example.com-error_log -# CustomLog logs/dummy-host.example.com-access_log common -# diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README deleted file mode 100644 index f5e96615a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README +++ /dev/null @@ -1,9 +0,0 @@ - -This directory holds configuration files for the Apache HTTP Server; -any files in this directory which have the ".conf" extension will be -processed as httpd configuration files. The directory is used in -addition to the directory /etc/httpd/conf.modules.d/, which contains -configuration files necessary to load modules. - -Files are processed in alphabetical order. - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf deleted file mode 100644 index a85cf5dca..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf +++ /dev/null @@ -1,94 +0,0 @@ -# -# Directives controlling the display of server-generated directory listings. -# -# Required modules: mod_authz_core, mod_authz_host, -# mod_autoindex, mod_alias -# -# To see the listing of a directory, the Options directive for the -# directory must include "Indexes", and the directory must not contain -# a file matching those listed in the DirectoryIndex directive. -# - -# -# IndexOptions: Controls the appearance of server-generated directory -# listings. -# -IndexOptions FancyIndexing HTMLTable VersionSort - -# We include the /icons/ alias for FancyIndexed directory listings. If -# you do not use FancyIndexing, you may comment this out. -# -Alias /icons/ "/usr/share/httpd/icons/" - - - Options Indexes MultiViews FollowSymlinks - AllowOverride None - Require all granted - - -# -# AddIcon* directives tell the server which icon to show for different -# files or filename extensions. These are only displayed for -# FancyIndexed directories. -# -AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip - -AddIconByType (TXT,/icons/text.gif) text/* -AddIconByType (IMG,/icons/image2.gif) image/* -AddIconByType (SND,/icons/sound2.gif) audio/* -AddIconByType (VID,/icons/movie.gif) video/* - -AddIcon /icons/binary.gif .bin .exe -AddIcon /icons/binhex.gif .hqx -AddIcon /icons/tar.gif .tar -AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv -AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip -AddIcon /icons/a.gif .ps .ai .eps -AddIcon /icons/layout.gif .html .shtml .htm .pdf -AddIcon /icons/text.gif .txt -AddIcon /icons/c.gif .c -AddIcon /icons/p.gif .pl .py -AddIcon /icons/f.gif .for -AddIcon /icons/dvi.gif .dvi -AddIcon /icons/uuencoded.gif .uu -AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl -AddIcon /icons/tex.gif .tex -AddIcon /icons/bomb.gif /core -AddIcon /icons/bomb.gif */core.* - -AddIcon /icons/back.gif .. -AddIcon /icons/hand.right.gif README -AddIcon /icons/folder.gif ^^DIRECTORY^^ -AddIcon /icons/blank.gif ^^BLANKICON^^ - -# -# DefaultIcon is which icon to show for files which do not have an icon -# explicitly set. -# -DefaultIcon /icons/unknown.gif - -# -# AddDescription allows you to place a short description after a file in -# server-generated indexes. These are only displayed for FancyIndexed -# directories. -# Format: AddDescription "description" filename -# -#AddDescription "GZIP compressed document" .gz -#AddDescription "tar archive" .tar -#AddDescription "GZIP compressed tar archive" .tgz - -# -# ReadmeName is the name of the README file the server will look for by -# default, and append to directory listings. -# -# HeaderName is the name of a file which should be prepended to -# directory indexes. -ReadmeName README.html -HeaderName HEADER.html - -# -# IndexIgnore is a set of filenames which directory indexing should ignore -# and not include in the listing. Shell-style wildcarding is permitted. -# -IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf deleted file mode 100644 index de7ac2777..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf +++ /dev/null @@ -1,7 +0,0 @@ - - ServerName centos.example.com - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf deleted file mode 100644 index 6e2502e9a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf +++ /dev/null @@ -1,211 +0,0 @@ -# -# When we also provide SSL we have to listen to the -# the HTTPS port in addition. -# -Listen 443 https - -## -## SSL Global Context -## -## All SSL configuration in this context applies both to -## the main server and all SSL-enabled virtual hosts. -## - -# Pass Phrase Dialog: -# Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal -# terminal dialog) has to provide the pass phrase on stdout. -SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog - -# Inter-Process Session Cache: -# Configure the SSL Session Cache: First the mechanism -# to use and second the expiring timeout (in seconds). -SSLSessionCache shmcb:/run/httpd/sslcache(512000) -SSLSessionCacheTimeout 300 - -# Pseudo Random Number Generator (PRNG): -# Configure one or more sources to seed the PRNG of the -# SSL library. The seed data should be of good random quality. -# WARNING! On some platforms /dev/random blocks if not enough entropy -# is available. This means you then cannot use the /dev/random device -# because it would lead to very long connection times (as long as -# it requires to make more entropy available). But usually those -# platforms additionally provide a /dev/urandom device which doesn't -# block. So, if available, use this one instead. Read the mod_ssl User -# Manual for more details. -SSLRandomSeed startup file:/dev/urandom 256 -SSLRandomSeed connect builtin -#SSLRandomSeed startup file:/dev/random 512 -#SSLRandomSeed connect file:/dev/random 512 -#SSLRandomSeed connect file:/dev/urandom 512 - -# -# Use "SSLCryptoDevice" to enable any supported hardware -# accelerators. Use "openssl engine -v" to list supported -# engine names. NOTE: If you enable an accelerator and the -# server does not start, consult the error logs and ensure -# your accelerator is functioning properly. -# -SSLCryptoDevice builtin -#SSLCryptoDevice ubsec - -## -## SSL Virtual Host Context -## - - - -# General setup for the virtual host, inherited from global configuration -#DocumentRoot "/var/www/html" -#ServerName www.example.com:443 - -# Use separate log files for the SSL virtual host; note that LogLevel -# is not inherited from httpd.conf. -ErrorLog logs/ssl_error_log -TransferLog logs/ssl_access_log -LogLevel warn - -# SSL Engine Switch: -# Enable/Disable SSL for this virtual host. -SSLEngine on - -# SSL Protocol support: -# List the enable protocol levels with which clients will be able to -# connect. Disable SSLv2 access by default: -SSLProtocol all -SSLv2 - -# SSL Cipher Suite: -# List the ciphers that the client is permitted to negotiate. -# See the mod_ssl documentation for a complete list. -SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA - -# Speed-optimized SSL Cipher configuration: -# If speed is your main concern (on busy HTTPS servers e.g.), -# you might want to force clients to specific, performance -# optimized ciphers. In this case, prepend those ciphers -# to the SSLCipherSuite list, and enable SSLHonorCipherOrder. -# Caveat: by giving precedence to RC4-SHA and AES128-SHA -# (as in the example below), most connections will no longer -# have perfect forward secrecy - if the server's key is -# compromised, captures of past or future traffic must be -# considered compromised, too. -#SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 -#SSLHonorCipherOrder on - -# Server Certificate: -# Point SSLCertificateFile at a PEM encoded certificate. If -# the certificate is encrypted, then you will be prompted for a -# pass phrase. Note that a kill -HUP will prompt again. A new -# certificate can be generated using the genkey(1) command. - -# Server Private Key: -# If the key is not combined with the certificate, use this -# directive to point at the key file. Keep in mind that if -# you've both a RSA and a DSA private key you can configure -# both in parallel (to also allow the use of DSA ciphers, etc.) - -# Server Certificate Chain: -# Point SSLCertificateChainFile at a file containing the -# concatenation of PEM encoded CA certificates which form the -# certificate chain for the server certificate. Alternatively -# the referenced file can be the same as SSLCertificateFile -# when the CA certificates are directly appended to the server -# certificate for convinience. -#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt - -# Certificate Authority (CA): -# Set the CA certificate verification path where to find CA -# certificates for client authentication or alternatively one -# huge file containing all of them (file must be PEM encoded) -#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt - -# Client Authentication (Type): -# Client certificate verification type and depth. Types are -# none, optional, require and optional_no_ca. Depth is a -# number which specifies how deeply to verify the certificate -# issuer chain before deciding the certificate is not valid. -#SSLVerifyClient require -#SSLVerifyDepth 10 - -# Access Control: -# With SSLRequire you can do per-directory access control based -# on arbitrary complex boolean expressions containing server -# variable checks and other lookup directives. The syntax is a -# mixture between C and Perl. See the mod_ssl documentation -# for more details. -# -#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ -# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ -# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ -# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ -# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ -# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ -# - -# SSL Engine Options: -# Set various options for the SSL engine. -# o FakeBasicAuth: -# Translate the client X.509 into a Basic Authorisation. This means that -# the standard Auth/DBMAuth methods can be used for access control. The -# user name is the `one line' version of the client's X.509 certificate. -# Note that no password is obtained from the user. Every entry in the user -# file needs this password: `xxj31ZMTZzkVA'. -# o ExportCertData: -# This exports two additional environment variables: SSL_CLIENT_CERT and -# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the -# server (always existing) and the client (only existing when client -# authentication is used). This can be used to import the certificates -# into CGI scripts. -# o StdEnvVars: -# This exports the standard SSL/TLS related `SSL_*' environment variables. -# Per default this exportation is switched off for performance reasons, -# because the extraction step is an expensive operation and is usually -# useless for serving static content. So one usually enables the -# exportation for CGI and SSI requests only. -# o StrictRequire: -# This denies access when "SSLRequireSSL" or "SSLRequire" applied even -# under a "Satisfy any" situation, i.e. when it applies access is denied -# and no other module can change it. -# o OptRenegotiate: -# This enables optimized SSL connection renegotiation handling when SSL -# directives are used in per-directory context. -#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - -# SSL Protocol Adjustments: -# The safe and default but still SSL/TLS standard compliant shutdown -# approach is that mod_ssl sends the close notify alert but doesn't wait for -# the close notify alert from client. When you need a different shutdown -# approach you can use one of the following variables: -# o ssl-unclean-shutdown: -# This forces an unclean shutdown when the connection is closed, i.e. no -# SSL close notify alert is send or allowed to received. This violates -# the SSL/TLS standard but is needed for some brain-dead browsers. Use -# this when you receive I/O errors because of the standard approach where -# mod_ssl sends the close notify alert. -# o ssl-accurate-shutdown: -# This forces an accurate shutdown when the connection is closed, i.e. a -# SSL close notify alert is send and mod_ssl waits for the close notify -# alert of the client. This is 100% SSL/TLS standard compliant, but in -# practice often causes hanging connections with brain-dead browsers. Use -# this only for browsers where you know that their SSL implementation -# works correctly. -# Notice: Most problems of broken clients are also related to the HTTP -# keep-alive facility, so you usually additionally want to disable -# keep-alive for those clients, too. Use variable "nokeepalive" for this. -# Similarly, one has to force some clients to use HTTP/1.0 to workaround -# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and -# "force-response-1.0" for this. -BrowserMatch "MSIE [2-5]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0 - -# Per-Server Logging: -# The home of a custom SSL log file. Use this when you want a -# compact non-error SSL logfile on a virtual host basis. -CustomLog logs/ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" - - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf deleted file mode 100644 index b5d7a49ef..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf +++ /dev/null @@ -1,36 +0,0 @@ -# -# UserDir: The name of the directory that is appended onto a user's home -# directory if a ~user request is received. -# -# The path to the end user account 'public_html' directory must be -# accessible to the webserver userid. This usually means that ~userid -# must have permissions of 711, ~userid/public_html must have permissions -# of 755, and documents contained therein must be world-readable. -# Otherwise, the client will only receive a "403 Forbidden" message. -# - - # - # UserDir is disabled by default since it can confirm the presence - # of a username on the system (depending on home directory - # permissions). - # - UserDir disabled - - # - # To enable requests to /~user/ to serve the user's public_html - # directory, remove the "UserDir disabled" line above, and uncomment - # the following line instead: - # - #UserDir public_html - - -# -# Control access to UserDir directories. The following is an example -# for a site where these directories are restricted to read-only. -# - - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - Require method GET POST OPTIONS - - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf deleted file mode 100644 index c1b6c11d9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf +++ /dev/null @@ -1,22 +0,0 @@ -# -# This configuration file enables the default "Welcome" page if there -# is no default index page present for the root URL. To disable the -# Welcome page, comment out all the lines below. -# -# NOTE: if this file is removed, it will be restored on upgrades. -# - - Options -Indexes - ErrorDocument 403 /.noindex.html - - - - AllowOverride None - Require all granted - - -Alias /.noindex.html /usr/share/httpd/noindex/index.html -Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css -Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css -Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif -Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf deleted file mode 100644 index 31d979f20..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf +++ /dev/null @@ -1,77 +0,0 @@ -# -# This file loads most of the modules included with the Apache HTTP -# Server itself. -# - -LoadModule access_compat_module modules/mod_access_compat.so -LoadModule actions_module modules/mod_actions.so -LoadModule alias_module modules/mod_alias.so -LoadModule allowmethods_module modules/mod_allowmethods.so -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule auth_digest_module modules/mod_auth_digest.so -LoadModule authn_anon_module modules/mod_authn_anon.so -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authn_dbd_module modules/mod_authn_dbd.so -LoadModule authn_dbm_module modules/mod_authn_dbm.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authn_socache_module modules/mod_authn_socache.so -LoadModule authz_core_module modules/mod_authz_core.so -LoadModule authz_dbd_module modules/mod_authz_dbd.so -LoadModule authz_dbm_module modules/mod_authz_dbm.so -LoadModule authz_groupfile_module modules/mod_authz_groupfile.so -LoadModule authz_host_module modules/mod_authz_host.so -LoadModule authz_owner_module modules/mod_authz_owner.so -LoadModule authz_user_module modules/mod_authz_user.so -LoadModule autoindex_module modules/mod_autoindex.so -LoadModule cache_module modules/mod_cache.so -LoadModule cache_disk_module modules/mod_cache_disk.so -LoadModule data_module modules/mod_data.so -LoadModule dbd_module modules/mod_dbd.so -LoadModule deflate_module modules/mod_deflate.so -LoadModule dir_module modules/mod_dir.so -LoadModule dumpio_module modules/mod_dumpio.so -LoadModule echo_module modules/mod_echo.so -LoadModule env_module modules/mod_env.so -LoadModule expires_module modules/mod_expires.so -LoadModule ext_filter_module modules/mod_ext_filter.so -LoadModule filter_module modules/mod_filter.so -LoadModule headers_module modules/mod_headers.so -LoadModule include_module modules/mod_include.so -LoadModule info_module modules/mod_info.so -LoadModule log_config_module modules/mod_log_config.so -LoadModule logio_module modules/mod_logio.so -LoadModule mime_magic_module modules/mod_mime_magic.so -LoadModule mime_module modules/mod_mime.so -LoadModule negotiation_module modules/mod_negotiation.so -LoadModule remoteip_module modules/mod_remoteip.so -LoadModule reqtimeout_module modules/mod_reqtimeout.so -LoadModule rewrite_module modules/mod_rewrite.so -LoadModule setenvif_module modules/mod_setenvif.so -LoadModule slotmem_plain_module modules/mod_slotmem_plain.so -LoadModule slotmem_shm_module modules/mod_slotmem_shm.so -LoadModule socache_dbm_module modules/mod_socache_dbm.so -LoadModule socache_memcache_module modules/mod_socache_memcache.so -LoadModule socache_shmcb_module modules/mod_socache_shmcb.so -LoadModule status_module modules/mod_status.so -LoadModule substitute_module modules/mod_substitute.so -LoadModule suexec_module modules/mod_suexec.so -LoadModule unique_id_module modules/mod_unique_id.so -LoadModule unixd_module modules/mod_unixd.so -LoadModule userdir_module modules/mod_userdir.so -LoadModule version_module modules/mod_version.so -LoadModule vhost_alias_module modules/mod_vhost_alias.so - -#LoadModule buffer_module modules/mod_buffer.so -#LoadModule watchdog_module modules/mod_watchdog.so -#LoadModule heartbeat_module modules/mod_heartbeat.so -#LoadModule heartmonitor_module modules/mod_heartmonitor.so -#LoadModule usertrack_module modules/mod_usertrack.so -#LoadModule dialup_module modules/mod_dialup.so -#LoadModule charset_lite_module modules/mod_charset_lite.so -#LoadModule log_debug_module modules/mod_log_debug.so -#LoadModule ratelimit_module modules/mod_ratelimit.so -#LoadModule reflector_module modules/mod_reflector.so -#LoadModule request_module modules/mod_request.so -#LoadModule sed_module modules/mod_sed.so -#LoadModule speling_module modules/mod_speling.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf deleted file mode 100644 index e6af8decd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf +++ /dev/null @@ -1,3 +0,0 @@ -LoadModule dav_module modules/mod_dav.so -LoadModule dav_fs_module modules/mod_dav_fs.so -LoadModule dav_lock_module modules/mod_dav_lock.so diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf deleted file mode 100644 index 9e0d0db6e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf +++ /dev/null @@ -1 +0,0 @@ -LoadModule lua_module modules/mod_lua.so diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf deleted file mode 100644 index 7bfd1d413..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf +++ /dev/null @@ -1,19 +0,0 @@ -# Select the MPM module which should be used by uncommenting exactly -# one of the following LoadModule lines: - -# prefork MPM: Implements a non-threaded, pre-forking web server -# See: http://httpd.apache.org/docs/2.4/mod/prefork.html -LoadModule mpm_prefork_module modules/mod_mpm_prefork.so - -# worker MPM: Multi-Processing Module implementing a hybrid -# multi-threaded multi-process web server -# See: http://httpd.apache.org/docs/2.4/mod/worker.html -# -#LoadModule mpm_worker_module modules/mod_mpm_worker.so - -# event MPM: A variant of the worker MPM with the goal of consuming -# threads only for connections with active processing -# See: http://httpd.apache.org/docs/2.4/mod/event.html -# -#LoadModule mpm_event_module modules/mod_mpm_event.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf deleted file mode 100644 index cc0bca077..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf +++ /dev/null @@ -1,16 +0,0 @@ -# This file configures all the proxy modules: -LoadModule proxy_module modules/mod_proxy.so -LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so -LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so -LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so -LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so -LoadModule proxy_ajp_module modules/mod_proxy_ajp.so -LoadModule proxy_balancer_module modules/mod_proxy_balancer.so -LoadModule proxy_connect_module modules/mod_proxy_connect.so -LoadModule proxy_express_module modules/mod_proxy_express.so -LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so -LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so -LoadModule proxy_ftp_module modules/mod_proxy_ftp.so -LoadModule proxy_http_module modules/mod_proxy_http.so -LoadModule proxy_scgi_module modules/mod_proxy_scgi.so -LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf deleted file mode 100644 index 53235cd76..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf +++ /dev/null @@ -1 +0,0 @@ -LoadModule ssl_module modules/mod_ssl.so diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf deleted file mode 100644 index b208c972d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf +++ /dev/null @@ -1,2 +0,0 @@ -# This file configures systemd module: -LoadModule systemd_module modules/mod_systemd.so diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf deleted file mode 100644 index 5b8b9362e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This configuration file loads a CGI module appropriate to the MPM -# which has been configured in 00-mpm.conf. mod_cgid should be used -# with a threaded MPM; mod_cgi with the prefork MPM. - - - LoadModule cgid_module modules/mod_cgid.so - - - LoadModule cgid_module modules/mod_cgid.so - - - LoadModule cgi_module modules/mod_cgi.so - - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf deleted file mode 100644 index a7af0dc1e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf +++ /dev/null @@ -1,353 +0,0 @@ -# -# This is the main Apache HTTP server configuration file. It contains the -# configuration directives that give the server its instructions. -# See for detailed information. -# In particular, see -# -# for a discussion of each configuration directive. -# -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. -# -# Configuration and logfile names: If the filenames you specify for many -# of the server's control files begin with "/" (or "drive:/" for Win32), the -# server will use that explicit path. If the filenames do *not* begin -# with "/", the value of ServerRoot is prepended -- so 'log/access_log' -# with ServerRoot set to '/www' will be interpreted by the -# server as '/www/log/access_log', where as '/log/access_log' will be -# interpreted as '/log/access_log'. - -# -# ServerRoot: The top of the directory tree under which the server's -# configuration, error, and log files are kept. -# -# Do not add a slash at the end of the directory path. If you point -# ServerRoot at a non-local disk, be sure to specify a local disk on the -# Mutex directive, if file-based mutexes are used. If you wish to share the -# same ServerRoot for multiple httpd daemons, you will need to change at -# least PidFile. -# -ServerRoot "/etc/httpd" - -# -# Listen: Allows you to bind Apache to specific IP addresses and/or -# ports, instead of the default. See also the -# directive. -# -# Change this to Listen on specific IP addresses as shown below to -# prevent Apache from glomming onto all bound IP addresses. -# -#Listen 12.34.56.78:80 -Listen 80 - -# -# Dynamic Shared Object (DSO) Support -# -# To be able to use the functionality of a module which was built as a DSO you -# have to place corresponding `LoadModule' lines at this location so the -# directives contained in it are actually available _before_ they are used. -# Statically compiled modules (those listed by `httpd -l') do not need -# to be loaded here. -# -# Example: -# LoadModule foo_module modules/mod_foo.so -# -Include conf.modules.d/*.conf - -# -# If you wish httpd to run as a different user or group, you must run -# httpd as root initially and it will switch. -# -# User/Group: The name (or #number) of the user/group to run httpd as. -# It is usually good practice to create a dedicated user and group for -# running httpd, as with most system services. -# -User apache -Group apache - -# 'Main' server configuration -# -# The directives in this section set up the values used by the 'main' -# server, which responds to any requests that aren't handled by a -# definition. These values also provide defaults for -# any containers you may define later in the file. -# -# All of these directives may appear inside containers, -# in which case these default settings will be overridden for the -# virtual host being defined. -# - -# -# ServerAdmin: Your address, where problems with the server should be -# e-mailed. This address appears on some server-generated pages, such -# as error documents. e.g. admin@your-domain.com -# -ServerAdmin root@localhost - -# -# ServerName gives the name and port that the server uses to identify itself. -# This can often be determined automatically, but we recommend you specify -# it explicitly to prevent problems during startup. -# -# If your host doesn't have a registered DNS name, enter its IP address here. -# -#ServerName www.example.com:80 - -# -# Deny access to the entirety of your server's filesystem. You must -# explicitly permit access to web content directories in other -# blocks below. -# - - AllowOverride none - Require all denied - - -# -# Note that from this point forward you must specifically allow -# particular features to be enabled - so if something's not working as -# you might expect, make sure that you have specifically enabled it -# below. -# - -# -# DocumentRoot: The directory out of which you will serve your -# documents. By default, all requests are taken from this directory, but -# symbolic links and aliases may be used to point to other locations. -# -DocumentRoot "/var/www/html" - -# -# Relax access to content within /var/www. -# - - AllowOverride None - # Allow open access: - Require all granted - - -# Further relax access to the default document root: - - # - # Possible values for the Options directive are "None", "All", - # or any combination of: - # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews - # - # Note that "MultiViews" must be named *explicitly* --- "Options All" - # doesn't give it to you. - # - # The Options directive is both complicated and important. Please see - # http://httpd.apache.org/docs/2.4/mod/core.html#options - # for more information. - # - Options Indexes FollowSymLinks - - # - # AllowOverride controls what directives may be placed in .htaccess files. - # It can be "All", "None", or any combination of the keywords: - # Options FileInfo AuthConfig Limit - # - AllowOverride None - - # - # Controls who can get stuff from this server. - # - Require all granted - - -# -# DirectoryIndex: sets the file that Apache will serve if a directory -# is requested. -# - - DirectoryIndex index.html - - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - -# -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog "logs/error_log" - -# -# LogLevel: Control the number of messages logged to the error_log. -# Possible values include: debug, info, notice, warn, error, crit, -# alert, emerg. -# -LogLevel warn - - - # - # The following directives define some format nicknames for use with - # a CustomLog directive (see below). - # - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined - LogFormat "%h %l %u %t \"%r\" %>s %b" common - - - # You need to enable mod_logio.c to use %I and %O - LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio - - - # - # The location and format of the access logfile (Common Logfile Format). - # If you do not define any access logfiles within a - # container, they will be logged here. Contrariwise, if you *do* - # define per- access logfiles, transactions will be - # logged therein and *not* in this file. - # - #CustomLog "logs/access_log" common - - # - # If you prefer a logfile with access, agent, and referer information - # (Combined Logfile Format) you can use the following directive. - # - CustomLog "logs/access_log" combined - - - - # - # Redirect: Allows you to tell clients about documents that used to - # exist in your server's namespace, but do not anymore. The client - # will make a new request for the document at its new location. - # Example: - # Redirect permanent /foo http://www.example.com/bar - - # - # Alias: Maps web paths into filesystem paths and is used to - # access content that does not live under the DocumentRoot. - # Example: - # Alias /webpath /full/filesystem/path - # - # If you include a trailing / on /webpath then the server will - # require it to be present in the URL. You will also likely - # need to provide a section to allow access to - # the filesystem path. - - # - # ScriptAlias: This controls which directories contain server scripts. - # ScriptAliases are essentially the same as Aliases, except that - # documents in the target directory are treated as applications and - # run by the server when requested rather than as documents sent to the - # client. The same rules about trailing "/" apply to ScriptAlias - # directives as to Alias. - # - ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" - - - -# -# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased -# CGI directory exists, if you have that configured. -# - - AllowOverride None - Options None - Require all granted - - - - # - # TypesConfig points to the file containing the list of mappings from - # filename extension to MIME-type. - # - TypesConfig /etc/mime.types - - # - # AddType allows you to add to or override the MIME configuration - # file specified in TypesConfig for specific file types. - # - #AddType application/x-gzip .tgz - # - # AddEncoding allows you to have certain browsers uncompress - # information on the fly. Note: Not all browsers support this. - # - #AddEncoding x-compress .Z - #AddEncoding x-gzip .gz .tgz - # - # If the AddEncoding directives above are commented-out, then you - # probably should define those extensions to indicate media types: - # - AddType application/x-compress .Z - AddType application/x-gzip .gz .tgz - - # - # AddHandler allows you to map certain file extensions to "handlers": - # actions unrelated to filetype. These can be either built into the server - # or added with the Action directive (see below) - # - # To use CGI scripts outside of ScriptAliased directories: - # (You will also need to add "ExecCGI" to the "Options" directive.) - # - #AddHandler cgi-script .cgi - - # For type maps (negotiated resources): - #AddHandler type-map var - - # - # Filters allow you to process content before it is sent to the client. - # - # To parse .shtml files for server-side includes (SSI): - # (You will also need to add "Includes" to the "Options" directive.) - # - AddType text/html .shtml - AddOutputFilter INCLUDES .shtml - - -# -# Specify a default charset for all content served; this enables -# interpretation of all content as UTF-8 by default. To use the -# default browser choice (ISO-8859-1), or to allow the META tags -# in HTML content to override this choice, comment out this -# directive: -# -AddDefaultCharset UTF-8 - - - # - # The mod_mime_magic module allows the server to use various hints from the - # contents of the file itself to determine its type. The MIMEMagicFile - # directive tells the module where the hint definitions are located. - # - MIMEMagicFile conf/magic - - -# -# Customizable error responses come in three flavors: -# 1) plain text 2) local redirects 3) external redirects -# -# Some examples: -#ErrorDocument 500 "The server made a boo boo." -#ErrorDocument 404 /missing.html -#ErrorDocument 404 "/cgi-bin/missing_handler.pl" -#ErrorDocument 402 http://www.example.com/subscription_info.html -# - -# -# EnableMMAP and EnableSendfile: On systems that support it, -# memory-mapping or the sendfile syscall may be used to deliver -# files. This usually improves server performance, but must -# be turned off when serving from networked-mounted -# filesystems or if support for these functions is otherwise -# broken on your system. -# Defaults if commented: EnableMMAP On, EnableSendfile Off -# -#EnableMMAP off -EnableSendfile on - -# Supplemental configuration -# -# Load config files in the "/etc/httpd/conf.d" directory, if any. -IncludeOptional conf.d/*.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic deleted file mode 100644 index 7c56119e9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic +++ /dev/null @@ -1,385 +0,0 @@ -# Magic data for mod_mime_magic Apache module (originally for file(1) command) -# The module is described in /manual/mod/mod_mime_magic.html -# -# The format is 4-5 columns: -# Column #1: byte number to begin checking from, ">" indicates continuation -# Column #2: type of data to match -# Column #3: contents of data to match -# Column #4: MIME type of result -# Column #5: MIME encoding of result (optional) - -#------------------------------------------------------------------------------ -# Localstuff: file(1) magic for locally observed files -# Add any locally observed files here. - -#------------------------------------------------------------------------------ -# end local stuff -#------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------ -# Java - -0 short 0xcafe ->2 short 0xbabe application/java - -#------------------------------------------------------------------------------ -# audio: file(1) magic for sound formats -# -# from Jan Nicolai Langfeldt , -# - -# Sun/NeXT audio data -0 string .snd ->12 belong 1 audio/basic ->12 belong 2 audio/basic ->12 belong 3 audio/basic ->12 belong 4 audio/basic ->12 belong 5 audio/basic ->12 belong 6 audio/basic ->12 belong 7 audio/basic - ->12 belong 23 audio/x-adpcm - -# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format -# that uses little-endian encoding and has a different magic number -# (0x0064732E in little-endian encoding). -0 lelong 0x0064732E ->12 lelong 1 audio/x-dec-basic ->12 lelong 2 audio/x-dec-basic ->12 lelong 3 audio/x-dec-basic ->12 lelong 4 audio/x-dec-basic ->12 lelong 5 audio/x-dec-basic ->12 lelong 6 audio/x-dec-basic ->12 lelong 7 audio/x-dec-basic -# compressed (G.721 ADPCM) ->12 lelong 23 audio/x-dec-adpcm - -# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM" -# AIFF audio data -8 string AIFF audio/x-aiff -# AIFF-C audio data -8 string AIFC audio/x-aiff -# IFF/8SVX audio data -8 string 8SVX audio/x-aiff - -# Creative Labs AUDIO stuff -# Standard MIDI data -0 string MThd audio/unknown -#>9 byte >0 (format %d) -#>11 byte >1 using %d channels -# Creative Music (CMF) data -0 string CTMF audio/unknown -# SoundBlaster instrument data -0 string SBI audio/unknown -# Creative Labs voice data -0 string Creative\ Voice\ File audio/unknown -## is this next line right? it came this way... -#>19 byte 0x1A -#>23 byte >0 - version %d -#>22 byte >0 \b.%d - -# [GRR 950115: is this also Creative Labs? Guessing that first line -# should be string instead of unknown-endian long...] -#0 long 0x4e54524b MultiTrack sound data -#0 string NTRK MultiTrack sound data -#>4 long x - version %ld - -# Microsoft WAVE format (*.wav) -# [GRR 950115: probably all of the shorts and longs should be leshort/lelong] -# Microsoft RIFF -0 string RIFF audio/unknown -# - WAVE format ->8 string WAVE audio/x-wav -# MPEG audio. -0 beshort&0xfff0 0xfff0 audio/mpeg -# C64 SID Music files, from Linus Walleij -0 string PSID audio/prs.sid - -#------------------------------------------------------------------------------ -# c-lang: file(1) magic for C programs or various scripts -# - -# XPM icons (Greg Roelofs, newt@uchicago.edu) -# ideally should go into "images", but entries below would tag XPM as C source -0 string /*\ XPM image/x-xbm 7bit - -# this first will upset you if you're a PL/1 shop... (are there any left?) -# in which case rm it; ascmagic will catch real C programs -# C or REXX program text -0 string /* text/plain -# C++ program text -0 string // text/plain - -#------------------------------------------------------------------------------ -# compress: file(1) magic for pure-compression formats (no archives) -# -# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc. -# -# Formats for various forms of compressed data -# Formats for "compress" proper have been moved into "compress.c", -# because it tries to uncompress it to figure out what's inside. - -# standard unix compress -0 string \037\235 application/octet-stream x-compress - -# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver) -0 string \037\213 application/octet-stream x-gzip - -# According to gzip.h, this is the correct byte order for packed data. -0 string \037\036 application/octet-stream -# -# This magic number is byte-order-independent. -# -0 short 017437 application/octet-stream - -# XXX - why *two* entries for "compacted data", one of which is -# byte-order independent, and one of which is byte-order dependent? -# -# compacted data -0 short 0x1fff application/octet-stream -0 string \377\037 application/octet-stream -# huf output -0 short 0145405 application/octet-stream - -# Squeeze and Crunch... -# These numbers were gleaned from the Unix versions of the programs to -# handle these formats. Note that I can only uncrunch, not crunch, and -# I didn't have a crunched file handy, so the crunch number is untested. -# Keith Waclena -#0 leshort 0x76FF squeezed data (CP/M, DOS) -#0 leshort 0x76FE crunched data (CP/M, DOS) - -# Freeze -#0 string \037\237 Frozen file 2.1 -#0 string \037\236 Frozen file 1.0 (or gzip 0.5) - -# lzh? -#0 string \037\240 LZH compressed data - -#------------------------------------------------------------------------------ -# frame: file(1) magic for FrameMaker files -# -# This stuff came on a FrameMaker demo tape, most of which is -# copyright, but this file is "published" as witness the following: -# -0 string \ -# and Anna Shergold -# -0 string \ -0 string \14 byte 12 (OS/2 1.x format) -#>14 byte 64 (OS/2 2.x format) -#>14 byte 40 (Windows 3.x format) -#0 string IC icon -#0 string PI pointer -#0 string CI color icon -#0 string CP color pointer -#0 string BA bitmap array - -0 string \x89PNG image/png -0 string FWS application/x-shockwave-flash -0 string CWS application/x-shockwave-flash - -#------------------------------------------------------------------------------ -# lisp: file(1) magic for lisp programs -# -# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com) -0 string ;; text/plain 8bit -# Emacs 18 - this is always correct, but not very magical. -0 string \012( application/x-elc -# Emacs 19 -0 string ;ELC\023\000\000\000 application/x-elc - -#------------------------------------------------------------------------------ -# mail.news: file(1) magic for mail and news -# -# There are tests to ascmagic.c to cope with mail and news. -0 string Relay-Version: message/rfc822 7bit -0 string #!\ rnews message/rfc822 7bit -0 string N#!\ rnews message/rfc822 7bit -0 string Forward\ to message/rfc822 7bit -0 string Pipe\ to message/rfc822 7bit -0 string Return-Path: message/rfc822 7bit -0 string Path: message/news 8bit -0 string Xref: message/news 8bit -0 string From: message/rfc822 7bit -0 string Article message/news 8bit -#------------------------------------------------------------------------------ -# msword: file(1) magic for MS Word files -# -# Contributor claims: -# Reversed-engineered MS Word magic numbers -# - -0 string \376\067\0\043 application/msword -0 string \333\245-\0\0\0 application/msword - -# disable this one because it applies also to other -# Office/OLE documents for which msword is not correct. See PR#2608. -#0 string \320\317\021\340\241\261 application/msword - - - -#------------------------------------------------------------------------------ -# printer: file(1) magic for printer-formatted files -# - -# PostScript -0 string %! application/postscript -0 string \004%! application/postscript - -# Acrobat -# (due to clamen@cs.cmu.edu) -0 string %PDF- application/pdf - -#------------------------------------------------------------------------------ -# sc: file(1) magic for "sc" spreadsheet -# -38 string Spreadsheet application/x-sc - -#------------------------------------------------------------------------------ -# tex: file(1) magic for TeX files -# -# XXX - needs byte-endian stuff (big-endian and little-endian DVI?) -# -# From - -# Although we may know the offset of certain text fields in TeX DVI -# and font files, we can't use them reliably because they are not -# zero terminated. [but we do anyway, christos] -0 string \367\002 application/x-dvi -#0 string \367\203 TeX generic font data -#0 string \367\131 TeX packed font data -#0 string \367\312 TeX virtual font data -#0 string This\ is\ TeX, TeX transcript text -#0 string This\ is\ METAFONT, METAFONT transcript text - -# There is no way to detect TeX Font Metric (*.tfm) files without -# breaking them apart and reading the data. The following patterns -# match most *.tfm files generated by METAFONT or afm2tfm. -#2 string \000\021 TeX font metric data -#2 string \000\022 TeX font metric data -#>34 string >\0 (%s) - -# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com) -#0 string \\input\ texinfo Texinfo source text -#0 string This\ is\ Info\ file GNU Info text - -# correct TeX magic for Linux (and maybe more) -# from Peter Tobias (tobias@server.et-inf.fho-emden.de) -# -0 leshort 0x02f7 application/x-dvi - -# RTF - Rich Text Format -0 string {\\rtf application/rtf - -#------------------------------------------------------------------------------ -# animation: file(1) magic for animation/movie formats -# -# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8) -# MPEG file -0 string \000\000\001\263 video/mpeg -# -# The contributor claims: -# I couldn't find a real magic number for these, however, this -# -appears- to work. Note that it might catch other files, too, -# so BE CAREFUL! -# -# Note that title and author appear in the two 20-byte chunks -# at decimal offsets 2 and 22, respectively, but they are XOR'ed with -# 255 (hex FF)! DL format SUCKS BIG ROCKS. -# -# DL file version 1 , medium format (160x100, 4 images/screen) -0 byte 1 video/unknown -0 byte 2 video/unknown -# Quicktime video, from Linus Walleij -# from Apple quicktime file format documentation. -4 string moov video/quicktime -4 string mdat video/quicktime - diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites deleted file mode 100644 index 6af1f63fa..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites +++ /dev/null @@ -1 +0,0 @@ -conf.d/centos.example.com.conf, centos.example.com diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd b/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd deleted file mode 100644 index 4bcb300c2..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file can be used to set additional environment variables for -# the httpd process, or pass additional options to the httpd -# executable. -# -# Note: With previous versions of httpd, the MPM could be changed by -# editing an "HTTPD" variable here. With the current version, that -# variable is now ignored. The MPM is a loadable module, and the -# choice of MPM can be changed by editing the configuration file -# /etc/httpd/conf.modules.d/00-mpm.conf. -# - -# -# To pass additional options (for instance, -D definitions) to the -# httpd binary at startup, set OPTIONS here. -# -OPTIONS="-D mock_define -D mock_define_too -D mock_value=TRUE -DMOCK_NOSEP -DNOSEP_TWO=NOSEP_VAL" - -# -# This setting ensures the httpd process is started in the "C" locale -# by default. (Some modules will not behave correctly if -# case-sensitive string comparisons are performed in a different -# locale.) -# -LANG=C diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf b/certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf deleted file mode 100644 index 14cf95f9e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf +++ /dev/null @@ -1,55 +0,0 @@ -# Global configuration - -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -ErrorLog ${APACHE_LOG_DIR}/error.log - -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# Include generic snippets of statements -IncludeOptional conf-enabled/ - -# Include the virtual host configurations: -IncludeOptional sites-enabled/*.conf - -Define COMPLEX - -Define tls_port 1234 -Define tls_port_str "1234" - -Define fnmatch_filename test_fnmatch.conf - - -Include test_variables.conf -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf b/certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf deleted file mode 100644 index 1e5307780..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf +++ /dev/null @@ -1,9 +0,0 @@ -# 3 - one arg directives -# 2 - two arg directives -# 1 - three arg directives -TestArgsDirective one_arg -TestArgsDirective one_arg two_arg -TestArgsDirective one_arg -TestArgsDirective one_arg two_arg -TestArgsDirective one_arg two_arg three_arg -TestArgsDirective one_arg diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf b/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf deleted file mode 100644 index 4e6b84edf..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf +++ /dev/null @@ -1 +0,0 @@ -FNMATCH_DIRECTIVE Success diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf b/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf deleted file mode 100644 index 1a9edff74..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf +++ /dev/null @@ -1,66 +0,0 @@ -TestVariablePort ${tls_port} -TestVariablePortStr "${tls_port_str}" - -LoadModule status_module modules/mod_status.so - -# Basic IfDefine - - VAR_DIRECTIVE success - LoadModule ssl_module modules/mod_ssl.so - - - - INVALID_VAR_DIRECTIVE failure - - - - INVALID_VAR_DIRECTIVE failure - - - - VAR_DIRECTIVE failure - - - -# Basic IfModule - - MOD_DIRECTIVE Success - - - - INVALID_MOD_DIRECTIVE failure - - - - INVALID_MOD_DIRECTIVE failure - - - - MOD_DIRECTIVE Success - - -# Nested Tests - - - NESTED_DIRECTIVE success - - - NESTED_DIRECTIVE success - - - - INVALID_NESTED_DIRECTIVE failure - - - - - INVALID_NESTED_DIRECTIVE failure - - - INVALID_NESTED_DIRECTIVE failure - - - - NESTED_DIRECTIVE success - - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf deleted file mode 100644 index 2a5bb7be2..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf +++ /dev/null @@ -1,196 +0,0 @@ -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See http://httpd.apache.org/docs/2.4/ for detailed information about -# the directives and /usr/share/doc/apache2/README.Debian about Debian specific -# hints. -# -# -# Summary of how the Apache 2 configuration works in Debian: -# The Apache 2 web server configuration in Debian is quite different to -# upstream's suggested way to configure the web server. This is because Debian's -# default Apache2 installation attempts to make adding and removing modules, -# virtual hosts, and extra configuration directives as flexible as possible, in -# order to make automating the changes and administering the server as easy as -# possible. - -# It is split into several files forming the configuration hierarchy outlined -# below, all located in the /etc/apache2/ directory: -# -# /etc/apache2/ -# |-- apache2.conf -# | `-- ports.conf -# |-- mods-enabled -# | |-- *.load -# | `-- *.conf -# |-- conf-enabled -# | `-- *.conf -# `-- sites-enabled -# `-- *.conf -# -# -# * apache2.conf is the main configuration file (this file). It puts the pieces -# together by including all remaining configuration files when starting up the -# web server. -# -# * ports.conf is always included from the main configuration file. It is -# supposed to determine listening ports for incoming connections which can be -# customized anytime. -# -# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ -# directories contain particular configuration snippets which manage modules, -# global configuration fragments, or virtual host configurations, -# respectively. -# -# They are activated by symlinking available configuration files from their -# respective *-available/ counterparts. These should be managed by using our -# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See -# their respective man pages for detailed information. -# -# * The binary is called apache2. Due to the use of environment variables, in -# the default configuration, apache2 needs to be started/stopped with -# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not -# work with the default configuration. - - -# Global configuration - -# -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# -Mutex file:${APACHE_LOCK_DIR} default - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. -# This needs to be set in /etc/apache2/envvars -# -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 5 - - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog ${APACHE_LOG_DIR}/error.log - -# -# LogLevel: Control the severity of messages logged to the error_log. -# Available values: trace8, ..., trace1, debug, info, notice, warn, -# error, crit, alert, emerg. -# It is also possible to configure the log level for particular modules, e.g. -# "LogLevel info ssl:warn" -# -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - -# Include list of ports to listen on -Include ports.conf - - -# Sets the default security model of the Apache2 HTTPD server. It does -# not allow access to the root filesystem outside of /usr/share and /var/www. -# The former is used by web applications packaged in Debian, -# the latter may be used for local directories served by the web server. If -# your system is serving content from a sub-directory in /srv you must allow -# access here, or in any related virtual host. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - AllowOverride None - Require all granted - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - -# The following directives define some format nicknames for use with -# a CustomLog directive. -# -# These deviate from the Common Log Format definitions in that they use %O -# (the actual bytes sent including headers) instead of %b (the size of the -# requested file), because the latter makes it impossible to detect partial -# requests. -# -# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. -# Use mod_remoteip instead. -# -LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined -LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %O" common -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# Include of directories ignores editors' and dpkg's backup files, -# see README.Debian for details. - -# Include generic snippets of statements -IncludeOptional conf-enabled/*.conf - -# Include the virtual host configurations: -IncludeOptional sites-enabled/*.conf - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf deleted file mode 100644 index 8e9178803..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf +++ /dev/null @@ -1,3 +0,0 @@ - - -ServerName invalid.net diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf deleted file mode 100644 index 5e9f5e9e7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf +++ /dev/null @@ -1,4 +0,0 @@ -# Define an access log for VirtualHosts that don't define their own logfile -CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf deleted file mode 100644 index eccfcb1fd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf +++ /dev/null @@ -1,35 +0,0 @@ -# Changing the following options will not really affect the security of the -# server, but might make attacks slightly more difficult in some cases. - -# -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minimal | Minor | Major | Prod -# where Full conveys the most information, and Prod the least. -#ServerTokens Minimal -ServerTokens OS -#ServerTokens Full - -# -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -#ServerSignature Off -ServerSignature On - -# -# Allow TRACE method -# -# Set to "extended" to also reflect the request body (only for testing and -# diagnostic purposes). -# -# Set to one of: On | Off | extended -TraceEnable Off -#TraceEnable On - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf deleted file mode 100644 index b02782dab..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf +++ /dev/null @@ -1,20 +0,0 @@ - - - Define ENABLE_USR_LIB_CGI_BIN - - - - Define ENABLE_USR_LIB_CGI_BIN - - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Require all granted - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf deleted file mode 120000 index 8af91e530..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf deleted file mode 120000 index 036c97fa7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf deleted file mode 120000 index d917f688e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars deleted file mode 100644 index a13d9a89e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars +++ /dev/null @@ -1,29 +0,0 @@ -# envvars - default environment variables for apache2ctl - -# this won't be correct after changing uid -unset HOME - -# for supporting multiple apache2 instances -if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then - SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" -else - SUFFIX= -fi - -# Since there is no sane way to get the parsed apache2 config in scripts, some -# settings are defined via environment variables and then used in apache2ctl, -# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. -export APACHE_RUN_USER=www-data -export APACHE_RUN_GROUP=www-data -# temporary state file location. This might be changed to /run in Wheezy+1 -export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid -export APACHE_RUN_DIR=/var/run/apache2$SUFFIX -export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX -# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. -export APACHE_LOG_DIR=/var/log/apache2$SUFFIX - -## The locale used by some modules like mod_dav -export LANG=C - -export LANG - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load deleted file mode 100644 index c6df2733b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load +++ /dev/null @@ -1,5 +0,0 @@ -# Depends: dav_svn - - Include mods-enabled/dav_svn.load - -LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load deleted file mode 100644 index a5867fff3..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load +++ /dev/null @@ -1,3 +0,0 @@ - - LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf deleted file mode 100644 index 801cbd6bd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf +++ /dev/null @@ -1,56 +0,0 @@ -# dav_svn.conf - Example Subversion/Apache configuration -# -# For details and further options see the Apache user manual and -# the Subversion book. -# -# NOTE: for a setup with multiple vhosts, you will want to do this -# configuration in /etc/apache2/sites-available/*, not here. - -# ... -# URL controls how the repository appears to the outside world. -# In this example clients access the repository as http://hostname/svn/ -# Note, a literal /svn should NOT exist in your document root. -# - - # Uncomment this to enable the repository - #DAV svn - - # Set this to the path to your repository - #SVNPath /var/lib/svn - # Alternatively, use SVNParentPath if you have multiple repositories under - # under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...). - # You need either SVNPath and SVNParentPath, but not both. - #SVNParentPath /var/lib/svn - - # Access control is done at 3 levels: (1) Apache authentication, via - # any of several methods. A "Basic Auth" section is commented out - # below. (2) Apache and , also commented out - # below. (3) mod_authz_svn is a svn-specific authorization module - # which offers fine-grained read/write access control for paths - # within a repository. (The first two layers are coarse-grained; you - # can only enable/disable access to an entire repository.) Note that - # mod_authz_svn is noticeably slower than the other two layers, so if - # you don't need the fine-grained control, don't configure it. - - # Basic Authentication is repository-wide. It is not secure unless - # you are using https. See the 'htpasswd' command to create and - # manage the password file - and the documentation for the - # 'auth_basic' and 'authn_file' modules, which you will need for this - # (enable them with 'a2enmod'). - #AuthType Basic - #AuthName "Subversion Repository" - #AuthUserFile /etc/apache2/dav_svn.passwd - - # To enable authorization via mod_authz_svn (enable that module separately): - # - #AuthzSVNAccessFile /etc/apache2/dav_svn.authz - # - - # The following three lines allow anonymous read, but make - # committers authenticate themselves. It requires the 'authz_user' - # module (enable it with 'a2enmod'). - # - #Require valid-user - # - -# diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load deleted file mode 100644 index e41e1581a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load +++ /dev/null @@ -1,7 +0,0 @@ -# Depends: dav - - - Include mods-enabled/dav.load - - LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load deleted file mode 100644 index b32f16264..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load +++ /dev/null @@ -1 +0,0 @@ -LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf deleted file mode 100644 index e9fcf4f9b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf +++ /dev/null @@ -1,89 +0,0 @@ - - - # Pseudo Random Number Generator (PRNG): - # Configure one or more sources to seed the PRNG of the SSL library. - # The seed data should be of good random quality. - # WARNING! On some platforms /dev/random blocks if not enough entropy - # is available. This means you then cannot use the /dev/random device - # because it would lead to very long connection times (as long as - # it requires to make more entropy available). But usually those - # platforms additionally provide a /dev/urandom device which doesn't - # block. So, if available, use this one instead. Read the mod_ssl User - # Manual for more details. - # - SSLRandomSeed startup builtin - SSLRandomSeed startup file:/dev/urandom 512 - SSLRandomSeed connect builtin - SSLRandomSeed connect file:/dev/urandom 512 - - ## - ## SSL Global Context - ## - ## All SSL configuration in this context applies both to - ## the main server and all SSL-enabled virtual hosts. - ## - - # - # Some MIME-types for downloading Certificates and CRLs - # - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - - # Pass Phrase Dialog: - # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal - # terminal dialog) has to provide the pass phrase on stdout. - SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase - - # Inter-Process Session Cache: - # Configure the SSL Session Cache: First the mechanism - # to use and second the expiring timeout (in seconds). - # (The mechanism dbm has known memory leaks and should not be used). - #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache - SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) - SSLSessionCacheTimeout 300 - - # Semaphore: - # Configure the path to the mutual exclusion semaphore the - # SSL engine uses internally for inter-process synchronization. - # (Disabled by default, the global Mutex directive consolidates by default - # this) - #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache - - - # SSL Cipher Suite: - # List the ciphers that the client is permitted to negotiate. See the - # ciphers(1) man page from the openssl package for list of all available - # options. - # Enable only secure ciphers: - SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 - - # Speed-optimized SSL Cipher configuration: - # If speed is your main concern (on busy HTTPS servers e.g.), - # you might want to force clients to specific, performance - # optimized ciphers. In this case, prepend those ciphers - # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. - # Caveat: by giving precedence to RC4-SHA and AES128-SHA - # (as in the example below), most connections will no longer - # have perfect forward secrecy - if the server's key is - # compromised, captures of past or future traffic must be - # considered compromised, too. - #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 - #SSLHonorCipherOrder on - - # The protocols to enable. - # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 - # SSL v2 is no longer supported - SSLProtocol all - - # Allow insecure renegotiation with clients which do not yet support the - # secure renegotiation protocol. Default: Off - #SSLInsecureRenegotiation on - - # Whether to forbid non-SNI clients to access name based virtual hosts. - # Default: Off - #SSLStrictSNIVHostCheck On - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load deleted file mode 100644 index 3d2336ae0..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load +++ /dev/null @@ -1,2 +0,0 @@ -# Depends: setenvif mime socache_shmcb -LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load deleted file mode 120000 index 7ac0725dd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/authz_svn.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load deleted file mode 120000 index 9dcfef6da..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf deleted file mode 120000 index 964c7bb0b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav_svn.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load deleted file mode 120000 index 4094e4173..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav_svn.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf deleted file mode 100644 index 5daec58c1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf +++ /dev/null @@ -1,15 +0,0 @@ -# If you just change the port or add more ports here, you will likely also -# have to change the VirtualHost statement in -# /etc/apache2/sites-enabled/000-default.conf - -Listen 80 - - - Listen 443 - - - - Listen 443 - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf deleted file mode 100644 index 1a5b7de47..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf +++ /dev/null @@ -1,11 +0,0 @@ - - ServerName wildcard.tld - ServerAlias ?.wildcard.tld - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf deleted file mode 100644 index 2bd4e1fe9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf +++ /dev/null @@ -1,12 +0,0 @@ - - - ServerName ip-172-30-0-17 - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf deleted file mode 100644 index b8046e6c9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf +++ /dev/null @@ -1,11 +0,0 @@ - - ServerName example.net - ServerAlias ??.example.net *.other.example.net *another.example.net - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf deleted file mode 120000 index 95f52f002..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/another_wildcard.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf deleted file mode 120000 index f7fdf1bbe..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/old-and-default.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf deleted file mode 120000 index a87af2c93..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/wildcard.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites deleted file mode 100644 index ab518ee5b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites +++ /dev/null @@ -1,3 +0,0 @@ -sites-available/certbot.conf, certbot.demo -sites-available/encryption-example.conf, encryption-example.demo -sites-available/ocsp-ssl.conf, ocspvhost.com diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf deleted file mode 100644 index 4ed016e07..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf +++ /dev/null @@ -1,198 +0,0 @@ -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See http://httpd.apache.org/docs/2.4/ for detailed information about -# the directives and /usr/share/doc/apache2/README.Debian about Debian specific -# hints. -# -# -# Summary of how the Apache 2 configuration works in Debian: -# The Apache 2 web server configuration in Debian is quite different to -# upstream's suggested way to configure the web server. This is because Debian's -# default Apache2 installation attempts to make adding and removing modules, -# virtual hosts, and extra configuration directives as flexible as possible, in -# order to make automating the changes and administering the server as easy as -# possible. - -# It is split into several files forming the configuration hierarchy outlined -# below, all located in the /etc/apache2/ directory: -# -# /etc/apache2/ -# |-- apache2.conf -# | `-- ports.conf -# |-- mods-enabled -# | |-- *.load -# | `-- *.conf -# |-- conf-enabled -# | `-- *.conf -# `-- sites-enabled -# `-- *.conf -# -# -# * apache2.conf is the main configuration file (this file). It puts the pieces -# together by including all remaining configuration files when starting up the -# web server. -# -# * ports.conf is always included from the main configuration file. It is -# supposed to determine listening ports for incoming connections which can be -# customized anytime. -# -# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ -# directories contain particular configuration snippets which manage modules, -# global configuration fragments, or virtual host configurations, -# respectively. -# -# They are activated by symlinking available configuration files from their -# respective *-available/ counterparts. These should be managed by using our -# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See -# their respective man pages for detailed information. -# -# * The binary is called apache2. Due to the use of environment variables, in -# the default configuration, apache2 needs to be started/stopped with -# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not -# work with the default configuration. - - -# Global configuration - -# -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# -Mutex file:${APACHE_LOCK_DIR} default - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. -# This needs to be set in /etc/apache2/envvars -# -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 5 - - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog ${APACHE_LOG_DIR}/error.log - -# -# LogLevel: Control the severity of messages logged to the error_log. -# Available values: trace8, ..., trace1, debug, info, notice, warn, -# error, crit, alert, emerg. -# It is also possible to configure the log level for particular modules, e.g. -# "LogLevel info ssl:warn" -# -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - -# Include list of ports to listen on -Include ports.conf - - -# Sets the default security model of the Apache2 HTTPD server. It does -# not allow access to the root filesystem outside of /usr/share and /var/www. -# The former is used by web applications packaged in Debian, -# the latter may be used for local directories served by the web server. If -# your system is serving content from a sub-directory in /srv you must allow -# access here, or in any related virtual host. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - AllowOverride None - Require all granted - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - - -# -# The following directives define some format nicknames for use with -# a CustomLog directive. -# -# These deviate from the Common Log Format definitions in that they use %O -# (the actual bytes sent including headers) instead of %b (the size of the -# requested file), because the latter makes it impossible to detect partial -# requests. -# -# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. -# Use mod_remoteip instead. -# -LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined -LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %O" common -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# Include of directories ignores editors' and dpkg's backup files, -# see README.Debian for details. - -# Include generic snippets of statements -IncludeOptional conf-enabled/*.conf - -# Include the virtual host configurations: -IncludeOptional sites-enabled/*.conf - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf deleted file mode 100644 index 5e9f5e9e7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf +++ /dev/null @@ -1,4 +0,0 @@ -# Define an access log for VirtualHosts that don't define their own logfile -CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf deleted file mode 100644 index 1dfe33c60..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf +++ /dev/null @@ -1,31 +0,0 @@ -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minimal | Minor | Major | Prod -# where Full conveys the most information, and Prod the least. -#ServerTokens Minimal -ServerTokens OS -#ServerTokens Full - -# -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -#ServerSignature Off -ServerSignature On - -# -# Allow TRACE method -# -# Set to "extended" to also reflect the request body (only for testing and -# diagnostic purposes). -# -# Set to one of: On | Off | extended -TraceEnable Off -#TraceEnable On - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf deleted file mode 100644 index b02782dab..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf +++ /dev/null @@ -1,20 +0,0 @@ - - - Define ENABLE_USR_LIB_CGI_BIN - - - - Define ENABLE_USR_LIB_CGI_BIN - - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Require all granted - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf deleted file mode 120000 index 8af91e530..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf deleted file mode 120000 index 036c97fa7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf deleted file mode 120000 index d917f688e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars deleted file mode 100644 index 8051c4544..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars +++ /dev/null @@ -1,28 +0,0 @@ -# envvars - default environment variables for apache2ctl - -# this won't be correct after changing uid -unset HOME - -# for supporting multiple apache2 instances -if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then - SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" -else - SUFFIX= -fi - -# Since there is no sane way to get the parsed apache2 config in scripts, some -# settings are defined via environment variables and then used in apache2ctl, -# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. -export APACHE_RUN_USER=www-data -export APACHE_RUN_GROUP=www-data -# temporary state file location. This might be changed to /run in Wheezy+1 -export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid -export APACHE_RUN_DIR=/var/run/apache2$SUFFIX -export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX -# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. -export APACHE_LOG_DIR=/var/log/apache2$SUFFIX - -## The locale used by some modules like mod_dav -export LANG=C - -export LANG diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf deleted file mode 100644 index e9fcf4f9b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf +++ /dev/null @@ -1,89 +0,0 @@ - - - # Pseudo Random Number Generator (PRNG): - # Configure one or more sources to seed the PRNG of the SSL library. - # The seed data should be of good random quality. - # WARNING! On some platforms /dev/random blocks if not enough entropy - # is available. This means you then cannot use the /dev/random device - # because it would lead to very long connection times (as long as - # it requires to make more entropy available). But usually those - # platforms additionally provide a /dev/urandom device which doesn't - # block. So, if available, use this one instead. Read the mod_ssl User - # Manual for more details. - # - SSLRandomSeed startup builtin - SSLRandomSeed startup file:/dev/urandom 512 - SSLRandomSeed connect builtin - SSLRandomSeed connect file:/dev/urandom 512 - - ## - ## SSL Global Context - ## - ## All SSL configuration in this context applies both to - ## the main server and all SSL-enabled virtual hosts. - ## - - # - # Some MIME-types for downloading Certificates and CRLs - # - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - - # Pass Phrase Dialog: - # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal - # terminal dialog) has to provide the pass phrase on stdout. - SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase - - # Inter-Process Session Cache: - # Configure the SSL Session Cache: First the mechanism - # to use and second the expiring timeout (in seconds). - # (The mechanism dbm has known memory leaks and should not be used). - #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache - SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) - SSLSessionCacheTimeout 300 - - # Semaphore: - # Configure the path to the mutual exclusion semaphore the - # SSL engine uses internally for inter-process synchronization. - # (Disabled by default, the global Mutex directive consolidates by default - # this) - #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache - - - # SSL Cipher Suite: - # List the ciphers that the client is permitted to negotiate. See the - # ciphers(1) man page from the openssl package for list of all available - # options. - # Enable only secure ciphers: - SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 - - # Speed-optimized SSL Cipher configuration: - # If speed is your main concern (on busy HTTPS servers e.g.), - # you might want to force clients to specific, performance - # optimized ciphers. In this case, prepend those ciphers - # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. - # Caveat: by giving precedence to RC4-SHA and AES128-SHA - # (as in the example below), most connections will no longer - # have perfect forward secrecy - if the server's key is - # compromised, captures of past or future traffic must be - # considered compromised, too. - #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 - #SSLHonorCipherOrder on - - # The protocols to enable. - # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 - # SSL v2 is no longer supported - SSLProtocol all - - # Allow insecure renegotiation with clients which do not yet support the - # secure renegotiation protocol. Default: Off - #SSLInsecureRenegotiation on - - # Whether to forbid non-SNI clients to access name based virtual hosts. - # Default: Off - #SSLStrictSNIVHostCheck On - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load deleted file mode 100644 index 3d2336ae0..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load +++ /dev/null @@ -1,2 +0,0 @@ -# Depends: setenvif mime socache_shmcb -LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf deleted file mode 100644 index 176b9d103..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf +++ /dev/null @@ -1,20 +0,0 @@ -# If you just change the port or add more ports here, you will likely also -# have to change the VirtualHost statement in -# /etc/apache2/sites-enabled/000-default.conf - -Listen 80 - -NameVirtualHost *:80 - - - Listen 443 - - - - Listen 443 - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet - -NameVirtualHost *:443 - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf deleted file mode 100644 index d81fe132d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf +++ /dev/null @@ -1,11 +0,0 @@ - - # How well does Certbot work without a ServerName/Alias? - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf deleted file mode 100644 index e659d4b07..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf +++ /dev/null @@ -1,38 +0,0 @@ - - - ServerAdmin webmaster@localhost - - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # SSL Engine Switch: - # Enable/Disable SSL for this virtual host. - SSLEngine on - - # A self-signed (snakeoil) certificate can be created by installing - # the ssl-cert package. See - # /usr/share/doc/apache2/README.Debian.gz for more info. - # If both key and certificate are stored in the same file, only the - # SSLCertificateFile directive is needed. - SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem - SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem - - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - - BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - # MSIE 7 and newer should be able to use keepalive - BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf deleted file mode 120000 index 3c4632b73..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/000-default.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites deleted file mode 100644 index 03d53dd61..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites +++ /dev/null @@ -1 +0,0 @@ -sites-available/000-default.conf, default.com diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf deleted file mode 100644 index 2a5bb7be2..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf +++ /dev/null @@ -1,196 +0,0 @@ -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See http://httpd.apache.org/docs/2.4/ for detailed information about -# the directives and /usr/share/doc/apache2/README.Debian about Debian specific -# hints. -# -# -# Summary of how the Apache 2 configuration works in Debian: -# The Apache 2 web server configuration in Debian is quite different to -# upstream's suggested way to configure the web server. This is because Debian's -# default Apache2 installation attempts to make adding and removing modules, -# virtual hosts, and extra configuration directives as flexible as possible, in -# order to make automating the changes and administering the server as easy as -# possible. - -# It is split into several files forming the configuration hierarchy outlined -# below, all located in the /etc/apache2/ directory: -# -# /etc/apache2/ -# |-- apache2.conf -# | `-- ports.conf -# |-- mods-enabled -# | |-- *.load -# | `-- *.conf -# |-- conf-enabled -# | `-- *.conf -# `-- sites-enabled -# `-- *.conf -# -# -# * apache2.conf is the main configuration file (this file). It puts the pieces -# together by including all remaining configuration files when starting up the -# web server. -# -# * ports.conf is always included from the main configuration file. It is -# supposed to determine listening ports for incoming connections which can be -# customized anytime. -# -# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ -# directories contain particular configuration snippets which manage modules, -# global configuration fragments, or virtual host configurations, -# respectively. -# -# They are activated by symlinking available configuration files from their -# respective *-available/ counterparts. These should be managed by using our -# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See -# their respective man pages for detailed information. -# -# * The binary is called apache2. Due to the use of environment variables, in -# the default configuration, apache2 needs to be started/stopped with -# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not -# work with the default configuration. - - -# Global configuration - -# -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# -Mutex file:${APACHE_LOCK_DIR} default - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. -# This needs to be set in /etc/apache2/envvars -# -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 5 - - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog ${APACHE_LOG_DIR}/error.log - -# -# LogLevel: Control the severity of messages logged to the error_log. -# Available values: trace8, ..., trace1, debug, info, notice, warn, -# error, crit, alert, emerg. -# It is also possible to configure the log level for particular modules, e.g. -# "LogLevel info ssl:warn" -# -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - -# Include list of ports to listen on -Include ports.conf - - -# Sets the default security model of the Apache2 HTTPD server. It does -# not allow access to the root filesystem outside of /usr/share and /var/www. -# The former is used by web applications packaged in Debian, -# the latter may be used for local directories served by the web server. If -# your system is serving content from a sub-directory in /srv you must allow -# access here, or in any related virtual host. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - AllowOverride None - Require all granted - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - -# The following directives define some format nicknames for use with -# a CustomLog directive. -# -# These deviate from the Common Log Format definitions in that they use %O -# (the actual bytes sent including headers) instead of %b (the size of the -# requested file), because the latter makes it impossible to detect partial -# requests. -# -# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. -# Use mod_remoteip instead. -# -LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined -LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %O" common -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# Include of directories ignores editors' and dpkg's backup files, -# see README.Debian for details. - -# Include generic snippets of statements -IncludeOptional conf-enabled/*.conf - -# Include the virtual host configurations: -IncludeOptional sites-enabled/*.conf - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars deleted file mode 100644 index a13d9a89e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars +++ /dev/null @@ -1,29 +0,0 @@ -# envvars - default environment variables for apache2ctl - -# this won't be correct after changing uid -unset HOME - -# for supporting multiple apache2 instances -if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then - SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" -else - SUFFIX= -fi - -# Since there is no sane way to get the parsed apache2 config in scripts, some -# settings are defined via environment variables and then used in apache2ctl, -# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. -export APACHE_RUN_USER=www-data -export APACHE_RUN_GROUP=www-data -# temporary state file location. This might be changed to /run in Wheezy+1 -export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid -export APACHE_RUN_DIR=/var/run/apache2$SUFFIX -export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX -# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. -export APACHE_LOG_DIR=/var/log/apache2$SUFFIX - -## The locale used by some modules like mod_dav -export LANG=C - -export LANG - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf deleted file mode 100644 index 5daec58c1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf +++ /dev/null @@ -1,15 +0,0 @@ -# If you just change the port or add more ports here, you will likely also -# have to change the VirtualHost statement in -# /etc/apache2/sites-enabled/000-default.conf - -Listen 80 - - - Listen 443 - - - - Listen 443 - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf deleted file mode 100644 index 6ab206b2d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf +++ /dev/null @@ -1,22 +0,0 @@ - - - ServerName banana.vomit.net - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - - - - ServerName banana.vomit.com - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf deleted file mode 100644 index 5f2b727bf..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf +++ /dev/null @@ -1,38 +0,0 @@ - - ServerName 1.multi.vhost.tld - ServerAlias first.multi.vhost.tld - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - - - ServerName 2.multi.vhost.tld - ServerAlias second.multi.vhost.tld - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined -RewriteEngine on -RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ b://u%{REQUEST_URI} [P,NE,L] -RewriteCond %{HTTPS} !=on -RewriteCond %{HTTPS} !^$ -RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,NE,R=permanent] - - - - - ServerName 3.multi.vhost.tld - ServerAlias third.multi.vhost.tld - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined -RewriteEngine on -RewriteRule "^/secrets/(.+)" "https://new.example.com/docs/$1" [R,L] -RewriteRule "^/docs/(.+)" "http://new.example.com/docs/$1" [R,L] - - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf deleted file mode 120000 index 032e6bcf0..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/default.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf deleted file mode 120000 index 7f0910ff4..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/multi-vhost.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf deleted file mode 100644 index 819a6bcb4..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf +++ /dev/null @@ -1,207 +0,0 @@ -# This is the main Apache server configuration file. It contains the -# configuration directives that give the server its instructions. -# See http://httpd.apache.org/docs/2.4/ for detailed information about -# the directives and /usr/share/doc/apache2/README.Debian about Debian specific -# hints. -# -# -# Summary of how the Apache 2 configuration works in Debian: -# The Apache 2 web server configuration in Debian is quite different to -# upstream's suggested way to configure the web server. This is because Debian's -# default Apache2 installation attempts to make adding and removing modules, -# virtual hosts, and extra configuration directives as flexible as possible, in -# order to make automating the changes and administering the server as easy as -# possible. - -# It is split into several files forming the configuration hierarchy outlined -# below, all located in the /etc/apache2/ directory: -# -# /etc/apache2/ -# |-- apache2.conf -# | `-- ports.conf -# |-- mods-enabled -# | |-- *.load -# | `-- *.conf -# |-- conf-enabled -# | `-- *.conf -# `-- sites-enabled -# `-- *.conf -# -# -# * apache2.conf is the main configuration file (this file). It puts the pieces -# together by including all remaining configuration files when starting up the -# web server. -# -# * ports.conf is always included from the main configuration file. It is -# supposed to determine listening ports for incoming connections which can be -# customized anytime. -# -# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ -# directories contain particular configuration snippets which manage modules, -# global configuration fragments, or virtual host configurations, -# respectively. -# -# They are activated by symlinking available configuration files from their -# respective *-available/ counterparts. These should be managed by using our -# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See -# their respective man pages for detailed information. -# -# * The binary is called apache2. Due to the use of environment variables, in -# the default configuration, apache2 needs to be started/stopped with -# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not -# work with the default configuration. - - -# Global configuration - -# -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# -Mutex file:${APACHE_LOCK_DIR} default - -# -# PidFile: The file in which the server should record its process -# identification number when it starts. -# This needs to be set in /etc/apache2/envvars -# -PidFile ${APACHE_PID_FILE} - -# -# Timeout: The number of seconds before receives and sends time out. -# -Timeout 300 - -# -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -# -KeepAlive On - -# -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -# -MaxKeepAliveRequests 100 - -# -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -# -KeepAliveTimeout 5 - - -# These need to be set in /etc/apache2/envvars -User ${APACHE_RUN_USER} -Group ${APACHE_RUN_GROUP} - -# -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -# -HostnameLookups Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -# -ErrorLog ${APACHE_LOG_DIR}/error.log - -# -# LogLevel: Control the severity of messages logged to the error_log. -# Available values: trace8, ..., trace1, debug, info, notice, warn, -# error, crit, alert, emerg. -# It is also possible to configure the log level for particular modules, e.g. -# "LogLevel info ssl:warn" -# -LogLevel warn - -# Include module configuration: -IncludeOptional mods-enabled/*.load -IncludeOptional mods-enabled/*.conf - -# Include list of ports to listen on -Include ports.conf - - -# Sets the default security model of the Apache2 HTTPD server. It does -# not allow access to the root filesystem outside of /usr/share and /var/www. -# The former is used by web applications packaged in Debian, -# the latter may be used for local directories served by the web server. If -# your system is serving content from a sub-directory in /srv you must allow -# access here, or in any related virtual host. - - Options FollowSymLinks - AllowOverride None - Require all denied - - - - AllowOverride None - Require all granted - - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted - - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -# -AccessFileName .htaccess - -# -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. -# - - Require all denied - - -# The following directives define some format nicknames for use with -# a CustomLog directive. -# -# These deviate from the Common Log Format definitions in that they use %O -# (the actual bytes sent including headers) instead of %b (the size of the -# requested file), because the latter makes it impossible to detect partial -# requests. -# -# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. -# Use mod_remoteip instead. -# -LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined -LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %O" common -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-agent}i" agent - -# Include of directories ignores editors' and dpkg's backup files, -# see README.Debian for details. - -# Include generic snippets of statements -IncludeOptional conf-enabled/*.conf - -# Include the virtual host configurations: -IncludeOptional sites-enabled/*.conf - - - - ServerName vhost.in.rootconf - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf deleted file mode 100644 index 8e9178803..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf +++ /dev/null @@ -1,3 +0,0 @@ - - -ServerName invalid.net diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf deleted file mode 100644 index 5e9f5e9e7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf +++ /dev/null @@ -1,4 +0,0 @@ -# Define an access log for VirtualHosts that don't define their own logfile -CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf deleted file mode 100644 index eccfcb1fd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf +++ /dev/null @@ -1,35 +0,0 @@ -# Changing the following options will not really affect the security of the -# server, but might make attacks slightly more difficult in some cases. - -# -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minimal | Minor | Major | Prod -# where Full conveys the most information, and Prod the least. -#ServerTokens Minimal -ServerTokens OS -#ServerTokens Full - -# -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -#ServerSignature Off -ServerSignature On - -# -# Allow TRACE method -# -# Set to "extended" to also reflect the request body (only for testing and -# diagnostic purposes). -# -# Set to one of: On | Off | extended -TraceEnable Off -#TraceEnable On - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf deleted file mode 100644 index b02782dab..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf +++ /dev/null @@ -1,20 +0,0 @@ - - - Define ENABLE_USR_LIB_CGI_BIN - - - - Define ENABLE_USR_LIB_CGI_BIN - - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Require all granted - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf deleted file mode 120000 index 8af91e530..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf deleted file mode 120000 index 036c97fa7..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf deleted file mode 120000 index d917f688e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf +++ /dev/null @@ -1 +0,0 @@ -../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars deleted file mode 100644 index a13d9a89e..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars +++ /dev/null @@ -1,29 +0,0 @@ -# envvars - default environment variables for apache2ctl - -# this won't be correct after changing uid -unset HOME - -# for supporting multiple apache2 instances -if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then - SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" -else - SUFFIX= -fi - -# Since there is no sane way to get the parsed apache2 config in scripts, some -# settings are defined via environment variables and then used in apache2ctl, -# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. -export APACHE_RUN_USER=www-data -export APACHE_RUN_GROUP=www-data -# temporary state file location. This might be changed to /run in Wheezy+1 -export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid -export APACHE_RUN_DIR=/var/run/apache2$SUFFIX -export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX -# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. -export APACHE_LOG_DIR=/var/log/apache2$SUFFIX - -## The locale used by some modules like mod_dav -export LANG=C - -export LANG - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load deleted file mode 100644 index c6df2733b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load +++ /dev/null @@ -1,5 +0,0 @@ -# Depends: dav_svn - - Include mods-enabled/dav_svn.load - -LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load deleted file mode 100644 index a5867fff3..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load +++ /dev/null @@ -1,3 +0,0 @@ - - LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf deleted file mode 100644 index 801cbd6bd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf +++ /dev/null @@ -1,56 +0,0 @@ -# dav_svn.conf - Example Subversion/Apache configuration -# -# For details and further options see the Apache user manual and -# the Subversion book. -# -# NOTE: for a setup with multiple vhosts, you will want to do this -# configuration in /etc/apache2/sites-available/*, not here. - -# ... -# URL controls how the repository appears to the outside world. -# In this example clients access the repository as http://hostname/svn/ -# Note, a literal /svn should NOT exist in your document root. -# - - # Uncomment this to enable the repository - #DAV svn - - # Set this to the path to your repository - #SVNPath /var/lib/svn - # Alternatively, use SVNParentPath if you have multiple repositories under - # under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...). - # You need either SVNPath and SVNParentPath, but not both. - #SVNParentPath /var/lib/svn - - # Access control is done at 3 levels: (1) Apache authentication, via - # any of several methods. A "Basic Auth" section is commented out - # below. (2) Apache and , also commented out - # below. (3) mod_authz_svn is a svn-specific authorization module - # which offers fine-grained read/write access control for paths - # within a repository. (The first two layers are coarse-grained; you - # can only enable/disable access to an entire repository.) Note that - # mod_authz_svn is noticeably slower than the other two layers, so if - # you don't need the fine-grained control, don't configure it. - - # Basic Authentication is repository-wide. It is not secure unless - # you are using https. See the 'htpasswd' command to create and - # manage the password file - and the documentation for the - # 'auth_basic' and 'authn_file' modules, which you will need for this - # (enable them with 'a2enmod'). - #AuthType Basic - #AuthName "Subversion Repository" - #AuthUserFile /etc/apache2/dav_svn.passwd - - # To enable authorization via mod_authz_svn (enable that module separately): - # - #AuthzSVNAccessFile /etc/apache2/dav_svn.authz - # - - # The following three lines allow anonymous read, but make - # committers authenticate themselves. It requires the 'authz_user' - # module (enable it with 'a2enmod'). - # - #Require valid-user - # - -# diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load deleted file mode 100644 index e41e1581a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load +++ /dev/null @@ -1,7 +0,0 @@ -# Depends: dav - - - Include mods-enabled/dav.load - - LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load deleted file mode 100644 index b32f16264..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load +++ /dev/null @@ -1 +0,0 @@ -LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf deleted file mode 100644 index e9fcf4f9b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf +++ /dev/null @@ -1,89 +0,0 @@ - - - # Pseudo Random Number Generator (PRNG): - # Configure one or more sources to seed the PRNG of the SSL library. - # The seed data should be of good random quality. - # WARNING! On some platforms /dev/random blocks if not enough entropy - # is available. This means you then cannot use the /dev/random device - # because it would lead to very long connection times (as long as - # it requires to make more entropy available). But usually those - # platforms additionally provide a /dev/urandom device which doesn't - # block. So, if available, use this one instead. Read the mod_ssl User - # Manual for more details. - # - SSLRandomSeed startup builtin - SSLRandomSeed startup file:/dev/urandom 512 - SSLRandomSeed connect builtin - SSLRandomSeed connect file:/dev/urandom 512 - - ## - ## SSL Global Context - ## - ## All SSL configuration in this context applies both to - ## the main server and all SSL-enabled virtual hosts. - ## - - # - # Some MIME-types for downloading Certificates and CRLs - # - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - - # Pass Phrase Dialog: - # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal - # terminal dialog) has to provide the pass phrase on stdout. - SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase - - # Inter-Process Session Cache: - # Configure the SSL Session Cache: First the mechanism - # to use and second the expiring timeout (in seconds). - # (The mechanism dbm has known memory leaks and should not be used). - #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache - SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) - SSLSessionCacheTimeout 300 - - # Semaphore: - # Configure the path to the mutual exclusion semaphore the - # SSL engine uses internally for inter-process synchronization. - # (Disabled by default, the global Mutex directive consolidates by default - # this) - #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache - - - # SSL Cipher Suite: - # List the ciphers that the client is permitted to negotiate. See the - # ciphers(1) man page from the openssl package for list of all available - # options. - # Enable only secure ciphers: - SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 - - # Speed-optimized SSL Cipher configuration: - # If speed is your main concern (on busy HTTPS servers e.g.), - # you might want to force clients to specific, performance - # optimized ciphers. In this case, prepend those ciphers - # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. - # Caveat: by giving precedence to RC4-SHA and AES128-SHA - # (as in the example below), most connections will no longer - # have perfect forward secrecy - if the server's key is - # compromised, captures of past or future traffic must be - # considered compromised, too. - #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 - #SSLHonorCipherOrder on - - # The protocols to enable. - # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 - # SSL v2 is no longer supported - SSLProtocol all - - # Allow insecure renegotiation with clients which do not yet support the - # secure renegotiation protocol. Default: Off - #SSLInsecureRenegotiation on - - # Whether to forbid non-SNI clients to access name based virtual hosts. - # Default: Off - #SSLStrictSNIVHostCheck On - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load deleted file mode 100644 index 3d2336ae0..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load +++ /dev/null @@ -1,2 +0,0 @@ -# Depends: setenvif mime socache_shmcb -LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load deleted file mode 120000 index 7ac0725dd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/authz_svn.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load deleted file mode 120000 index 9dcfef6da..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf deleted file mode 120000 index 964c7bb0b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav_svn.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load deleted file mode 120000 index 4094e4173..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load +++ /dev/null @@ -1 +0,0 @@ -../mods-available/dav_svn.load \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf deleted file mode 100644 index 5daec58c1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf +++ /dev/null @@ -1,15 +0,0 @@ -# If you just change the port or add more ports here, you will likely also -# have to change the VirtualHost statement in -# /etc/apache2/sites-enabled/000-default.conf - -Listen 80 - - - Listen 443 - - - - Listen 443 - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf deleted file mode 100644 index 2bd4e1fe9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf +++ /dev/null @@ -1,12 +0,0 @@ - - - ServerName ip-172-30-0-17 - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf deleted file mode 100644 index 965ca2222..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf +++ /dev/null @@ -1,43 +0,0 @@ - -ServerName certbot.demo -ServerAlias www.certbot.demo -ServerAdmin webmaster@localhost - -DocumentRoot /var/www-certbot-reworld/static/ - -Options FollowSymLinks -AllowOverride None - - -Options Indexes FollowSymLinks MultiViews -AllowOverride None -Order allow,deny -allow from all - - -ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - -AllowOverride None -Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch -Order allow,deny -Allow from all - - -ErrorLog ${APACHE_LOG_DIR}/error.log - -# Possible values include: debug, info, notice, warn, error, crit, -# alert, emerg. -LogLevel warn - -CustomLog ${APACHE_LOG_DIR}/access.log combined - -Alias /doc/ "/usr/share/doc/" - -Options Indexes MultiViews FollowSymLinks -AllowOverride None -Order deny,allow -Deny from all -Allow from 127.0.0.0/255.0.0.0 ::1/128 - - - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf deleted file mode 100644 index 849b42e9f..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf +++ /dev/null @@ -1,36 +0,0 @@ - - - ServerAdmin webmaster@localhost - - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # A self-signed (snakeoil) certificate can be created by installing - # the ssl-cert package. See - # /usr/share/doc/apache2/README.Debian.gz for more info. - # If both key and certificate are stored in the same file, only the - # SSLCertificateFile directive is needed. - SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem - SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem - - - #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - - BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - # MSIE 7 and newer should be able to use keepalive - BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf deleted file mode 100644 index a3025ae8a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf +++ /dev/null @@ -1,40 +0,0 @@ - - - ServerAdmin webmaster@localhost - - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # SSL Engine Switch: - # Enable/Disable SSL for this virtual host. - SSLEngine on - - # A self-signed (snakeoil) certificate can be created by installing - # the ssl-cert package. See - # /usr/share/doc/apache2/README.Debian.gz for more info. - # If both key and certificate are stored in the same file, only the - # SSLCertificateFile directive is needed. - SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem - SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem - - - #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - SSLOptions +StdEnvVars - - - BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - # MSIE 7 and newer should be able to use keepalive - BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf deleted file mode 100644 index 5684651fb..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf +++ /dev/null @@ -1,9 +0,0 @@ - - ServerName duplicate.example.com - - ServerAdmin webmaster@certbot.demo - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf deleted file mode 100644 index e3ac21fac..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf +++ /dev/null @@ -1,14 +0,0 @@ - - - ServerName duplicate.example.com - - ServerAdmin webmaster@certbot.demo - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - -SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem -SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem - - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf deleted file mode 100644 index 862040fc1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf +++ /dev/null @@ -1,42 +0,0 @@ - - ServerName encryption-example.demo - ServerAdmin webmaster@localhost - - DocumentRoot /var/www-encryption-example/static/ - - Options FollowSymLinks - AllowOverride None - - - Options Indexes FollowSymLinks MultiViews - AllowOverride None - Order allow,deny - allow from all - - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - - ErrorLog ${APACHE_LOG_DIR}/error.log - - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog ${APACHE_LOG_DIR}/access.log combined - - Alias /doc/ "/usr/share/doc/" - - Options Indexes MultiViews FollowSymLinks - AllowOverride None - Order deny,allow - Deny from all - Allow from 127.0.0.0/255.0.0.0 ::1/128 - - - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf deleted file mode 100644 index 6a6579007..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf +++ /dev/null @@ -1,15 +0,0 @@ - - - ServerName $domain - ServerAlias www.$domain - DocumentRoot /var/www/html - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - -Use VHost macro1 test.com -Use VHost macro2 hostname.org -Use VHost macro3 apache.org - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf deleted file mode 100644 index 631cf16c8..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf +++ /dev/null @@ -1,36 +0,0 @@ - -SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000) - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - ServerName ocspvhost.com - - ServerAdmin webmaster@dumpbits.com - DocumentRoot /var/www/html - - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf -SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem -SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem -SSLUseStapling on - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf deleted file mode 100644 index 33e30a63b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf +++ /dev/null @@ -1,13 +0,0 @@ - - - ServerName ip-172-30-0-17 - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ServerAlias *.blue.purple.com - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf deleted file mode 120000 index 3c4632b73..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/000-default.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf deleted file mode 120000 index 4d08c763f..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/certbot.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf deleted file mode 120000 index 103c1b68d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/default-ssl-port-only.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf deleted file mode 120000 index d02890bbd..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/default-ssl.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf deleted file mode 120000 index a69ee3c1d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/duplicatehttp.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf deleted file mode 120000 index a52ee1ccb..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/duplicatehttps.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf deleted file mode 120000 index 417818069..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/encryption-example.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf deleted file mode 120000 index 44f254304..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/mod_macro-example.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf deleted file mode 100644 index 31cb6093c..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf +++ /dev/null @@ -1,9 +0,0 @@ - -ServerName nonsym.link -ServerAdmin webmaster@localhost - -DocumentRoot /var/www-certbot-reworld/static/ - -ErrorLog ${APACHE_LOG_DIR}/error.log -CustomLog ${APACHE_LOG_DIR}/access.log combined - diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf deleted file mode 120000 index b25ee0482..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/ocsp-ssl.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf deleted file mode 120000 index a87af2c93..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf +++ /dev/null @@ -1 +0,0 @@ -../sites-available/wildcard.conf \ No newline at end of file diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites b/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites deleted file mode 100644 index ab518ee5b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites +++ /dev/null @@ -1,3 +0,0 @@ -sites-available/certbot.conf, certbot.demo -sites-available/encryption-example.conf, encryption-example.demo -sites-available/ocsp-ssl.conf, ocspvhost.com diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf deleted file mode 100644 index e5693ffff..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf +++ /dev/null @@ -1,157 +0,0 @@ -# This is a modification of the default Apache 2.4 configuration file -# for Gentoo Linux. -# -# Support: -# http://www.gentoo.org/main/en/lists.xml [mailing lists] -# http://forums.gentoo.org/ [web forums] -# irc://irc.freenode.net#gentoo-apache [irc chat] -# -# Bug Reports: -# http://bugs.gentoo.org [gentoo related bugs] -# http://httpd.apache.org/bug_report.html [apache httpd related bugs] -# -# -# This is the main Apache HTTP server configuration file. It contains the -# configuration directives that give the server its instructions. -# See for detailed information. -# In particular, see -# -# for a discussion of each configuration directive. -# -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. -# -# Configuration and logfile names: If the filenames you specify for many -# of the server's control files begin with "/" (or "drive:/" for Win32), the -# server will use that explicit path. If the filenames do *not* begin -# with "/", the value of ServerRoot is prepended -- so "var/log/apache2/foo_log" -# with ServerRoot set to "/usr" will be interpreted by the -# server as "/usr/var/log/apache2/foo.log". - -# ServerRoot: The top of the directory tree under which the server's -# configuration, error, and log files are kept. -# -# Do not add a slash at the end of the directory path. If you point -# ServerRoot at a non-local disk, be sure to point the LockFile directive -# at a local disk. If you wish to share the same ServerRoot for multiple -# httpd daemons, you will need to change at least LockFile and PidFile. -# Comment: The LockFile directive has been replaced by the Mutex directive -ServerRoot "/usr/lib64/apache2" - -# Dynamic Shared Object (DSO) Support -# -# To be able to use the functionality of a module which was built as a DSO you -# have to place corresponding `LoadModule' lines at this location so the -# directives contained in it are actually available _before_ they are used. -# Statically compiled modules (those listed by `httpd -l') do not need -# to be loaded here. -# -# Example: -# LoadModule foo_module modules/mod_foo.so -# -# GENTOO: Automatically defined based on APACHE2_MODULES USE_EXPAND variable. -# Do not change manually, it will be overwritten on upgrade. -# -# The following modules are considered as the default configuration. -# If you wish to disable one of them, you may have to alter other -# configuration directives. -# -# Change these at your own risk! - -LoadModule actions_module modules/mod_actions.so -LoadModule alias_module modules/mod_alias.so -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule authn_anon_module modules/mod_authn_anon.so -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authn_dbm_module modules/mod_authn_dbm.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authz_core_module modules/mod_authz_core.so -LoadModule authz_dbm_module modules/mod_authz_dbm.so -LoadModule authz_groupfile_module modules/mod_authz_groupfile.so -LoadModule authz_host_module modules/mod_authz_host.so -LoadModule authz_owner_module modules/mod_authz_owner.so -LoadModule authz_user_module modules/mod_authz_user.so -LoadModule autoindex_module modules/mod_autoindex.so - -LoadModule cache_module modules/mod_cache.so - -LoadModule cgi_module modules/mod_cgi.so -LoadModule cgid_module modules/mod_cgid.so - -LoadModule dav_module modules/mod_dav.so - - -LoadModule dav_fs_module modules/mod_dav_fs.so - - -LoadModule dav_lock_module modules/mod_dav_lock.so - -LoadModule deflate_module modules/mod_deflate.so -LoadModule dir_module modules/mod_dir.so -LoadModule env_module modules/mod_env.so -LoadModule expires_module modules/mod_expires.so -LoadModule ext_filter_module modules/mod_ext_filter.so - -LoadModule file_cache_module modules/mod_file_cache.so - -LoadModule filter_module modules/mod_filter.so -LoadModule headers_module modules/mod_headers.so -LoadModule include_module modules/mod_include.so - -LoadModule info_module modules/mod_info.so - -LoadModule log_config_module modules/mod_log_config.so -LoadModule logio_module modules/mod_logio.so -LoadModule mime_module modules/mod_mime.so -LoadModule mime_magic_module modules/mod_mime_magic.so -LoadModule negotiation_module modules/mod_negotiation.so -LoadModule rewrite_module modules/mod_rewrite.so -LoadModule setenvif_module modules/mod_setenvif.so - -LoadModule socache_shmcb_module modules/mod_socache_shmcb.so - -LoadModule speling_module modules/mod_speling.so - -LoadModule ssl_module modules/mod_ssl.so - - -LoadModule status_module modules/mod_status.so - -LoadModule unique_id_module modules/mod_unique_id.so -LoadModule unixd_module modules/mod_unixd.so - -LoadModule userdir_module modules/mod_userdir.so - -LoadModule usertrack_module modules/mod_usertrack.so -LoadModule vhost_alias_module modules/mod_vhost_alias.so - -# If you wish httpd to run as a different user or group, you must run -# httpd as root initially and it will switch. -# -# User/Group: The name (or #number) of the user/group to run httpd as. -# It is usually good practice to create a dedicated user and group for -# running httpd, as with most system services. -User apache -Group apache - -# Supplemental configuration -# -# Most of the configuration files in the /etc/apache2/modules.d/ directory can -# be turned on using APACHE2_OPTS in /etc/conf.d/apache2 to add extra features -# or to modify the default configuration of the server. -# -# To know which flag to add to APACHE2_OPTS, look at the first line of the -# the file, which will usually be an where OPTION is the -# flag to use. - -Include modules.d/*.conf - -# Virtual-host support -# -# Gentoo has made using virtual-hosts easy. In /etc/apache2/vhosts.d/ we -# include a default vhost (enabled by adding -D DEFAULT_VHOST to -# APACHE2_OPTS in /etc/conf.d/apache2). -Include vhosts.d/*.conf - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic deleted file mode 100644 index 7c56119e9..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic +++ /dev/null @@ -1,385 +0,0 @@ -# Magic data for mod_mime_magic Apache module (originally for file(1) command) -# The module is described in /manual/mod/mod_mime_magic.html -# -# The format is 4-5 columns: -# Column #1: byte number to begin checking from, ">" indicates continuation -# Column #2: type of data to match -# Column #3: contents of data to match -# Column #4: MIME type of result -# Column #5: MIME encoding of result (optional) - -#------------------------------------------------------------------------------ -# Localstuff: file(1) magic for locally observed files -# Add any locally observed files here. - -#------------------------------------------------------------------------------ -# end local stuff -#------------------------------------------------------------------------------ - -#------------------------------------------------------------------------------ -# Java - -0 short 0xcafe ->2 short 0xbabe application/java - -#------------------------------------------------------------------------------ -# audio: file(1) magic for sound formats -# -# from Jan Nicolai Langfeldt , -# - -# Sun/NeXT audio data -0 string .snd ->12 belong 1 audio/basic ->12 belong 2 audio/basic ->12 belong 3 audio/basic ->12 belong 4 audio/basic ->12 belong 5 audio/basic ->12 belong 6 audio/basic ->12 belong 7 audio/basic - ->12 belong 23 audio/x-adpcm - -# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format -# that uses little-endian encoding and has a different magic number -# (0x0064732E in little-endian encoding). -0 lelong 0x0064732E ->12 lelong 1 audio/x-dec-basic ->12 lelong 2 audio/x-dec-basic ->12 lelong 3 audio/x-dec-basic ->12 lelong 4 audio/x-dec-basic ->12 lelong 5 audio/x-dec-basic ->12 lelong 6 audio/x-dec-basic ->12 lelong 7 audio/x-dec-basic -# compressed (G.721 ADPCM) ->12 lelong 23 audio/x-dec-adpcm - -# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM" -# AIFF audio data -8 string AIFF audio/x-aiff -# AIFF-C audio data -8 string AIFC audio/x-aiff -# IFF/8SVX audio data -8 string 8SVX audio/x-aiff - -# Creative Labs AUDIO stuff -# Standard MIDI data -0 string MThd audio/unknown -#>9 byte >0 (format %d) -#>11 byte >1 using %d channels -# Creative Music (CMF) data -0 string CTMF audio/unknown -# SoundBlaster instrument data -0 string SBI audio/unknown -# Creative Labs voice data -0 string Creative\ Voice\ File audio/unknown -## is this next line right? it came this way... -#>19 byte 0x1A -#>23 byte >0 - version %d -#>22 byte >0 \b.%d - -# [GRR 950115: is this also Creative Labs? Guessing that first line -# should be string instead of unknown-endian long...] -#0 long 0x4e54524b MultiTrack sound data -#0 string NTRK MultiTrack sound data -#>4 long x - version %ld - -# Microsoft WAVE format (*.wav) -# [GRR 950115: probably all of the shorts and longs should be leshort/lelong] -# Microsoft RIFF -0 string RIFF audio/unknown -# - WAVE format ->8 string WAVE audio/x-wav -# MPEG audio. -0 beshort&0xfff0 0xfff0 audio/mpeg -# C64 SID Music files, from Linus Walleij -0 string PSID audio/prs.sid - -#------------------------------------------------------------------------------ -# c-lang: file(1) magic for C programs or various scripts -# - -# XPM icons (Greg Roelofs, newt@uchicago.edu) -# ideally should go into "images", but entries below would tag XPM as C source -0 string /*\ XPM image/x-xbm 7bit - -# this first will upset you if you're a PL/1 shop... (are there any left?) -# in which case rm it; ascmagic will catch real C programs -# C or REXX program text -0 string /* text/plain -# C++ program text -0 string // text/plain - -#------------------------------------------------------------------------------ -# compress: file(1) magic for pure-compression formats (no archives) -# -# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc. -# -# Formats for various forms of compressed data -# Formats for "compress" proper have been moved into "compress.c", -# because it tries to uncompress it to figure out what's inside. - -# standard unix compress -0 string \037\235 application/octet-stream x-compress - -# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver) -0 string \037\213 application/octet-stream x-gzip - -# According to gzip.h, this is the correct byte order for packed data. -0 string \037\036 application/octet-stream -# -# This magic number is byte-order-independent. -# -0 short 017437 application/octet-stream - -# XXX - why *two* entries for "compacted data", one of which is -# byte-order independent, and one of which is byte-order dependent? -# -# compacted data -0 short 0x1fff application/octet-stream -0 string \377\037 application/octet-stream -# huf output -0 short 0145405 application/octet-stream - -# Squeeze and Crunch... -# These numbers were gleaned from the Unix versions of the programs to -# handle these formats. Note that I can only uncrunch, not crunch, and -# I didn't have a crunched file handy, so the crunch number is untested. -# Keith Waclena -#0 leshort 0x76FF squeezed data (CP/M, DOS) -#0 leshort 0x76FE crunched data (CP/M, DOS) - -# Freeze -#0 string \037\237 Frozen file 2.1 -#0 string \037\236 Frozen file 1.0 (or gzip 0.5) - -# lzh? -#0 string \037\240 LZH compressed data - -#------------------------------------------------------------------------------ -# frame: file(1) magic for FrameMaker files -# -# This stuff came on a FrameMaker demo tape, most of which is -# copyright, but this file is "published" as witness the following: -# -0 string \ -# and Anna Shergold -# -0 string \ -0 string \14 byte 12 (OS/2 1.x format) -#>14 byte 64 (OS/2 2.x format) -#>14 byte 40 (Windows 3.x format) -#0 string IC icon -#0 string PI pointer -#0 string CI color icon -#0 string CP color pointer -#0 string BA bitmap array - -0 string \x89PNG image/png -0 string FWS application/x-shockwave-flash -0 string CWS application/x-shockwave-flash - -#------------------------------------------------------------------------------ -# lisp: file(1) magic for lisp programs -# -# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com) -0 string ;; text/plain 8bit -# Emacs 18 - this is always correct, but not very magical. -0 string \012( application/x-elc -# Emacs 19 -0 string ;ELC\023\000\000\000 application/x-elc - -#------------------------------------------------------------------------------ -# mail.news: file(1) magic for mail and news -# -# There are tests to ascmagic.c to cope with mail and news. -0 string Relay-Version: message/rfc822 7bit -0 string #!\ rnews message/rfc822 7bit -0 string N#!\ rnews message/rfc822 7bit -0 string Forward\ to message/rfc822 7bit -0 string Pipe\ to message/rfc822 7bit -0 string Return-Path: message/rfc822 7bit -0 string Path: message/news 8bit -0 string Xref: message/news 8bit -0 string From: message/rfc822 7bit -0 string Article message/news 8bit -#------------------------------------------------------------------------------ -# msword: file(1) magic for MS Word files -# -# Contributor claims: -# Reversed-engineered MS Word magic numbers -# - -0 string \376\067\0\043 application/msword -0 string \333\245-\0\0\0 application/msword - -# disable this one because it applies also to other -# Office/OLE documents for which msword is not correct. See PR#2608. -#0 string \320\317\021\340\241\261 application/msword - - - -#------------------------------------------------------------------------------ -# printer: file(1) magic for printer-formatted files -# - -# PostScript -0 string %! application/postscript -0 string \004%! application/postscript - -# Acrobat -# (due to clamen@cs.cmu.edu) -0 string %PDF- application/pdf - -#------------------------------------------------------------------------------ -# sc: file(1) magic for "sc" spreadsheet -# -38 string Spreadsheet application/x-sc - -#------------------------------------------------------------------------------ -# tex: file(1) magic for TeX files -# -# XXX - needs byte-endian stuff (big-endian and little-endian DVI?) -# -# From - -# Although we may know the offset of certain text fields in TeX DVI -# and font files, we can't use them reliably because they are not -# zero terminated. [but we do anyway, christos] -0 string \367\002 application/x-dvi -#0 string \367\203 TeX generic font data -#0 string \367\131 TeX packed font data -#0 string \367\312 TeX virtual font data -#0 string This\ is\ TeX, TeX transcript text -#0 string This\ is\ METAFONT, METAFONT transcript text - -# There is no way to detect TeX Font Metric (*.tfm) files without -# breaking them apart and reading the data. The following patterns -# match most *.tfm files generated by METAFONT or afm2tfm. -#2 string \000\021 TeX font metric data -#2 string \000\022 TeX font metric data -#>34 string >\0 (%s) - -# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com) -#0 string \\input\ texinfo Texinfo source text -#0 string This\ is\ Info\ file GNU Info text - -# correct TeX magic for Linux (and maybe more) -# from Peter Tobias (tobias@server.et-inf.fho-emden.de) -# -0 leshort 0x02f7 application/x-dvi - -# RTF - Rich Text Format -0 string {\\rtf application/rtf - -#------------------------------------------------------------------------------ -# animation: file(1) magic for animation/movie formats -# -# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8) -# MPEG file -0 string \000\000\001\263 video/mpeg -# -# The contributor claims: -# I couldn't find a real magic number for these, however, this -# -appears- to work. Note that it might catch other files, too, -# so BE CAREFUL! -# -# Note that title and author appear in the two 20-byte chunks -# at decimal offsets 2 and 22, respectively, but they are XOR'ed with -# 255 (hex FF)! DL format SUCKS BIG ROCKS. -# -# DL file version 1 , medium format (160x100, 4 images/screen) -0 byte 1 video/unknown -0 byte 2 video/unknown -# Quicktime video, from Linus Walleij -# from Apple quicktime file format documentation. -4 string moov video/quicktime -4 string mdat video/quicktime - diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf deleted file mode 100644 index 38635aa9d..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf +++ /dev/null @@ -1,131 +0,0 @@ -# This configuration file reflects default settings for Apache HTTP Server. -# You may change these, but chances are that you may not need to. - -# Timeout: The number of seconds before receives and sends time out. -Timeout 300 - -# KeepAlive: Whether or not to allow persistent connections (more than -# one request per connection). Set to "Off" to deactivate. -KeepAlive On - -# MaxKeepAliveRequests: The maximum number of requests to allow -# during a persistent connection. Set to 0 to allow an unlimited amount. -# We recommend you leave this number high, for maximum performance. -MaxKeepAliveRequests 100 - -# KeepAliveTimeout: Number of seconds to wait for the next request from the -# same client on the same connection. -KeepAliveTimeout 15 - -# UseCanonicalName: Determines how Apache constructs self-referencing -# URLs and the SERVER_NAME and SERVER_PORT variables. -# When set "Off", Apache will use the Hostname and Port supplied -# by the client. When set "On", Apache will use the value of the -# ServerName directive. -UseCanonicalName Off - -# AccessFileName: The name of the file to look for in each directory -# for additional configuration directives. See also the AllowOverride -# directive. -AccessFileName .htaccess - -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minor | Minimal | Major | Prod -# where Full conveys the most information, and Prod the least. -ServerTokens Prod - -# TraceEnable -# This directive overrides the behavior of TRACE for both the core server and -# mod_proxy. The default TraceEnable on permits TRACE requests per RFC 2616, -# which disallows any request body to accompany the request. TraceEnable off -# causes the core server and mod_proxy to return a 405 (Method not allowed) -# error to the client. -# For security reasons this is turned off by default. (bug #240680) -TraceEnable off - -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -ServerSignature On - -# HostnameLookups: Log the names of clients or just their IP addresses -# e.g., www.apache.org (on) or 204.62.129.132 (off). -# The default is off because it'd be overall better for the net if people -# had to knowingly turn this feature on, since enabling it means that -# each client request will result in AT LEAST one lookup request to the -# nameserver. -HostnameLookups Off - -# EnableMMAP and EnableSendfile: On systems that support it, -# memory-mapping or the sendfile syscall is used to deliver -# files. This usually improves server performance, but must -# be turned off when serving from networked-mounted -# filesystems or if support for these functions is otherwise -# broken on your system. -EnableMMAP On -EnableSendfile Off - -# FileETag: Configures the file attributes that are used to create -# the ETag (entity tag) response header field when the document is -# based on a static file. (The ETag value is used in cache management -# to save network bandwidth.) -FileETag MTime Size - -# ContentDigest: This directive enables the generation of Content-MD5 -# headers as defined in RFC1864 respectively RFC2616. -# The Content-MD5 header provides an end-to-end message integrity -# check (MIC) of the entity-body. A proxy or client may check this -# header for detecting accidental modification of the entity-body -# in transit. -# Note that this can cause performance problems on your server since -# the message digest is computed on every request (the values are -# not cached). -# Content-MD5 is only sent for documents served by the core, and not -# by any module. For example, SSI documents, output from CGI scripts, -# and byte range responses do not have this header. -ContentDigest Off - -# ErrorLog: The location of the error log file. -# If you do not specify an ErrorLog directive within a -# container, error messages relating to that virtual host will be -# logged here. If you *do* define an error logfile for a -# container, that host's errors will be logged there and not here. -ErrorLog /var/log/apache2/error_log - -# LogLevel: Control the number of messages logged to the error_log. -# Possible values include: debug, info, notice, warn, error, crit, -# alert, emerg. -LogLevel warn - -# We configure the "default" to be a very restrictive set of features. - - Options FollowSymLinks - AllowOverride None - Require all denied - - -# DirectoryIndex: sets the file that Apache will serve if a directory -# is requested. -# -# The index.html.var file (a type-map) is used to deliver content- -# negotiated documents. The MultiViews Options can be used for the -# same purpose, but it is much slower. -# -# Do not change this entry unless you know what you are doing. - - DirectoryIndex index.html index.html.var - - -# The following lines prevent .htaccess and .htpasswd files from being -# viewed by Web clients. - - Require all denied - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf deleted file mode 100644 index 61479fa53..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf +++ /dev/null @@ -1,57 +0,0 @@ -# The configuration below implements multi-language error documents through -# content-negotiation. - -# Customizable error responses come in three flavors: -# 1) plain text 2) local redirects 3) external redirects -# Some examples: -#ErrorDocument 500 "The server made a boo boo." -#ErrorDocument 404 /missing.html -#ErrorDocument 404 "/cgi-bin/missing_handler.pl" -#ErrorDocument 402 http://www.example.com/subscription_info.html - -# Required modules: mod_alias, mod_include, mod_negotiation -# We use Alias to redirect any /error/HTTP_.html.var response to -# our collection of by-error message multi-language collections. We use -# includes to substitute the appropriate text. -# You can modify the messages' appearance without changing any of the -# default HTTP_.html.var files by adding the line: -# Alias /error/include/ "/your/include/path/" -# which allows you to create your own set of files by starting with the -# /var/www/localhost/error/include/ files and copying them to /your/include/path/, -# even on a per-VirtualHost basis. The default include files will display -# your Apache version number and your ServerAdmin email address regardless -# of the setting of ServerSignature. - - -Alias /error/ "/usr/share/apache2/error/" - - - AllowOverride None - Options IncludesNoExec - AddOutputFilter Includes html - AddHandler type-map var - Require all granted - LanguagePriority en cs de es fr it ja ko nl pl pt-br ro sv tr - ForceLanguagePriority Prefer Fallback - - -ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var -ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var -ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var -ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var -ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var -ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var -ErrorDocument 410 /error/HTTP_GONE.html.var -ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var -ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var -ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var -ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var -ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var -ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var -ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var -ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var -ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var -ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf deleted file mode 100644 index c429bf94c..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf +++ /dev/null @@ -1,133 +0,0 @@ -# Settings for hosting different languages. - -# DefaultLanguage and AddLanguage allows you to specify the language of -# a document. You can then use content negotiation to give a browser a -# file in a language the user can understand. -# -# Specify a default language. This means that all data -# going out without a specific language tag (see below) will -# be marked with this one. You probably do NOT want to set -# this unless you are sure it is correct for all cases. -# -# It is generally better to not mark a page as -# being a certain language than marking it with the wrong -# language! -# -# DefaultLanguage nl -# -# Note 1: The suffix does not have to be the same as the language -# keyword --- those with documents in Polish (whose net-standard -# language code is pl) may wish to use "AddLanguage pl .po" to -# avoid the ambiguity with the common suffix for perl scripts. -# -# Note 2: The example entries below illustrate that in some cases -# the two character 'Language' abbreviation is not identical to -# the two character 'Country' code for its country, -# E.g. 'Danmark/dk' versus 'Danish/da'. -# -# Note 3: In the case of 'ltz' we violate the RFC by using a three char -# specifier. There is 'work in progress' to fix this and get -# the reference data for rfc1766 cleaned up. -# -# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) -# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) -# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) -# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) -# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) -# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) -AddLanguage ca .ca -AddLanguage cs .cz .cs -AddLanguage da .dk -AddLanguage de .de -AddLanguage el .el -AddLanguage en .en -AddLanguage eo .eo -AddLanguage es .es -AddLanguage et .et -AddLanguage fr .fr -AddLanguage he .he -AddLanguage hr .hr -AddLanguage it .it -AddLanguage ja .ja -AddLanguage ko .ko -AddLanguage ltz .ltz -AddLanguage nl .nl -AddLanguage nn .nn -AddLanguage no .no -AddLanguage pl .po -AddLanguage pt .pt -AddLanguage pt-BR .pt-br -AddLanguage ru .ru -AddLanguage sv .sv -AddLanguage zh-CN .zh-cn -AddLanguage zh-TW .zh-tw - -# LanguagePriority allows you to give precedence to some languages -# in case of a tie during content negotiation. -# -# Just list the languages in decreasing order of preference. We have -# more or less alphabetized them here. You probably want to change this. -LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW - -# ForceLanguagePriority allows you to serve a result page rather than -# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) -# [in case no accepted languages matched the available variants] -ForceLanguagePriority Prefer Fallback - -# Commonly used filename extensions to character sets. You probably -# want to avoid clashes with the language extensions, unless you -# are good at carefully testing your setup after each change. -# See http://www.iana.org/assignments/character-sets for the -# official list of charset names and their respective RFCs. -AddCharset us-ascii.ascii .us-ascii -AddCharset ISO-8859-1 .iso8859-1 .latin1 -AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen -AddCharset ISO-8859-3 .iso8859-3 .latin3 -AddCharset ISO-8859-4 .iso8859-4 .latin4 -AddCharset ISO-8859-5 .iso8859-5 .cyr .iso-ru -AddCharset ISO-8859-6 .iso8859-6 .arb .arabic -AddCharset ISO-8859-7 .iso8859-7 .grk .greek -AddCharset ISO-8859-8 .iso8859-8 .heb .hebrew -AddCharset ISO-8859-9 .iso8859-9 .latin5 .trk -AddCharset ISO-8859-10 .iso8859-10 .latin6 -AddCharset ISO-8859-13 .iso8859-13 -AddCharset ISO-8859-14 .iso8859-14 .latin8 -AddCharset ISO-8859-15 .iso8859-15 .latin9 -AddCharset ISO-8859-16 .iso8859-16 .latin10 -AddCharset ISO-2022-JP .iso2022-jp .jis -AddCharset ISO-2022-KR .iso2022-kr .kis -AddCharset ISO-2022-CN .iso2022-cn .cis -AddCharset Big5.Big5 .big5 .b5 -AddCharset cn-Big5 .cn-big5 -# For russian, more than one charset is used (depends on client, mostly): -AddCharset WINDOWS-1251 .cp-1251 .win-1251 -AddCharset CP866 .cp866 -AddCharset KOI8 .koi8 -AddCharset KOI8-E .koi8-e -AddCharset KOI8-r .koi8-r .koi8-ru -AddCharset KOI8-U .koi8-u -AddCharset KOI8-ru .koi8-uk .ua -AddCharset ISO-10646-UCS-2 .ucs2 -AddCharset ISO-10646-UCS-4 .ucs4 -AddCharset UTF-7 .utf7 -AddCharset UTF-8 .utf8 -AddCharset UTF-16 .utf16 -AddCharset UTF-16BE .utf16be -AddCharset UTF-16LE .utf16le -AddCharset UTF-32 .utf32 -AddCharset UTF-32BE .utf32be -AddCharset UTF-32LE .utf32le -AddCharset euc-cn .euc-cn -AddCharset euc-gb .euc-gb -AddCharset euc-jp .euc-jp -AddCharset euc-kr .euc-kr -# Not sure how euc-tw got in - IANA doesn't list it??? -AddCharset EUC-TW .euc-tw -AddCharset gb2312 .gb2312 .gb -AddCharset iso-10646-ucs-2 .ucs-2 .iso-10646-ucs-2 -AddCharset iso-10646-ucs-4 .ucs-4 .iso-10646-ucs-4 -AddCharset shift_jis .shift_jis .sjis - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf deleted file mode 100644 index 10bf48317..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf +++ /dev/null @@ -1,85 +0,0 @@ - - - - -# We include the /icons/ alias for FancyIndexed directory listings. If -# you do not use FancyIndexing, you may comment this out. -Alias /icons/ "/usr/share/apache2/icons/" - - - Options Indexes MultiViews - AllowOverride None - Require all granted - - - -# Directives controlling the display of server-generated directory listings. -# -# To see the listing of a directory, the Options directive for the -# directory must include "Indexes", and the directory must not contain -# a file matching those listed in the DirectoryIndex directive. - -# IndexOptions: Controls the appearance of server-generated directory -# listings. -IndexOptions FancyIndexing VersionSort - -# AddIcon* directives tell the server which icon to show for different -# files or filename extensions. These are only displayed for -# FancyIndexed directories. -AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip - -AddIconByType (TXT,/icons/text.gif) text/* -AddIconByType (IMG,/icons/image2.gif) image/* -AddIconByType (SND,/icons/sound2.gif) audio/* -AddIconByType (VID,/icons/movie.gif) video/* - -AddIcon /icons/binary.gif .bin .exe -AddIcon /icons/binhex.gif .hqx -AddIcon /icons/tar.gif .tar -AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv -AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip -AddIcon /icons/a.gif .ps .ai .eps -AddIcon /icons/layout.gif .html .shtml .htm .pdf -AddIcon /icons/text.gif .txt -AddIcon /icons/c.gif .c -AddIcon /icons/p.gif .pl .py -AddIcon /icons/f.gif .for -AddIcon /icons/dvi.gif .dvi -AddIcon /icons/uuencoded.gif .uu -AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl -AddIcon /icons/tex.gif .tex -AddIcon /icons/bomb.gif core - -AddIcon /icons/back.gif .. -AddIcon /icons/hand.right.gif README -AddIcon /icons/folder.gif ^^DIRECTORY^^ -AddIcon /icons/blank.gif ^^BLANKICON^^ - -# DefaultIcon is which icon to show for files which do not have an icon -# explicitly set. -DefaultIcon /icons/unknown.gif - -# AddDescription allows you to place a short description after a file in -# server-generated indexes. These are only displayed for FancyIndexed -# directories. -# Format: AddDescription "description" filename - -#AddDescription "GZIP compressed document" .gz -#AddDescription "tar archive" .tar -#AddDescription "GZIP compressed tar archive" .tgz - -# ReadmeName is the name of the README file the server will look for by -# default, and append to directory listings. - -# HeaderName is the name of a file which should be prepended to -# directory indexes. -ReadmeName README.html -HeaderName HEADER.html - -# IndexIgnore is a set of filenames which directory indexing should ignore -# and not include in the listing. Shell-style wildcarding is permitted. -IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf deleted file mode 100644 index 2cd32c477..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf +++ /dev/null @@ -1,10 +0,0 @@ - -# Allow remote server configuration reports, with the URL of -# http://servername/server-info - - SetHandler server-info - Require local - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf deleted file mode 100644 index ce0238eee..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf +++ /dev/null @@ -1,35 +0,0 @@ - -# The following directives define some format nicknames for use with -# a CustomLog directive (see below). -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined -LogFormat "%h %l %u %t \"%r\" %>s %b" common - -LogFormat "%{Referer}i -> %U" referer -LogFormat "%{User-Agent}i" agent -LogFormat "%v %h %l %u %t \"%r\" %>s %b %T" script -LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" VLOG=%{VLOG}e" vhost - - -# You need to enable mod_logio.c to use %I and %O -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio -LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" vhostio - - -# The location and format of the access logfile (Common Logfile Format). -# If you do not define any access logfiles within a -# container, they will be logged here. Contrariwise, if you *do* -# define per- access logfiles, transactions will be -# logged therein and *not* in this file. -CustomLog /var/log/apache2/access_log common - -# If you would like to have agent and referer logfiles, -# uncomment the following directives. -#CustomLog /var/log/apache2/referer_log referer -#CustomLog /var/log/apache2/agent_logs agent - -# If you prefer a logfile with access, agent, and referer information -# (Combined Logfile Format) you can use the following directive. -#CustomLog /var/log/apache2/access_log combined - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf deleted file mode 100644 index fb8a9a5d5..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf +++ /dev/null @@ -1,46 +0,0 @@ - -# TypesConfig points to the file containing the list of mappings from -# filename extension to MIME-type. -TypesConfig /etc/mime.types - -# AddType allows you to add to or override the MIME configuration -# file specified in TypesConfig for specific file types. -#AddType application/x-gzip .tgz - -# AddEncoding allows you to have certain browsers uncompress -# information on the fly. Note: Not all browsers support this. -#AddEncoding x-compress .Z -#AddEncoding x-gzip .gz .tgz - -# If the AddEncoding directives above are commented-out, then you -# probably should define those extensions to indicate media types: -AddType application/x-compress .Z -AddType application/x-gzip .gz .tgz - -# AddHandler allows you to map certain file extensions to "handlers": -# actions unrelated to filetype. These can be either built into the server -# or added with the Action directive (see below) - -# To use CGI scripts outside of ScriptAliased directories: -# (You will also need to add "ExecCGI" to the "Options" directive.) -#AddHandler cgi-script .cgi - -# For type maps (negotiated resources): -#AddHandler type-map var - -# Filters allow you to process content before it is sent to the client. -# -# To parse .shtml files for server-side includes (SSI): -# (You will also need to add "Includes" to the "Options" directive.) -#AddType text/html .shtml -#AddOutputFilter INCLUDES .shtml - - - -# The mod_mime_magic module allows the server to use various hints from the -# contents of the file itself to determine its type. The MIMEMagicFile -# directive tells the module where the hint definitions are located. -MIMEMagicFile /etc/apache2/magic - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf deleted file mode 100644 index ed8b3c7cb..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf +++ /dev/null @@ -1,15 +0,0 @@ - -# Allow server status reports generated by mod_status, -# with the URL of http://servername/server-status - - SetHandler server-status - Require local - - -# ExtendedStatus controls whether Apache will generate "full" status -# information (ExtendedStatus On) or just basic information (ExtendedStatus -# Off) when the "server-status" handler is called. -ExtendedStatus On - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf deleted file mode 100644 index 0087126c4..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf +++ /dev/null @@ -1,32 +0,0 @@ -# Settings for user home directories - -# UserDir: The name of the directory that is appended onto a user's home -# directory if a ~user request is received. Note that you must also set -# the default access control for these directories, as in the example below. -UserDir public_html - -# Control access to UserDir directories. The following is an example -# for a site where these directories are restricted to read-only. - - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - - Require all granted - - - Require all denied - - - -# Suexec isn't really required to run cgi-scripts, but it's a really good -# idea if you have multiple users serving websites... - - - Options ExecCGI - SetHandler cgi-script - - - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf deleted file mode 100644 index bcb9b6b47..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf +++ /dev/null @@ -1,99 +0,0 @@ -# Server-Pool Management (MPM specific) - -# PidFile: The file in which the server should record its process -# identification number when it starts. -# -# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING -PidFile /run/apache2.pid - -# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. -# Mutex file:/run/apache_mpm_mutex - -# Only one of the below sections will be relevant on your -# installed httpd. Use "/usr/sbin/apache2 -l" to find out the -# active mpm. - -# common MPM configuration -# These configuration directives apply to all MPMs -# -# StartServers: Number of child server processes created at startup -# MaxRequestWorkers: Maximum number of child processes to serve requests -# MaxConnectionsPerChild: Limit on the number of connections that an individual -# child server will handle during its life - - -# prefork MPM -# This is the default MPM if USE=-threads -# -# MinSpareServers: Minimum number of idle child server processes -# MaxSpareServers: Maximum number of idle child server processes - - StartServers 5 - MinSpareServers 5 - MaxSpareServers 10 - MaxRequestWorkers 150 - MaxConnectionsPerChild 10000 - - -# worker MPM -# This is the default MPM if USE=threads -# -# MinSpareThreads: Minimum number of idle threads available to handle request spikes -# MaxSpareThreads: Maximum number of idle threads -# ThreadsPerChild: Number of threads created by each child process - - StartServers 2 - MinSpareThreads 25 - MaxSpareThreads 75 - ThreadsPerChild 25 - MaxRequestWorkers 150 - MaxConnectionsPerChild 10000 - - -# event MPM -# -# MinSpareThreads: Minimum number of idle threads available to handle request spikes -# MaxSpareThreads: Maximum number of idle threads -# ThreadsPerChild: Number of threads created by each child process - - StartServers 2 - MinSpareThreads 25 - MaxSpareThreads 75 - ThreadsPerChild 25 - MaxRequestWorkers 150 - MaxConnectionsPerChild 10000 - - -# peruser MPM -# -# MinSpareProcessors: Minimum number of idle child server processes -# MinProcessors: Minimum number of processors per virtual host -# MaxProcessors: Maximum number of processors per virtual host -# ExpireTimeout: Maximum idle time before a child is killed, 0 to disable -# Multiplexer: Specify a Multiplexer child configuration. -# Processor: Specify a user and group for a specific child process - - MinSpareProcessors 2 - MinProcessors 2 - MaxProcessors 10 - MaxRequestWorkers 150 - MaxConnectionsPerChild 1000 - ExpireTimeout 1800 - - Multiplexer nobody nobody - Processor apache apache - - -# itk MPM -# -# MinSpareServers: Minimum number of idle child server processes -# MaxSpareServers: Maximum number of idle child server processes - - StartServers 5 - MinSpareServers 5 - MaxSpareServers 10 - MaxRequestWorkers 150 - MaxConnectionsPerChild 10000 - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf deleted file mode 100644 index 520d9fd82..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf +++ /dev/null @@ -1,10 +0,0 @@ - -# 128MB cache for objects < 2MB -CacheEnable mem / -MCacheSize 131072 -MCacheMaxObjectCount 1000 -MCacheMinObjectSize 1 -MCacheMaxObjectSize 2097152 - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf deleted file mode 100644 index f51de4641..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf +++ /dev/null @@ -1,67 +0,0 @@ -# Note: The following must must be present to support -# starting without SSL on platforms with no /dev/random equivalent -# but a statically compiled-in mod_ssl. - -SSLRandomSeed startup builtin -SSLRandomSeed connect builtin - - - -# This is the Apache server configuration file providing SSL support. -# It contains the configuration directives to instruct the server how to -# serve pages over an https connection. For detailing information about these -# directives see - -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. - -## Pseudo Random Number Generator (PRNG): -# Configure one or more sources to seed the PRNG of the SSL library. -# The seed data should be of good random quality. -# WARNING! On some platforms /dev/random blocks if not enough entropy -# is available. This means you then cannot use the /dev/random device -# because it would lead to very long connection times (as long as -# it requires to make more entropy available). But usually those -# platforms additionally provide a /dev/urandom device which doesn't -# block. So, if available, use this one instead. Read the mod_ssl User -# Manual for more details. -#SSLRandomSeed startup file:/dev/random 512 -#SSLRandomSeed startup file:/dev/urandom 512 -#SSLRandomSeed connect file:/dev/random 512 -#SSLRandomSeed connect file:/dev/urandom 512 - -## SSL Global Context: -# All SSL configuration in this context applies both to the main server and -# all SSL-enabled virtual hosts. - -# Some MIME-types for downloading Certificates and CRLs - - AddType application/x-x509-ca-cert .crt - AddType application/x-pkcs7-crl .crl - - -## Pass Phrase Dialog: -# Configure the pass phrase gathering process. The filtering dialog program -# (`builtin' is a internal terminal dialog) has to provide the pass phrase on -# stdout. -SSLPassPhraseDialog builtin - -## Inter-Process Session Cache: -# Configure the SSL Session Cache: First the mechanism to use and second the -# expiring timeout (in seconds). -#SSLSessionCache dbm:/run/ssl_scache -SSLSessionCache shmcb:/run/ssl_scache(512000) -SSLSessionCacheTimeout 300 - -## Semaphore: -# Configure the path to the mutual exclusion semaphore the SSL engine uses -# internally for inter-process synchronization. -Mutex file:/run/apache_ssl_mutex ssl-cache - -## SSL Compression: -# Known to be vulnerable thus disabled by default (bug #507324). -SSLCompression off - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf deleted file mode 100644 index e4c9454e0..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf +++ /dev/null @@ -1,9 +0,0 @@ - - - # enable debugging for this module - #LogLevel http2:info - - #Enable HTTP/2 support - Protocols h2 h2c http/1.1 - - diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf deleted file mode 100644 index 36f6b9cca..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf +++ /dev/null @@ -1,19 +0,0 @@ - -DavLockDB "/var/lib/dav/lockdb" - -# The following directives disable redirects on non-GET requests for -# a directory that does not include the trailing slash. This fixes a -# problem with several clients that do not appropriately handle -# redirects for folders with DAV methods. - -BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully -BrowserMatch "MS FrontPage" redirect-carefully -BrowserMatch "^WebDrive" redirect-carefully -BrowserMatch "^WebDAVFS/1.[012345678]" redirect-carefully -BrowserMatch "^gnome-vfs/1.0" redirect-carefully -BrowserMatch "^XML Spy" redirect-carefully -BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf deleted file mode 100644 index 883061fee..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf +++ /dev/null @@ -1,18 +0,0 @@ -# Examples below are taken from the online documentation -# Refer to: -# http://localhost/manual/mod/mod_ldap.html -# http://localhost/manual/mod/mod_auth_ldap.html - -LDAPSharedCacheSize 200000 -LDAPCacheEntries 1024 -LDAPCacheTTL 600 -LDAPOpCacheEntries 1024 -LDAPOpCacheTTL 600 - - - SetHandler ldap-status - Require local - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf deleted file mode 100644 index bb395473c..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf +++ /dev/null @@ -1,191 +0,0 @@ - - - -# see bug #178966 why this is in here - -# When we also provide SSL we have to listen to the HTTPS port -# Note: Configurations that use IPv6 but not IPv4-mapped addresses need two -# Listen directives: "Listen [::]:443" and "Listen 0.0.0.0:443" -Listen 443 - - - ServerName localhost - Include /etc/apache2/vhosts.d/default_vhost.include - ErrorLog /var/log/apache2/ssl_error_log - - - TransferLog /var/log/apache2/ssl_access_log - - - ## SSL Engine Switch: - # Enable/Disable SSL for this virtual host. - SSLEngine on - - ## SSLProtocol: - # Don't use SSLv2 anymore as it's considered to be broken security-wise. - # Also disable SSLv3 as most modern browsers are capable of TLS. - SSLProtocol ALL -SSLv2 -SSLv3 - - ## SSL Cipher Suite: - # List the ciphers that the client is permitted to negotiate. - # See the mod_ssl documentation for a complete list. - # This list of ciphers is recommended by mozilla and was stripped off - # its RC4 ciphers. (bug #506924) - SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:HIGH:!RC4:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK - - ## SSLHonorCipherOrder: - # Prefer the server's cipher preference order as the client may have a - # weak default order. - SSLHonorCipherOrder On - - ## Server Certificate: - # Point SSLCertificateFile at a PEM encoded certificate. If the certificate - # is encrypted, then you will be prompted for a pass phrase. Note that a - # kill -HUP will prompt again. Keep in mind that if you have both an RSA - # and a DSA certificate you can configure both in parallel (to also allow - # the use of DSA ciphers, etc.) - SSLCertificateFile /etc/ssl/apache2/server.crt - - ## Server Private Key: - # If the key is not combined with the certificate, use this directive to - # point at the key file. Keep in mind that if you've both a RSA and a DSA - # private key you can configure both in parallel (to also allow the use of - # DSA ciphers, etc.) - SSLCertificateKeyFile /etc/ssl/apache2/server.key - - ## Server Certificate Chain: - # Point SSLCertificateChainFile at a file containing the concatenation of - # PEM encoded CA certificates which form the certificate chain for the - # server certificate. Alternatively the referenced file can be the same as - # SSLCertificateFile when the CA certificates are directly appended to the - # server certificate for convinience. - #SSLCertificateChainFile /etc/ssl/apache2/ca.crt - - ## Certificate Authority (CA): - # Set the CA certificate verification path where to find CA certificates - # for client authentication or alternatively one huge file containing all - # of them (file must be PEM encoded). - # Note: Inside SSLCACertificatePath you need hash symlinks to point to the - # certificate files. Use the provided Makefile to update the hash symlinks - # after changes. - #SSLCACertificatePath /etc/ssl/apache2/ssl.crt - #SSLCACertificateFile /etc/ssl/apache2/ca-bundle.crt - - ## Certificate Revocation Lists (CRL): - # Set the CA revocation path where to find CA CRLs for client authentication - # or alternatively one huge file containing all of them (file must be PEM - # encoded). - # Note: Inside SSLCARevocationPath you need hash symlinks to point to the - # certificate files. Use the provided Makefile to update the hash symlinks - # after changes. - #SSLCARevocationPath /etc/ssl/apache2/ssl.crl - #SSLCARevocationFile /etc/ssl/apache2/ca-bundle.crl - - ## Client Authentication (Type): - # Client certificate verification type and depth. Types are none, optional, - # require and optional_no_ca. Depth is a number which specifies how deeply - # to verify the certificate issuer chain before deciding the certificate is - # not valid. - #SSLVerifyClient require - #SSLVerifyDepth 10 - - ## Access Control: - # With SSLRequire you can do per-directory access control based on arbitrary - # complex boolean expressions containing server variable checks and other - # lookup directives. The syntax is a mixture between C and Perl. See the - # mod_ssl documentation for more details. - # - # #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ - # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ - # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ - # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ - # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ - # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ - # - - ## SSL Engine Options: - # Set various options for the SSL engine. - - ## FakeBasicAuth: - # Translate the client X.509 into a Basic Authorisation. This means that the - # standard Auth/DBMAuth methods can be used for access control. The user - # name is the `one line' version of the client's X.509 certificate. - # Note that no password is obtained from the user. Every entry in the user - # file needs this password: `xxj31ZMTZzkVA'. - - ## ExportCertData: - # This exports two additional environment variables: SSL_CLIENT_CERT and - # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the server - # (always existing) and the client (only existing when client - # authentication is used). This can be used to import the certificates into - # CGI scripts. - - ## StdEnvVars: - # This exports the standard SSL/TLS related `SSL_*' environment variables. - # Per default this exportation is switched off for performance reasons, - # because the extraction step is an expensive operation and is usually - # useless for serving static content. So one usually enables the exportation - # for CGI and SSI requests only. - - ## StrictRequire: - # This denies access when "SSLRequireSSL" or "SSLRequire" applied even under - # a "Satisfy any" situation, i.e. when it applies access is denied and no - # other module can change it. - - ## OptRenegotiate: - # This enables optimized SSL connection renegotiation handling when SSL - # directives are used in per-directory context. - #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - - SSLOptions +StdEnvVars - - - - SSLOptions +StdEnvVars - - - ## SSL Protocol Adjustments: - # The safe and default but still SSL/TLS standard compliant shutdown - # approach is that mod_ssl sends the close notify alert but doesn't wait - # for the close notify alert from client. When you need a different - # shutdown approach you can use one of the following variables: - - ## ssl-unclean-shutdown: - # This forces an unclean shutdown when the connection is closed, i.e. no - # SSL close notify alert is send or allowed to received. This violates the - # SSL/TLS standard but is needed for some brain-dead browsers. Use this when - # you receive I/O errors because of the standard approach where mod_ssl - # sends the close notify alert. - - ## ssl-accurate-shutdown: - # This forces an accurate shutdown when the connection is closed, i.e. a - # SSL close notify alert is send and mod_ssl waits for the close notify - # alert of the client. This is 100% SSL/TLS standard compliant, but in - # practice often causes hanging connections with brain-dead browsers. Use - # this only for browsers where you know that their SSL implementation works - # correctly. - # Notice: Most problems of broken clients are also related to the HTTP - # keep-alive facility, so you usually additionally want to disable - # keep-alive for those clients, too. Use variable "nokeepalive" for this. - # Similarly, one has to force some clients to use HTTP/1.0 to workaround - # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and - # "force-response-1.0" for this. - - BrowserMatch ".*MSIE.*" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 - - - ## Per-Server Logging: - # The home of a custom SSL log file. Use this when you want a compact - # non-error SSL logfile on a virtual host basis. - - CustomLog /var/log/apache2/ssl_request_log \ - "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" - - - - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf deleted file mode 100644 index b9766b5f1..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf +++ /dev/null @@ -1,45 +0,0 @@ -# Virtual Hosts -# -# If you want to maintain multiple domains/hostnames on your -# machine you can setup VirtualHost containers for them. Most configurations -# use only name-based virtual hosts so the server doesn't need to worry about -# IP addresses. This is indicated by the asterisks in the directives below. -# -# Please see the documentation at -# -# for further details before you try to setup virtual hosts. -# -# You may use the command line option '-S' to verify your virtual host -# configuration. - - -# see bug #178966 why this is in here - -# Listen: Allows you to bind Apache to specific IP addresses and/or -# ports, instead of the default. See also the -# directive. -# -# Change this to Listen on specific IP addresses as shown below to -# prevent Apache from glomming onto all bound IP addresses. -# -#Listen 12.34.56.78:80 -Listen 80 - -# When virtual hosts are enabled, the main host defined in the default -# httpd.conf configuration will go away. We redefine it here so that it is -# still available. -# -# If you disable this vhost by removing -D DEFAULT_VHOST from -# /etc/conf.d/apache2, the first defined virtual host elsewhere will be -# the default. - - ServerName localhost - Include /etc/apache2/vhosts.d/default_vhost.include - - - ServerEnvironment apache apache - - - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include deleted file mode 100644 index af6ece85b..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include +++ /dev/null @@ -1,71 +0,0 @@ -# ServerAdmin: Your address, where problems with the server should be -# e-mailed. This address appears on some server-generated pages, such -# as error documents. e.g. admin@your-domain.com -ServerAdmin root@localhost - -# DocumentRoot: The directory out of which you will serve your -# documents. By default, all requests are taken from this directory, but -# symbolic links and aliases may be used to point to other locations. -# -# If you change this to something that isn't under /var/www then suexec -# will no longer work. -DocumentRoot "/var/www/localhost/htdocs" - -# This should be changed to whatever you set DocumentRoot to. - - # Possible values for the Options directive are "None", "All", - # or any combination of: - # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews - # - # Note that "MultiViews" must be named *explicitly* --- "Options All" - # doesn't give it to you. - # - # The Options directive is both complicated and important. Please see - # http://httpd.apache.org/docs/2.4/mod/core.html#options - # for more information. - Options Indexes FollowSymLinks - - # AllowOverride controls what directives may be placed in .htaccess files. - # It can be "All", "None", or any combination of the keywords: - # Options FileInfo AuthConfig Limit - AllowOverride All - - # Controls who can get stuff from this server. - Require all granted - - - - # Redirect: Allows you to tell clients about documents that used to - # exist in your server's namespace, but do not anymore. The client - # will make a new request for the document at its new location. - # Example: - # Redirect permanent /foo http://www.example.com/bar - - # Alias: Maps web paths into filesystem paths and is used to - # access content that does not live under the DocumentRoot. - # Example: - # Alias /webpath /full/filesystem/path - # - # If you include a trailing / on /webpath then the server will - # require it to be present in the URL. You will also likely - # need to provide a section to allow access to - # the filesystem path. - - # ScriptAlias: This controls which directories contain server scripts. - # ScriptAliases are essentially the same as Aliases, except that - # documents in the target directory are treated as applications and - # run by the server when requested rather than as documents sent to the - # client. The same rules about trailing "/" apply to ScriptAlias - # directives as to Alias. - ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/" - - -# "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased -# CGI directory exists, if you have that configured. - - AllowOverride None - Options None - Require all granted - - -# vim: ts=4 filetype=apache diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf deleted file mode 100644 index 41de4d236..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf +++ /dev/null @@ -1,7 +0,0 @@ - - ServerName gentoo.example.com - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 deleted file mode 100644 index b7ecb4f2a..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 +++ /dev/null @@ -1,74 +0,0 @@ -# /etc/conf.d/apache2: config file for /etc/init.d/apache2 - -# When you install a module it is easy to activate or deactivate the modules -# and other features of apache using the APACHE2_OPTS line. Every module should -# install a configuration in /etc/apache2/modules.d. In that file will have an -# directive where NNN is the option to enable that module. -# -# Here are the options available in the default configuration: -# -# AUTH_DIGEST Enables mod_auth_digest -# AUTHNZ_LDAP Enables authentication through mod_ldap (available if USE=ldap) -# CACHE Enables mod_cache -# DAV Enables mod_dav -# ERRORDOCS Enables default error documents for many languages. -# INFO Enables mod_info, a useful module for debugging -# LANGUAGE Enables content-negotiation based on language and charset. -# LDAP Enables mod_ldap (available if USE=ldap) -# MANUAL Enables /manual/ to be the apache manual (available if USE=docs) -# MEM_CACHE Enables default configuration mod_mem_cache -# PROXY Enables mod_proxy -# SSL Enables SSL (available if USE=ssl) -# STATUS Enabled mod_status, a useful module for statistics -# SUEXEC Enables running CGI scripts (in USERDIR) through suexec. -# USERDIR Enables /~username mapping to /home/username/public_html -# -# -# The following two options provide the default virtual host for the HTTP and -# HTTPS protocol. YOU NEED TO ENABLE AT LEAST ONE OF THEM, otherwise apache -# will not listen for incomming connections on the approriate port. -# -# DEFAULT_VHOST Enables name-based virtual hosts, with the default -# virtual host being in /var/www/localhost/htdocs -# SSL_DEFAULT_VHOST Enables default vhost for SSL (you should enable this -# when you enable SSL) -# -APACHE2_OPTS="-D DEFAULT_VHOST -D INFO -D SSL -D SSL_DEFAULT_VHOST -D LANGUAGE" - -# Extended options for advanced uses of Apache ONLY -# You don't need to edit these unless you are doing crazy Apache stuff -# As not having them set correctly, or feeding in an incorrect configuration -# via them will result in Apache failing to start -# YOU HAVE BEEN WARNED. - -# PID file -#PIDFILE=/var/run/apache2.pid - -# timeout for startup/shutdown checks -#TIMEOUT=10 - -# ServerRoot setting -#SERVERROOT=/usr/lib64/apache2 - -# Configuration file location -# - If this does NOT start with a '/', then it is treated relative to -# $SERVERROOT by Apache -#CONFIGFILE=/etc/apache2/httpd.conf - -# Location to log startup errors to -# They are normally dumped to your terminal. -#STARTUPERRORLOG="/var/log/apache2/startuperror.log" - -# A command that outputs a formatted text version of the HTML at the URL -# of the command line. Designed for lynx, however other programs may work. -#LYNX="lynx -dump" - -# The URL to your server's mod_status status page. -# Required for status and fullstatus -#STATUSURL="http://localhost/server-status" - -# Method to use when reloading the server -# Valid options are 'restart' and 'graceful' -# See http://httpd.apache.org/docs/2.2/stopping.html for information on -# what they do and how they differ. -#RELOAD_TYPE="graceful" diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites b/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites deleted file mode 100644 index 7f0b3a8b3..000000000 --- a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites +++ /dev/null @@ -1,3 +0,0 @@ -vhosts.d/gentoo.example.com.conf, gentoo.example.com -vhosts.d/00_default_vhost.conf, localhost -vhosts.d/00_default_ssl_vhost.conf, localhost diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/certbot_apache/tests/util.py deleted file mode 100644 index 3349e0ed8..000000000 --- a/certbot-apache/certbot_apache/tests/util.py +++ /dev/null @@ -1,239 +0,0 @@ -"""Common utilities for certbot_apache.""" -import shutil -import sys -import unittest - -import augeas -import josepy as jose -import mock -import zope.component - -from certbot.compat import os -from certbot.display import util as display_util -from certbot.plugins import common -from certbot.tests import util as test_util - -from certbot_apache._internal import configurator -from certbot_apache._internal import entrypoint -from certbot_apache._internal import obj - - -class ApacheTest(unittest.TestCase): - - def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", - config_root="debian_apache_2_4/multiple_vhosts/apache2", - vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): - # pylint: disable=arguments-differ - super(ApacheTest, self).setUp() - - self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( - test_dir=test_dir, - pkg="certbot_apache.tests") - - self.config_path = os.path.join(self.temp_dir, config_root) - self.vhost_path = os.path.join(self.temp_dir, vhost_root) - - self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( - "rsa512_key.pem")) - - self.config = get_apache_configurator(self.config_path, vhost_root, - self.config_dir, self.work_dir) - - # Make sure all vhosts in sites-enabled are symlinks (Python packaging - # does not preserve symlinks) - sites_enabled = os.path.join(self.config_path, "sites-enabled") - if not os.path.exists(sites_enabled): - return - - for vhost_basename in os.listdir(sites_enabled): - # Keep the one non-symlink test vhost in place - if vhost_basename == "non-symlink.conf": - continue - vhost = os.path.join(sites_enabled, vhost_basename) - if not os.path.islink(vhost): # pragma: no cover - os.remove(vhost) - target = os.path.join( - os.path.pardir, "sites-available", vhost_basename) - os.symlink(target, vhost) - - def tearDown(self): - shutil.rmtree(self.temp_dir) - shutil.rmtree(self.config_dir) - shutil.rmtree(self.work_dir) - - -class ParserTest(ApacheTest): - - def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", - config_root="debian_apache_2_4/multiple_vhosts/apache2", - vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): - super(ParserTest, self).setUp(test_dir, config_root, vhost_root) - - zope.component.provideUtility(display_util.FileDisplay(sys.stdout, - False)) - - from certbot_apache._internal.parser import ApacheParser - self.aug = augeas.Augeas( - flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) - with mock.patch("certbot_apache._internal.parser.ApacheParser." - "update_runtime_variables"): - self.parser = ApacheParser( - self.config_path, self.vhost_path, configurator=self.config) - - -def get_apache_configurator( - config_path, vhost_path, - config_dir, work_dir, version=(2, 4, 7), - os_info="generic", - conf_vhost_path=None): - """Create an Apache Configurator with the specified options. - - :param conf: Function that returns binary paths. self.conf in Configurator - - """ - backups = os.path.join(work_dir, "backups") - mock_le_config = mock.MagicMock( - apache_server_root=config_path, - apache_vhost_root=None, - apache_le_vhost_ext="-le-ssl.conf", - apache_challenge_location=config_path, - apache_enmod=None, - backup_dir=backups, - config_dir=config_dir, - http01_port=80, - temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS"), - work_dir=work_dir) - - with mock.patch("certbot_apache._internal.configurator.util.run_script"): - with mock.patch("certbot_apache._internal.configurator.util." - "exe_exists") as mock_exe_exists: - mock_exe_exists.return_value = True - with mock.patch("certbot_apache._internal.parser.ApacheParser." - "update_runtime_variables"): - try: - config_class = entrypoint.OVERRIDE_CLASSES[os_info] - except KeyError: - config_class = configurator.ApacheConfigurator - config = config_class(config=mock_le_config, name="apache", - version=version) - if not conf_vhost_path: - config_class.OS_DEFAULTS["vhost_root"] = vhost_path - else: - # Custom virtualhost path was requested - config.config.apache_vhost_root = conf_vhost_path - config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"] - config.prepare() - return config - - -def get_vh_truth(temp_dir, config_name): - """Return the ground truth for the specified directory.""" - if config_name == "debian_apache_2_4/multiple_vhosts": - prefix = os.path.join( - temp_dir, config_name, "apache2/sites-enabled") - - aug_pre = "/files" + prefix - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "encryption-example.conf"), - os.path.join(aug_pre, "encryption-example.conf/Virtualhost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "encryption-example.demo"), - obj.VirtualHost( - os.path.join(prefix, "default-ssl.conf"), - os.path.join(aug_pre, - "default-ssl.conf/IfModule/VirtualHost"), - set([obj.Addr.fromstring("_default_:443")]), True, True), - obj.VirtualHost( - os.path.join(prefix, "000-default.conf"), - os.path.join(aug_pre, "000-default.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80"), - obj.Addr.fromstring("[::]:80")]), - False, True, "ip-172-30-0-17"), - obj.VirtualHost( - os.path.join(prefix, "certbot.conf"), - os.path.join(aug_pre, "certbot.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - "certbot.demo", aliases=["www.certbot.demo"]), - obj.VirtualHost( - os.path.join(prefix, "mod_macro-example.conf"), - os.path.join(aug_pre, - "mod_macro-example.conf/Macro/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - modmacro=True), - obj.VirtualHost( - os.path.join(prefix, "default-ssl-port-only.conf"), - os.path.join(aug_pre, ("default-ssl-port-only.conf/" - "IfModule/VirtualHost")), - set([obj.Addr.fromstring("_default_:443")]), True, True), - obj.VirtualHost( - os.path.join(prefix, "wildcard.conf"), - os.path.join(aug_pre, "wildcard.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - "ip-172-30-0-17", aliases=["*.blue.purple.com"]), - obj.VirtualHost( - os.path.join(prefix, "ocsp-ssl.conf"), - os.path.join(aug_pre, "ocsp-ssl.conf/IfModule/VirtualHost"), - set([obj.Addr.fromstring("10.2.3.4:443")]), True, True, - "ocspvhost.com"), - obj.VirtualHost( - os.path.join(prefix, "non-symlink.conf"), - os.path.join(aug_pre, "non-symlink.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - "nonsym.link"), - obj.VirtualHost( - os.path.join(prefix, "default-ssl-port-only.conf"), - os.path.join(aug_pre, - "default-ssl-port-only.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), True, True, ""), - obj.VirtualHost( - os.path.join(temp_dir, config_name, - "apache2/apache2.conf"), - "/files" + os.path.join(temp_dir, config_name, - "apache2/apache2.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - "vhost.in.rootconf"), - obj.VirtualHost( - os.path.join(prefix, "duplicatehttp.conf"), - os.path.join(aug_pre, "duplicatehttp.conf/VirtualHost"), - set([obj.Addr.fromstring("10.2.3.4:80")]), False, True, - "duplicate.example.com"), - obj.VirtualHost( - os.path.join(prefix, "duplicatehttps.conf"), - os.path.join(aug_pre, "duplicatehttps.conf/IfModule/VirtualHost"), - set([obj.Addr.fromstring("10.2.3.4:443")]), True, True, - "duplicate.example.com")] - return vh_truth - if config_name == "debian_apache_2_4/multi_vhosts": - prefix = os.path.join( - temp_dir, config_name, "apache2/sites-available") - aug_pre = "/files" + prefix - vh_truth = [ - obj.VirtualHost( - os.path.join(prefix, "default.conf"), - os.path.join(aug_pre, "default.conf/VirtualHost[1]"), - set([obj.Addr.fromstring("*:80")]), - False, True, "ip-172-30-0-17"), - obj.VirtualHost( - os.path.join(prefix, "default.conf"), - os.path.join(aug_pre, "default.conf/VirtualHost[2]"), - set([obj.Addr.fromstring("*:80")]), - False, True, "banana.vomit.com"), - obj.VirtualHost( - os.path.join(prefix, "multi-vhost.conf"), - os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[1]"), - set([obj.Addr.fromstring("*:80")]), - False, True, "1.multi.vhost.tld"), - obj.VirtualHost( - os.path.join(prefix, "multi-vhost.conf"), - os.path.join(aug_pre, "multi-vhost.conf/IfModule/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, "2.multi.vhost.tld"), - obj.VirtualHost( - os.path.join(prefix, "multi-vhost.conf"), - os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[2]"), - set([obj.Addr.fromstring("*:80")]), - False, True, "3.multi.vhost.tld")] - return vh_truth - return None # pragma: no cover diff --git a/certbot-apache/tests/apache-conf-files/NEEDED.txt b/certbot-apache/tests/apache-conf-files/NEEDED.txt new file mode 100644 index 000000000..c3606fefe --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/NEEDED.txt @@ -0,0 +1,6 @@ +Issues for which some kind of test case should be constructable, but we do not +currently have one: + +https://github.com/certbot/certbot/issues/1213 +https://github.com/certbot/certbot/issues/1602 + diff --git a/certbot-apache/tests/apache-conf-files/apache-conf-test b/certbot-apache/tests/apache-conf-files/apache-conf-test new file mode 100755 index 000000000..4838a6eee --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/apache-conf-test @@ -0,0 +1,88 @@ +#!/bin/bash + +# 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 + +function CleanupExit() { + echo control c, exiting tests... + if [ "$f" != "" ] ; then + Cleanup + fi + exit 1 +} + +function Setup() { + if [ "$APPEND_APACHECONF" = "" ] ; then + sudo cp "$f" "$EA"/sites-available/ + sudo ln -sf "$EA/sites-available/$f" "$EA/sites-enabled/$f" + echo " + + ServerName example.com + DocumentRoot /tmp/ + ErrorLog /tmp/error.log + CustomLog /tmp/requests.log combined +" | sudo tee $EA/sites-available/throwaway-example.conf >/dev/null + sudo ln -sf $EA/sites-available/throwaway-example.conf $EA/sites-enabled/throwaway-example.conf + else + TMP="/tmp/`basename \"$APPEND_APACHECONF\"`.$$" + sudo cp -a "$APPEND_APACHECONF" "$TMP" + sudo bash -c "cat \"$f\" >> \"$APPEND_APACHECONF\"" + fi +} + +function Cleanup() { + if [ "$APPEND_APACHECONF" = "" ] ; then + sudo rm /etc/apache2/sites-{enabled,available}/"$f" + sudo rm $EA/sites-available/throwaway-example.conf + sudo rm $EA/sites-enabled/throwaway-example.conf + else + sudo mv "$TMP" "$APPEND_APACHECONF" + fi +} + +# if our environment asks us to enable modules, do our best! +if [ "$1" = --debian-modules ] ; then + sudo apt-get install -y apache2 + sudo apt-get install -y libapache2-mod-wsgi + sudo apt-get install -y libapache2-mod-macro + + for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do + echo -n enabling $mod + sudo a2enmod $mod + 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 | $CERTBOT_CMD 2>&1` + if echo $RESULT | grep -Eq \("Which names would you like"\|"mod_macro is not yet"\) ; then + echo passed + else + echo failed + echo $RESULT + echo + echo + FAILS=`expr $FAILS + 1` + fi + Cleanup +done +if [ "$FAILS" -ne 0 ] ; then + exit 1 +fi +exit 0 diff --git a/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py b/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py new file mode 100755 index 000000000..68bd6287d --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +""" +This executable script wraps the apache-conf-test bash script, in order to setup a pebble instance +before its execution. Directory URL is passed through the SERVER environment variable. +""" +import os +import subprocess +import sys + +from certbot_integration_tests.utils import acme_server + +SCRIPT_DIRNAME = os.path.dirname(__file__) + + +def main(args=None): + if not args: + args = sys.argv[1:] + with acme_server.ACMEServer('pebble', [], False) as acme_xdist: + environ = os.environ.copy() + environ['SERVER'] = acme_xdist['directory_url'] + command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')] + command.extend(args) + return subprocess.call(command, env=environ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf b/certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf new file mode 100644 index 000000000..7d97b23d0 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf @@ -0,0 +1,52 @@ + + ServerAdmin webmaster@localhost + ServerAlias www.example.com + ServerName example.com + DocumentRoot /var/www/example.com/www/ + SSLEngine on + + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$ + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + + Options FollowSymLinks + AllowOverride All + + + Options Indexes FollowSymLinks MultiViews + AllowOverride All + Order allow,deny + allow from all + # This directive allows us to have apache2's default start page + # in /apache2-default/, but still have / go to the right place + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + ErrorLog /var/log/apache2/error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog /var/log/apache2/access.log combined + ServerSignature On + + Alias /apache_doc/ "/usr/share/doc/" + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order deny,allow + Deny from all + Allow from 127.0.0.0/255.0.0.0 ::1/128 + + + diff --git a/certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf new file mode 100644 index 000000000..444f0dade --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf @@ -0,0 +1,295 @@ + + AllowOverride None + Require all denied + + + + DocumentRoot /var/www/sjau.ch/web + + ServerName sjau.ch + ServerAlias www.sjau.ch + ServerAdmin webmaster@sjau.ch + + ErrorLog /var/log/ispconfig/httpd/sjau.ch/error.log + + Alias /error/ "/var/www/sjau.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client1/web2/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web2 client1 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web2 client1 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client1/web2/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + + + DocumentRoot /var/www/sjau.ch/web + + ServerName sjau.ch + ServerAlias www.sjau.ch + ServerAdmin webmaster@sjau.ch + + ErrorLog /var/log/ispconfig/httpd/sjau.ch/error.log + + Alias /error/ "/var/www/sjau.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client1/web2/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web2 client1 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web2/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web2 client1 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client1/web2/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + diff --git a/certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf new file mode 100644 index 000000000..0388abc2c --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf @@ -0,0 +1,593 @@ + + AllowOverride None + Require all denied + + + + DocumentRoot /var/www/ensemen.ch/web + + ServerName ensemen.ch + ServerAlias www.ensemen.ch + ServerAdmin webmaster@ensemen.ch + + ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log + + Alias /error/ "/var/www/ensemen.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web17 client4 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web17 client4 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client4/web17/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + + + DocumentRoot /var/www/ensemen.ch/web + + ServerName ensemen.ch + ServerAlias www.ensemen.ch + ServerAdmin webmaster@ensemen.ch + + ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log + + Alias /error/ "/var/www/ensemen.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + SSLEngine on + SSLProtocol All -SSLv2 -SSLv3 + SSLCertificateFile /var/www/clients/client4/web17/ssl/ensemen.ch.crt + SSLCertificateKeyFile /var/www/clients/client4/web17/ssl/ensemen.ch.key + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web17 client4 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web17 client4 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client4/web17/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + + + DocumentRoot /var/www/ensemen.ch/web + + ServerName ensemen.ch + ServerAlias www.ensemen.ch + ServerAdmin webmaster@ensemen.ch + + ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log + + Alias /error/ "/var/www/ensemen.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web17 client4 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web17 client4 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client4/web17/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + + + DocumentRoot /var/www/ensemen.ch/web + + ServerName ensemen.ch + ServerAlias www.ensemen.ch + ServerAdmin webmaster@ensemen.ch + + ErrorLog /var/log/ispconfig/httpd/ensemen.ch/error.log + + Alias /error/ "/var/www/ensemen.ch/web/error/" + ErrorDocument 400 /error/400.html + ErrorDocument 401 /error/401.html + ErrorDocument 403 /error/403.html + ErrorDocument 404 /error/404.html + ErrorDocument 405 /error/405.html + ErrorDocument 500 /error/500.html + ErrorDocument 502 /error/502.html + ErrorDocument 503 /error/503.html + + + SSLEngine on + SSLProtocol All -SSLv2 -SSLv3 + SSLCertificateFile /var/www/clients/client4/web17/ssl/ensemen.ch.crt + SSLCertificateKeyFile /var/www/clients/client4/web17/ssl/ensemen.ch.key + + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + # Clear PHP settings of this website + + SetHandler None + + Options +FollowSymLinks + AllowOverride All + Require all granted + + + + + Options +ExecCGI + + RubyRequire apache/ruby-run + #RubySafeLevel 0 + AddType text/html .rb + AddType text/html .rbx + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + SetHandler ruby-object + RubyHandler Apache::RubyRun.instance + + + + + + + + SetHandler mod_python + + PythonHandler mod_python.publisher + PythonDebug On + + + + # cgi enabled + + Require all granted + + ScriptAlias /cgi-bin/ /var/www/clients/client4/web17/cgi-bin/ + + SetHandler cgi-script + + # suexec enabled + + SuexecUserGroup web17 client4 + + # php as fast-cgi enabled + # For config options see: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + + IdleTimeout 300 + ProcessLifeTime 3600 + # MaxProcessCount 1000 + DefaultMinClassProcessCount 0 + DefaultMaxClassProcessCount 100 + IPCConnectTimeout 3 + IPCCommTimeout 600 + BusyTimeout 3600 + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + SetHandler fcgid-script + + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php3 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php4 + FCGIWrapper /var/www/php-fcgi-scripts/web17/.php-fcgi-starter .php5 + Options +ExecCGI + AllowOverride All + Require all granted + + + + # add support for apache mpm_itk + + AssignUserId web17 client4 + + + + # Do not execute PHP files in webdav directory + + + SecRuleRemoveById 960015 + SecRuleRemoveById 960032 + + + SetHandler None + + + DavLockDB /var/www/clients/client4/web17/tmp/DavLock + # DO NOT REMOVE THE COMMENTS! + # IF YOU REMOVE THEM, WEBDAV WILL NOT WORK ANYMORE! + # WEBDAV BEGIN + # WEBDAV END + + + + diff --git a/certbot-apache/tests/apache-conf-files/passing/1626-1531.conf b/certbot-apache/tests/apache-conf-files/passing/1626-1531.conf new file mode 100644 index 000000000..1622a57df --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/1626-1531.conf @@ -0,0 +1,37 @@ + + ServerAdmin denver@ossguy.com + ServerName c-beta.ossguy.com + + Alias /robots.txt /home/denver/www/c-beta.ossguy.com/static/robots.txt + Alias /favicon.ico /home/denver/www/c-beta.ossguy.com/static/favicon.ico + + AliasMatch /(.*\.css) /home/denver/www/c-beta.ossguy.com/static/$1 + AliasMatch /(.*\.js) /home/denver/www/c-beta.ossguy.com/static/$1 + AliasMatch /(.*\.png) /home/denver/www/c-beta.ossguy.com/static/$1 + AliasMatch /(.*\.gif) /home/denver/www/c-beta.ossguy.com/static/$1 + AliasMatch /(.*\.jpg) /home/denver/www/c-beta.ossguy.com/static/$1 + + WSGIScriptAlias / /home/denver/www/c-beta.ossguy.com/django.wsgi + WSGIDaemonProcess c-beta-ossguy user=www-data group=www-data home=/var/www processes=5 threads=10 maximum-requests=1000 umask=0007 display-name=c-beta-ossguy + WSGIProcessGroup c-beta-ossguy + WSGIApplicationGroup %{GLOBAL} + + DocumentRoot /home/denver/www/c-beta.ossguy.com/static + + + Options -Indexes +FollowSymLinks -MultiViews + Require all granted + AllowOverride None + + + + Options +Indexes +FollowSymLinks -MultiViews + Require all granted + AllowOverride None + + + # Custom log file locations + LogLevel warn + ErrorLog /tmp/error.log + CustomLog /tmp/access.log combined + diff --git a/certbot-apache/tests/apache-conf-files/passing/README.modules b/certbot-apache/tests/apache-conf-files/passing/README.modules new file mode 100644 index 000000000..32c3ef019 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/README.modules @@ -0,0 +1,6 @@ +# Modules required to parse these conf files: +ssl +rewrite +macro +wsgi +deflate diff --git a/certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf b/certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf new file mode 100644 index 000000000..73a9b746c --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf @@ -0,0 +1,14 @@ + + ServerAdmin root@localhost + ServerName anarcat.wiki.orangeseeds.org:80 + + + UserDir disabled + + RewriteEngine On + RewriteRule ^/(.*) http\:\/\/anarc\.at\/$1 [L,R,NE] + + ErrorLog /var/log/apache2/1531error.log + LogLevel warn + CustomLog /var/log/apache2/1531access.log combined + diff --git a/certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf b/certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf new file mode 100644 index 000000000..4c3fa2af1 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf @@ -0,0 +1,428 @@ +# --------------------------------------------------------------- +# Core ModSecurity Rule Set ver.2.2.6 +# Copyright (C) 2006-2012 Trustwave All rights reserved. +# +# The OWASP ModSecurity Core Rule Set is distributed under +# Apache Software License (ASL) version 2 +# Please see the enclosed LICENCE file for full details. +# --------------------------------------------------------------- + + +# +# -- [[ Recommended Base Configuration ]] ------------------------------------------------- +# +# The configuration directives/settings in this file are used to control +# the OWASP ModSecurity CRS. These settings do **NOT** configure the main +# ModSecurity settings such as: +# +# - SecRuleEngine +# - SecRequestBodyAccess +# - SecAuditEngine +# - SecDebugLog +# +# You should use the modsecurity.conf-recommended file that comes with the +# ModSecurity source code archive. +# +# Ref: http://mod-security.svn.sourceforge.net/viewvc/mod-security/m2/trunk/modsecurity.conf-recommended +# + + +# +# -- [[ Rule Version ]] ------------------------------------------------------------------- +# +# Rule version data is added to the "Producer" line of Section H of the Audit log: +# +# - Producer: ModSecurity for Apache/2.7.0-rc1 (http://www.modsecurity.org/); OWASP_CRS/2.2.4. +# +# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecComponentSignature +# +#SecComponentSignature "OWASP_CRS/2.2.6" + + +# +# -- [[ Modes of Operation: Self-Contained vs. Collaborative Detection ]] ----------------- +# +# Each detection rule uses the "block" action which will inherit the SecDefaultAction +# specified below. Your settings here will determine which mode of operation you use. +# +# -- [[ Self-Contained Mode ]] -- +# Rules inherit the "deny" disruptive action. The first rule that matches will block. +# +# -- [[ Collaborative Detection Mode ]] -- +# This is a "delayed blocking" mode of operation where each matching rule will inherit +# the "pass" action and will only contribute to anomaly scores. Transactional blocking +# can be applied +# +# -- [[ Alert Logging Control ]] -- +# You have three options - +# +# - To log to both the Apache error_log and ModSecurity audit_log file use: "log" +# - To log *only* to the ModSecurity audit_log file use: "nolog,auditlog" +# - To log *only* to the Apache error_log file use: "log,noauditlog" +# +# Ref: http://blog.spiderlabs.com/2010/11/advanced-topic-of-the-week-traditional-vs-anomaly-scoring-detection-modes.html +# Ref: https://sourceforge.net/apps/mediawiki/mod-security/index.php?title=Reference_Manual#SecDefaultAction +# +#SecDefaultAction "phase:1,deny,log" + + +# +# -- [[ Collaborative Detection Severity Levels ]] ---------------------------------------- +# +# These are the default scoring points for each severity level. You may +# adjust these to you liking. These settings will be used in macro expansion +# in the rules to increment the anomaly scores when rules match. +# +# These are the default Severity ratings (with anomaly scores) of the individual rules - +# +# - 2: Critical - Anomaly Score of 5. +# Is the highest severity level possible without correlation. It is +# normally generated by the web attack rules (40 level files). +# - 3: Error - Anomaly Score of 4. +# Is generated mostly from outbound leakage rules (50 level files). +# - 4: Warning - Anomaly Score of 3. +# Is generated by malicious client rules (35 level files). +# - 5: Notice - Anomaly Score of 2. +# Is generated by the Protocol policy and anomaly files. +# +#SecAction \ + "id:'900001', \ + phase:1, \ + t:none, \ + setvar:tx.critical_anomaly_score=5, \ + setvar:tx.error_anomaly_score=4, \ + setvar:tx.warning_anomaly_score=3, \ + setvar:tx.notice_anomaly_score=2, \ + nolog, \ + pass" + + +# +# -- [[ Collaborative Detection Scoring Threshold Levels ]] ------------------------------ +# +# These variables are used in macro expansion in the 49 inbound blocking and 59 +# outbound blocking files. +# +# **MUST HAVE** ModSecurity v2.5.12 or higher to use macro expansion in numeric +# operators. If you have an earlier version, edit the 49/59 files directly to +# set the appropriate anomaly score levels. +# +# You should set the score to the proper threshold you would prefer. If set to "5" +# it will work similarly to previous Mod CRS rules and will create an event in the error_log +# file if there are any rules that match. If you would like to lessen the number of events +# generated in the error_log file, you should increase the anomaly score threshold to +# something like "20". This would only generate an event in the error_log file if +# there are multiple lower severity rule matches or if any 1 higher severity item matches. +# +#SecAction \ + "id:'900002', \ + phase:1, \ + t:none, \ + setvar:tx.inbound_anomaly_score_level=5, \ + nolog, \ + pass" + + +#SecAction \ + "id:'900003', \ + phase:1, \ + t:none, \ + setvar:tx.outbound_anomaly_score_level=4, \ + nolog, \ + pass" + + +# +# -- [[ Collaborative Detection Blocking ]] ----------------------------------------------- +# +# This is a collaborative detection mode where each rule will increment an overall +# anomaly score for the transaction. The scores are then evaluated in the following files: +# +# Inbound anomaly score - checked in the modsecurity_crs_49_inbound_blocking.conf file +# Outbound anomaly score - checked in the modsecurity_crs_59_outbound_blocking.conf file +# +# If you want to use anomaly scoring mode, then uncomment this line. +# +#SecAction \ + "id:'900004', \ + phase:1, \ + t:none, \ + setvar:tx.anomaly_score_blocking=on, \ + nolog, \ + pass" + + +# +# -- [[ GeoIP Database ]] ----------------------------------------------------------------- +# +# There are some rulesets that need to inspect the GEO data of the REMOTE_ADDR data. +# +# You must first download the MaxMind GeoIP Lite City DB - +# +# http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz +# +# You then need to define the proper path for the SecGeoLookupDb directive +# +# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html +# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html +# +#SecGeoLookupDb /opt/modsecurity/lib/GeoLiteCity.dat + +# +# -- [[ Regression Testing Mode ]] -------------------------------------------------------- +# +# If you are going to run the regression testing mode, you should uncomment the +# following rule. It will enable DetectionOnly mode for the SecRuleEngine and +# will enable Response Header tagging so that the client testing script can see +# which rule IDs have matched. +# +# You must specify the your source IP address where you will be running the tests +# from. +# +#SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" \ + "id:'900005', \ + phase:1, \ + t:none, \ + ctl:ruleEngine=DetectionOnly, \ + setvar:tx.regression_testing=1, \ + nolog, \ + pass" + + +# +# -- [[ HTTP Policy Settings ]] ---------------------------------------------------------- +# +# Set the following policy settings here and they will be propagated to the 23 rules +# file (modsecurity_common_23_request_limits.conf) by using macro expansion. +# If you run into false positives, you can adjust the settings here. +# +# Only the max number of args is uncommented by default as there are a high rate +# of false positives. Uncomment the items you wish to set. +# +# +# -- Maximum number of arguments in request limited +#SecAction \ + "id:'900006', \ + phase:1, \ + t:none, \ + setvar:tx.max_num_args=255, \ + nolog, \ + pass" + +# +# -- Limit argument name length +#SecAction \ + "id:'900007', \ + phase:1, \ + t:none, \ + setvar:tx.arg_name_length=100, \ + nolog, \ + pass" + +# +# -- Limit value name length +#SecAction \ + "id:'900008', \ + phase:1, \ + t:none, \ + setvar:tx.arg_length=400, \ + nolog, \ + pass" + +# +# -- Limit arguments total length +#SecAction \ + "id:'900009', \ + phase:1, \ + t:none, \ + setvar:tx.total_arg_length=64000, \ + nolog, \ + pass" + +# +# -- Individual file size is limited +#SecAction \ + "id:'900010', \ + phase:1, \ + t:none, \ + setvar:tx.max_file_size=1048576, \ + nolog, \ + pass" + +# +# -- Combined file size is limited +#SecAction \ + "id:'900011', \ + phase:1, \ + t:none, \ + setvar:tx.combined_file_sizes=1048576, \ + nolog, \ + pass" + + +# +# Set the following policy settings here and they will be propagated to the 30 rules +# file (modsecurity_crs_30_http_policy.conf) by using macro expansion. +# If you run into false positives, you can adjust the settings here. +# +#SecAction \ + "id:'900012', \ + phase:1, \ + t:none, \ + setvar:'tx.allowed_methods=GET HEAD POST OPTIONS', \ + setvar:'tx.allowed_request_content_type=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/x-amf', \ + setvar:'tx.allowed_http_versions=HTTP/0.9 HTTP/1.0 HTTP/1.1', \ + setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/', \ + setvar:'tx.restricted_headers=/Proxy-Connection/ /Lock-Token/ /Content-Range/ /Translate/ /via/ /if/', \ + nolog, \ + pass" + + +# +# -- [[ Content Security Policy (CSP) Settings ]] ----------------------------------------- +# +# The purpose of these settings is to send CSP response headers to +# Mozilla FireFox users so that you can enforce how dynamic content +# is used. CSP usage helps to prevent XSS attacks against your users. +# +# Reference Link: +# +# https://developer.mozilla.org/en/Security/CSP +# +# Uncomment this SecAction line if you want use CSP enforcement. +# You need to set the appropriate directives and settings for your site/domain and +# and activate the CSP file in the experimental_rules directory. +# +# Ref: http://blog.spiderlabs.com/2011/04/modsecurity-advanced-topic-of-the-week-integrating-content-security-policy-csp.html +# +#SecAction \ + "id:'900013', \ + phase:1, \ + t:none, \ + setvar:tx.csp_report_only=1, \ + setvar:tx.csp_report_uri=/csp_violation_report, \ + setenv:'csp_policy=allow \'self\'; img-src *.yoursite.com; media-src *.yoursite.com; style-src *.yoursite.com; frame-ancestors *.yoursite.com; script-src *.yoursite.com; report-uri %{tx.csp_report_uri}', \ + nolog, \ + pass" + + +# +# -- [[ Brute Force Protection ]] --------------------------------------------------------- +# +# If you are using the Brute Force Protection rule set, then uncomment the following +# lines and set the following variables: +# - Protected URLs: resources to protect (e.g. login pages) - set to your login page +# - Burst Time Slice Interval: time interval window to monitor for bursts +# - Request Threshold: request # threshold to trigger a burst +# - Block Period: temporary block timeout +# +#SecAction \ + "id:'900014', \ + phase:1, \ + t:none, \ + setvar:'tx.brute_force_protected_urls=/login.jsp /partner_login.php', \ + setvar:'tx.brute_force_burst_time_slice=60', \ + setvar:'tx.brute_force_counter_threshold=10', \ + setvar:'tx.brute_force_block_timeout=300', \ + nolog, \ + pass" + + +# +# -- [[ DoS Protection ]] ---------------------------------------------------------------- +# +# If you are using the DoS Protection rule set, then uncomment the following +# lines and set the following variables: +# - Burst Time Slice Interval: time interval window to monitor for bursts +# - Request Threshold: request # threshold to trigger a burst +# - Block Period: temporary block timeout +# +#SecAction \ + "id:'900015', \ + phase:1, \ + t:none, \ + setvar:'tx.dos_burst_time_slice=60', \ + setvar:'tx.dos_counter_threshold=100', \ + setvar:'tx.dos_block_timeout=600', \ + nolog, \ + pass" + + +# +# -- [[ Check UTF encoding ]] ----------------------------------------------------------- +# +# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise +# it will result in false positives. +# +# Uncomment this line if your site uses UTF8 encoding +#SecAction \ + "id:'900016', \ + phase:1, \ + t:none, \ + setvar:tx.crs_validate_utf8_encoding=1, \ + nolog, \ + pass" + + +# +# -- [[ Enable XML Body Parsing ]] ------------------------------------------------------- +# +# The rules in this file will trigger the XML parser upon an XML request +# +# Initiate XML Processor in case of xml content-type +# +#SecRule REQUEST_HEADERS:Content-Type "text/xml" \ + "id:'900017', \ + phase:1, \ + t:none,t:lowercase, \ + nolog, \ + pass, \ + chain" + #SecRule REQBODY_PROCESSOR "!@streq XML" \ + "ctl:requestBodyProcessor=XML" + + +# +# -- [[ Global and IP Collections ]] ----------------------------------------------------- +# +# Create both Global and IP collections for rules to use +# There are some CRS rules that assume that these two collections +# have already been initiated. +# +#SecRule REQUEST_HEADERS:User-Agent "^(.*)$" \ + "id:'900018', \ + phase:1, \ + t:none,t:sha1,t:hexEncode, \ + setvar:tx.ua_hash=%{matched_var}, \ + nolog, \ + pass" + + +#SecRule REQUEST_HEADERS:x-forwarded-for "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" \ + "id:'900019', \ + phase:1, \ + t:none, \ + capture, \ + setvar:tx.real_ip=%{tx.1}, \ + nolog, \ + pass" + + +#SecRule &TX:REAL_IP "!@eq 0" \ + "id:'900020', \ + phase:1, \ + t:none, \ + initcol:global=global, \ + initcol:ip=%{tx.real_ip}_%{tx.ua_hash}, \ + nolog, \ + pass" + + +#SecRule &TX:REAL_IP "@eq 0" \ + "id:'900021', \ + phase:1, \ + t:none, \ + initcol:global=global, \ + initcol:ip=%{remote_addr}_%{tx.ua_hash}, \ + nolog, \ + pass" diff --git a/certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf b/certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf new file mode 100644 index 000000000..4733ffa4a --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf @@ -0,0 +1,116 @@ +# +# Apache/PHP/Drupal settings: +# + +# Protect files and directories from prying eyes. + + Order allow,deny + + +# Don't show directory listings for URLs which map to a directory. +Options -Indexes + +# Follow symbolic links in this directory. +Options +FollowSymLinks + +# Make Drupal handle any 404 errors. +ErrorDocument 404 /index.php + +# Force simple error message for requests for non-existent favicon.ico. + + # There is no end quote below, for compatibility with Apache 1.3. + ErrorDocument 404 "The requested file favicon.ico was not found. + + +# Set the default handler. +DirectoryIndex index.php + +# Override PHP settings. More in sites/default/settings.php +# but the following cannot be changed at runtime. + +# PHP 4, Apache 1. + + php_value magic_quotes_gpc 0 + php_value register_globals 0 + php_value session.auto_start 0 + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_value mbstring.encoding_translation 0 + + +# PHP 4, Apache 2. + + php_value magic_quotes_gpc 0 + php_value register_globals 0 + php_value session.auto_start 0 + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_value mbstring.encoding_translation 0 + + +# PHP 5, Apache 1 and 2. + + php_value magic_quotes_gpc 0 + php_value register_globals 0 + php_value session.auto_start 0 + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_value mbstring.encoding_translation 0 + + +# Requires mod_expires to be enabled. + + # Enable expirations. + ExpiresActive On + + # Cache all files for 2 weeks after access (A). + ExpiresDefault A1209600 + + + # Do not allow PHP scripts to be cached unless they explicitly send cache + # headers themselves. Otherwise all scripts would have to overwrite the + # headers set by mod_expires if they want another caching behavior. This may + # fail if an error occurs early in the bootstrap process, and it may cause + # problems if a non-Drupal PHP file is installed in a subdirectory. + ExpiresActive Off + + + +# Various rewrite rules. + + RewriteEngine on + + # If your site can be accessed both with and without the 'www.' prefix, you + # can use one of the following settings to redirect users to your preferred + # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: + # + # To redirect all users to access the site WITH the 'www.' prefix, + # (http://example.com/... will be redirected to http://www.example.com/...) + # adapt and uncomment the following: + # RewriteCond %{HTTP_HOST} ^example\.com$ [NC] + # RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301] + # + # To redirect all users to access the site WITHOUT the 'www.' prefix, + # (http://www.example.com/... will be redirected to http://example.com/...) + # uncomment and adapt the following: + # RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] + # RewriteRule ^(.*)$ http://example.com/$1 [L,R=301] + + # Modify the RewriteBase if you are using Drupal in a subdirectory or in a + # VirtualDocumentRoot and the rewrite rules are not working properly. + # For example if your site is at http://example.com/drupal uncomment and + # modify the following line: + # RewriteBase /drupal + # + # If your site is running in a VirtualDocumentRoot at http://example.com/, + # uncomment the following line: + # RewriteBase / + + # Rewrite URLs of the form 'x' to the form 'index.php?q=x'. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !=/favicon.ico + RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] + + +# $Id$ diff --git a/certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf b/certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf new file mode 100644 index 000000000..a1aab7a39 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf @@ -0,0 +1,149 @@ +# +# Apache/PHP/Drupal settings: +# + +# Protect files and directories from prying eyes. + + Order allow,deny + + +# Don't show directory listings for URLs which map to a directory. +Options -Indexes + +# Follow symbolic links in this directory. +Options +FollowSymLinks + +# Make Drupal handle any 404 errors. +ErrorDocument 404 /index.php + +# Set the default handler. +DirectoryIndex index.php index.html index.htm + +# Override PHP settings that cannot be changed at runtime. See +# sites/default/default.settings.php and drupal_environment_initialize() in +# includes/bootstrap.inc for settings that can be changed at runtime. + +# PHP 5, Apache 1 and 2. + + php_flag magic_quotes_gpc off + php_flag magic_quotes_sybase off + php_flag register_globals off + php_flag session.auto_start off + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_flag mbstring.encoding_translation off + + +# Requires mod_expires to be enabled. + + # Enable expirations. + ExpiresActive On + + # Cache all files for 2 weeks after access (A). + ExpiresDefault A1209600 + + + # Do not allow PHP scripts to be cached unless they explicitly send cache + # headers themselves. Otherwise all scripts would have to overwrite the + # headers set by mod_expires if they want another caching behavior. This may + # fail if an error occurs early in the bootstrap process, and it may cause + # problems if a non-Drupal PHP file is installed in a subdirectory. + ExpiresActive Off + + + +# Various rewrite rules. + + RewriteEngine on + + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + + # Make sure Authorization HTTP header is available to PHP + # even when running as CGI or FastCGI. + RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Block access to "hidden" directories whose names begin with a period. This + # includes directories used by version control systems such as Subversion or + # Git to store control files. Files whose names begin with a period, as well + # as the control files used by CVS, are protected by the FilesMatch directive + # above. + # + # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is + # not possible to block access to entire directories from .htaccess, because + # is not allowed here. + # + # If you do not have mod_rewrite installed, you should remove these + # directories from your webroot or otherwise protect them from being + # downloaded. + RewriteRule "(^|/)\." - [F] + + # If your site can be accessed both with and without the 'www.' prefix, you + # can use one of the following settings to redirect users to your preferred + # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: + # + # To redirect all users to access the site WITH the 'www.' prefix, + # (http://example.com/... will be redirected to http://www.example.com/...) + # uncomment the following: + # RewriteCond %{HTTP_HOST} . + # RewriteCond %{HTTP_HOST} !^www\. [NC] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # + # To redirect all users to access the site WITHOUT the 'www.' prefix, + # (http://www.example.com/... will be redirected to http://example.com/...) + # uncomment the following: + # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] + + # Modify the RewriteBase if you are using Drupal in a subdirectory or in a + # VirtualDocumentRoot and the rewrite rules are not working properly. + # For example if your site is at http://example.com/drupal uncomment and + # modify the following line: + # RewriteBase /drupal + # + # If your site is running in a VirtualDocumentRoot at http://example.com/, + # uncomment the following line: + # RewriteBase / + + # Pass all requests not referring directly to files in the filesystem to + # index.php. Clean URLs are handled in drupal_environment_initialize(). + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !=/favicon.ico + RewriteRule ^ index.php [L] + + # Rules to correctly serve gzip compressed CSS and JS files. + # Requires both mod_rewrite and mod_headers to be enabled. + + # Serve gzip compressed CSS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.css $1\.css\.gz [QSA] + + # Serve gzip compressed JS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.js $1\.js\.gz [QSA] + + # Serve correct content types, and prevent mod_deflate double gzip. + RewriteRule .css.gz$ - [T=text/css,E=no-gzip:1] + RewriteRule .js.gz$ - [T=text/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding gzip + # Force proxies to cache gzipped & non-gzipped css/js files separately. + Header append Vary Accept-Encoding + + + + +# Add headers to all responses. + + # Disable content sniffing, since it's an attack vector. + Header always set X-Content-Type-Options nosniff + diff --git a/certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf b/certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf new file mode 100644 index 000000000..1ea53dfab --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf @@ -0,0 +1,2 @@ +RewriteCond %{HTTP:Content-Disposition} \.php [NC] +RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.+/trackback/?\ HTTP/ [NC] diff --git a/certbot-apache/tests/apache-conf-files/passing/example-1755.conf b/certbot-apache/tests/apache-conf-files/passing/example-1755.conf new file mode 100644 index 000000000..260029576 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/example-1755.conf @@ -0,0 +1,36 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName www.example.com + ServerAlias example.com +SetOutputFilter DEFLATE +# Do not attempt to compress the following extensions +SetEnvIfNoCase Request_URI \ +\.(?:gif|jpe?g|png|swf|flv|zip|gz|tar|mp3|mp4|m4v)$ no-gzip dont-vary + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/proof + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/apache-conf-files/passing/example-ssl.conf b/certbot-apache/tests/apache-conf-files/passing/example-ssl.conf new file mode 100644 index 000000000..31deb7647 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/example-ssl.conf @@ -0,0 +1,136 @@ + + ServerName example.com + ServerAlias www.example.com + ServerAdmin webmaster@localhost + + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + # SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + # Server Certificate Chain: + # Point SSLCertificateChainFile at a file containing the + # concatenation of PEM encoded CA certificates which form the + # certificate chain for the server certificate. Alternatively + # the referenced file can be the same as SSLCertificateFile + # when the CA certificates are directly appended to the server + # certificate for convenience. + #SSLCertificateChainFile /etc/apache2/ssl.crt/server-ca.crt + + # Certificate Authority (CA): + # Set the CA certificate verification path where to find CA + # certificates for client authentication or alternatively one + # huge file containing all of them (file must be PEM encoded) + # Note: Inside SSLCACertificatePath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCACertificatePath /etc/ssl/certs/ + #SSLCACertificateFile /etc/apache2/ssl.crt/ca-bundle.crt + + # Certificate Revocation Lists (CRL): + # Set the CA revocation path where to find CA CRLs for client + # authentication or alternatively one huge file containing all + # of them (file must be PEM encoded) + # Note: Inside SSLCARevocationPath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCARevocationPath /etc/apache2/ssl.crl/ + #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl + + # Client Authentication (Type): + # Client certificate verification type and depth. Types are + # none, optional, require and optional_no_ca. Depth is a + # number which specifies how deeply to verify the certificate + # issuer chain before deciding the certificate is not valid. + #SSLVerifyClient require + #SSLVerifyDepth 10 + + # SSL Engine Options: + # Set various options for the SSL engine. + # o FakeBasicAuth: + # Translate the client X.509 into a Basic Authorisation. This means that + # the standard Auth/DBMAuth methods can be used for access control. The + # user name is the `one line' version of the client's X.509 certificate. + # Note that no password is obtained from the user. Every entry in the user + # file needs this password: `xxj31ZMTZzkVA'. + # o ExportCertData: + # This exports two additional environment variables: SSL_CLIENT_CERT and + # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the + # server (always existing) and the client (only existing when client + # authentication is used). This can be used to import the certificates + # into CGI scripts. + # o StdEnvVars: + # This exports the standard SSL/TLS related `SSL_*' environment variables. + # Per default this exportation is switched off for performance reasons, + # because the extraction step is an expensive operation and is usually + # useless for serving static content. So one usually enables the + # exportation for CGI and SSI requests only. + # o OptRenegotiate: + # This enables optimized SSL connection renegotiation handling when SSL + # directives are used in per-directory context. + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + # SSL Protocol Adjustments: + # The safe and default but still SSL/TLS standard compliant shutdown + # approach is that mod_ssl sends the close notify alert but doesn't wait for + # the close notify alert from client. When you need a different shutdown + # approach you can use one of the following variables: + # o ssl-unclean-shutdown: + # This forces an unclean shutdown when the connection is closed, i.e. no + # SSL close notify alert is send or allowed to received. This violates + # the SSL/TLS standard but is needed for some brain-dead browsers. Use + # this when you receive I/O errors because of the standard approach where + # mod_ssl sends the close notify alert. + # o ssl-accurate-shutdown: + # This forces an accurate shutdown when the connection is closed, i.e. a + # SSL close notify alert is send and mod_ssl waits for the close notify + # alert of the client. This is 100% SSL/TLS standard compliant, but in + # practice often causes hanging connections with brain-dead browsers. Use + # this only for browsers where you know that their SSL implementation + # works correctly. + # Notice: Most problems of broken clients are also related to the HTTP + # keep-alive facility, so you usually additionally want to disable + # keep-alive for those clients, too. Use variable "nokeepalive" for this. + # Similarly, one has to force some clients to use HTTP/1.0 to workaround + # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and + # "force-response-1.0" for this. + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/apache-conf-files/passing/example.conf b/certbot-apache/tests/apache-conf-files/passing/example.conf new file mode 100644 index 000000000..60bdeead6 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/example.conf @@ -0,0 +1,32 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName www.example.com + ServerAlias example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt new file mode 100644 index 000000000..73dc64223 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt @@ -0,0 +1,222 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration +# + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the Mutex documentation (available +# at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +#ServerRoot "/etc/apache2" + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +Mutex file:${APACHE_LOCK_DIR} default + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# +# Options Indexes FollowSymLinks +# AllowOverride None +# Require all granted +# + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + + +# +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +#LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%t \"%r\" %>s %O \"%{User-Agent}i\"" vhost_combined + +#LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +#LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "- %t \"%r\" %>s %b" noip + +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +#IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf new file mode 100644 index 000000000..dbfae3765 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf @@ -0,0 +1,67 @@ +#LoadModule ssl_module modules/mod_ssl.so + +Listen 4443 + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName www.eiserneketten.de + + SSLEngine on + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log noip + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + Options FollowSymLinks + AllowOverride None + Order Deny,Allow + #Deny from All + + + Alias / /eiserneketten/pages/eiserneketten.html +SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem +SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key +SSLCertificateChainFile /etc/ssl/certs/ssl-cert-snakeoil.pem +Include /etc/letsencrypt/options-ssl-apache.conf + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet + +# +# Directives to allow use of AWStats as a CGI +# +Alias /awstatsclasses "/usr/local/awstats/wwwroot/classes/" +Alias /awstatscss "/usr/local/awstats/wwwroot/css/" +Alias /awstatsicons "/usr/local/awstats/wwwroot/icon/" +ScriptAlias /awstats/ "/usr/local/awstats/wwwroot/cgi-bin/" + +# +# This is to permit URL access to scripts/files in AWStats directory. +# + + Options None + AllowOverride None + Order allow,deny + Allow from all + + diff --git a/certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf b/certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf new file mode 100644 index 000000000..f257dd9a8 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf @@ -0,0 +1,21 @@ + + + WSGIDaemonProcess _graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120 user=www-data group=www-data + WSGIProcessGroup _graphite + WSGIImportScript /usr/share/graphite-web/graphite.wsgi process-group=_graphite application-group=%{GLOBAL} + WSGIScriptAlias / /usr/share/graphite-web/graphite.wsgi + + Alias /content/ /usr/share/graphite-web/static/ + + SetHandler None + + + ErrorLog ${APACHE_LOG_DIR}/graphite-web_error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/graphite-web_access.log combined + + diff --git a/certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf new file mode 100644 index 000000000..ad988dc05 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf @@ -0,0 +1,9 @@ + +DocumentRoot /tmp +ServerName example.com +ServerAlias www.example.com +CustomLog ${APACHE_LOG_DIR}/example.log combined + + AllowOverride All + + diff --git a/certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf new file mode 100644 index 000000000..e2b4fd3da --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf @@ -0,0 +1,18 @@ + +DocumentRoot /tmp +ServerName example.com +ServerAlias www.example.com +CustomLog ${APACHE_LOG_DIR}/example.log combined + + AllowOverride All + + + SSLEngine on + + SSLHonorCipherOrder On + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" + + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + diff --git a/certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf new file mode 100644 index 000000000..f2d2ecbea --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf @@ -0,0 +1,9 @@ + +DocumentRoot /tmp +ServerName example.com +ServerAlias www.example.com +CustomLog ${APACHE_LOG_DIR}/example.log combined + + AllowOverride All + + diff --git a/certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf new file mode 100644 index 000000000..f5b7a2b45 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf @@ -0,0 +1,18 @@ + +DocumentRoot /tmp +ServerName example.com +ServerAlias www.example.com +CustomLog ${APACHE_LOG_DIR}/example.log combined + + AllowOverride All + + + SSLEngine on + + SSLHonorCipherOrder On + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" + + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + diff --git a/certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf b/certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf new file mode 100644 index 000000000..7d97b23d0 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf @@ -0,0 +1,52 @@ + + ServerAdmin webmaster@localhost + ServerAlias www.example.com + ServerName example.com + DocumentRoot /var/www/example.com/www/ + SSLEngine on + + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRS$ + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + + Options FollowSymLinks + AllowOverride All + + + Options Indexes FollowSymLinks MultiViews + AllowOverride All + Order allow,deny + allow from all + # This directive allows us to have apache2's default start page + # in /apache2-default/, but still have / go to the right place + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + ErrorLog /var/log/apache2/error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog /var/log/apache2/access.log combined + ServerSignature On + + Alias /apache_doc/ "/usr/share/doc/" + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order deny,allow + Deny from all + Allow from 127.0.0.0/255.0.0.0 ::1/128 + + + diff --git a/certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf b/certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf new file mode 100644 index 000000000..d327c9421 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf @@ -0,0 +1,33 @@ + + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName $host + + ServerAdmin webmaster@localhost + DocumentRoot $dir + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + +Use Vhost goxogle.com 80 /var/www/goxogle/ +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf b/certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf new file mode 100644 index 000000000..d0ac81fa3 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf @@ -0,0 +1,13 @@ +Alias /owncloud /usr/share/owncloud + + + Options +FollowSymLinks + AllowOverride All + + order allow,deny + allow from all + + = 2.3> + Require all granted + + diff --git a/certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf b/certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf new file mode 100644 index 000000000..26214e7b0 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf @@ -0,0 +1,7 @@ + + RewriteEngine On + RewriteCond %{REQUEST_URI} ^.*(,|;|:|<|>|">|"<|/|\\\.\.\\).* [NC,OR] + RewriteCond %{REQUEST_URI} ^.*(\=|\@|\[|\]|\^|\`|\{|\}|\~).* [NC,OR] + RewriteCond %{REQUEST_URI} ^.*(\'|%0A|%0D|%27|%3C|%3E|%00).* [NC] + RewriteRule ^(.*)$ - [F,L] + diff --git a/certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf b/certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf new file mode 100644 index 000000000..72ced7fb3 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf @@ -0,0 +1,61 @@ +# Those aliases do not work properly with several hosts on your apache server +# Uncomment them to use it or adapt them to your configuration +# Alias /roundcube/program/js/tiny_mce/ /usr/share/tinymce/www/ +# Alias /roundcube /var/lib/roundcube + +# Access to tinymce files + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + = 2.3> + Require all granted + + + Order allow,deny + Allow from all + + + + + Options +FollowSymLinks + # This is needed to parse /var/lib/roundcube/.htaccess. See its + # content before setting AllowOverride to None. + AllowOverride All + = 2.3> + Require all granted + + + Order allow,deny + Allow from all + + + +# Protecting basic directories: + + Options -FollowSymLinks + AllowOverride None + + + + Options -FollowSymLinks + AllowOverride None + = 2.3> + Require all denied + + + Order allow,deny + Deny from all + + + + + Options -FollowSymLinks + AllowOverride None + = 2.3> + Require all denied + + + Order allow,deny + Deny from all + + diff --git a/certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf b/certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf new file mode 100644 index 000000000..8f65e4773 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf @@ -0,0 +1,284 @@ + +NameVirtualHost 0.0.0.0:7080 +NameVirtualHost [00000:000:000:000::0]:7080 +NameVirtualHost 0.0.0.0:7080 + +NameVirtualHost 127.0.0.1:7080 +NameVirtualHost 0.0.0.0:7081 +NameVirtualHost [0000:000:000:000::2]:7081 +NameVirtualHost 0.0.0.0:7081 + +NameVirtualHost 127.0.0.1:7081 + +ServerName "example.com" +ServerAdmin "srv@example.com" + +DocumentRoot /tmp + + + LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog + + + LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog + + +TraceEnable off + +ServerTokens ProductOnly + + + AllowOverride "All" + Options SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + php_admin_flag engine off + + + + php_admin_flag engine off + + + + + + AllowOverride "All" + Options SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + php_admin_flag engine off + + + php_admin_flag engine off + + + + + Header add X-Powered-By PleskLin + + + + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecRequestBodyLimit 134217728 + SecResponseBodyAccess Off + SecResponseBodyLimit 524288 + SecAuditEngine On + SecAuditLog "/var/log/modsec_audit.log" + SecAuditLogType serial + + +#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf" + + + ServerName "default" + UseCanonicalName Off + DocumentRoot /tmp + ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" + + + SSLEngine off + + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + + php_admin_flag engine on + + + + php_admin_flag engine on + + + + + + + + + + ServerName "default-0_0_0_0" + UseCanonicalName Off + DocumentRoot /tmp + ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" + + SSLEngine on + SSLVerifyClient none + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + + php_admin_flag engine on + + + + php_admin_flag engine on + + + + + + + ServerName "default-0000_000_000_00000__2" + UseCanonicalName Off + DocumentRoot /tmp + ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" + + SSLEngine on + SSLVerifyClient none + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + + php_admin_flag engine on + + + + php_admin_flag engine on + + + + + + + ServerName "default-0_0_0_0" + UseCanonicalName Off + DocumentRoot /tmp + ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin" + + SSLEngine on + SSLVerifyClient none + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + + #SSLCACertificateFile "/usr/local/psa/var/certificates/cert-nLy6Z1" + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + + php_admin_flag engine on + + + + php_admin_flag engine on + + + + + + + + + + DocumentRoot /tmp + ServerName lists + ServerAlias lists.* + UseCanonicalName Off + + ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" + + Alias "/icons/" "/var/www/icons/" + Alias "/pipermail/" "/var/lib/mailman/archives/public/" + + + SSLEngine off + + + + Options FollowSymLinks + Order allow,deny + Allow from all + + + + + + + DocumentRoot /tmp + ServerName lists + ServerAlias lists.* + UseCanonicalName Off + + ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" + + Alias "/icons/" "/var/www/icons/" + Alias "/pipermail/" "/var/lib/mailman/archives/public/" + + SSLEngine on + SSLVerifyClient none + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + + + Options FollowSymLinks + Order allow,deny + Allow from all + + + + + + + RPAFproxy_ips 0.0.0.0 [00000:000:000:00000::2] 0.0.0.0 + + + RPAFproxy_ips 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0 + + + RemoteIPInternalProxy 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0 + RemoteIPHeader X-Forwarded-For + diff --git a/certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf b/certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf new file mode 100644 index 000000000..3f2f96965 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf @@ -0,0 +1,247 @@ +#ATTENTION! +# +#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY, +#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED. + +NameVirtualHost 192.168.100.218:80 +NameVirtualHost 10.128.178.192:80 + +NameVirtualHost 192.168.100.218:443 +NameVirtualHost 10.128.178.192:443 + + +ServerName "254020-web1.example.com" +ServerAdmin "name@example.com" + +DocumentRoot "/tmp" + + + LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog + + + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog + + + TraceEnable off + +ServerTokens ProductOnly + + + AllowOverride "All" + Options SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + +php_admin_flag engine off + + + +php_admin_flag engine off + + + + + + AllowOverride All + Options SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + php_admin_flag engine off + + + php_admin_flag engine off + + + + + Header add X-Powered-By PleskLin + + + + JkWorkersFile "/etc/httpd/conf/workers.properties" + JkLogFile /var/log/httpd/mod_jk.log + JkLogLevel info + + +#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf" + + + + ServerName "default" + UseCanonicalName Off + DocumentRoot "/tmp" + ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" + + + + SSLEngine off + + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + +php_admin_flag engine on + + + +php_admin_flag engine on + + + + + + + + + + ServerName "default-192_168_100_218" + UseCanonicalName Off + DocumentRoot "/tmp" + ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" + + + SSLEngine on + SSLVerifyClient none + #SSLCertificateFile "/usr/local/psa/var/certificates/cert-9MgutN" + + #SSLCACertificateFile "/usr/local/psa/var/certificates/cert-s6Wx3P" + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + +php_admin_flag engine on + + + +php_admin_flag engine on + + + + + + + ServerName "default-10_128_178_192" + UseCanonicalName Off + DocumentRoot "/tmp" + ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin" + + + SSLEngine on + SSLVerifyClient none + #SSLCertificateFile "/usr/local/psa/var/certificates/certxfb6025" + + + + AllowOverride None + Options None + Order allow,deny + Allow from all + + + + + +php_admin_flag engine on + + + +php_admin_flag engine on + + + + + + + + + + + DocumentRoot "/tmp" + ServerName lists + ServerAlias lists.* + UseCanonicalName Off + + ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" + + Alias "/icons/" "/var/www/icons/" + Alias "/pipermail/" "/var/lib/mailman/archives/public/" + + + SSLEngine off + + + + + Options FollowSymLinks + Order allow,deny + Allow from all + + + + + + + DocumentRoot "/tmp" + ServerName lists + ServerAlias lists.* + UseCanonicalName Off + + ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/" + + Alias "/icons/" "/var/www/icons/" + Alias "/pipermail/" "/var/lib/mailman/archives/public/" + + SSLEngine on + SSLVerifyClient none + #SSLCertificateFile "/usr/local/psa/var/certificates/certxfb6025" + + + + Options FollowSymLinks + Order allow,deny + Allow from all + + + + + + + RPAFproxy_ips 192.168.100.218 10.128.178.192 + + + RPAFproxy_ips 192.168.100.218 10.128.178.192 + diff --git a/certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf b/certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf new file mode 100644 index 000000000..89e2fb25c --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf @@ -0,0 +1,44 @@ + + ServerName semacode.com + ServerAlias www.semacode.com + DocumentRoot /tmp/ + TransferLog /tmp/access + ErrorLog /tmp/error + Redirect /posts/rss http://semacode.com/feed + Redirect permanent /weblog http://semacode.com/blog + +#ProxyPreserveHost On +# ProxyPass /past http://old.semacode.com + #ProxyPassReverse /past http://old.semacode.com +# + # Order allow,deny + #Allow from all +# + + Redirect /stylesheets/inside.css http://old.semacode.com/stylesheets/inside.css + RedirectMatch /images/portal/(.*) http://old.semacode.com/images/portal/$1 + Redirect /images/invisible.gif http://old.semacode.com/images/invisible.gif + RedirectMatch /javascripts/(.*) http://old.semacode.com/javascripts/$1 + + RewriteEngine on + RewriteRule ^/past/(.*) http://old.semacode.com/past/$1 [L,P] + RewriteCond %{HTTP_HOST} !^semacode\.com$ [NC] + RewriteCond %{HTTP_HOST} !^$ + RewriteRule ^/(.*) http://semacode.com/$1 [L,R] + + + + + + ServerName old.semacode.com + ServerAlias www.old.semacode.com + DocumentRoot /home/simon/semacode-server/semacode/website/trunk/public + TransferLog /tmp/access-old + ErrorLog /tmp/error-old + + Options FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + diff --git a/certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess b/certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess new file mode 100644 index 000000000..1c06d5497 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess @@ -0,0 +1 @@ +SSLRequire %{SSL_CLIENT_S_DN_CN} in {"foo@bar.com", "bar@foo.com"} diff --git a/certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf b/certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf new file mode 100644 index 000000000..5d3cef423 --- /dev/null +++ b/certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf @@ -0,0 +1,28 @@ + + + ServerAdmin info@somethingnewentertainment.com + ServerName somethingnewentertainment.com + DocumentRoot /var/www/html + + ErrorLog /var/log/apache2/error.log + CustomLog /var/log/apache2/access.log combined + + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + SSLHonorCipherOrder on + SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EEC DH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRS A RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4" + + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + diff --git a/certbot-apache/tests/autohsts_test.py b/certbot-apache/tests/autohsts_test.py new file mode 100644 index 000000000..5911f2b88 --- /dev/null +++ b/certbot-apache/tests/autohsts_test.py @@ -0,0 +1,189 @@ +# pylint: disable=too-many-lines +"""Test for certbot_apache._internal.configurator AutoHSTS functionality""" +import re +import unittest +import mock +# six is used in mock.patch() +import six # pylint: disable=unused-import + +from certbot import errors +from certbot_apache._internal import constants + +import util + + +class AutoHSTSTest(util.ApacheTest): + """Tests for AutoHSTS feature""" + # pylint: disable=protected-access + + def setUp(self): # pylint: disable=arguments-differ + super(AutoHSTSTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.config.parser.modules.add("headers_module") + self.config.parser.modules.add("mod_headers.c") + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def get_autohsts_value(self, vh_path): + """ Get value from Strict-Transport-Security header """ + header_path = self.config.parser.find_dir("Header", None, vh_path) + if header_path: + pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' + for head in header_path: + if re.search(pat, self.config.parser.aug.get(head).lower()): + return self.config.parser.aug.get( + head.replace("arg[3]", "arg[4]")) + return None # pragma: no cover + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") + def test_autohsts_enable_headers_mod(self, mock_enable, _restart): + self.config.parser.modules.discard("headers_module") + self.config.parser.modules.discard("mod_header.c") + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + self.assertTrue(mock_enable.called) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + def test_autohsts_deploy_already_exists(self, _restart): + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + self.assertRaises(errors.PluginEnhancementAlreadyPresent, + self.config.enable_autohsts, + mock.MagicMock(), ["ocspvhost.com"]) + + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.prepare") + def test_autohsts_increase(self, mock_prepare, _mock_restart): + self.config._prepared = False + maxage = "\"max-age={0}\"" + initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) + inc_val = maxage.format(constants.AUTOHSTS_STEPS[1]) + + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + # Verify initial value + self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), + initial_val) + # Increase + self.config.update_autohsts(mock.MagicMock()) + # Verify increased value + self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), + inc_val) + self.assertTrue(mock_prepare.called) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._autohsts_increase") + def test_autohsts_increase_noop(self, mock_increase, _restart): + maxage = "\"max-age={0}\"" + initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + # Verify initial value + self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), + initial_val) + + self.config.update_autohsts(mock.MagicMock()) + # Freq not patched, so value shouldn't increase + self.assertFalse(mock_increase.called) + + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + def test_autohsts_increase_no_header(self, _restart): + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + # Remove the header + dir_locs = self.config.parser.find_dir("Header", None, + self.vh_truth[7].path) + dir_loc = "/".join(dir_locs[0].split("/")[:-1]) + self.config.parser.aug.remove(dir_loc) + self.assertRaises(errors.PluginError, + self.config.update_autohsts, + mock.MagicMock()) + + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + def test_autohsts_increase_and_make_permanent(self, _mock_restart): + maxage = "\"max-age={0}\"" + max_val = maxage.format(constants.AUTOHSTS_PERMANENT) + mock_lineage = mock.MagicMock() + mock_lineage.key_path = "/etc/apache2/ssl/key-certbot_15.pem" + self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) + for i in range(len(constants.AUTOHSTS_STEPS)-1): + # Ensure that value is not made permanent prematurely + self.config.deploy_autohsts(mock_lineage) + 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.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), + cur_val) + # Ensure that the value is raised to max + 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.assertEqual(self.get_autohsts_value(self.vh_truth[7].path), + max_val) + + def test_autohsts_update_noop(self): + with mock.patch("time.time") as mock_time: + # Time mock is used to make sure that the execution does not + # continue when no autohsts entries exist in pluginstorage + self.config.update_autohsts(mock.MagicMock()) + self.assertFalse(mock_time.called) + + def test_autohsts_make_permanent_noop(self): + self.config.storage.put = mock.MagicMock() + self.config.deploy_autohsts(mock.MagicMock()) + # Make sure that the execution does not continue when no entries in store + self.assertFalse(self.config.storage.put.called) + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + def test_autohsts_no_ssl_vhost(self, mock_select): + mock_select.return_value = self.vh_truth[0] + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: + self.assertRaises(errors.PluginError, + self.config.enable_autohsts, + mock.MagicMock(), "invalid.example.com") + self.assertTrue( + "Certbot was not able to find SSL" in mock_log.call_args[0][0]) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.add_vhost_id") + def test_autohsts_dont_enhance_twice(self, mock_id, _restart): + mock_id.return_value = "1234567" + self.config.enable_autohsts(mock.MagicMock(), + ["ocspvhost.com", "ocspvhost.com"]) + self.assertEqual(mock_id.call_count, 1) + + def test_autohsts_remove_orphaned(self): + # pylint: disable=protected-access + self.config._autohsts_fetch_state() + self.config._autohsts["orphan_id"] = {"laststep": 0, "timestamp": 0} + + self.config._autohsts_save_state() + self.config.update_autohsts(mock.MagicMock()) + self.assertFalse("orphan_id" in self.config._autohsts) + # Make sure it's removed from the pluginstorage file as well + self.config._autohsts = None + self.config._autohsts_fetch_state() + self.assertFalse(self.config._autohsts) + + def test_autohsts_make_permanent_vhost_not_found(self): + # pylint: disable=protected-access + self.config._autohsts_fetch_state() + self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0} + self.config._autohsts_save_state() + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: + self.config.deploy_autohsts(mock.MagicMock()) + self.assertTrue(mock_log.called) + self.assertTrue( + "VirtualHost with id orphan_id was not" in mock_log.call_args[0][0]) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/centos6_test.py b/certbot-apache/tests/centos6_test.py new file mode 100644 index 000000000..6c0fae24c --- /dev/null +++ b/certbot-apache/tests/centos6_test.py @@ -0,0 +1,223 @@ +"""Test for certbot_apache._internal.configurator for CentOS 6 overrides""" +import unittest + +from certbot.compat import os +from certbot.errors import MisconfigurationError + +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos +from certbot_apache._internal import parser + +import util + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "httpd/conf.d") + + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "test.example.com.conf"), + os.path.join(aug_pre, "test.example.com.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "test.example.com"), + obj.VirtualHost( + os.path.join(prefix, "ssl.conf"), + os.path.join(aug_pre, "ssl.conf/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), + True, True, None) + ] + return vh_truth + +class CentOS6Tests(util.ApacheTest): + """Tests for CentOS 6""" + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos6_apache/apache" + config_root = "centos6_apache/apache/httpd" + vhost_root = "centos6_apache/apache/httpd/conf.d" + super(CentOS6Tests, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + version=(2, 2, 15), os_info="centos") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos6_apache/apache") + + def test_get_parser(self): + self.assertTrue(isinstance(self.config.parser, + override_centos.CentOSParser)) + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + found = 0 + + for vhost in vhs: + for centos_truth in self.vh_truth: + if vhost == centos_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 2) + + def test_loadmod_default(self): + ssl_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + self.assertEqual(len(ssl_loadmods), 1) + # Make sure the LoadModule ssl_module is in ssl.conf (default) + self.assertTrue("ssl.conf" in ssl_loadmods[0]) + # ...and that it's not inside of + self.assertFalse("IfModule" in ssl_loadmods[0]) + + # Get the example vhost + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + + # We should now have LoadModule ssl_module in root conf and ssl.conf + self.assertEqual(len(post_loadmods), 2) + for lm in post_loadmods: + # lm[:-7] removes "/arg[#]" from the path + arguments = self.config.parser.get_all_args(lm[:-7]) + self.assertEqual(arguments, ["ssl_module", "modules/mod_ssl.so"]) + # ...and both of them should be wrapped in + # lm[:-17] strips off /directive/arg[1] from the path. + ifmod_args = self.config.parser.get_all_args(lm[:-17]) + self.assertTrue("!mod_ssl.c" in ifmod_args) + + def test_loadmod_multiple(self): + sslmod_args = ["ssl_module", "modules/mod_ssl.so"] + # Adds another LoadModule to main httpd.conf in addtition to ssl.conf + self.config.parser.add_dir(self.config.parser.loc["default"], "LoadModule", + sslmod_args) + self.config.save() + pre_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + # LoadModules are not within IfModule blocks + self.assertFalse(any(["ifmodule" in m.lower() for m in pre_loadmods])) + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", exclude=False) + + for mod in post_loadmods: + self.assertTrue(self.config.parser.not_modssl_ifmodule(mod)) #pylint: disable=no-member + + def test_loadmod_rootconf_exists(self): + sslmod_args = ["ssl_module", "modules/mod_ssl.so"] + rootconf_ifmod = self.config.parser.get_ifmod( + parser.get_aug_path(self.config.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) + self.config.save() + # Get the example vhost + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + + root_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", + start=parser.get_aug_path(self.config.parser.loc["default"]), + exclude=False) + + mods = [lm for lm in root_loadmods if self.config.parser.loc["default"] in lm] + + self.assertEqual(len(mods), 1) + # [:-7] removes "/arg[#]" from the path + self.assertEqual( + self.config.parser.get_all_args(mods[0][:-7]), + sslmod_args) + + def test_neg_loadmod_already_on_path(self): + loadmod_args = ["ssl_module", "modules/mod_ssl.so"] + ifmod = self.config.parser.get_ifmod( + self.vh_truth[1].path, "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(ifmod[:-1], "LoadModule", loadmod_args) + self.config.parser.add_dir(self.vh_truth[1].path, "LoadModule", loadmod_args) + self.config.save() + pre_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) + self.assertEqual(len(pre_loadmods), 2) + # The ssl.conf now has two LoadModule directives, one inside of + # !mod_ssl.c IfModule + self.config.assoc["test.example.com"] = self.vh_truth[0] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + # Ensure that the additional LoadModule wasn't written into the IfModule + post_loadmods = self.config.parser.find_dir( + "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) + self.assertEqual(len(post_loadmods), 1) + + def test_loadmod_non_duplicate(self): + # the modules/mod_ssl.so exists in ssl.conf + sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"] + rootconf_ifmod = self.config.parser.get_ifmod( + parser.get_aug_path(self.config.parser.loc["default"]), + "!mod_ssl.c", beginning=True) + self.config.parser.add_dir(rootconf_ifmod[:-1], "LoadModule", sslmod_args) + self.config.save() + self.config.assoc["test.example.com"] = self.vh_truth[0] + pre_matches = self.config.parser.find_dir("LoadModule", + "ssl_module", exclude=False) + + self.assertRaises(MisconfigurationError, self.config.deploy_cert, + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + + post_matches = self.config.parser.find_dir("LoadModule", + "ssl_module", exclude=False) + # Make sure that none was changed + self.assertEqual(pre_matches, post_matches) + + def test_loadmod_not_found(self): + # Remove all existing LoadModule ssl_module... directives + orig_loadmods = self.config.parser.find_dir("LoadModule", + "ssl_module", + exclude=False) + for mod in orig_loadmods: + noarg_path = mod.rpartition("/")[0] + self.config.parser.aug.remove(noarg_path) + self.config.save() + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + + post_loadmods = self.config.parser.find_dir("LoadModule", + "ssl_module", + exclude=False) + self.assertFalse(post_loadmods) + + def test_no_ifmod_search_false(self): + #pylint: disable=no-member + + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "/path/does/not/include/ifmod" + )) + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "" + )) + self.assertFalse(self.config.parser.not_modssl_ifmodule( + "/path/includes/IfModule/but/no/arguments" + )) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py new file mode 100644 index 000000000..2f7ab9b54 --- /dev/null +++ b/certbot-apache/tests/centos_test.py @@ -0,0 +1,196 @@ +"""Test for certbot_apache._internal.configurator for Centos overrides""" +import unittest + +import mock + +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os + +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos + +import util + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "httpd/conf.d") + + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "centos.example.com.conf"), + os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "centos.example.com"), + obj.VirtualHost( + os.path.join(prefix, "ssl.conf"), + os.path.join(aug_pre, "ssl.conf/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), + True, True, None) + ] + return vh_truth + +class FedoraRestartTest(util.ApacheTest): + """Tests for Fedora specific self-signed certificate override""" + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(FedoraRestartTest, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora_old") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def _run_fedora_test(self): + self.assertIsInstance(self.config, override_centos.CentOSConfigurator) + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ["fedora", "28"] + self.config.config_test() + + def test_non_fedora_error(self): + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + mock_test.side_effect = errors.MisconfigurationError + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ["not_fedora"] + self.assertRaises(errors.MisconfigurationError, + self.config.config_test) + + def test_fedora_restart_error(self): + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + with mock.patch("certbot.util.run_script") as mock_run: + mock_run.side_effect = errors.SubprocessError + self.assertRaises(errors.MisconfigurationError, + self._run_fedora_test) + + def test_fedora_restart(self): + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + with mock.patch("certbot.util.run_script") as mock_run: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + self._run_fedora_test() + self.assertEqual(mock_test.call_count, 2) + self.assertEqual(mock_run.call_args[0][0], + ['systemctl', 'restart', 'httpd']) + + +class MultipleVhostsTestCentOS(util.ApacheTest): + """Multiple vhost tests for CentOS / RHEL family of distros""" + + _multiprocess_can_split_ = True + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(MultipleVhostsTestCentOS, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="centos") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def test_get_parser(self): + self.assertIsInstance(self.config.parser, override_centos.CentOSParser) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_opportunistic_httpd_runtime_parsing(self, mock_get): + define_val = ( + 'Define: TEST1\n' + 'Define: TEST2\n' + 'Define: DUMP_RUN_CFG\n' + ) + mod_val = ( + 'Loaded Modules:\n' + ' mock_module (static)\n' + ' another_module (static)\n' + ) + def mock_get_cfg(command): + """Mock httpd process stdout""" + if command == ['apachectl', '-t', '-D', 'DUMP_RUN_CFG']: + return define_val + elif command == ['apachectl', '-t', '-D', 'DUMP_MODULES']: + return mod_val + return "" + mock_get.side_effect = mock_get_cfg + self.config.parser.modules = set() + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("centos", "7") + self.config.parser.update_runtime_variables() + + 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) + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + found = 0 + + for vhost in vhs: + for centos_truth in self.vh_truth: + if vhost == centos_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 2) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_get_sysconfig_vars(self, mock_cfg): + """Make sure we read the sysconfig OPTIONS variable correctly""" + # Return nothing for the process calls + mock_cfg.return_value = "" + self.config.parser.sysconfig_filep = filesystem.realpath( + os.path.join(self.config.parser.root, "../sysconfig/httpd")) + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("centos", "7") + self.config.parser.update_runtime_variables() + + self.assertTrue("mock_define" in self.config.parser.variables.keys()) + self.assertTrue("mock_define_too" in self.config.parser.variables.keys()) + self.assertTrue("mock_value" in self.config.parser.variables.keys()) + self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) + self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) + self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) + + @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) + + @mock.patch("certbot_apache._internal.configurator.util.run_script") + def test_alt_restart_errors(self, mock_run_script): + mock_run_script.side_effect = [None, + errors.SubprocessError, + errors.SubprocessError] + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/complex_parsing_test.py b/certbot-apache/tests/complex_parsing_test.py new file mode 100644 index 000000000..b42e82996 --- /dev/null +++ b/certbot-apache/tests/complex_parsing_test.py @@ -0,0 +1,129 @@ +"""Tests for certbot_apache._internal.parser.""" +import shutil +import unittest + +from certbot import errors +from certbot.compat import os + +import util + + +class ComplexParserTest(util.ParserTest): + """Apache Parser Test.""" + + def setUp(self): # pylint: disable=arguments-differ + super(ComplexParserTest, self).setUp( + "complex_parsing", "complex_parsing") + + self.setup_variables() + # This needs to happen after due to setup_variables not being run + # until after + self.parser.parse_modules() # pylint: disable=protected-access + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + def setup_variables(self): + """Set up variables for parser.""" + self.parser.variables.update( + { + "COMPLEX": "", + "tls_port": "1234", + "fnmatch_filename": "test_fnmatch.conf", + "tls_port_str": "1234" + } + ) + + def test_filter_args_num(self): + """Note: This may also fail do to Include conf-enabled/ syntax.""" + matches = self.parser.find_dir("TestArgsDirective") + + self.assertEqual(len(self.parser.filter_args_num(matches, 1)), 3) + self.assertEqual(len(self.parser.filter_args_num(matches, 2)), 2) + self.assertEqual(len(self.parser.filter_args_num(matches, 3)), 1) + + def test_basic_variable_parsing(self): + matches = self.parser.find_dir("TestVariablePort") + + self.assertEqual(len(matches), 1) + self.assertEqual(self.parser.get_arg(matches[0]), "1234") + + def test_basic_variable_parsing_quotes(self): + matches = self.parser.find_dir("TestVariablePortStr") + + self.assertEqual(len(matches), 1) + self.assertEqual(self.parser.get_arg(matches[0]), "1234") + + def test_invalid_variable_parsing(self): + del self.parser.variables["tls_port"] + + matches = self.parser.find_dir("TestVariablePort") + self.assertRaises( + errors.PluginError, self.parser.get_arg, matches[0]) + + def test_basic_ifdefine(self): + self.assertEqual(len(self.parser.find_dir("VAR_DIRECTIVE")), 2) + self.assertEqual(len(self.parser.find_dir("INVALID_VAR_DIRECTIVE")), 0) + + def test_basic_ifmodule(self): + self.assertEqual(len(self.parser.find_dir("MOD_DIRECTIVE")), 2) + self.assertEqual( + len(self.parser.find_dir("INVALID_MOD_DIRECTIVE")), 0) + + def test_nested(self): + self.assertEqual(len(self.parser.find_dir("NESTED_DIRECTIVE")), 3) + self.assertEqual( + len(self.parser.find_dir("INVALID_NESTED_DIRECTIVE")), 0) + + def test_load_modules(self): + """If only first is found, there is bad variable parsing.""" + self.assertTrue("status_module" in self.parser.modules) + self.assertTrue("mod_status.c" in self.parser.modules) + + # This is in an IfDefine + self.assertTrue("ssl_module" in self.parser.modules) + self.assertTrue("mod_ssl.c" in self.parser.modules) + + def verify_fnmatch(self, arg, hit=True): + """Test if Include was correctly parsed.""" + from certbot_apache._internal import parser + self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), + "Include", [arg]) + if hit: + self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE")) + else: + self.assertFalse(self.parser.find_dir("FNMATCH_DIRECTIVE")) + + # NOTE: Only run one test per function otherwise you will have + # inf recursion + def test_include(self): + self.verify_fnmatch("test_fnmatch.?onf") + + def test_include_complex(self): + self.verify_fnmatch("../complex_parsing/[te][te]st_*.?onf") + + def test_include_fullpath(self): + self.verify_fnmatch(os.path.join(self.config_path, + "test_fnmatch.conf")) + + def test_include_fullpath_trailing_slash(self): + self.verify_fnmatch(self.config_path + "//") + + def test_include_single_quotes(self): + self.verify_fnmatch("'" + self.config_path + "'") + + def test_include_double_quotes(self): + self.verify_fnmatch('"' + self.config_path + '"') + + def test_include_variable(self): + self.verify_fnmatch("../complex_parsing/${fnmatch_filename}") + + def test_include_missing(self): + # This should miss + self.verify_fnmatch("test_*.onf", False) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/configurator_reverter_test.py b/certbot-apache/tests/configurator_reverter_test.py new file mode 100644 index 000000000..5718d782f --- /dev/null +++ b/certbot-apache/tests/configurator_reverter_test.py @@ -0,0 +1,85 @@ +"""Test for certbot_apache._internal.configurator implementations of reverter""" +import shutil +import unittest + +import mock + +from certbot import errors + +import util + + +class ConfiguratorReverterTest(util.ApacheTest): + """Test for ApacheConfigurator reverter methods""" + + + def setUp(self): # pylint: disable=arguments-differ + super(ConfiguratorReverterTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def tearDown(self): + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + shutil.rmtree(self.temp_dir) + + def test_bad_save_checkpoint(self): + self.config.reverter.add_to_checkpoint = mock.Mock( + side_effect=errors.ReverterError) + self.config.parser.add_dir( + self.vh_truth[0].path, "Test", "bad_save_ckpt") + self.assertRaises(errors.PluginError, self.config.save) + + def test_bad_save_finalize_checkpoint(self): + self.config.reverter.finalize_checkpoint = mock.Mock( + side_effect=errors.ReverterError) + self.config.parser.add_dir( + self.vh_truth[0].path, "Test", "bad_save_ckpt") + self.assertRaises(errors.PluginError, self.config.save, "Title") + + def test_finalize_save(self): + mock_finalize = mock.Mock() + self.config.reverter = mock_finalize + self.config.save("Example Title") + + self.assertTrue(mock_finalize.is_called) + + def test_revert_challenge_config(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + + self.config.revert_challenge_config() + self.assertEqual(mock_load.call_count, 1) + + def test_revert_challenge_config_error(self): + self.config.reverter.revert_temporary_config = mock.Mock( + side_effect=errors.ReverterError) + + self.assertRaises( + errors.PluginError, self.config.revert_challenge_config) + + def test_rollback_checkpoints(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + + self.config.rollback_checkpoints() + self.assertEqual(mock_load.call_count, 1) + + def test_rollback_error(self): + self.config.reverter.rollback_checkpoints = mock.Mock( + side_effect=errors.ReverterError) + self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) + + def test_recovery_routine_reload(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + self.config.recovery_routine() + self.assertEqual(mock_load.call_count, 1) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py new file mode 100644 index 000000000..4a75eaedc --- /dev/null +++ b/certbot-apache/tests/configurator_test.py @@ -0,0 +1,1773 @@ +# pylint: disable=too-many-lines +"""Test for certbot_apache._internal.configurator.""" +import copy +import shutil +import socket +import tempfile +import unittest + +import mock +# six is used in mock.patch() +import six # pylint: disable=unused-import + +from acme import challenges + +from certbot import achallenges +from certbot import crypto_util +from certbot import errors +from certbot.compat import os +from certbot.compat import filesystem +from certbot.tests import acme_util +from certbot.tests import util as certbot_util + +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants +from certbot_apache._internal import obj +from certbot_apache._internal import parser + +import util + + +class MultipleVhostsTest(util.ApacheTest): + """Test two standard well-configured HTTP vhosts.""" + + def setUp(self): # pylint: disable=arguments-differ + super(MultipleVhostsTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + self.config = self.mock_deploy_cert(self.config) + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + + def mock_deploy_cert(self, config): + """A test for a mock deploy cert""" + config.real_deploy_cert = self.config.deploy_cert + + def mocked_deploy_cert(*args, **kwargs): + """a helper to mock a deployed cert""" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" + with mock.patch(g_mod): + config.real_deploy_cert(*args, **kwargs) + self.config.deploy_cert = mocked_deploy_cert + return self.config + + @mock.patch("certbot_apache._internal.configurator.path_surgery") + def test_prepare_no_install(self, mock_surgery): + silly_path = {"PATH": "/tmp/nothingness2342"} + mock_surgery.return_value = False + with mock.patch.dict('os.environ', silly_path): + self.assertRaises(errors.NoInstallationError, self.config.prepare) + self.assertEqual(mock_surgery.call_count, 1) + + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") + def test_prepare_version(self, mock_exe_exists, _): + mock_exe_exists.return_value = True + self.config.version = None + self.config.config_test = mock.Mock() + self.config.get_version = mock.Mock(return_value=(1, 1)) + + self.assertRaises( + errors.NotSupportedError, self.config.prepare) + + def test_prepare_locked(self): + server_root = self.config.conf("server-root") + self.config.config_test = mock.Mock() + os.remove(os.path.join(server_root, ".certbot.lock")) + certbot_util.lock_and_call(self._test_prepare_locked, server_root) + + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") + def _test_prepare_locked(self, unused_parser, unused_exe_exists): + try: + self.config.prepare() + except errors.PluginError as err: + err_msg = str(err) + self.assertTrue("lock" in err_msg) + self.assertTrue(self.config.conf("server-root") in err_msg) + else: # pragma: no cover + self.fail("Exception wasn't raised!") + + def test_add_parser_arguments(self): # pylint: disable=no-self-use + from certbot_apache._internal.configurator import ApacheConfigurator + # Weak test.. + ApacheConfigurator.add_parser_arguments(mock.MagicMock()) + + def test_docs_parser_arguments(self): + os.environ["CERTBOT_DOCS"] = "1" + from certbot_apache._internal.configurator import ApacheConfigurator + mock_add = mock.MagicMock() + ApacheConfigurator.add_parser_arguments(mock_add) + parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", + "vhost_root", "logs_root", "challenge_location", + "handle_modules", "handle_sites", "ctl"] + exp = dict() + + for k in ApacheConfigurator.OS_DEFAULTS: + if k in parserargs: + exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] + # Special cases + exp["vhost-root"] = None + + found = set() + for call in mock_add.call_args_list: + found.add(call[0][0]) + + # Make sure that all (and only) the expected values exist + self.assertEqual(len(mock_add.call_args_list), len(found)) + for e in exp: + self.assertTrue(e in found) + + del os.environ["CERTBOT_DOCS"] + + def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES + for cls in OVERRIDE_CLASSES.values(): + cls.add_parser_arguments(mock.MagicMock()) + + def test_all_configurators_defaults_defined(self): + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES + from certbot_apache._internal.configurator import ApacheConfigurator + parameters = set(ApacheConfigurator.OS_DEFAULTS.keys()) + for cls in OVERRIDE_CLASSES.values(): + self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys()))) + + def test_constant(self): + self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in + self.config.option("server_root")) + self.assertEqual(self.config.option("nonexistent"), None) + + @certbot_util.patch_get_utility() + def test_get_all_names(self, mock_getutility): + mock_utility = mock_getutility() + mock_utility.notification = mock.MagicMock(return_value=True) + names = self.config.get_all_names() + self.assertEqual(names, set( + ["certbot.demo", "ocspvhost.com", "encryption-example.demo", + "nonsym.link", "vhost.in.rootconf", "www.certbot.demo", + "duplicate.example.com"] + )) + + @certbot_util.patch_get_utility() + @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") + def test_get_all_names_addrs(self, mock_gethost, mock_getutility): + mock_gethost.side_effect = [("google.com", "", ""), socket.error] + mock_utility = mock_getutility() + mock_utility.notification.return_value = True + vhost = obj.VirtualHost( + "fp", "ap", + set([obj.Addr(("8.8.8.8", "443")), + obj.Addr(("zombo.com",)), + obj.Addr(("192.168.1.2"))]), + True, False) + + self.config.vhosts.append(vhost) + + names = self.config.get_all_names() + self.assertEqual(len(names), 9) + self.assertTrue("zombo.com" in names) + self.assertTrue("google.com" in names) + self.assertTrue("certbot.demo" in names) + + def test_get_bad_path(self): + self.assertEqual(apache_util.get_file_path(None), None) + self.assertEqual(apache_util.get_file_path("nonexistent"), None) + self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access + + def test_get_aug_internal_path(self): + from certbot_apache._internal.apache_util import get_internal_aug_path + internal_paths = [ + "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost", + "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost", + "IfModule/VirtualHost"] + + for i, internal_path in enumerate(internal_paths): + self.assertEqual( + get_internal_aug_path(self.vh_truth[i].path), internal_path) + + def test_bad_servername_alias(self): + ssl_vh1 = obj.VirtualHost( + "fp1", "ap1", set([obj.Addr(("*", "443"))]), + True, False) + # pylint: disable=protected-access + self.config._add_servernames(ssl_vh1) + self.assertTrue( + self.config._add_servername_alias("oy_vey", ssl_vh1) is None) + + def test_add_servernames_alias(self): + self.config.parser.add_dir( + self.vh_truth[2].path, "ServerAlias", ["*.le.co"]) + # pylint: disable=protected-access + self.config._add_servernames(self.vh_truth[2]) + self.assertEqual( + self.vh_truth[2].get_names(), set(["*.le.co", "ip-172-30-0-17"])) + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 12) + found = 0 + + for vhost in vhs: + for truth in self.vh_truth: + if vhost == truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + + self.assertEqual(found, 12) + + # Handle case of non-debian layout get_virtual_hosts + with mock.patch( + "certbot_apache._internal.configurator.ApacheConfigurator.conf" + ) as mock_conf: + mock_conf.return_value = False + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 12) + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + def test_choose_vhost_none_avail(self, mock_select): + mock_select.return_value = None + self.assertRaises( + errors.PluginError, self.config.choose_vhost, "none.com") + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + def test_choose_vhost_select_vhost_ssl(self, mock_select): + mock_select.return_value = self.vh_truth[1] + self.assertEqual( + self.vh_truth[1], self.config.choose_vhost("none.com")) + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") + def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select): + mock_select.return_value = self.vh_truth[0] + mock_conf.return_value = False + chosen_vhost = self.config.choose_vhost("none.com") + self.vh_truth[0].aliases.add("none.com") + self.assertEqual( + self.vh_truth[0].get_names(), chosen_vhost.get_names()) + + # Make sure we go from HTTP -> HTTPS + self.assertFalse(self.vh_truth[0].ssl) + self.assertTrue(chosen_vhost.ssl) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") + @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") + def test_choose_vhost_and_servername_addition(self, mock_add, mock_find): + ret_vh = self.vh_truth[8] + ret_vh.enabled = False + mock_find.return_value = self.vh_truth[8] + self.config.choose_vhost("whatever.com") + self.assertTrue(mock_add.called) + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + def test_choose_vhost_select_vhost_with_temp(self, mock_select): + mock_select.return_value = self.vh_truth[0] + chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) + self.assertEqual(self.vh_truth[0], chosen_vhost) + + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): + mock_select.return_value = self.vh_truth[3] + conflicting_vhost = obj.VirtualHost( + "path", "aug_path", set([obj.Addr.fromstring("*:443")]), + True, True) + self.config.vhosts.append(conflicting_vhost) + + self.assertRaises( + errors.PluginError, self.config.choose_vhost, "none.com") + + def test_find_best_http_vhost_default(self): + vh = obj.VirtualHost( + "fp", "ap", set([obj.Addr.fromstring("_default_:80")]), False, True) + self.config.vhosts = [vh] + self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh) + + def test_find_best_http_vhost_port(self): + port = "8080" + vh = obj.VirtualHost( + "fp", "ap", set([obj.Addr.fromstring("*:" + port)]), + False, True, "encryption-example.demo") + self.config.vhosts.append(vh) + self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh) + + def test_findbest_continues_on_short_domain(self): + # pylint: disable=protected-access + chosen_vhost = self.config._find_best_vhost("purple.com") + self.assertEqual(None, chosen_vhost) + + def test_findbest_continues_on_long_domain(self): + # pylint: disable=protected-access + chosen_vhost = self.config._find_best_vhost("green.red.purple.com") + self.assertEqual(None, chosen_vhost) + + def test_find_best_vhost(self): + # pylint: disable=protected-access + self.assertEqual( + self.vh_truth[3], self.config._find_best_vhost("certbot.demo")) + self.assertEqual( + self.vh_truth[0], + self.config._find_best_vhost("encryption-example.demo")) + self.assertEqual( + self.config._find_best_vhost("does-not-exist.com"), None) + + def test_find_best_vhost_variety(self): + # pylint: disable=protected-access + ssl_vh = obj.VirtualHost( + "fp", "ap", set([obj.Addr(("*", "443")), + obj.Addr(("zombo.com",))]), + True, False) + self.config.vhosts.append(ssl_vh) + self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) + + def test_find_best_vhost_default(self): + # pylint: disable=protected-access + # Assume only the two default vhosts. + self.config.vhosts = [ + vh for vh in self.config.vhosts + if vh.name not in ["certbot.demo", "nonsym.link", + "encryption-example.demo", "duplicate.example.com", + "ocspvhost.com", "vhost.in.rootconf"] + and "*.blue.purple.com" not in vh.aliases + ] + self.assertEqual( + self.config._find_best_vhost("encryption-example.demo"), + self.vh_truth[2]) + + def test_non_default_vhosts(self): + # pylint: disable=protected-access + vhosts = self.config._non_default_vhosts(self.config.vhosts) + self.assertEqual(len(vhosts), 10) + + def test_deploy_cert_enable_new_vhost(self): + # Create + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + + self.assertFalse(ssl_vhost.enabled) + self.config.deploy_cert( + "encryption-example.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.assertTrue(ssl_vhost.enabled) + + def test_no_duplicate_include(self): + def mock_find_dir(directive, argument, _): + """Mock method for parser.find_dir""" + if directive == "Include" and argument.endswith("options-ssl-apache.conf"): + return ["/path/to/whatever"] + return None # pragma: no cover + + mock_add = mock.MagicMock() + self.config.parser.add_dir = mock_add + self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access + tried_to_add = False + for a in mock_add.call_args_list: + if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: + tried_to_add = True + # Include should be added, find_dir is not patched, and returns falsy + self.assertTrue(tried_to_add) + + self.config.parser.find_dir = mock_find_dir + mock_add.reset_mock() + self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access + for a in mock_add.call_args_list: + if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: + self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \ + # pragma: no cover + + def test_deploy_cert(self): + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + # Patch _add_dummy_ssl_directives to make sure we write them correctly + # pylint: disable=protected-access + orig_add_dummy = self.config._add_dummy_ssl_directives + def mock_add_dummy_ssl(vhostpath): + """Mock method for _add_dummy_ssl_directives""" + def find_args(path, directive): + """Return list of arguments in requested directive at path""" + f_args = [] + dirs = self.config.parser.find_dir(directive, None, + path) + for d in dirs: + f_args.append(self.config.parser.get_arg(d)) + return f_args + # Verify that the dummy directives do not exist + self.assertFalse( + "insert_cert_file_path" in find_args(vhostpath, + "SSLCertificateFile")) + self.assertFalse( + "insert_key_file_path" in find_args(vhostpath, + "SSLCertificateKeyFile")) + orig_add_dummy(vhostpath) + # Verify that the dummy directives exist + self.assertTrue( + "insert_cert_file_path" in find_args(vhostpath, + "SSLCertificateFile")) + self.assertTrue( + "insert_key_file_path" in find_args(vhostpath, + "SSLCertificateKeyFile")) + # pylint: disable=protected-access + self.config._add_dummy_ssl_directives = mock_add_dummy_ssl + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + self.config.deploy_cert( + "random.demo", + "example/cert.pem", "example/key.pem", "example/cert_chain.pem") + self.config.save() + + # Verify ssl_module was enabled. + self.assertTrue(self.vh_truth[1].enabled) + self.assertTrue("ssl_module" in self.config.parser.modules) + + loc_cert = self.config.parser.find_dir( + "sslcertificatefile", "example/cert.pem", self.vh_truth[1].path) + loc_key = self.config.parser.find_dir( + "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) + loc_chain = self.config.parser.find_dir( + "SSLCertificateChainFile", "example/cert_chain.pem", + self.vh_truth[1].path) + + # Verify one directive was found in the correct file + self.assertEqual(len(loc_cert), 1) + self.assertEqual( + apache_util.get_file_path(loc_cert[0]), + self.vh_truth[1].filep) + + self.assertEqual(len(loc_key), 1) + self.assertEqual( + apache_util.get_file_path(loc_key[0]), + self.vh_truth[1].filep) + + self.assertEqual(len(loc_chain), 1) + self.assertEqual( + apache_util.get_file_path(loc_chain[0]), + self.vh_truth[1].filep) + + # One more time for chain directive setting + self.config.deploy_cert( + "random.demo", + "two/cert.pem", "two/key.pem", "two/cert_chain.pem") + self.assertTrue(self.config.parser.find_dir( + "SSLCertificateChainFile", "two/cert_chain.pem", + self.vh_truth[1].path)) + + def test_deploy_cert_invalid_vhost(self): + """For test cases where the `ApacheConfigurator` class' `_deploy_cert` + method is called with an invalid vhost parameter. Currently this tests + that a PluginError is appropriately raised when important directives + are missing in an SSL module.""" + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + + def side_effect(*args): + """Mocks case where an SSLCertificateFile directive can be found + but an SSLCertificateKeyFile directive is missing.""" + if "SSLCertificateFile" in args: + return ["example/cert.pem"] + return [] + + mock_find_dir = mock.MagicMock(return_value=[]) + mock_find_dir.side_effect = side_effect + + self.config.parser.find_dir = mock_find_dir + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + + self.assertRaises( + errors.PluginError, self.config.deploy_cert, "random.demo", + "example/cert.pem", "example/key.pem", "example/cert_chain.pem") + + # Remove side_effect to mock case where both SSLCertificateFile + # and SSLCertificateKeyFile directives are missing + self.config.parser.find_dir.side_effect = None + self.assertRaises( + errors.PluginError, self.config.deploy_cert, "random.demo", + "example/cert.pem", "example/key.pem", "example/cert_chain.pem") + + def test_is_name_vhost(self): + addr = obj.Addr.fromstring("*:80") + self.assertTrue(self.config.is_name_vhost(addr)) + self.config.version = (2, 2) + self.assertFalse(self.config.is_name_vhost(addr)) + + def test_add_name_vhost(self): + self.config.add_name_vhost(obj.Addr.fromstring("*:443")) + self.config.add_name_vhost(obj.Addr.fromstring("*:80")) + self.assertTrue(self.config.parser.find_dir( + "NameVirtualHost", "*:443", exclude=False)) + self.assertTrue(self.config.parser.find_dir( + "NameVirtualHost", "*:80")) + + def test_add_listen_80(self): + mock_find = mock.Mock() + mock_add_dir = mock.Mock() + mock_find.return_value = [] + self.config.parser.find_dir = mock_find + self.config.parser.add_dir = mock_add_dir + self.config.ensure_listen("80") + self.assertTrue(mock_add_dir.called) + self.assertTrue(mock_find.called) + self.assertEqual(mock_add_dir.call_args[0][1], "Listen") + self.assertEqual(mock_add_dir.call_args[0][2], "80") + + def test_add_listen_80_named(self): + mock_find = mock.Mock() + mock_find.return_value = ["test1", "test2", "test3"] + mock_get = mock.Mock() + mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] + mock_add_dir = mock.Mock() + + self.config.parser.find_dir = mock_find + self.config.parser.get_arg = mock_get + self.config.parser.add_dir = mock_add_dir + + self.config.ensure_listen("80") + self.assertEqual(mock_add_dir.call_count, 0) + + # Reset return lists and inputs + mock_add_dir.reset_mock() + mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] + + # Test + self.config.ensure_listen("8080") + self.assertEqual(mock_add_dir.call_count, 3) + self.assertTrue(mock_add_dir.called) + self.assertEqual(mock_add_dir.call_args[0][1], "Listen") + call_found = False + for mock_call in mock_add_dir.mock_calls: + if mock_call[1][2] == ['1.2.3.4:8080']: + call_found = True + self.assertTrue(call_found) + + def test_prepare_server_https(self): + mock_enable = mock.Mock() + self.config.enable_mod = mock_enable + + mock_find = mock.Mock() + mock_add_dir = mock.Mock() + mock_find.return_value = [] + + # This will test the Add listen + self.config.parser.find_dir = mock_find + self.config.parser.add_dir_to_ifmodssl = mock_add_dir + self.config.prepare_server_https("443") + # Changing the order these modules are enabled breaks the reverter + self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb") + self.assertEqual(mock_enable.call_args[0][0], "ssl") + self.assertEqual(mock_enable.call_args[1], {"temp": False}) + + self.config.prepare_server_https("8080", temp=True) + # Changing the order these modules are enabled breaks the reverter + self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb") + self.assertEqual(mock_enable.call_args[0][0], "ssl") + # Enable mod is temporary + self.assertEqual(mock_enable.call_args[1], {"temp": True}) + + self.assertEqual(mock_add_dir.call_count, 2) + + def test_prepare_server_https_named_listen(self): + mock_find = mock.Mock() + mock_find.return_value = ["test1", "test2", "test3"] + mock_get = mock.Mock() + mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] + mock_add_dir = mock.Mock() + mock_enable = mock.Mock() + + self.config.parser.find_dir = mock_find + self.config.parser.get_arg = mock_get + self.config.parser.add_dir_to_ifmodssl = mock_add_dir + self.config.enable_mod = mock_enable + + # Test Listen statements with specific ip listeed + self.config.prepare_server_https("443") + # Should be 0 as one interface already listens to 443 + self.assertEqual(mock_add_dir.call_count, 0) + + # Reset return lists and inputs + mock_add_dir.reset_mock() + mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] + + # Test + self.config.prepare_server_https("8080", temp=True) + self.assertEqual(mock_add_dir.call_count, 3) + call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)] + self.assertEqual( + sorted(call_args_list), + sorted([["1.2.3.4:8080", "https"], + ["[::1]:8080", "https"], + ["1.1.1.1:8080", "https"]])) + + # mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"] + # mock_find.return_value = ["test1", "test2", "test3"] + # self.config.parser.get_arg = mock_get + # self.config.prepare_server_https("8080", temp=True) + # self.assertEqual(self.listens, 0) + + def test_prepare_server_https_needed_listen(self): + mock_find = mock.Mock() + mock_find.return_value = ["test1", "test2"] + mock_get = mock.Mock() + mock_get.side_effect = ["1.2.3.4:8080", "80"] + mock_add_dir = mock.Mock() + mock_enable = mock.Mock() + + self.config.parser.find_dir = mock_find + self.config.parser.get_arg = mock_get + self.config.parser.add_dir_to_ifmodssl = mock_add_dir + self.config.enable_mod = mock_enable + + self.config.prepare_server_https("443") + self.assertEqual(mock_add_dir.call_count, 1) + + def test_prepare_server_https_mixed_listen(self): + + mock_find = mock.Mock() + mock_find.return_value = ["test1", "test2"] + mock_get = mock.Mock() + mock_get.side_effect = ["1.2.3.4:8080", "443"] + mock_add_dir = mock.Mock() + mock_enable = mock.Mock() + + self.config.parser.find_dir = mock_find + self.config.parser.get_arg = mock_get + self.config.parser.add_dir_to_ifmodssl = mock_add_dir + self.config.enable_mod = mock_enable + + # Test Listen statements with specific ip listeed + self.config.prepare_server_https("443") + # Should only be 2 here, as the third interface + # already listens to the correct port + self.assertEqual(mock_add_dir.call_count, 0) + + def test_make_vhost_ssl_with_mock_span(self): + # span excludes the closing tag in older versions + # of Augeas + return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142] + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: + mock_span.return_value = return_value + self.test_make_vhost_ssl() + + def test_make_vhost_ssl_with_mock_span2(self): + # span includes the closing tag in newer versions + # of Augeas + return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157] + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: + mock_span.return_value = return_value + self.test_make_vhost_ssl() + + def test_make_vhost_ssl_nonsymlink(self): + ssl_vhost_slink = self.config.make_vhost_ssl(self.vh_truth[8]) + self.assertTrue(ssl_vhost_slink.ssl) + self.assertTrue(ssl_vhost_slink.enabled) + self.assertEqual(ssl_vhost_slink.name, "nonsym.link") + + def test_make_vhost_ssl_nonexistent_vhost_path(self): + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) + self.assertEqual(os.path.dirname(ssl_vhost.filep), + os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) + + def test_make_vhost_ssl(self): + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) + + self.assertEqual( + ssl_vhost.filep, + os.path.join(self.config_path, "sites-available", + "encryption-example-le-ssl.conf")) + + self.assertEqual(ssl_vhost.path, + "/files" + ssl_vhost.filep + "/IfModule/Virtualhost") + self.assertEqual(len(ssl_vhost.addrs), 1) + self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs) + self.assertEqual(ssl_vhost.name, "encryption-example.demo") + self.assertTrue(ssl_vhost.ssl) + self.assertFalse(ssl_vhost.enabled) + + self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]), + self.config.is_name_vhost(ssl_vhost)) + + self.assertEqual(len(self.config.vhosts), 13) + + def test_clean_vhost_ssl(self): + # pylint: disable=protected-access + for directive in ["SSLCertificateFile", "SSLCertificateKeyFile", + "SSLCertificateChainFile", "SSLCACertificatePath"]: + for _ in range(10): + self.config.parser.add_dir(self.vh_truth[1].path, + directive, ["bogus"]) + self.config.save() + + self.config._clean_vhost(self.vh_truth[1]) + self.config.save() + + loc_cert = self.config.parser.find_dir( + 'SSLCertificateFile', None, self.vh_truth[1].path, False) + loc_key = self.config.parser.find_dir( + 'SSLCertificateKeyFile', None, self.vh_truth[1].path, False) + loc_chain = self.config.parser.find_dir( + 'SSLCertificateChainFile', None, self.vh_truth[1].path, False) + loc_cacert = self.config.parser.find_dir( + 'SSLCACertificatePath', None, self.vh_truth[1].path, False) + + self.assertEqual(len(loc_cert), 1) + self.assertEqual(len(loc_key), 1) + + self.assertEqual(len(loc_chain), 0) + + self.assertEqual(len(loc_cacert), 10) + + def test_deduplicate_directives(self): + # pylint: disable=protected-access + DIRECTIVE = "Foo" + for _ in range(10): + self.config.parser.add_dir(self.vh_truth[1].path, + DIRECTIVE, ["bar"]) + self.config.save() + + self.config._deduplicate_directives(self.vh_truth[1].path, [DIRECTIVE]) + self.config.save() + + self.assertEqual( + len(self.config.parser.find_dir( + DIRECTIVE, None, self.vh_truth[1].path, False)), 1) + + def test_remove_directives(self): + # pylint: disable=protected-access + DIRECTIVES = ["Foo", "Bar"] + for directive in DIRECTIVES: + for _ in range(10): + self.config.parser.add_dir(self.vh_truth[2].path, + directive, ["baz"]) + self.config.save() + + self.config._remove_directives(self.vh_truth[2].path, DIRECTIVES) + self.config.save() + + for directive in DIRECTIVES: + self.assertEqual( + len(self.config.parser.find_dir( + directive, None, self.vh_truth[2].path, False)), 0) + + def test_make_vhost_ssl_bad_write(self): + mock_open = mock.mock_open() + # This calls open + self.config.reverter.register_file_creation = mock.Mock() + mock_open.side_effect = IOError + with mock.patch("six.moves.builtins.open", mock_open): + self.assertRaises( + errors.PluginError, + self.config.make_vhost_ssl, self.vh_truth[0]) + + def test_get_ssl_vhost_path(self): + # pylint: disable=protected-access + self.assertTrue( + self.config._get_ssl_vhost_path("example_path").endswith(".conf")) + + def test_add_name_vhost_if_necessary(self): + # pylint: disable=protected-access + self.config.add_name_vhost = mock.Mock() + self.config.version = (2, 2) + self.config._add_name_vhost_if_necessary(self.vh_truth[0]) + self.assertTrue(self.config.add_name_vhost.called) + + new_addrs = set() + for addr in self.vh_truth[0].addrs: + new_addrs.add(obj.Addr(("_default_", addr.get_port(),))) + + self.vh_truth[0].addrs = new_addrs + self.config._add_name_vhost_if_necessary(self.vh_truth[0]) + self.assertEqual(self.config.add_name_vhost.call_count, 2) + + @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + def test_perform(self, mock_restart, mock_http_perform): + # Only tests functionality specific to configurator.perform + # Note: As more challenges are offered this will have to be expanded + account_key, achalls = self.get_key_and_achalls() + + expected = [achall.response(account_key) for achall in achalls] + mock_http_perform.return_value = expected + + responses = self.config.perform(achalls) + + self.assertEqual(mock_http_perform.call_count, 1) + self.assertEqual(responses, expected) + + self.assertEqual(mock_restart.call_count, 1) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_cleanup(self, mock_cfg, mock_restart): + mock_cfg.return_value = "" + _, achalls = self.get_key_and_achalls() + + for achall in achalls: + self.config._chall_out.add(achall) # pylint: disable=protected-access + + for i, achall in enumerate(achalls): + self.config.cleanup([achall]) + if i == len(achalls) - 1: + self.assertTrue(mock_restart.called) + else: + self.assertFalse(mock_restart.called) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_cleanup_no_errors(self, mock_cfg, mock_restart): + mock_cfg.return_value = "" + _, achalls = self.get_key_and_achalls() + self.config.http_doer = mock.MagicMock() + + for achall in achalls: + self.config._chall_out.add(achall) # pylint: disable=protected-access + + self.config.cleanup([achalls[-1]]) + self.assertFalse(mock_restart.called) + + self.config.cleanup(achalls) + self.assertTrue(mock_restart.called) + + @mock.patch("certbot.util.run_script") + def test_get_version(self, mock_script): + mock_script.return_value = ( + "Server Version: Apache/2.4.2 (Debian)", "") + self.assertEqual(self.config.get_version(), (2, 4, 2)) + + mock_script.return_value = ( + "Server Version: Apache/2 (Linux)", "") + self.assertEqual(self.config.get_version(), (2,)) + + mock_script.return_value = ( + "Server Version: Apache (Debian)", "") + self.assertRaises(errors.PluginError, self.config.get_version) + + mock_script.return_value = ( + "Server Version: Apache/2.3{0} Apache/2.4.7".format( + os.linesep), "") + self.assertRaises(errors.PluginError, self.config.get_version) + + mock_script.side_effect = errors.SubprocessError("Can't find program") + self.assertRaises(errors.PluginError, self.config.get_version) + + @mock.patch("certbot_apache._internal.configurator.util.run_script") + def test_restart(self, _): + self.config.restart() + + @mock.patch("certbot_apache._internal.configurator.util.run_script") + def test_restart_bad_process(self, mock_run_script): + mock_run_script.side_effect = [None, errors.SubprocessError] + + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + @mock.patch("certbot.util.run_script") + def test_config_test(self, _): + self.config.config_test() + + @mock.patch("certbot.util.run_script") + def test_config_test_bad_process(self, mock_run_script): + mock_run_script.side_effect = errors.SubprocessError + + self.assertRaises(errors.MisconfigurationError, + self.config.config_test) + + def test_more_info(self): + self.assertTrue(self.config.more_info()) + + def test_get_chall_pref(self): + self.assertTrue(isinstance(self.config.get_chall_pref(""), list)) + + def test_install_ssl_options_conf(self): + path = os.path.join(self.work_dir, "test_it") + other_path = os.path.join(self.work_dir, "other_test_it") + self.config.install_ssl_options_conf(path, other_path) + self.assertTrue(os.path.isfile(path)) + self.assertTrue(os.path.isfile(other_path)) + + # TEST ENHANCEMENTS + def test_supported_enhancements(self): + self.assertTrue(isinstance(self.config.supported_enhancements(), list)) + + def test_find_http_vhost_without_ancestor(self): + # pylint: disable=protected-access + vhost = self.vh_truth[0] + vhost.ssl = True + vhost.ancestor = None + res = self.config._get_http_vhost(vhost) + self.assertEqual(self.vh_truth[0].name, res.name) + self.assertEqual(self.vh_truth[0].aliases, res.aliases) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + @mock.patch("certbot.util.exe_exists") + def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get): + self.config.parser.modules.add("rewrite_module") + mock_exe.return_value = True + ssl_vh1 = obj.VirtualHost( + "fp1", "ap1", set([obj.Addr(("*", "443"))]), + True, False) + ssl_vh1.name = "satoshi.com" + self.config.vhosts.append(ssl_vh1) + mock_sel_vhost.return_value = None + mock_get.return_value = None + + self.assertRaises( + errors.PluginError, + self.config.enhance, "satoshi.com", "redirect") + + def test_enhance_unknown_enhancement(self): + self.assertRaises( + errors.PluginError, + self.config.enhance, "certbot.demo", "unknown_enhancement") + + def test_enhance_no_ssl_vhost(self): + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: + self.assertRaises(errors.PluginError, self.config.enhance, + "certbot.demo", "redirect") + # Check that correct logger.warning was printed + self.assertTrue("not able to find" in mock_log.call_args[0][0]) + self.assertTrue("\"redirect\"" in mock_log.call_args[0][0]) + + mock_log.reset_mock() + + self.assertRaises(errors.PluginError, self.config.enhance, + "certbot.demo", "ensure-http-header", "Test") + # Check that correct logger.warning was printed + self.assertTrue("not able to find" in mock_log.call_args[0][0]) + self.assertTrue("Test" in mock_log.call_args[0][0]) + + @mock.patch("certbot.util.exe_exists") + def test_ocsp_stapling(self, mock_exe): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + self.config.get_version = mock.Mock(return_value=(2, 4, 7)) + mock_exe.return_value = True + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "staple-ocsp") + + # Get the ssl vhost for certbot.demo + ssl_vhost = self.config.assoc["certbot.demo"] + + ssl_use_stapling_aug_path = self.config.parser.find_dir( + "SSLUseStapling", "on", ssl_vhost.path) + + self.assertEqual(len(ssl_use_stapling_aug_path), 1) + + ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) + stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', + "shmcb:/var/run/apache2/stapling_cache(128000)", + ssl_vhost_aug_path) + + self.assertEqual(len(stapling_cache_aug_path), 1) + + @mock.patch("certbot.util.exe_exists") + def test_ocsp_stapling_twice(self, mock_exe): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + self.config.get_version = mock.Mock(return_value=(2, 4, 7)) + mock_exe.return_value = True + + # Checking the case with already enabled ocsp stapling configuration + self.config.choose_vhost("ocspvhost.com") + self.config.enhance("ocspvhost.com", "staple-ocsp") + + # Get the ssl vhost for letsencrypt.demo + ssl_vhost = self.config.assoc["ocspvhost.com"] + + ssl_use_stapling_aug_path = self.config.parser.find_dir( + "SSLUseStapling", "on", ssl_vhost.path) + + self.assertEqual(len(ssl_use_stapling_aug_path), 1) + ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) + stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', + "shmcb:/var/run/apache2/stapling_cache(128000)", + ssl_vhost_aug_path) + + self.assertEqual(len(stapling_cache_aug_path), 1) + + + @mock.patch("certbot.util.exe_exists") + def test_ocsp_unsupported_apache_version(self, mock_exe): + mock_exe.return_value = True + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + self.config.get_version = mock.Mock(return_value=(2, 2, 0)) + self.config.choose_vhost("certbot.demo") + + self.assertRaises(errors.PluginError, + self.config.enhance, "certbot.demo", "staple-ocsp") + + + def test_get_http_vhost_third_filter(self): + ssl_vh = obj.VirtualHost( + "fp", "ap", set([obj.Addr(("*", "443"))]), + True, False) + ssl_vh.name = "satoshi.com" + self.config.vhosts.append(ssl_vh) + + # pylint: disable=protected-access + http_vh = self.config._get_http_vhost(ssl_vh) + self.assertFalse(http_vh.ssl) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_http_header_hsts(self, mock_exe, _): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("headers_module") + mock_exe.return_value = True + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "ensure-http-header", + "Strict-Transport-Security") + + # Get the ssl vhost for certbot.demo + ssl_vhost = self.config.assoc["certbot.demo"] + + # These are not immediately available in find_dir even with save() and + # load(). They must be found in sites-available + hsts_header = self.config.parser.find_dir( + "Header", None, ssl_vhost.path) + + # four args to HSTS header + self.assertEqual(len(hsts_header), 4) + + def test_http_header_hsts_twice(self): + self.config.parser.modules.add("mod_ssl.c") + # skip the enable mod + self.config.parser.modules.add("headers_module") + + # This will create an ssl vhost for encryption-example.demo + self.config.choose_vhost("encryption-example.demo") + self.config.enhance("encryption-example.demo", "ensure-http-header", + "Strict-Transport-Security") + + self.assertRaises( + errors.PluginEnhancementAlreadyPresent, + self.config.enhance, "encryption-example.demo", + "ensure-http-header", "Strict-Transport-Security") + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_http_header_uir(self, mock_exe, _): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("headers_module") + + mock_exe.return_value = True + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "ensure-http-header", + "Upgrade-Insecure-Requests") + + self.assertTrue("headers_module" in self.config.parser.modules) + + # Get the ssl vhost for certbot.demo + ssl_vhost = self.config.assoc["certbot.demo"] + + # These are not immediately available in find_dir even with save() and + # load(). They must be found in sites-available + uir_header = self.config.parser.find_dir( + "Header", None, ssl_vhost.path) + + # four args to HSTS header + self.assertEqual(len(uir_header), 4) + + def test_http_header_uir_twice(self): + self.config.parser.modules.add("mod_ssl.c") + # skip the enable mod + self.config.parser.modules.add("headers_module") + + # This will create an ssl vhost for encryption-example.demo + self.config.choose_vhost("encryption-example.demo") + self.config.enhance("encryption-example.demo", "ensure-http-header", + "Upgrade-Insecure-Requests") + + self.assertRaises( + errors.PluginEnhancementAlreadyPresent, + self.config.enhance, "encryption-example.demo", + "ensure-http-header", "Upgrade-Insecure-Requests") + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_redirect_well_formed_http(self, mock_exe, _): + self.config.parser.modules.add("rewrite_module") + self.config.parser.update_runtime_variables = mock.Mock() + mock_exe.return_value = True + self.config.get_version = mock.Mock(return_value=(2, 2)) + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "redirect") + + # These are not immediately available in find_dir even with save() and + # load(). They must be found in sites-available + rw_engine = self.config.parser.find_dir( + "RewriteEngine", "on", self.vh_truth[3].path) + rw_rule = self.config.parser.find_dir( + "RewriteRule", None, self.vh_truth[3].path) + + self.assertEqual(len(rw_engine), 1) + # three args to rw_rule + self.assertEqual(len(rw_rule), 3) + + # [:-3] to remove the vhost index number + self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) + self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) + + def test_rewrite_rule_exists(self): + # Skip the enable mod + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 3, 9)) + self.config.parser.add_dir( + self.vh_truth[3].path, "RewriteRule", ["Unknown"]) + # pylint: disable=protected-access + self.assertTrue(self.config._is_rewrite_exists(self.vh_truth[3])) + + def test_rewrite_engine_exists(self): + # Skip the enable mod + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 3, 9)) + self.config.parser.add_dir( + self.vh_truth[3].path, "RewriteEngine", "on") + # pylint: disable=protected-access + self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3])) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_redirect_with_existing_rewrite(self, mock_exe, _): + self.config.parser.modules.add("rewrite_module") + self.config.parser.update_runtime_variables = mock.Mock() + mock_exe.return_value = True + self.config.get_version = mock.Mock(return_value=(2, 2, 0)) + + # Create a preexisting rewrite rule + self.config.parser.add_dir( + self.vh_truth[3].path, "RewriteRule", ["UnknownPattern", + "UnknownTarget"]) + self.config.save() + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "redirect") + + # These are not immediately available in find_dir even with save() and + # load(). They must be found in sites-available + rw_engine = self.config.parser.find_dir( + "RewriteEngine", "on", self.vh_truth[3].path) + rw_rule = self.config.parser.find_dir( + "RewriteRule", None, self.vh_truth[3].path) + + self.assertEqual(len(rw_engine), 1) + # three args to rw_rule + 1 arg for the pre existing rewrite + self.assertEqual(len(rw_rule), 5) + # [:-3] to remove the vhost index number + self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) + self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) + + self.assertTrue("rewrite_module" in self.config.parser.modules) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_redirect_with_old_https_redirection(self, mock_exe, _): + self.config.parser.modules.add("rewrite_module") + self.config.parser.update_runtime_variables = mock.Mock() + mock_exe.return_value = True + self.config.get_version = mock.Mock(return_value=(2, 2, 0)) + + ssl_vhost = self.config.choose_vhost("certbot.demo") + + # pylint: disable=protected-access + http_vhost = self.config._get_http_vhost(ssl_vhost) + + # Create an old (previously suppoorted) https redirectoin rewrite rule + self.config.parser.add_dir( + http_vhost.path, "RewriteRule", + ["^", + "https://%{SERVER_NAME}%{REQUEST_URI}", + "[L,QSA,R=permanent]"]) + + self.config.save() + + try: + self.config.enhance("certbot.demo", "redirect") + except errors.PluginEnhancementAlreadyPresent: + args_paths = self.config.parser.find_dir( + "RewriteRule", None, http_vhost.path, False) + arg_vals = [self.config.parser.aug.get(x) for x in args_paths] + self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) + + + def test_redirect_with_conflict(self): + self.config.parser.modules.add("rewrite_module") + ssl_vh = obj.VirtualHost( + "fp", "ap", set([obj.Addr(("*", "443")), + obj.Addr(("zombo.com",))]), + True, False) + # No names ^ this guy should conflict. + + # pylint: disable=protected-access + self.assertRaises( + errors.PluginError, self.config._enable_redirect, ssl_vh, "") + + def test_redirect_two_domains_one_vhost(self): + # Skip the enable mod + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 3, 9)) + + # Creates ssl vhost for the domain + self.config.choose_vhost("red.blue.purple.com") + + self.config.enhance("red.blue.purple.com", "redirect") + verify_no_redirect = ("certbot_apache._internal.configurator." + "ApacheConfigurator._verify_no_certbot_redirect") + with mock.patch(verify_no_redirect) as mock_verify: + self.config.enhance("green.blue.purple.com", "redirect") + self.assertFalse(mock_verify.called) + + def test_redirect_from_previous_run(self): + # Skip the enable mod + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 3, 9)) + self.config.choose_vhost("red.blue.purple.com") + self.config.enhance("red.blue.purple.com", "redirect") + # Clear state about enabling redirect on this run + # pylint: disable=protected-access + self.config._enhanced_vhosts["redirect"].clear() + + self.assertRaises( + errors.PluginEnhancementAlreadyPresent, + self.config.enhance, "green.blue.purple.com", "redirect") + + def test_create_own_redirect(self): + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 3, 9)) + # For full testing... give names... + self.vh_truth[1].name = "default.com" + self.vh_truth[1].aliases = set(["yes.default.com"]) + + # pylint: disable=protected-access + self.config._enable_redirect(self.vh_truth[1], "") + self.assertEqual(len(self.config.vhosts), 13) + + def test_create_own_redirect_for_old_apache_version(self): + self.config.parser.modules.add("rewrite_module") + self.config.get_version = mock.Mock(return_value=(2, 2)) + # For full testing... give names... + self.vh_truth[1].name = "default.com" + self.vh_truth[1].aliases = set(["yes.default.com"]) + + # pylint: disable=protected-access + self.config._enable_redirect(self.vh_truth[1], "") + self.assertEqual(len(self.config.vhosts), 13) + + def test_sift_rewrite_rule(self): + # pylint: disable=protected-access + small_quoted_target = "RewriteRule ^ \"http://\"" + self.assertFalse(self.config._sift_rewrite_rule(small_quoted_target)) + + https_target = "RewriteRule ^ https://satoshi" + self.assertTrue(self.config._sift_rewrite_rule(https_target)) + + normal_target = "RewriteRule ^/(.*) http://www.a.com:1234/$1 [L,R]" + self.assertFalse(self.config._sift_rewrite_rule(normal_target)) + + not_rewriterule = "NotRewriteRule ^ ..." + self.assertFalse(self.config._sift_rewrite_rule(not_rewriterule)) + + def get_key_and_achalls(self): + """Return testing achallenges.""" + account_key = self.rsa512jwk + achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01( + token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"), + "pending"), + domain="encryption-example.demo", account_key=account_key) + achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01( + token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"), + "pending"), + domain="certbot.demo", account_key=account_key) + achall3 = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=(b'x' * 16)), "pending"), + domain="example.org", account_key=account_key) + + return account_key, (achall1, achall2, achall3) + + def test_enable_site_nondebian(self): + inc_path = "/path/to/wherever" + vhost = self.vh_truth[0] + vhost.enabled = False + vhost.filep = inc_path + self.assertFalse(self.config.parser.find_dir("Include", inc_path)) + self.assertFalse( + os.path.dirname(inc_path) in self.config.parser.existing_paths) + self.config.enable_site(vhost) + self.assertTrue(self.config.parser.find_dir("Include", inc_path)) + self.assertTrue( + os.path.dirname(inc_path) in self.config.parser.existing_paths) + self.assertTrue( + os.path.basename(inc_path) in self.config.parser.existing_paths[ + os.path.dirname(inc_path)]) + + def test_deploy_cert_not_parsed_path(self): + # Make sure that we add include to root config for vhosts when + # handle-sites is false + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("socache_shmcb_module") + tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) + filesystem.chmod(tmp_path, 0o755) + mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path" + mock_a = "certbot_apache._internal.parser.ApacheParser.add_include" + + with mock.patch(mock_p) as mock_path: + mock_path.return_value = os.path.join(tmp_path, "whatever.conf") + with mock.patch(mock_a) as mock_add: + self.config.deploy_cert( + "encryption-example.demo", + "example/cert.pem", "example/key.pem", + "example/cert_chain.pem") + # Test that we actually called add_include + self.assertTrue(mock_add.called) + shutil.rmtree(tmp_path) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") + def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed): + ret_vh = self.vh_truth[8] + ret_vh.enabled = True + self.config.enable_site(ret_vh) + # Make sure that we return early + self.assertFalse(mock_parsed.called) + + def test_enable_mod_unsupported(self): + self.assertRaises(errors.MisconfigurationError, + self.config.enable_mod, + "whatever") + + def test_wildcard_domain(self): + # pylint: disable=protected-access + cases = {u"*.example.org": True, b"*.x.example.org": True, + u"a.example.org": False, b"a.x.example.org": False} + for key in cases: + self.assertEqual(self.config._wildcard_domain(key), cases[key]) + + def test_choose_vhosts_wildcard(self): + # pylint: disable=protected-access + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + mock_select_vhs.return_value = [self.vh_truth[3]] + vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", + create_ssl=True) + # Check that the dialog was called with one vh: certbot.demo + 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.assertEqual(len(vhs), 1) + self.assertTrue(vhs[0].name == "certbot.demo") + self.assertTrue(vhs[0].ssl) + + self.assertFalse(vhs[0] == self.vh_truth[3]) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") + def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): + # pylint: disable=protected-access + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + mock_select_vhs.return_value = [self.vh_truth[1]] + vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", + create_ssl=False) + self.assertFalse(mock_makessl.called) + self.assertEqual(vhs[0], self.vh_truth[1]) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") + def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w): + # pylint: disable=protected-access + # Already SSL vhost + mock_vh_for_w.return_value = [self.vh_truth[7]] + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" + with mock.patch(mock_path) as mock_select_vhs: + mock_select_vhs.return_value = [self.vh_truth[7]] + vhs = self.config._choose_vhosts_wildcard("whatever", + create_ssl=True) + 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.assertEqual(len(vhs), 1) + self.assertTrue(vhs[0].ssl) + self.assertEqual(vhs[0], self.vh_truth[7]) + + + def test_deploy_cert_wildcard(self): + # pylint: disable=protected-access + mock_choose_vhosts = mock.MagicMock() + mock_choose_vhosts.return_value = [self.vh_truth[7]] + self.config._choose_vhosts_wildcard = mock_choose_vhosts + mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert" + with mock.patch(mock_d) as mock_dep: + self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", + "/tmp/path", "/tmp/path", "/tmp/path") + self.assertTrue(mock_dep.called) + 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._internal.display_ops.select_vhost_multiple") + def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): + # pylint: disable=protected-access + mock_dialog.return_value = [] + self.assertRaises(errors.PluginError, + self.config.deploy_cert, + "*.wild.cat", "/tmp/path", "/tmp/path", + "/tmp/path", "/tmp/path") + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") + def test_enhance_wildcard_after_install(self, mock_choose): + # pylint: disable=protected-access + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("headers_module") + self.vh_truth[3].ssl = True + self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]] + self.config.enhance("*.certbot.demo", "ensure-http-header", + "Upgrade-Insecure-Requests") + self.assertFalse(mock_choose.called) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") + def test_enhance_wildcard_no_install(self, mock_choose): + self.vh_truth[3].ssl = True + mock_choose.return_value = [self.vh_truth[3]] + self.config.parser.modules.add("mod_ssl.c") + self.config.parser.modules.add("headers_module") + self.config.enhance("*.certbot.demo", "ensure-http-header", + "Upgrade-Insecure-Requests") + self.assertTrue(mock_choose.called) + + def test_add_vhost_id(self): + for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]: + vh_id = self.config.add_vhost_id(vh) + self.assertEqual(vh, self.config.find_vhost_by_id(vh_id)) + + def test_find_vhost_by_id_404(self): + self.assertRaises(errors.PluginError, + self.config.find_vhost_by_id, + "nonexistent") + + def test_add_vhost_id_already_exists(self): + first_id = self.config.add_vhost_id(self.vh_truth[0]) + second_id = self.config.add_vhost_id(self.vh_truth[0]) + self.assertEqual(first_id, second_id) + + def test_realpath_replaces_symlink(self): + orig_match = self.config.parser.aug.match + mock_vhost = copy.deepcopy(self.vh_truth[0]) + mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') + mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') + mock_vhost.enabled = False + self.config.parser.parse_file(mock_vhost.filep) + + def mock_match(aug_expr): + """Return a mocked match list of VirtualHosts""" + if "/mocked/path" in aug_expr: + return [self.vh_truth[1].path, self.vh_truth[0].path, mock_vhost.path] + return orig_match(aug_expr) + + self.config.parser.parser_paths = ["/mocked/path"] + self.config.parser.aug.match = mock_match + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + self.assertTrue(vhs[0] == self.vh_truth[1]) + # mock_vhost should have replaced the vh_truth[0], because its filepath + # isn't a symlink + self.assertTrue(vhs[1] == mock_vhost) + + +class AugeasVhostsTest(util.ApacheTest): + """Test vhosts with illegal names dependent on augeas version.""" + # pylint: disable=protected-access + + def setUp(self): # pylint: disable=arguments-differ + td = "debian_apache_2_4/augeas_vhosts" + cr = "debian_apache_2_4/augeas_vhosts/apache2" + vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available" + super(AugeasVhostsTest, self).setUp(test_dir=td, + config_root=cr, + vhost_root=vr) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, + self.work_dir) + + def test_choosevhost_with_illegal_name(self): + self.config.parser.aug = mock.MagicMock() + self.config.parser.aug.match.side_effect = RuntimeError + path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" + chosen_vhost = self.config._create_vhost(path) + self.assertEqual(None, chosen_vhost) + + def test_choosevhost_works(self): + path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" + chosen_vhost = self.config._create_vhost(path) + self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") + def test_get_vhost_continue(self, mock_vhost): + mock_vhost.return_value = None + vhs = self.config.get_virtual_hosts() + self.assertEqual([], vhs) + + def test_choose_vhost_with_matching_wildcard(self): + names = ( + "an.example.net", "another.example.net", "an.other.example.net") + for name in names: + self.assertFalse(name in self.config.choose_vhost(name).aliases) + + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") + def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): + mock_conflicts.return_value = False + mock_path = "certbot_apache._internal.display_ops.select_vhost" + with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): + for name in ("a.example.net", "other.example.net"): + self.assertTrue(name in self.config.choose_vhost(name).aliases) + + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") + def test_choose_vhost_wildcard_not_found(self, mock_conflicts): + mock_conflicts.return_value = False + mock_path = "certbot_apache._internal.display_ops.select_vhost" + names = ( + "abc.example.net", "not.there.tld", "aa.wildcard.tld" + ) + with mock.patch(mock_path) as mock_select: + mock_select.return_value = self.config.vhosts[0] + for name in names: + orig_cc = mock_select.call_count + self.config.choose_vhost(name) + self.assertEqual(mock_select.call_count - orig_cc, 1) + + def test_choose_vhost_wildcard_found(self): + mock_path = "certbot_apache._internal.display_ops.select_vhost" + names = ( + "ab.example.net", "a.wildcard.tld", "yetanother.example.net" + ) + with mock.patch(mock_path) as mock_select: + mock_select.return_value = self.config.vhosts[0] + for name in names: + self.config.choose_vhost(name) + self.assertEqual(mock_select.call_count, 0) + + def test_augeas_span_error(self): + broken_vhost = self.config.vhosts[0] + broken_vhost.path = broken_vhost.path + "/nonexistent" + self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, + broken_vhost) + +class MultiVhostsTest(util.ApacheTest): + """Test configuration with multiple virtualhosts in a single file.""" + # pylint: disable=protected-access + + def setUp(self): # pylint: disable=arguments-differ + td = "debian_apache_2_4/multi_vhosts" + cr = "debian_apache_2_4/multi_vhosts/apache2" + vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available" + super(MultiVhostsTest, self).setUp(test_dir=td, + config_root=cr, + vhost_root=vr) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, + self.config_dir, self.work_dir, conf_vhost_path=self.vhost_path) + self.vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multi_vhosts") + + def test_make_vhost_ssl(self): + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) + + self.assertEqual( + ssl_vhost.filep, + os.path.join(self.config_path, "sites-available", + "default-le-ssl.conf")) + + self.assertEqual(ssl_vhost.path, + "/files" + ssl_vhost.filep + "/IfModule/VirtualHost") + self.assertEqual(len(ssl_vhost.addrs), 1) + self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs) + self.assertEqual(ssl_vhost.name, "banana.vomit.com") + self.assertTrue(ssl_vhost.ssl) + self.assertFalse(ssl_vhost.enabled) + + + self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]), + self.config.is_name_vhost(ssl_vhost)) + + mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" + with mock.patch(mock_path) as mock_getpath: + mock_getpath.return_value = None + self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, + self.vh_truth[1]) + + def test_get_new_path(self): + with_index_1 = ["/path[1]/section[1]"] + without_index = ["/path/section"] + with_index_2 = ["/path[2]/section[2]"] + self.assertEqual(self.config._get_new_vh_path(without_index, + with_index_1), + None) + self.assertEqual(self.config._get_new_vh_path(without_index, + with_index_2), + with_index_2[0]) + + both = with_index_1 + with_index_2 + self.assertEqual(self.config._get_new_vh_path(without_index, both), + with_index_2[0]) + + @certbot_util.patch_get_utility() + def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_get_utility): + self.config.parser.modules.add("rewrite_module") + + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4]) + + self.assertTrue(self.config.parser.find_dir( + "RewriteEngine", "on", ssl_vhost.path, False)) + + 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/(.+)\" " + "\"http://new.example.com/docs/$1\" [R,L]") + self.assertTrue(commented_rewrite_rule in conf_text) + self.assertTrue(uncommented_rewrite_rule in conf_text) + mock_get_utility().add_message.assert_called_once_with(mock.ANY, + mock.ANY) + + @certbot_util.patch_get_utility() + def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_get_utility): + self.config.parser.modules.add("rewrite_module") + + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3]) + + 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") + not_commented_rewrite_rule = ("RewriteRule " + "^(.*)$ b://u%{REQUEST_URI} [P,NE,L]") + + commented_cond1 = "# RewriteCond %{HTTPS} !=on" + commented_cond2 = "# RewriteCond %{HTTPS} !^$" + commented_rewrite_rule = ("# RewriteRule ^ " + "https://%{SERVER_NAME}%{REQUEST_URI} " + "[L,NE,R=permanent]") + + self.assertTrue(not_commented_cond1 in conf_line_set) + self.assertTrue(not_commented_rewrite_rule in conf_line_set) + + self.assertTrue(commented_cond1 in conf_line_set) + self.assertTrue(commented_cond2 in conf_line_set) + self.assertTrue(commented_rewrite_rule in conf_line_set) + mock_get_utility().add_message.assert_called_once_with(mock.ANY, + mock.ANY) + + +class InstallSslOptionsConfTest(util.ApacheTest): + """Test that the options-ssl-nginx.conf file is installed and updated properly.""" + + def setUp(self): # pylint: disable=arguments-differ + super(InstallSslOptionsConfTest, self).setUp() + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir) + + def _call(self): + self.config.install_ssl_options_conf(self.config.mod_ssl_conf, + self.config.updated_mod_ssl_conf_digest) + + def _current_ssl_options_hash(self): + return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC")) + + def _assert_current_file(self): + self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) + self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), + self._current_ssl_options_hash()) + + def test_no_file(self): + # prepare should have placed a file there + self._assert_current_file() + os.remove(self.config.mod_ssl_conf) + self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) + self._call() + self._assert_current_file() + + def test_current_file(self): + self._assert_current_file() + self._call() + self._assert_current_file() + + def test_prev_file_updates_to_current(self): + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES + ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match") + with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: + mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] + self._call() + self._assert_current_file() + + def test_manually_modified_current_file_does_not_update(self): + with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) + self.assertEqual(crypto_util.sha256sum( + self.config.option("MOD_SSL_CONF_SRC")), + self._current_ssl_options_hash()) + self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), + self._current_ssl_options_hash()) + + def test_manually_modified_past_file_warns(self): + with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: + mod_ssl_conf.write("a new line for the wrong hash\n") + with open(self.config.updated_mod_ssl_conf_digest, "w") as f: + f.write("hashofanoldversion") + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertEqual(mock_logger.warning.call_args[0][0], + "%s has been manually modified; updated file " + "saved to %s. We recommend updating %s for security purposes.") + self.assertEqual(crypto_util.sha256sum( + self.config.option("MOD_SSL_CONF_SRC")), + self._current_ssl_options_hash()) + # only print warning once + with mock.patch("certbot.plugins.common.logger") as mock_logger: + self._call() + self.assertFalse(mock_logger.warning.called) + + def test_current_file_hash_in_all_hashes(self): + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES + self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended" + " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py new file mode 100644 index 000000000..1c6e3bf71 --- /dev/null +++ b/certbot-apache/tests/debian_test.py @@ -0,0 +1,214 @@ +"""Test for certbot_apache._internal.configurator for Debian overrides""" +import shutil +import unittest + +import mock + +from certbot import errors +from certbot.compat import os + +from certbot_apache._internal import apache_util +from certbot_apache._internal import obj + +import util + + +class MultipleVhostsTestDebian(util.ApacheTest): + """Multiple vhost tests for Debian family of distros""" + + _multiprocess_can_split_ = True + + def setUp(self): # pylint: disable=arguments-differ + super(MultipleVhostsTestDebian, self).setUp() + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="debian") + self.config = self.mock_deploy_cert(self.config) + self.vh_truth = util.get_vh_truth(self.temp_dir, + "debian_apache_2_4/multiple_vhosts") + + def mock_deploy_cert(self, config): + """A test for a mock deploy cert""" + config.real_deploy_cert = self.config.deploy_cert + + def mocked_deploy_cert(*args, **kwargs): + """a helper to mock a deployed cert""" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" + d_mod = "certbot_apache._internal.override_debian.DebianConfigurator.enable_mod" + with mock.patch(g_mod): + with mock.patch(d_mod): + config.real_deploy_cert(*args, **kwargs) + self.config.deploy_cert = mocked_deploy_cert + return self.config + + def test_enable_mod_unsupported_dirs(self): + shutil.rmtree(os.path.join(self.config.parser.root, "mods-enabled")) + self.assertRaises( + errors.NotSupportedError, self.config.enable_mod, "ssl") + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): + mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") + mock_popen().returncode = 0 + mock_exe_exists.return_value = True + + self.config.enable_mod("ssl") + self.assertTrue("ssl_module" in self.config.parser.modules) + self.assertTrue("mod_ssl.c" in self.config.parser.modules) + + self.assertTrue(mock_run_script.called) + + def test_deploy_cert_enable_new_vhost(self): + # Create + ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + self.assertFalse(ssl_vhost.enabled) + self.config.deploy_cert( + "encryption-example.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.assertTrue(ssl_vhost.enabled) + # Make sure that we don't error out if symlink already exists + ssl_vhost.enabled = False + self.assertFalse(ssl_vhost.enabled) + self.config.deploy_cert( + "encryption-example.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.assertTrue(ssl_vhost.enabled) + + def test_enable_site_failure(self): + self.config.parser.root = "/tmp/nonexistent" + with mock.patch("certbot.compat.os.path.isdir") as mock_dir: + mock_dir.return_value = True + with mock.patch("certbot.compat.os.path.islink") as mock_link: + mock_link.return_value = False + self.assertRaises( + errors.NotSupportedError, + self.config.enable_site, + obj.VirtualHost("asdf", "afsaf", set(), False, False)) + + def test_deploy_cert_newssl(self): + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, + self.work_dir, version=(2, 4, 16)) + self.config = self.mock_deploy_cert(self.config) + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + self.config.deploy_cert( + "random.demo", "example/cert.pem", "example/key.pem", + "example/cert_chain.pem", "example/fullchain.pem") + self.config.save() + + # Verify ssl_module was enabled. + self.assertTrue(self.vh_truth[1].enabled) + self.assertTrue("ssl_module" in self.config.parser.modules) + + loc_cert = self.config.parser.find_dir( + "sslcertificatefile", "example/fullchain.pem", + self.vh_truth[1].path) + loc_key = self.config.parser.find_dir( + "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) + + # Verify one directive was found in the correct file + self.assertEqual(len(loc_cert), 1) + self.assertEqual( + apache_util.get_file_path(loc_cert[0]), + self.vh_truth[1].filep) + + self.assertEqual(len(loc_key), 1) + self.assertEqual( + apache_util.get_file_path(loc_key[0]), + self.vh_truth[1].filep) + + def test_deploy_cert_newssl_no_fullchain(self): + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, + self.work_dir, version=(2, 4, 16)) + self.config = self.mock_deploy_cert(self.config) + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + self.assertRaises(errors.PluginError, + lambda: self.config.deploy_cert( + "random.demo", "example/cert.pem", + "example/key.pem")) + + def test_deploy_cert_old_apache_no_chain(self): + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, + self.work_dir, version=(2, 4, 7)) + self.config = self.mock_deploy_cert(self.config) + self.config.parser.modules.add("ssl_module") + self.config.parser.modules.add("mod_ssl.c") + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + self.assertRaises(errors.PluginError, + lambda: self.config.deploy_cert( + "random.demo", "example/cert.pem", + "example/key.pem")) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_ocsp_stapling_enable_mod(self, mock_exe, _): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + self.config.get_version = mock.Mock(return_value=(2, 4, 7)) + mock_exe.return_value = True + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "staple-ocsp") + self.assertTrue("socache_shmcb_module" in self.config.parser.modules) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_ensure_http_header_enable_mod(self, mock_exe, _): + self.config.parser.update_runtime_variables = mock.Mock() + self.config.parser.modules.add("mod_ssl.c") + mock_exe.return_value = True + + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "ensure-http-header", + "Strict-Transport-Security") + self.assertTrue("headers_module" in self.config.parser.modules) + + @mock.patch("certbot.util.run_script") + @mock.patch("certbot.util.exe_exists") + def test_redirect_enable_mod(self, mock_exe, _): + self.config.parser.update_runtime_variables = mock.Mock() + mock_exe.return_value = True + self.config.get_version = mock.Mock(return_value=(2, 2)) + # This will create an ssl vhost for certbot.demo + self.config.choose_vhost("certbot.demo") + self.config.enhance("certbot.demo", "redirect") + self.assertTrue("rewrite_module" in self.config.parser.modules) + + def test_enable_site_already_enabled(self): + self.assertTrue(self.vh_truth[1].enabled) + self.config.enable_site(self.vh_truth[1]) + + def test_enable_site_call_parent(self): + with mock.patch( + "certbot_apache._internal.configurator.ApacheConfigurator.enable_site") as e_s: + self.config.parser.root = "/tmp/nonexistent" + vh = self.vh_truth[0] + vh.enabled = False + self.config.enable_site(vh) + self.assertTrue(e_s.called) + + @mock.patch("certbot.util.exe_exists") + def test_enable_mod_no_disable(self, mock_exe_exists): + mock_exe_exists.return_value = False + self.assertRaises( + errors.MisconfigurationError, self.config.enable_mod, "ssl") + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/display_ops_test.py b/certbot-apache/tests/display_ops_test.py new file mode 100644 index 000000000..7463071e4 --- /dev/null +++ b/certbot-apache/tests/display_ops_test.py @@ -0,0 +1,109 @@ +"""Test certbot_apache._internal.display_ops.""" +import unittest + +import mock + +from certbot import errors + +from certbot.display import util as display_util + +from certbot.tests import util as certbot_util + +from certbot_apache._internal import obj + +from certbot_apache._internal.display_ops import select_vhost_multiple + +import util + + +class SelectVhostMultiTest(unittest.TestCase): + """Tests for certbot_apache._internal.display_ops.select_vhost_multiple.""" + + def setUp(self): + self.base_dir = "/example_path" + self.vhosts = util.get_vh_truth( + self.base_dir, "debian_apache_2_4/multiple_vhosts") + + def test_select_no_input(self): + self.assertFalse(select_vhost_multiple([])) + + @certbot_util.patch_get_utility() + def test_select_correct(self, mock_util): + mock_util().checklist.return_value = ( + display_util.OK, [self.vhosts[3].display_repr(), + self.vhosts[2].display_repr()]) + vhs = select_vhost_multiple([self.vhosts[3], + self.vhosts[2], + self.vhosts[1]]) + self.assertTrue(self.vhosts[2] in vhs) + self.assertTrue(self.vhosts[3] in vhs) + self.assertFalse(self.vhosts[1] in vhs) + + @certbot_util.patch_get_utility() + def test_select_cancel(self, mock_util): + mock_util().checklist.return_value = (display_util.CANCEL, "whatever") + vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]]) + self.assertFalse(vhs) + +class SelectVhostTest(unittest.TestCase): + """Tests for certbot_apache._internal.display_ops.select_vhost.""" + + def setUp(self): + self.base_dir = "/example_path" + self.vhosts = util.get_vh_truth( + self.base_dir, "debian_apache_2_4/multiple_vhosts") + + @classmethod + def _call(cls, vhosts): + from certbot_apache._internal.display_ops import select_vhost + return select_vhost("example.com", vhosts) + + @certbot_util.patch_get_utility() + def test_successful_choice(self, mock_util): + mock_util().menu.return_value = (display_util.OK, 3) + self.assertEqual(self.vhosts[3], self._call(self.vhosts)) + + @certbot_util.patch_get_utility() + def test_noninteractive(self, mock_util): + mock_util().menu.side_effect = errors.MissingCommandlineFlag("no vhost default") + try: + self._call(self.vhosts) + except errors.MissingCommandlineFlag as e: + self.assertTrue("vhost ambiguity" in str(e)) + + @certbot_util.patch_get_utility() + def test_more_info_cancel(self, mock_util): + mock_util().menu.side_effect = [ + (display_util.CANCEL, -1), + ] + + self.assertEqual(None, self._call(self.vhosts)) + + def test_no_vhosts(self): + self.assertEqual(self._call([]), None) + + @mock.patch("certbot_apache._internal.display_ops.display_util") + @certbot_util.patch_get_utility() + @mock.patch("certbot_apache._internal.display_ops.logger") + def test_small_display(self, mock_logger, mock_util, mock_display_util): + mock_display_util.WIDTH = 20 + mock_util().menu.return_value = (display_util.OK, 0) + self._call(self.vhosts) + + self.assertEqual(mock_logger.debug.call_count, 1) + + @certbot_util.patch_get_utility() + def test_multiple_names(self, mock_util): + mock_util().menu.return_value = (display_util.OK, 5) + + self.vhosts.append( + obj.VirtualHost( + "path", "aug_path", set([obj.Addr.fromstring("*:80")]), + False, False, + "wildcard.com", set(["*.wildcard.com"]))) + + self.assertEqual(self.vhosts[5], self._call(self.vhosts)) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/entrypoint_test.py b/certbot-apache/tests/entrypoint_test.py new file mode 100644 index 000000000..04c393bdf --- /dev/null +++ b/certbot-apache/tests/entrypoint_test.py @@ -0,0 +1,47 @@ +"""Test for certbot_apache._internal.entrypoint for override class resolution""" +import unittest + +import mock + +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint + + +class EntryPointTest(unittest.TestCase): + """Entrypoint tests""" + + _multiprocess_can_split_ = True + + def test_get_configurator(self): + + with mock.patch("certbot.util.get_os_info") as mock_info: + for distro in entrypoint.OVERRIDE_CLASSES: + return_value = (distro, "whatever") + if distro == 'fedora_old': + return_value = ('fedora', '28') + elif distro == 'fedora': + return_value = ('fedora', '29') + mock_info.return_value = return_value + self.assertEqual(entrypoint.get_configurator(), + entrypoint.OVERRIDE_CLASSES[distro]) + + def test_nonexistent_like(self): + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ("nonexistent", "irrelevant") + with mock.patch("certbot.util.get_systemd_os_like") as mock_like: + for like in entrypoint.OVERRIDE_CLASSES: + mock_like.return_value = [like] + self.assertEqual(entrypoint.get_configurator(), + entrypoint.OVERRIDE_CLASSES[like]) + + def test_nonexistent_generic(self): + with mock.patch("certbot.util.get_os_info") as mock_info: + mock_info.return_value = ("nonexistent", "irrelevant") + with mock.patch("certbot.util.get_systemd_os_like") as mock_like: + mock_like.return_value = ["unknonwn"] + self.assertEqual(entrypoint.get_configurator(), + configurator.ApacheConfigurator) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py new file mode 100644 index 000000000..38435edfb --- /dev/null +++ b/certbot-apache/tests/fedora_test.py @@ -0,0 +1,196 @@ +"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides""" +import unittest + +import mock + +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os + +from certbot_apache._internal import obj +from certbot_apache._internal import override_fedora + +import util + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "httpd/conf.d") + + aug_pre = "/files" + prefix + # TODO: eventually, these tests should have a dedicated configuration instead + # of reusing the ones from centos_test + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "centos.example.com.conf"), + os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"), + {obj.Addr.fromstring("*:80")}, + False, True, "centos.example.com"), + obj.VirtualHost( + os.path.join(prefix, "ssl.conf"), + os.path.join(aug_pre, "ssl.conf/VirtualHost"), + {obj.Addr.fromstring("_default_:443")}, + True, True, None) + ] + return vh_truth + + +class FedoraRestartTest(util.ApacheTest): + """Tests for Fedora specific self-signed certificate override""" + + # TODO: eventually, these tests should have a dedicated configuration instead + # of reusing the ones from centos_test + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(FedoraRestartTest, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def _run_fedora_test(self): + self.assertIsInstance(self.config, override_fedora.FedoraConfigurator) + self.config.config_test() + + def test_fedora_restart_error(self): + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + with mock.patch("certbot.util.run_script") as mock_run: + mock_run.side_effect = errors.SubprocessError + self.assertRaises(errors.MisconfigurationError, + self._run_fedora_test) + + def test_fedora_restart(self): + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" + with mock.patch(c_test) as mock_test: + with mock.patch("certbot.util.run_script") as mock_run: + # First call raises error, second doesn't + mock_test.side_effect = [errors.MisconfigurationError, ''] + self._run_fedora_test() + self.assertEqual(mock_test.call_count, 2) + self.assertEqual(mock_run.call_args[0][0], + ['systemctl', 'restart', 'httpd']) + + +class MultipleVhostsTestFedora(util.ApacheTest): + """Multiple vhost tests for CentOS / RHEL family of distros""" + + _multiprocess_can_split_ = True + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "centos7_apache/apache" + config_root = "centos7_apache/apache/httpd" + vhost_root = "centos7_apache/apache/httpd/conf.d" + super(MultipleVhostsTestFedora, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="fedora") + self.vh_truth = get_vh_truth( + self.temp_dir, "centos7_apache/apache") + + def test_get_parser(self): + self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_opportunistic_httpd_runtime_parsing(self, mock_get): + define_val = ( + 'Define: TEST1\n' + 'Define: TEST2\n' + 'Define: DUMP_RUN_CFG\n' + ) + mod_val = ( + 'Loaded Modules:\n' + ' mock_module (static)\n' + ' another_module (static)\n' + ) + def mock_get_cfg(command): + """Mock httpd process stdout""" + if command == ['httpd', '-t', '-D', 'DUMP_RUN_CFG']: + return define_val + elif command == ['httpd', '-t', '-D', 'DUMP_MODULES']: + return mod_val + return "" + mock_get.side_effect = mock_get_cfg + self.config.parser.modules = set() + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("fedora", "29") + self.config.parser.update_runtime_variables() + + 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) + + @mock.patch("certbot_apache._internal.configurator.util.run_script") + def test_get_version(self, mock_run_script): + mock_run_script.return_value = ('', None) + self.assertRaises(errors.PluginError, self.config.get_version) + self.assertEqual(mock_run_script.call_args[0][0][0], 'httpd') + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 2) + found = 0 + + for vhost in vhs: + for centos_truth in self.vh_truth: + if vhost == centos_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 2) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_get_sysconfig_vars(self, mock_cfg): + """Make sure we read the sysconfig OPTIONS variable correctly""" + # Return nothing for the process calls + mock_cfg.return_value = "" + self.config.parser.sysconfig_filep = filesystem.realpath( + os.path.join(self.config.parser.root, "../sysconfig/httpd")) + self.config.parser.variables = {} + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the CentOS httpd constants + mock_osi.return_value = ("fedora", "29") + self.config.parser.update_runtime_variables() + + self.assertTrue("mock_define" in self.config.parser.variables.keys()) + self.assertTrue("mock_define_too" in self.config.parser.variables.keys()) + self.assertTrue("mock_value" in self.config.parser.variables.keys()) + self.assertEqual("TRUE", self.config.parser.variables["mock_value"]) + self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) + self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) + + @mock.patch("certbot_apache._internal.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.assertEqual(mock_run_script.call_count, 3) + + @mock.patch("certbot_apache._internal.configurator.util.run_script") + def test_alt_restart_errors(self, mock_run_script): + mock_run_script.side_effect = [None, + errors.SubprocessError, + errors.SubprocessError] + self.assertRaises(errors.MisconfigurationError, self.config.restart) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py new file mode 100644 index 000000000..152c36ba9 --- /dev/null +++ b/certbot-apache/tests/gentoo_test.py @@ -0,0 +1,140 @@ +"""Test for certbot_apache._internal.configurator for Gentoo overrides""" +import unittest + +import mock + +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os + +from certbot_apache._internal import obj +from certbot_apache._internal import override_gentoo + +import util + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + prefix = os.path.join( + temp_dir, config_name, "apache2/vhosts.d") + + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "gentoo.example.com.conf"), + os.path.join(aug_pre, "gentoo.example.com.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "gentoo.example.com"), + obj.VirtualHost( + os.path.join(prefix, "00_default_vhost.conf"), + os.path.join(aug_pre, "00_default_vhost.conf/IfDefine/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "localhost"), + obj.VirtualHost( + os.path.join(prefix, "00_default_ssl_vhost.conf"), + os.path.join(aug_pre, + "00_default_ssl_vhost.conf" + + "/IfDefine/IfDefine/IfModule/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), + True, True, "localhost") + ] + return vh_truth + +class MultipleVhostsTestGentoo(util.ApacheTest): + """Multiple vhost tests for non-debian distro""" + + _multiprocess_can_split_ = True + + def setUp(self): # pylint: disable=arguments-differ + test_dir = "gentoo_apache/apache" + config_root = "gentoo_apache/apache/apache2" + vhost_root = "gentoo_apache/apache/apache2/vhosts.d" + super(MultipleVhostsTestGentoo, self).setUp(test_dir=test_dir, + config_root=config_root, + vhost_root=vhost_root) + + # pylint: disable=line-too-long + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"): + self.config = util.get_apache_configurator( + self.config_path, self.vhost_path, self.config_dir, self.work_dir, + os_info="gentoo") + self.vh_truth = get_vh_truth( + self.temp_dir, "gentoo_apache/apache") + + def test_get_parser(self): + self.assertTrue(isinstance(self.config.parser, + override_gentoo.GentooParser)) + + def test_get_virtual_hosts(self): + """Make sure all vhosts are being properly found.""" + vhs = self.config.get_virtual_hosts() + self.assertEqual(len(vhs), 3) + found = 0 + + for vhost in vhs: + for gentoo_truth in self.vh_truth: + if vhost == gentoo_truth: + found += 1 + break + else: + raise Exception("Missed: %s" % vhost) # pragma: no cover + self.assertEqual(found, 3) + + def test_get_sysconfig_vars(self): + """Make sure we read the Gentoo APACHE2_OPTS variable correctly""" + defines = ['DEFAULT_VHOST', 'INFO', + 'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE'] + self.config.parser.apacheconfig_filep = filesystem.realpath( + os.path.join(self.config.parser.root, "../conf.d/apache2")) + self.config.parser.variables = {} + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): + self.config.parser.update_runtime_variables() + for define in defines: + self.assertTrue(define in self.config.parser.variables.keys()) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.parse_from_subprocess") + def test_no_binary_configdump(self, mock_subprocess): + """Make sure we don't call binary dumps other than modules from Apache + as this is not supported in Gentoo currently""" + + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): + self.config.parser.update_runtime_variables() + self.config.parser.reset_modules() + self.assertFalse(mock_subprocess.called) + + self.config.parser.update_runtime_variables() + self.config.parser.reset_modules() + self.assertTrue(mock_subprocess.called) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_opportunistic_httpd_runtime_parsing(self, mock_get): + mod_val = ( + 'Loaded Modules:\n' + ' mock_module (static)\n' + ' another_module (static)\n' + ) + def mock_get_cfg(command): + """Mock httpd process stdout""" + if command == ['apache2ctl', 'modules']: + return mod_val + return None # pragma: no cover + mock_get.side_effect = mock_get_cfg + self.config.parser.modules = set() + + with mock.patch("certbot.util.get_os_info") as mock_osi: + # Make sure we have the have the Gentoo httpd constants + mock_osi.return_value = ("gentoo", "123") + self.config.parser.update_runtime_variables() + + 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._internal.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.assertEqual(mock_run_script.call_count, 3) + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py new file mode 100644 index 000000000..99f5e7186 --- /dev/null +++ b/certbot-apache/tests/http_01_test.py @@ -0,0 +1,229 @@ +"""Test for certbot_apache._internal.http_01.""" +import unittest +import mock + +from acme import challenges +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module + +from certbot import achallenges +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os +from certbot.tests import acme_util + +from certbot_apache._internal.parser import get_aug_path + +import util + + +NUM_ACHALLS = 3 + + +class ApacheHttp01Test(util.ApacheTest): + """Test for certbot_apache._internal.http_01.ApacheHttp01.""" + + def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ + super(ApacheHttp01Test, self).setUp(*args, **kwargs) + + self.account_key = self.rsa512jwk + self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge] + vh_truth = util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/multiple_vhosts") + # Takes the vhosts for encryption-example.demo, certbot.demo + # and vhost.in.rootconf + self.vhosts = [vh_truth[0], vh_truth[3], vh_truth[10]] + + for i in range(NUM_ACHALLS): + self.achalls.append( + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((chr(ord('a') + i).encode() * 16))), + "pending"), + domain=self.vhosts[i].name, account_key=self.account_key)) + + modules = ["ssl", "rewrite", "authz_core", "authz_host"] + for mod in modules: + self.config.parser.modules.add("mod_{0}.c".format(mod)) + self.config.parser.modules.add(mod + "_module") + + from certbot_apache._internal.http_01 import ApacheHttp01 + self.http = ApacheHttp01(self.config) + + def test_empty_perform(self): + self.assertFalse(self.http.perform()) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") + def test_enable_modules_apache_2_2(self, mock_enmod): + self.config.version = (2, 2) + self.config.parser.modules.remove("authz_host_module") + self.config.parser.modules.remove("mod_authz_host.c") + + enmod_calls = self.common_enable_modules_test(mock_enmod) + self.assertEqual(enmod_calls[0][0][0], "authz_host") + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") + def test_enable_modules_apache_2_4(self, mock_enmod): + self.config.parser.modules.remove("authz_core_module") + self.config.parser.modules.remove("mod_authz_core.c") + + enmod_calls = self.common_enable_modules_test(mock_enmod) + self.assertEqual(enmod_calls[0][0][0], "authz_core") + + def common_enable_modules_test(self, mock_enmod): + """Tests enabling mod_rewrite and other modules.""" + self.config.parser.modules.remove("rewrite_module") + self.config.parser.modules.remove("mod_rewrite.c") + + self.http.prepare_http01_modules() + + self.assertTrue(mock_enmod.called) + calls = mock_enmod.call_args_list + other_calls = [] + for call in calls: + if call[0][0] != "rewrite": + other_calls.append(call) + + # If these lists are equal, we never enabled mod_rewrite + self.assertNotEqual(calls, other_calls) + return other_calls + + def test_same_vhost(self): + vhost = next(v for v in self.config.vhosts if v.name == "certbot.demo") + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'a' * 16))), + "pending"), + domain=vhost.name, account_key=self.account_key), + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'b' * 16))), + "pending"), + domain=next(iter(vhost.aliases)), account_key=self.account_key) + ] + self.common_perform_test(achalls, [vhost]) + + def test_anonymous_vhost(self): + vhosts = [v for v in self.config.vhosts if not v.ssl] + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'a' * 16))), + "pending"), + domain="something.nonexistent", account_key=self.account_key)] + self.common_perform_test(achalls, vhosts) + + def test_configure_multiple_vhosts(self): + vhosts = [v for v in self.config.vhosts if "duplicate.example.com" in v.get_names()] + self.assertEqual(len(vhosts), 2) + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'a' * 16))), + "pending"), + domain="duplicate.example.com", account_key=self.account_key)] + self.common_perform_test(achalls, vhosts) + + def test_no_vhost(self): + for achall in self.achalls: + self.http.add_chall(achall) + self.config.config.http01_port = 12345 + self.assertRaises(errors.PluginError, self.http.perform) + + def test_perform_1_achall_apache_2_2(self): + self.combinations_perform_test(num_achalls=1, minor_version=2) + + def test_perform_1_achall_apache_2_4(self): + self.combinations_perform_test(num_achalls=1, minor_version=4) + + def test_perform_2_achall_apache_2_2(self): + self.combinations_perform_test(num_achalls=2, minor_version=2) + + def test_perform_2_achall_apache_2_4(self): + self.combinations_perform_test(num_achalls=2, minor_version=4) + + def test_perform_3_achall_apache_2_2(self): + self.combinations_perform_test(num_achalls=3, minor_version=2) + + def test_perform_3_achall_apache_2_4(self): + self.combinations_perform_test(num_achalls=3, minor_version=4) + + def test_activate_disabled_vhost(self): + vhosts = [v for v in self.config.vhosts if v.name == "certbot.demo"] + achalls = [ + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb( + challenges.HTTP01(token=((b'a' * 16))), + "pending"), + domain="certbot.demo", account_key=self.account_key)] + vhosts[0].enabled = False + self.common_perform_test(achalls, vhosts) + matches = self.config.parser.find_dir( + "Include", vhosts[0].filep, + get_aug_path(self.config.parser.loc["default"])) + self.assertEqual(len(matches), 1) + + def combinations_perform_test(self, num_achalls, minor_version): + """Test perform with the given achall count and Apache version.""" + achalls = self.achalls[:num_achalls] + vhosts = self.vhosts[:num_achalls] + self.config.version = (2, minor_version) + self.common_perform_test(achalls, vhosts) + + def common_perform_test(self, achalls, vhosts): + """Tests perform with the given achalls.""" + challenge_dir = self.http.challenge_dir + self.assertFalse(os.path.exists(challenge_dir)) + for achall in achalls: + self.http.add_chall(achall) + + expected_response = [ + achall.response(self.account_key) for achall in achalls] + self.assertEqual(self.http.perform(), expected_response) + + self.assertTrue(os.path.isdir(self.http.challenge_dir)) + self.assertTrue(filesystem.has_min_permissions(self.http.challenge_dir, 0o755)) + self._test_challenge_conf() + + for achall in achalls: + self._test_challenge_file(achall) + + for vhost in vhosts: + matches = self.config.parser.find_dir("Include", + self.http.challenge_conf_pre, + vhost.path) + self.assertEqual(len(matches), 1) + matches = self.config.parser.find_dir("Include", + self.http.challenge_conf_post, + vhost.path) + self.assertEqual(len(matches), 1) + + self.assertTrue(os.path.exists(challenge_dir)) + + def _test_challenge_conf(self): + with open(self.http.challenge_conf_pre) as f: + pre_conf_contents = f.read() + + with open(self.http.challenge_conf_post) as f: + post_conf_contents = f.read() + + self.assertTrue("RewriteEngine on" in pre_conf_contents) + self.assertTrue("RewriteRule" in pre_conf_contents) + + self.assertTrue(self.http.challenge_dir in post_conf_contents) + if self.config.version < (2, 4): + self.assertTrue("Allow from all" in post_conf_contents) + else: + self.assertTrue("Require all granted" in post_conf_contents) + + def _test_challenge_file(self, achall): + name = os.path.join(self.http.challenge_dir, achall.chall.encode("token")) + validation = achall.validation(self.account_key) + + self.assertTrue(filesystem.has_min_permissions(name, 0o644)) + with open(name, 'rb') as f: + self.assertEqual(f.read(), validation.encode()) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/obj_test.py b/certbot-apache/tests/obj_test.py new file mode 100644 index 000000000..1761b9c94 --- /dev/null +++ b/certbot-apache/tests/obj_test.py @@ -0,0 +1,141 @@ +"""Tests for certbot_apache._internal.obj.""" +import unittest + + +class VirtualHostTest(unittest.TestCase): + """Test the VirtualHost class.""" + + def setUp(self): + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost + + self.addr1 = Addr.fromstring("127.0.0.1") + self.addr2 = Addr.fromstring("127.0.0.1:443") + self.addr_default = Addr.fromstring("_default_:443") + + self.vhost1 = VirtualHost( + "filep", "vh_path", set([self.addr1]), False, False, "localhost") + + self.vhost1b = VirtualHost( + "filep", "vh_path", set([self.addr1]), False, False, "localhost") + + self.vhost2 = VirtualHost( + "fp", "vhp", set([self.addr2]), False, False, "localhost") + + def test_repr(self): + self.assertEqual(repr(self.addr2), + "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))") + + def test_eq(self): + self.assertTrue(self.vhost1b == self.vhost1) + self.assertFalse(self.vhost1 == self.vhost2) + self.assertEqual(str(self.vhost1b), str(self.vhost1)) + self.assertFalse(self.vhost1b == 1234) + + def test_ne(self): + self.assertTrue(self.vhost1 != self.vhost2) + self.assertFalse(self.vhost1 != self.vhost1b) + + def test_conflicts(self): + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost + + complex_vh = VirtualHost( + "fp", "vhp", + set([Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")]), + False, False) + self.assertTrue(complex_vh.conflicts([self.addr1])) + self.assertTrue(complex_vh.conflicts([self.addr2])) + self.assertFalse(complex_vh.conflicts([self.addr_default])) + + self.assertTrue(self.vhost1.conflicts([self.addr2])) + self.assertFalse(self.vhost1.conflicts([self.addr_default])) + + self.assertFalse(self.vhost2.conflicts([self.addr1, + self.addr_default])) + + def test_same_server(self): + from certbot_apache._internal.obj import VirtualHost + no_name1 = VirtualHost( + "fp", "vhp", set([self.addr1]), False, False, None) + no_name2 = VirtualHost( + "fp", "vhp", set([self.addr2]), False, False, None) + no_name3 = VirtualHost( + "fp", "vhp", set([self.addr_default]), + False, False, None) + no_name4 = VirtualHost( + "fp", "vhp", set([self.addr2, self.addr_default]), + False, False, None) + + self.assertTrue(self.vhost1.same_server(self.vhost2)) + self.assertTrue(no_name1.same_server(no_name2)) + + self.assertFalse(self.vhost1.same_server(no_name1)) + self.assertFalse(no_name1.same_server(no_name3)) + self.assertFalse(no_name1.same_server(no_name4)) + + +class AddrTest(unittest.TestCase): + """Test obj.Addr.""" + def setUp(self): + from certbot_apache._internal.obj import Addr + self.addr = Addr.fromstring("*:443") + + self.addr1 = Addr.fromstring("127.0.0.1") + self.addr2 = Addr.fromstring("127.0.0.1:*") + + self.addr_defined = Addr.fromstring("127.0.0.1:443") + self.addr_default = Addr.fromstring("_default_:443") + + def test_wildcard(self): + self.assertFalse(self.addr.is_wildcard()) + self.assertTrue(self.addr1.is_wildcard()) + self.assertTrue(self.addr2.is_wildcard()) + + def test_get_sni_addr(self): + from certbot_apache._internal.obj import Addr + self.assertEqual( + self.addr.get_sni_addr("443"), Addr.fromstring("*:443")) + self.assertEqual( + self.addr.get_sni_addr("225"), Addr.fromstring("*:225")) + self.assertEqual( + self.addr1.get_sni_addr("443"), Addr.fromstring("127.0.0.1")) + + def test_conflicts(self): + # Note: Defined IP is more important than defined port in match + self.assertTrue(self.addr.conflicts(self.addr1)) + self.assertTrue(self.addr.conflicts(self.addr2)) + self.assertTrue(self.addr.conflicts(self.addr_defined)) + self.assertFalse(self.addr.conflicts(self.addr_default)) + + self.assertFalse(self.addr1.conflicts(self.addr)) + self.assertTrue(self.addr1.conflicts(self.addr_defined)) + self.assertFalse(self.addr1.conflicts(self.addr_default)) + + self.assertFalse(self.addr_defined.conflicts(self.addr1)) + self.assertFalse(self.addr_defined.conflicts(self.addr2)) + self.assertFalse(self.addr_defined.conflicts(self.addr)) + self.assertFalse(self.addr_defined.conflicts(self.addr_default)) + + self.assertTrue(self.addr_default.conflicts(self.addr)) + self.assertTrue(self.addr_default.conflicts(self.addr1)) + self.assertTrue(self.addr_default.conflicts(self.addr_defined)) + + # Self test + self.assertTrue(self.addr.conflicts(self.addr)) + self.assertTrue(self.addr1.conflicts(self.addr1)) + # This is a tricky one... + self.assertTrue(self.addr1.conflicts(self.addr2)) + + def test_equal(self): + self.assertTrue(self.addr1 == self.addr2) + self.assertFalse(self.addr == self.addr1) + self.assertFalse(self.addr == 123) + + def test_not_equal(self): + self.assertFalse(self.addr1 != self.addr2) + self.assertTrue(self.addr != self.addr1) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py new file mode 100644 index 000000000..36d02292e --- /dev/null +++ b/certbot-apache/tests/parser_test.py @@ -0,0 +1,402 @@ +"""Tests for certbot_apache._internal.parser.""" +import shutil +import unittest + +import mock + +from certbot import errors +from certbot.compat import os + +import util + + +class BasicParserTest(util.ParserTest): + """Apache Parser Test.""" + + def setUp(self): # pylint: disable=arguments-differ + super(BasicParserTest, self).setUp() + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + def test_bad_parse(self): + self.parser.parse_file(os.path.join(self.parser.root, + "conf-available", "bad_conf_file.conf")) + self.assertRaises( + errors.PluginError, self.parser.check_parsing_errors, "httpd.aug") + + def test_bad_save(self): + mock_save = mock.Mock() + mock_save.side_effect = IOError + self.parser.aug.save = mock_save + self.assertRaises(errors.PluginError, self.parser.unsaved_files) + + def test_aug_version(self): + mock_match = mock.Mock(return_value=["something"]) + self.parser.aug.match = mock_match + # pylint: disable=protected-access + self.assertEqual(self.parser.check_aug_version(), + ["something"]) + self.parser.aug.match.side_effect = RuntimeError + self.assertFalse(self.parser.check_aug_version()) + + def test_find_config_root_no_root(self): + # pylint: disable=protected-access + os.remove(self.parser.loc["root"]) + self.assertRaises( + errors.NoInstallationError, self.parser._find_config_root) + + def test_parse_file(self): + """Test parse_file. + + certbot.conf is chosen as the test file as it will not be + included during the normal course of execution. + + """ + file_path = os.path.join( + self.config_path, "not-parsed-by-default", "certbot.conf") + + self.parser.parse_file(file_path) # pylint: disable=protected-access + + # search for the httpd incl + matches = self.parser.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % file_path) + + self.assertTrue(matches) + + def test_find_dir(self): + test = self.parser.find_dir("Listen", "80") + # This will only look in enabled hosts + test2 = self.parser.find_dir("documentroot") + + self.assertEqual(len(test), 1) + self.assertEqual(len(test2), 8) + + def test_add_dir(self): + aug_default = "/files" + self.parser.loc["default"] + self.parser.add_dir(aug_default, "AddDirective", "test") + + self.assertTrue( + self.parser.find_dir("AddDirective", "test", aug_default)) + + self.parser.add_dir(aug_default, "AddList", ["1", "2", "3", "4"]) + matches = self.parser.find_dir("AddList", None, aug_default) + for i, match in enumerate(matches): + self.assertEqual(self.parser.aug.get(match), str(i + 1)) + + def test_add_dir_beginning(self): + aug_default = "/files" + self.parser.loc["default"] + self.parser.add_dir_beginning(aug_default, + "AddDirectiveBeginning", + "testBegin") + + self.assertTrue( + self.parser.find_dir("AddDirectiveBeginning", "testBegin", aug_default)) + + self.assertEqual( + self.parser.aug.get(aug_default+"/directive[1]"), + "AddDirectiveBeginning") + self.parser.add_dir_beginning(aug_default, "AddList", ["1", "2", "3", "4"]) + matches = self.parser.find_dir("AddList", None, aug_default) + for i, match in enumerate(matches): + self.assertEqual(self.parser.aug.get(match), str(i + 1)) + + def test_empty_arg(self): + self.assertEqual(None, + self.parser.get_arg("/files/whatever/nonexistent")) + + def test_add_dir_to_ifmodssl(self): + """test add_dir_to_ifmodssl. + + Path must be valid before attempting to add to augeas + + """ + from certbot_apache._internal.parser import get_aug_path + # This makes sure that find_dir will work + self.parser.modules.add("mod_ssl.c") + + self.parser.add_dir_to_ifmodssl( + get_aug_path(self.parser.loc["default"]), + "FakeDirective", ["123"]) + + matches = self.parser.find_dir("FakeDirective", "123") + + self.assertEqual(len(matches), 1) + self.assertTrue("IfModule" in matches[0]) + + def test_add_dir_to_ifmodssl_multiple(self): + from certbot_apache._internal.parser import get_aug_path + # This makes sure that find_dir will work + self.parser.modules.add("mod_ssl.c") + + self.parser.add_dir_to_ifmodssl( + get_aug_path(self.parser.loc["default"]), + "FakeDirective", ["123", "456", "789"]) + + matches = self.parser.find_dir("FakeDirective") + + self.assertEqual(len(matches), 3) + self.assertTrue("IfModule" in matches[0]) + + def test_get_aug_path(self): + from certbot_apache._internal.parser import get_aug_path + self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) + + def test_set_locations(self): + with mock.patch("certbot_apache._internal.parser.os.path") as mock_path: + + mock_path.isfile.side_effect = [False, False] + + # pylint: disable=protected-access + results = self.parser._set_locations() + + self.assertEqual(results["default"], results["listen"]) + self.assertEqual(results["default"], results["name"]) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser.get_arg") + def test_parse_modules_bad_syntax(self, mock_arg, mock_find): + mock_find.return_value = ["1", "2", "3", "4", "5", "6", "7", "8"] + mock_arg.return_value = None + with mock.patch("certbot_apache._internal.parser.logger") as mock_logger: + self.parser.parse_modules() + # Make sure that we got None return value and logged the file + self.assertTrue(mock_logger.debug.called) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_update_runtime_variables(self, mock_cfg, _): + define_val = ( + 'ServerRoot: "/etc/apache2"\n' + 'Main DocumentRoot: "/var/www"\n' + 'Main ErrorLog: "/var/log/apache2/error.log"\n' + 'Mutex ssl-stapling: using_defaults\n' + 'Mutex ssl-cache: using_defaults\n' + 'Mutex default: dir="/var/lock/apache2" mechanism=fcntl\n' + 'Mutex watchdog-callback: using_defaults\n' + 'PidFile: "/var/run/apache2/apache2.pid"\n' + 'Define: TEST\n' + 'Define: DUMP_RUN_CFG\n' + 'Define: U_MICH\n' + 'Define: TLS=443\n' + 'Define: example_path=Documents/path\n' + 'User: name="www-data" id=33 not_used\n' + 'Group: name="www-data" id=33 not_used\n' + ) + inc_val = ( + 'Included configuration files:\n' + ' (*) /etc/apache2/apache2.conf\n' + ' (146) /etc/apache2/mods-enabled/access_compat.load\n' + ' (146) /etc/apache2/mods-enabled/alias.load\n' + ' (146) /etc/apache2/mods-enabled/auth_basic.load\n' + ' (146) /etc/apache2/mods-enabled/authn_core.load\n' + ' (146) /etc/apache2/mods-enabled/authn_file.load\n' + ' (146) /etc/apache2/mods-enabled/authz_core.load\n' + ' (146) /etc/apache2/mods-enabled/authz_host.load\n' + ' (146) /etc/apache2/mods-enabled/authz_user.load\n' + ' (146) /etc/apache2/mods-enabled/autoindex.load\n' + ' (146) /etc/apache2/mods-enabled/deflate.load\n' + ' (146) /etc/apache2/mods-enabled/dir.load\n' + ' (146) /etc/apache2/mods-enabled/env.load\n' + ' (146) /etc/apache2/mods-enabled/filter.load\n' + ' (146) /etc/apache2/mods-enabled/mime.load\n' + ' (146) /etc/apache2/mods-enabled/mpm_event.load\n' + ' (146) /etc/apache2/mods-enabled/negotiation.load\n' + ' (146) /etc/apache2/mods-enabled/reqtimeout.load\n' + ' (146) /etc/apache2/mods-enabled/setenvif.load\n' + ' (146) /etc/apache2/mods-enabled/socache_shmcb.load\n' + ' (146) /etc/apache2/mods-enabled/ssl.load\n' + ' (146) /etc/apache2/mods-enabled/status.load\n' + ' (147) /etc/apache2/mods-enabled/alias.conf\n' + ' (147) /etc/apache2/mods-enabled/autoindex.conf\n' + ' (147) /etc/apache2/mods-enabled/deflate.conf\n' + ) + mod_val = ( + 'Loaded Modules:\n' + ' core_module (static)\n' + ' so_module (static)\n' + ' watchdog_module (static)\n' + ' http_module (static)\n' + ' log_config_module (static)\n' + ' logio_module (static)\n' + ' version_module (static)\n' + ' unixd_module (static)\n' + ' access_compat_module (shared)\n' + ' alias_module (shared)\n' + ' auth_basic_module (shared)\n' + ' authn_core_module (shared)\n' + ' authn_file_module (shared)\n' + ' authz_core_module (shared)\n' + ' authz_host_module (shared)\n' + ' authz_user_module (shared)\n' + ' autoindex_module (shared)\n' + ' deflate_module (shared)\n' + ' dir_module (shared)\n' + ' env_module (shared)\n' + ' filter_module (shared)\n' + ' mime_module (shared)\n' + ' mpm_event_module (shared)\n' + ' negotiation_module (shared)\n' + ' reqtimeout_module (shared)\n' + ' setenvif_module (shared)\n' + ' socache_shmcb_module (shared)\n' + ' ssl_module (shared)\n' + ' status_module (shared)\n' + ) + + def mock_get_vars(cmd): + """Mock command output""" + if cmd[-1] == "DUMP_RUN_CFG": + return define_val + elif cmd[-1] == "DUMP_INCLUDES": + return inc_val + elif cmd[-1] == "DUMP_MODULES": + return mod_val + return None # pragma: no cover + + mock_cfg.side_effect = mock_get_vars + + expected_vars = {"TEST": "", "U_MICH": "", "TLS": "443", + "example_path": "Documents/path"} + + self.parser.modules = set() + with mock.patch( + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: + self.parser.update_runtime_variables() + self.assertEqual(self.parser.variables, expected_vars) + self.assertEqual(len(self.parser.modules), 58) + # None of the includes in inc_val should be in parsed paths. + # Make sure we tried to include them all. + self.assertEqual(mock_parse.call_count, 25) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_update_runtime_variables_alt_values(self, mock_cfg, _): + inc_val = ( + 'Included configuration files:\n' + ' (*) {0}\n' + ' (146) /etc/apache2/mods-enabled/access_compat.load\n' + ' (146) {1}/mods-enabled/alias.load\n' + ).format(self.parser.loc["root"], + os.path.dirname(self.parser.loc["root"])) + + mock_cfg.return_value = inc_val + self.parser.modules = set() + + with mock.patch( + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: + self.parser.update_runtime_variables() + # No matching modules should have been found + self.assertEqual(len(self.parser.modules), 0) + # Only one of the three includes do not exist in already parsed + # path derived from root configuration Include statements + self.assertEqual(mock_parse.call_count, 1) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_update_runtime_vars_bad_output(self, mock_cfg): + mock_cfg.return_value = "Define: TLS=443=24" + self.parser.update_runtime_variables() + + mock_cfg.return_value = "Define: DUMP_RUN_CFG\nDefine: TLS=443=24" + self.assertRaises( + errors.PluginError, self.parser.update_runtime_variables) + + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): + mock_popen.side_effect = OSError + mock_opt.return_value = "nonexistent" + self.assertRaises( + errors.MisconfigurationError, + self.parser.update_runtime_variables) + + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") + def test_update_runtime_vars_bad_exit(self, mock_popen): + mock_popen().communicate.return_value = ("", "") + mock_popen.returncode = -1 + self.assertRaises( + errors.MisconfigurationError, + self.parser.update_runtime_variables) + + def test_add_comment(self): + from certbot_apache._internal.parser import get_aug_path + self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456") + comm = self.parser.find_comments("123456") + self.assertEqual(len(comm), 1) + self.assertTrue(self.parser.loc["name"] in comm[0]) + + +class ParserInitTest(util.ApacheTest): + def setUp(self): # pylint: disable=arguments-differ + super(ParserInitTest, self).setUp() + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + @mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas") + def test_prepare_no_augeas(self, mock_init_augeas): + from certbot_apache._internal.parser import ApacheParser + mock_init_augeas.side_effect = errors.NoInstallationError + self.config.config_test = mock.Mock() + self.assertRaises( + errors.NoInstallationError, ApacheParser, + os.path.relpath(self.config_path), "/dummy/vhostpath", + version=(2, 4, 22), configurator=self.config) + + def test_init_old_aug(self): + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser.check_aug_version") as mock_c: + mock_c.return_value = False + self.assertRaises( + errors.NotSupportedError, + ApacheParser, os.path.relpath(self.config_path), + "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") + def test_unparseable(self, mock_cfg): + from certbot_apache._internal.parser import ApacheParser + mock_cfg.return_value = ('Define: TEST') + self.assertRaises( + errors.PluginError, + ApacheParser, os.path.relpath(self.config_path), + "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config) + + def test_root_normalized(self): + from certbot_apache._internal.parser import ApacheParser + + with mock.patch("certbot_apache._internal.parser.ApacheParser." + "update_runtime_variables"): + path = os.path.join( + self.temp_dir, + "debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2") + + parser = ApacheParser(path, "/dummy/vhostpath", configurator=self.config) + + self.assertEqual(parser.root, self.config_path) + + def test_root_absolute(self): + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." + "update_runtime_variables"): + parser = ApacheParser( + os.path.relpath(self.config_path), + "/dummy/vhostpath", configurator=self.config) + + self.assertEqual(parser.root, self.config_path) + + def test_root_no_trailing_slash(self): + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." + "update_runtime_variables"): + parser = ApacheParser( + self.config_path + os.path.sep, + "/dummy/vhostpath", configurator=self.config) + self.assertEqual(parser.root, self.config_path) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README new file mode 100644 index 000000000..c12e149f2 --- /dev/null +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README @@ -0,0 +1,9 @@ + +This directory holds Apache 2.0 module-specific configuration files; +any files in this directory which have the ".conf" extension will be +processed as Apache configuration files. + +Files are processed in alphabetical order, so if using configuration +directives which depend on, say, mod_perl being loaded, ensure that +these are placed in a filename later in the sort order than "perl.conf". + diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf new file mode 100644 index 000000000..fb2174af1 --- /dev/null +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf @@ -0,0 +1,222 @@ +# +# This is the Apache server configuration file providing SSL support. +# It contains the configuration directives to instruct the server how to +# serve pages over an https connection. For detailing information about these +# directives see +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# + +LoadModule ssl_module modules/mod_ssl.so + +# +# When we also provide SSL we have to listen to the +# the HTTPS port in addition. +# +Listen 443 + +## +## SSL Global Context +## +## All SSL configuration in this context applies both to +## the main server and all SSL-enabled virtual hosts. +## + +# Pass Phrase Dialog: +# Configure the pass phrase gathering process. +# The filtering dialog program (`builtin' is a internal +# terminal dialog) has to provide the pass phrase on stdout. +SSLPassPhraseDialog builtin + +# Inter-Process Session Cache: +# Configure the SSL Session Cache: First the mechanism +# to use and second the expiring timeout (in seconds). +SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000) +SSLSessionCacheTimeout 300 + +# Semaphore: +# Configure the path to the mutual exclusion semaphore the +# SSL engine uses internally for inter-process synchronization. +SSLMutex default + +# Pseudo Random Number Generator (PRNG): +# Configure one or more sources to seed the PRNG of the +# SSL library. The seed data should be of good random quality. +# WARNING! On some platforms /dev/random blocks if not enough entropy +# is available. This means you then cannot use the /dev/random device +# because it would lead to very long connection times (as long as +# it requires to make more entropy available). But usually those +# platforms additionally provide a /dev/urandom device which doesn't +# block. So, if available, use this one instead. Read the mod_ssl User +# Manual for more details. +SSLRandomSeed startup file:/dev/urandom 256 +SSLRandomSeed connect builtin +#SSLRandomSeed startup file:/dev/random 512 +#SSLRandomSeed connect file:/dev/random 512 +#SSLRandomSeed connect file:/dev/urandom 512 + +# +# Use "SSLCryptoDevice" to enable any supported hardware +# accelerators. Use "openssl engine -v" to list supported +# engine names. NOTE: If you enable an accelerator and the +# server does not start, consult the error logs and ensure +# your accelerator is functioning properly. +# +SSLCryptoDevice builtin +#SSLCryptoDevice ubsec + +## +## SSL Virtual Host Context +## + + + +# General setup for the virtual host, inherited from global configuration +#DocumentRoot "/var/www/html" +#ServerName www.example.com:443 + +# Use separate log files for the SSL virtual host; note that LogLevel +# is not inherited from httpd.conf. +ErrorLog logs/ssl_error_log +TransferLog logs/ssl_access_log +LogLevel warn + +# SSL Engine Switch: +# Enable/Disable SSL for this virtual host. +SSLEngine on + +# SSL Protocol support: +# List the enable protocol levels with which clients will be able to +# connect. Disable SSLv2 access by default: +SSLProtocol all -SSLv2 + +# SSL Cipher Suite: +# List the ciphers that the client is permitted to negotiate. +# See the mod_ssl documentation for a complete list. +SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:+3DES + +# Server Certificate: +# Point SSLCertificateFile at a PEM encoded certificate. If +# the certificate is encrypted, then you will be prompted for a +# pass phrase. Note that a kill -HUP will prompt again. A new +# certificate can be generated using the genkey(1) command. +SSLCertificateFile /etc/pki/tls/certs/localhost.crt + +# Server Private Key: +# If the key is not combined with the certificate, use this +# directive to point at the key file. Keep in mind that if +# you've both a RSA and a DSA private key you can configure +# both in parallel (to also allow the use of DSA ciphers, etc.) +SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + +# Server Certificate Chain: +# Point SSLCertificateChainFile at a file containing the +# concatenation of PEM encoded CA certificates which form the +# certificate chain for the server certificate. Alternatively +# the referenced file can be the same as SSLCertificateFile +# when the CA certificates are directly appended to the server +# certificate for convinience. +#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt + +# Certificate Authority (CA): +# Set the CA certificate verification path where to find CA +# certificates for client authentication or alternatively one +# huge file containing all of them (file must be PEM encoded) +#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt + +# Client Authentication (Type): +# Client certificate verification type and depth. Types are +# none, optional, require and optional_no_ca. Depth is a +# number which specifies how deeply to verify the certificate +# issuer chain before deciding the certificate is not valid. +#SSLVerifyClient require +#SSLVerifyDepth 10 + +# Access Control: +# With SSLRequire you can do per-directory access control based +# on arbitrary complex boolean expressions containing server +# variable checks and other lookup directives. The syntax is a +# mixture between C and Perl. See the mod_ssl documentation +# for more details. +# +#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ +# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ +# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ +# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ +# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ +# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ +# + +# SSL Engine Options: +# Set various options for the SSL engine. +# o FakeBasicAuth: +# Translate the client X.509 into a Basic Authorisation. This means that +# the standard Auth/DBMAuth methods can be used for access control. The +# user name is the `one line' version of the client's X.509 certificate. +# Note that no password is obtained from the user. Every entry in the user +# file needs this password: `xxj31ZMTZzkVA'. +# o ExportCertData: +# This exports two additional environment variables: SSL_CLIENT_CERT and +# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the +# server (always existing) and the client (only existing when client +# authentication is used). This can be used to import the certificates +# into CGI scripts. +# o StdEnvVars: +# This exports the standard SSL/TLS related `SSL_*' environment variables. +# Per default this exportation is switched off for performance reasons, +# because the extraction step is an expensive operation and is usually +# useless for serving static content. So one usually enables the +# exportation for CGI and SSI requests only. +# o StrictRequire: +# This denies access when "SSLRequireSSL" or "SSLRequire" applied even +# under a "Satisfy any" situation, i.e. when it applies access is denied +# and no other module can change it. +# o OptRenegotiate: +# This enables optimized SSL connection renegotiation handling when SSL +# directives are used in per-directory context. +#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + +# SSL Protocol Adjustments: +# The safe and default but still SSL/TLS standard compliant shutdown +# approach is that mod_ssl sends the close notify alert but doesn't wait for +# the close notify alert from client. When you need a different shutdown +# approach you can use one of the following variables: +# o ssl-unclean-shutdown: +# This forces an unclean shutdown when the connection is closed, i.e. no +# SSL close notify alert is send or allowed to received. This violates +# the SSL/TLS standard but is needed for some brain-dead browsers. Use +# this when you receive I/O errors because of the standard approach where +# mod_ssl sends the close notify alert. +# o ssl-accurate-shutdown: +# This forces an accurate shutdown when the connection is closed, i.e. a +# SSL close notify alert is send and mod_ssl waits for the close notify +# alert of the client. This is 100% SSL/TLS standard compliant, but in +# practice often causes hanging connections with brain-dead browsers. Use +# this only for browsers where you know that their SSL implementation +# works correctly. +# Notice: Most problems of broken clients are also related to the HTTP +# keep-alive facility, so you usually additionally want to disable +# keep-alive for those clients, too. Use variable "nokeepalive" for this. +# Similarly, one has to force some clients to use HTTP/1.0 to workaround +# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and +# "force-response-1.0" for this. +SetEnvIf User-Agent ".*MSIE.*" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + +# Per-Server Logging: +# The home of a custom SSL log file. Use this when you want a +# compact non-error SSL logfile on a virtual host basis. +CustomLog logs/ssl_request_log \ + "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" + + + diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf new file mode 100644 index 000000000..3dd7b18f1 --- /dev/null +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf @@ -0,0 +1,7 @@ + + ServerName test.example.com + ServerAdmin webmaster@dummy-host.example.com + DocumentRoot /var/www/htdocs + ErrorLog logs/dummy-host.example.com-error_log + CustomLog logs/dummy-host.example.com-access_log common + diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf new file mode 100644 index 000000000..c1d23c512 --- /dev/null +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf @@ -0,0 +1,11 @@ +# +# This configuration file enables the default "Welcome" +# page if there is no default index page present for +# the root URL. To disable the Welcome page, comment +# out all the lines below. +# + + Options -Indexes + ErrorDocument 403 /error/noindex.html + + diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf new file mode 100644 index 000000000..579d194ce --- /dev/null +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf @@ -0,0 +1,1009 @@ +# +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# The configuration directives are grouped into three basic sections: +# 1. Directives that control the operation of the Apache server process as a +# whole (the 'global environment'). +# 2. Directives that define the parameters of the 'main' or 'default' server, +# which responds to requests that aren't handled by a virtual host. +# These directives also provide default values for the settings +# of all virtual hosts. +# 3. Settings for virtual hosts, which allow Web requests to be sent to +# different IP addresses or hostnames and have them handled by the +# same Apache server process. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/foo.log" +# with ServerRoot set to "/etc/httpd" will be interpreted by the +# server as "/etc/httpd/logs/foo.log". +# + +### Section 1: Global Environment +# +# The directives in this section affect the overall operation of Apache, +# such as the number of concurrent requests it can handle or where it +# can find its configuration files. +# + +# +# Don't give away too much information about all the subcomponents +# we are running. Comment out this line if you don't mind remote sites +# finding out what major optional modules you are running +ServerTokens OS + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the LockFile documentation +# (available at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +ServerRoot "/etc/httpd" + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. Note the PIDFILE variable in +# /etc/sysconfig/httpd must be set appropriately if this location is +# changed. +# +PidFile run/httpd.pid + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 60 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive Off + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 15 + +## +## Server-Pool Size Regulation (MPM specific) +## + +# prefork MPM +# StartServers: number of server processes to start +# MinSpareServers: minimum number of server processes which are kept spare +# MaxSpareServers: maximum number of server processes which are kept spare +# ServerLimit: maximum value for MaxClients for the lifetime of the server +# MaxClients: maximum number of server processes allowed to start +# MaxRequestsPerChild: maximum number of requests a server process serves + +StartServers 8 +MinSpareServers 5 +MaxSpareServers 20 +ServerLimit 256 +MaxClients 256 +MaxRequestsPerChild 4000 + + +# worker MPM +# StartServers: initial number of server processes to start +# MaxClients: maximum number of simultaneous client connections +# MinSpareThreads: minimum number of worker threads which are kept spare +# MaxSpareThreads: maximum number of worker threads which are kept spare +# ThreadsPerChild: constant number of worker threads in each server process +# MaxRequestsPerChild: maximum number of requests a server process serves + +StartServers 4 +MaxClients 300 +MinSpareThreads 25 +MaxSpareThreads 75 +ThreadsPerChild 25 +MaxRequestsPerChild 0 + + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, in addition to the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses (0.0.0.0) +# +#Listen 12.34.56.78:80 +Listen 80 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_alias_module modules/mod_authn_alias.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_default_module modules/mod_authn_default.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_default_module modules/mod_authz_default.so +LoadModule ldap_module modules/mod_ldap.so +LoadModule authnz_ldap_module modules/mod_authnz_ldap.so +LoadModule include_module modules/mod_include.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule env_module modules/mod_env.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule expires_module modules/mod_expires.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule headers_module modules/mod_headers.so +LoadModule usertrack_module modules/mod_usertrack.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule mime_module modules/mod_mime.so +LoadModule dav_module modules/mod_dav.so +LoadModule status_module modules/mod_status.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule info_module modules/mod_info.so +LoadModule dav_fs_module modules/mod_dav_fs.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule dir_module modules/mod_dir.so +LoadModule actions_module modules/mod_actions.so +LoadModule speling_module modules/mod_speling.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule alias_module modules/mod_alias.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule proxy_module modules/mod_proxy.so +LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +LoadModule proxy_connect_module modules/mod_proxy_connect.so +LoadModule cache_module modules/mod_cache.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule disk_cache_module modules/mod_disk_cache.so +LoadModule cgi_module modules/mod_cgi.so +LoadModule version_module modules/mod_version.so + +# +# The following modules are not loaded by default: +# +#LoadModule asis_module modules/mod_asis.so +#LoadModule authn_dbd_module modules/mod_authn_dbd.so +#LoadModule cern_meta_module modules/mod_cern_meta.so +#LoadModule cgid_module modules/mod_cgid.so +#LoadModule dbd_module modules/mod_dbd.so +#LoadModule dumpio_module modules/mod_dumpio.so +#LoadModule filter_module modules/mod_filter.so +#LoadModule ident_module modules/mod_ident.so +#LoadModule log_forensic_module modules/mod_log_forensic.so +#LoadModule unique_id_module modules/mod_unique_id.so +# + +# +# Load config files from the config directory "/etc/httpd/conf.d". +# +Include conf.d/*.conf + +# +# ExtendedStatus controls whether Apache will generate "full" status +# information (ExtendedStatus On) or just basic information (ExtendedStatus +# Off) when the "server-status" handler is called. The default is Off. +# +#ExtendedStatus On + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# . On SCO (ODT 3) use "User nouser" and "Group nogroup". +# . On HPUX you may not be able to use shared memory as nobody, and the +# suggested workaround is to create a user www and use that user. +# NOTE that some kernels refuse to setgid(Group) or semctl(IPC_SET) +# when the value of (unsigned)Group is above 60000; +# don't use Group #-1 on these systems! +# +User apache +Group apache + +### Section 2: 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin root@localhost + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If this is not set to valid DNS name for your host, server-generated +# redirections will not work. See also the UseCanonicalName directive. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# You will have to access it by its address anyway, and this will make +# redirections work in a sensible way. +# +#ServerName www.example.com:80 + +# +# UseCanonicalName: Determines how Apache constructs self-referencing +# URLs and the SERVER_NAME and SERVER_PORT variables. +# When set "Off", Apache will use the Hostname and Port supplied +# by the client. When set "On", Apache will use the value of the +# ServerName directive. +# +UseCanonicalName Off + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "/var/www/html" + +# +# Each directory to which Apache has access can be configured with respect +# to which services and features are allowed and/or disabled in that +# directory (and its subdirectories). +# +# First, we configure the "default" to be a very restrictive set of +# features. +# + + Options FollowSymLinks + AllowOverride None + + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# This should be changed to whatever you set DocumentRoot to. +# + + +# +# Possible values for the Options directive are "None", "All", +# or any combination of: +# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews +# +# Note that "MultiViews" must be named *explicitly* --- "Options All" +# doesn't give it to you. +# +# The Options directive is both complicated and important. Please see +# http://httpd.apache.org/docs/2.2/mod/core.html#options +# for more information. +# + Options Indexes FollowSymLinks + +# +# AllowOverride controls what directives may be placed in .htaccess files. +# It can be "All", "None", or any combination of the keywords: +# Options FileInfo AuthConfig Limit +# + AllowOverride None + +# +# Controls who can get stuff from this server. +# + Order allow,deny + Allow from all + + + +# +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. +# +# The path to the end user account 'public_html' directory must be +# accessible to the webserver userid. This usually means that ~userid +# must have permissions of 711, ~userid/public_html must have permissions +# of 755, and documents contained therein must be world-readable. +# Otherwise, the client will only receive a "403 Forbidden" message. +# +# See also: http://httpd.apache.org/docs/misc/FAQ.html#forbidden +# + + # + # UserDir is disabled by default since it can confirm the presence + # of a username on the system (depending on home directory + # permissions). + # + UserDir disabled + + # + # To enable requests to /~user/ to serve the user's public_html + # directory, remove the "UserDir disabled" line above, and uncomment + # the following line instead: + # + #UserDir public_html + + + +# +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. +# +# +# AllowOverride FileInfo AuthConfig Limit +# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec +# +# Order allow,deny +# Allow from all +# +# +# Order deny,allow +# Deny from all +# +# + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# +# The index.html.var file (a type-map) is used to deliver content- +# negotiated documents. The MultiViews Option can be used for the +# same purpose, but it is much slower. +# +DirectoryIndex index.html index.html.var + +# +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Order allow,deny + Deny from all + Satisfy All + + +# +# TypesConfig describes where the mime.types file (or equivalent) is +# to be found. +# +TypesConfig /etc/mime.types + +# +# DefaultType is the default MIME type the server will use for a document +# if it cannot otherwise determine one, such as from filename extensions. +# If your server contains mostly text or HTML documents, "text/plain" is +# a good value. If most of your content is binary, such as applications +# or images, you may want to use "application/octet-stream" instead to +# keep browsers from trying to display binary files as though they are +# text. +# +DefaultType text/plain + +# +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +# + +# MIMEMagicFile /usr/share/magic.mime + MIMEMagicFile conf/magic + + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# +# EnableMMAP: Control whether memory-mapping is used to deliver +# files (assuming that the underlying OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. On some systems, turning it off (regardless of +# filesystem) can improve performance; for details, please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablemmap +# +#EnableMMAP off + +# +# EnableSendfile: Control whether the sendfile kernel support is +# used to deliver files (assuming that the OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. Please see +# http://httpd.apache.org/docs/2.2/mod/core.html#enablesendfile +# +#EnableSendfile off + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog logs/error_log + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + +# +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). +# +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# "combinedio" includes actual counts of actual bytes received (%I) and sent (%O); this +# requires the mod_logio module to be loaded. +#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + +# +# The location and format of the access logfile (Common Logfile Format). +# If you do not define any access logfiles within a +# container, they will be logged here. Contrariwise, if you *do* +# define per- access logfiles, transactions will be +# logged therein and *not* in this file. +# +#CustomLog logs/access_log common + +# +# If you would like to have separate agent and referer logfiles, uncomment +# the following directives. +# +#CustomLog logs/referer_log referer +#CustomLog logs/agent_log agent + +# +# For a single logfile with access, agent, and referer information +# (Combined Logfile Format), use the following directive: +# +CustomLog logs/access_log combined + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +# +ServerSignature On + +# +# Aliases: Add here as many aliases as you need (with no limit). The format is +# Alias fakename realname +# +# Note that if you include a trailing / on fakename then the server will +# require it to be present in the URL. So "/icons" isn't aliased in this +# example, only "/icons/". If the fakename is slash-terminated, then the +# realname must also be slash terminated, and if the fakename omits the +# trailing slash, the realname must also omit it. +# +# We include the /icons/ alias for FancyIndexed directory listings. If you +# do not use FancyIndexing, you may comment this out. +# +Alias /icons/ "/var/www/icons/" + + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + +# +# WebDAV module configuration section. +# + + # Location of the WebDAV lock database. + DAVLockDB /var/lib/dav/lockdb + + +# +# ScriptAlias: This controls which directories contain server scripts. +# ScriptAliases are essentially the same as Aliases, except that +# documents in the realname directory are treated as applications and +# run by the server when requested rather than as documents sent to the client. +# The same rules about trailing "/" apply to ScriptAlias directives as to +# Alias. +# +ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" + +# +# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# + + AllowOverride None + Options None + Order allow,deny + Allow from all + + +# +# Redirect allows you to tell clients about documents which used to exist in +# your server's namespace, but do not anymore. This allows you to tell the +# clients where to look for the relocated document. +# Example: +# Redirect permanent /foo http://www.example.com/bar + +# +# Directives controlling the display of server-generated directory listings. +# + +# +# IndexOptions: Controls the appearance of server-generated directory +# listings. +# +IndexOptions FancyIndexing VersionSort NameWidth=* HTMLTable Charset=UTF-8 + +# +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. +# +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif /core + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. +# +DefaultIcon /icons/unknown.gif + +# +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename +# +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. +# +# HeaderName is the name of a file which should be prepended to +# directory indexes. +ReadmeName README.html +HeaderName HEADER.html + +# +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. +# +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + +# +# DefaultLanguage and AddLanguage allows you to specify the language of +# a document. You can then use content negotiation to give a browser a +# file in a language the user can understand. +# +# Specify a default language. This means that all data +# going out without a specific language tag (see below) will +# be marked with this one. You probably do NOT want to set +# this unless you are sure it is correct for all cases. +# +# * It is generally better to not mark a page as +# * being a certain language than marking it with the wrong +# * language! +# +# DefaultLanguage nl +# +# Note 1: The suffix does not have to be the same as the language +# keyword --- those with documents in Polish (whose net-standard +# language code is pl) may wish to use "AddLanguage pl .po" to +# avoid the ambiguity with the common suffix for perl scripts. +# +# Note 2: The example entries below illustrate that in some cases +# the two character 'Language' abbreviation is not identical to +# the two character 'Country' code for its country, +# E.g. 'Danmark/dk' versus 'Danish/da'. +# +# Note 3: In the case of 'ltz' we violate the RFC by using a three char +# specifier. There is 'work in progress' to fix this and get +# the reference data for rfc1766 cleaned up. +# +# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) +# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) +# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) +# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) +# Norwegian (no) - Polish (pl) - Portugese (pt) +# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) +# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) +# +AddLanguage ca .ca +AddLanguage cs .cz .cs +AddLanguage da .dk +AddLanguage de .de +AddLanguage el .el +AddLanguage en .en +AddLanguage eo .eo +AddLanguage es .es +AddLanguage et .et +AddLanguage fr .fr +AddLanguage he .he +AddLanguage hr .hr +AddLanguage it .it +AddLanguage ja .ja +AddLanguage ko .ko +AddLanguage ltz .ltz +AddLanguage nl .nl +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pl .po +AddLanguage pt .pt +AddLanguage pt-BR .pt-br +AddLanguage ru .ru +AddLanguage sv .sv +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw + +# +# LanguagePriority allows you to give precedence to some languages +# in case of a tie during content negotiation. +# +# Just list the languages in decreasing order of preference. We have +# more or less alphabetized them here. You probably want to change this. +# +LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW + +# +# ForceLanguagePriority allows you to serve a result page rather than +# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) +# [in case no accepted languages matched the available variants] +# +ForceLanguagePriority Prefer Fallback + +# +# Specify a default charset for all content served; this enables +# interpretation of all content as UTF-8 by default. To use the +# default browser choice (ISO-8859-1), or to allow the META tags +# in HTML content to override this choice, comment out this +# directive: +# +AddDefaultCharset UTF-8 + +# +# AddType allows you to add to or override the MIME configuration +# file mime.types for specific file types. +# +#AddType application/x-tar .tgz + +# +# AddEncoding allows you to have certain browsers uncompress +# information on the fly. Note: Not all browsers support this. +# Despite the name similarity, the following Add* directives have nothing +# to do with the FancyIndexing customization directives above. +# +#AddEncoding x-compress .Z +#AddEncoding x-gzip .gz .tgz + +# If the AddEncoding directives above are commented-out, then you +# probably should define those extensions to indicate media types: +# +AddType application/x-compress .Z +AddType application/x-gzip .gz .tgz + +# +# MIME-types for downloading Certificates and CRLs +# +AddType application/x-x509-ca-cert .crt +AddType application/x-pkcs7-crl .crl + +# +# AddHandler allows you to map certain file extensions to "handlers": +# actions unrelated to filetype. These can be either built into the server +# or added with the Action directive (see below) +# +# To use CGI scripts outside of ScriptAliased directories: +# (You will also need to add "ExecCGI" to the "Options" directive.) +# +#AddHandler cgi-script .cgi + +# +# For files that include their own HTTP headers: +# +#AddHandler send-as-is asis + +# +# For type maps (negotiated resources): +# (This is enabled by default to allow the Apache "It Worked" page +# to be distributed in multiple languages.) +# +AddHandler type-map var + +# +# Filters allow you to process content before it is sent to the client. +# +# To parse .shtml files for server-side includes (SSI): +# (You will also need to add "Includes" to the "Options" directive.) +# +AddType text/html .shtml +AddOutputFilter INCLUDES .shtml + +# +# Action lets you define media types that will execute a script whenever +# a matching file is called. This eliminates the need for repeated URL +# pathnames for oft-used CGI file processors. +# Format: Action media/type /cgi-script/location +# Format: Action handler-name /cgi-script/location +# + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# Putting this all together, we can internationalize error responses. +# +# We use Alias to redirect any /error/HTTP_.html.var response to +# our collection of by-error message multi-language collections. We use +# includes to substitute the appropriate text. +# +# You can modify the messages' appearance without changing any of the +# default HTTP_.html.var files by adding the line: +# +# Alias /error/include/ "/your/include/path/" +# +# which allows you to create your own set of files by starting with the +# /var/www/error/include/ files and +# copying them to /your/include/path/, even on a per-VirtualHost basis. +# + +Alias /error/ "/var/www/error/" + + + + + AllowOverride None + Options IncludesNoExec + AddOutputFilter Includes html + AddHandler type-map var + Order allow,deny + Allow from all + LanguagePriority en es de fr + ForceLanguagePriority Prefer Fallback + + +# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var +# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var +# ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var +# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var +# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var +# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var +# ErrorDocument 410 /error/HTTP_GONE.html.var +# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var +# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var +# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var +# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var +# ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var +# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var +# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var +# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var +# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var +# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var + + + + +# +# The following directives modify normal HTTP response behavior to +# handle known problems with browser implementations. +# +BrowserMatch "Mozilla/2" nokeepalive +BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 +BrowserMatch "RealPlayer 4\.0" force-response-1.0 +BrowserMatch "Java/1\.0" force-response-1.0 +BrowserMatch "JDK/1\.0" force-response-1.0 + +# +# The following directive disables redirects on non-GET requests for +# a directory that does not include the trailing slash. This fixes a +# problem with Microsoft WebFolders which does not appropriately handle +# redirects for folders with DAV methods. +# Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. +# +BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully +BrowserMatch "MS FrontPage" redirect-carefully +BrowserMatch "^WebDrive" redirect-carefully +BrowserMatch "^WebDAVFS/1.[0123]" redirect-carefully +BrowserMatch "^gnome-vfs/1.0" redirect-carefully +BrowserMatch "^XML Spy" redirect-carefully +BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully + +# +# Allow server status reports generated by mod_status, +# with the URL of http://servername/server-status +# Change the ".example.com" to match your domain to enable. +# +# +# SetHandler server-status +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Allow remote server configuration reports, with the URL of +# http://servername/server-info (requires that mod_info.c be loaded). +# Change the ".example.com" to match your domain to enable. +# +# +# SetHandler server-info +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Proxy Server directives. Uncomment the following lines to +# enable the proxy server: +# +# +#ProxyRequests On +# +# +# Order deny,allow +# Deny from all +# Allow from .example.com +# + +# +# Enable/disable the handling of HTTP/1.1 "Via:" headers. +# ("Full" adds the server version; "Block" removes all outgoing Via: headers) +# Set to one of: Off | On | Full | Block +# +#ProxyVia On + +# +# To enable a cache of proxied content, uncomment the following lines. +# See http://httpd.apache.org/docs/2.2/mod/mod_cache.html for more details. +# +# +# CacheEnable disk / +# CacheRoot "/var/cache/mod_proxy" +# +# + +# +# End of proxy directives. + +### Section 3: Virtual Hosts +# +# VirtualHost: If you want to maintain multiple domains/hostnames on your +# machine you can setup VirtualHost containers for them. Most configurations +# use only name-based virtual hosts so the server doesn't need to worry about +# IP addresses. This is indicated by the asterisks in the directives below. +# +# Please see the documentation at +# +# for further details before you try to setup virtual hosts. +# +# You may use the command line option '-S' to verify your virtual host +# configuration. + +# +# Use name-based virtual hosting. +# +#NameVirtualHost *:80 +# +# NOTE: NameVirtualHost cannot be used without a port specifier +# (e.g. :80) if mod_ssl is being used, due to the nature of the +# SSL protocol. +# + +# +# VirtualHost example: +# Almost any Apache directive may go into a VirtualHost container. +# The first VirtualHost section is used for requests without a known +# server name. +# +# +# ServerAdmin webmaster@dummy-host.example.com +# DocumentRoot /www/docs/dummy-host.example.com +# ServerName dummy-host.example.com +# ErrorLog logs/dummy-host.example.com-error_log +# CustomLog logs/dummy-host.example.com-access_log common +# diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README new file mode 100644 index 000000000..f5e96615a --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README @@ -0,0 +1,9 @@ + +This directory holds configuration files for the Apache HTTP Server; +any files in this directory which have the ".conf" extension will be +processed as httpd configuration files. The directory is used in +addition to the directory /etc/httpd/conf.modules.d/, which contains +configuration files necessary to load modules. + +Files are processed in alphabetical order. + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf new file mode 100644 index 000000000..a85cf5dca --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf @@ -0,0 +1,94 @@ +# +# Directives controlling the display of server-generated directory listings. +# +# Required modules: mod_authz_core, mod_authz_host, +# mod_autoindex, mod_alias +# +# To see the listing of a directory, the Options directive for the +# directory must include "Indexes", and the directory must not contain +# a file matching those listed in the DirectoryIndex directive. +# + +# +# IndexOptions: Controls the appearance of server-generated directory +# listings. +# +IndexOptions FancyIndexing HTMLTable VersionSort + +# We include the /icons/ alias for FancyIndexed directory listings. If +# you do not use FancyIndexing, you may comment this out. +# +Alias /icons/ "/usr/share/httpd/icons/" + + + Options Indexes MultiViews FollowSymlinks + AllowOverride None + Require all granted + + +# +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. +# +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif /core +AddIcon /icons/bomb.gif */core.* + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. +# +DefaultIcon /icons/unknown.gif + +# +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename +# +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. +# +# HeaderName is the name of a file which should be prepended to +# directory indexes. +ReadmeName README.html +HeaderName HEADER.html + +# +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. +# +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf new file mode 100644 index 000000000..de7ac2777 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf @@ -0,0 +1,7 @@ + + ServerName centos.example.com + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf new file mode 100644 index 000000000..6e2502e9a --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf @@ -0,0 +1,211 @@ +# +# When we also provide SSL we have to listen to the +# the HTTPS port in addition. +# +Listen 443 https + +## +## SSL Global Context +## +## All SSL configuration in this context applies both to +## the main server and all SSL-enabled virtual hosts. +## + +# Pass Phrase Dialog: +# Configure the pass phrase gathering process. +# The filtering dialog program (`builtin' is a internal +# terminal dialog) has to provide the pass phrase on stdout. +SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog + +# Inter-Process Session Cache: +# Configure the SSL Session Cache: First the mechanism +# to use and second the expiring timeout (in seconds). +SSLSessionCache shmcb:/run/httpd/sslcache(512000) +SSLSessionCacheTimeout 300 + +# Pseudo Random Number Generator (PRNG): +# Configure one or more sources to seed the PRNG of the +# SSL library. The seed data should be of good random quality. +# WARNING! On some platforms /dev/random blocks if not enough entropy +# is available. This means you then cannot use the /dev/random device +# because it would lead to very long connection times (as long as +# it requires to make more entropy available). But usually those +# platforms additionally provide a /dev/urandom device which doesn't +# block. So, if available, use this one instead. Read the mod_ssl User +# Manual for more details. +SSLRandomSeed startup file:/dev/urandom 256 +SSLRandomSeed connect builtin +#SSLRandomSeed startup file:/dev/random 512 +#SSLRandomSeed connect file:/dev/random 512 +#SSLRandomSeed connect file:/dev/urandom 512 + +# +# Use "SSLCryptoDevice" to enable any supported hardware +# accelerators. Use "openssl engine -v" to list supported +# engine names. NOTE: If you enable an accelerator and the +# server does not start, consult the error logs and ensure +# your accelerator is functioning properly. +# +SSLCryptoDevice builtin +#SSLCryptoDevice ubsec + +## +## SSL Virtual Host Context +## + + + +# General setup for the virtual host, inherited from global configuration +#DocumentRoot "/var/www/html" +#ServerName www.example.com:443 + +# Use separate log files for the SSL virtual host; note that LogLevel +# is not inherited from httpd.conf. +ErrorLog logs/ssl_error_log +TransferLog logs/ssl_access_log +LogLevel warn + +# SSL Engine Switch: +# Enable/Disable SSL for this virtual host. +SSLEngine on + +# SSL Protocol support: +# List the enable protocol levels with which clients will be able to +# connect. Disable SSLv2 access by default: +SSLProtocol all -SSLv2 + +# SSL Cipher Suite: +# List the ciphers that the client is permitted to negotiate. +# See the mod_ssl documentation for a complete list. +SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA + +# Speed-optimized SSL Cipher configuration: +# If speed is your main concern (on busy HTTPS servers e.g.), +# you might want to force clients to specific, performance +# optimized ciphers. In this case, prepend those ciphers +# to the SSLCipherSuite list, and enable SSLHonorCipherOrder. +# Caveat: by giving precedence to RC4-SHA and AES128-SHA +# (as in the example below), most connections will no longer +# have perfect forward secrecy - if the server's key is +# compromised, captures of past or future traffic must be +# considered compromised, too. +#SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 +#SSLHonorCipherOrder on + +# Server Certificate: +# Point SSLCertificateFile at a PEM encoded certificate. If +# the certificate is encrypted, then you will be prompted for a +# pass phrase. Note that a kill -HUP will prompt again. A new +# certificate can be generated using the genkey(1) command. + +# Server Private Key: +# If the key is not combined with the certificate, use this +# directive to point at the key file. Keep in mind that if +# you've both a RSA and a DSA private key you can configure +# both in parallel (to also allow the use of DSA ciphers, etc.) + +# Server Certificate Chain: +# Point SSLCertificateChainFile at a file containing the +# concatenation of PEM encoded CA certificates which form the +# certificate chain for the server certificate. Alternatively +# the referenced file can be the same as SSLCertificateFile +# when the CA certificates are directly appended to the server +# certificate for convinience. +#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt + +# Certificate Authority (CA): +# Set the CA certificate verification path where to find CA +# certificates for client authentication or alternatively one +# huge file containing all of them (file must be PEM encoded) +#SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt + +# Client Authentication (Type): +# Client certificate verification type and depth. Types are +# none, optional, require and optional_no_ca. Depth is a +# number which specifies how deeply to verify the certificate +# issuer chain before deciding the certificate is not valid. +#SSLVerifyClient require +#SSLVerifyDepth 10 + +# Access Control: +# With SSLRequire you can do per-directory access control based +# on arbitrary complex boolean expressions containing server +# variable checks and other lookup directives. The syntax is a +# mixture between C and Perl. See the mod_ssl documentation +# for more details. +# +#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ +# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ +# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ +# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ +# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ +# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ +# + +# SSL Engine Options: +# Set various options for the SSL engine. +# o FakeBasicAuth: +# Translate the client X.509 into a Basic Authorisation. This means that +# the standard Auth/DBMAuth methods can be used for access control. The +# user name is the `one line' version of the client's X.509 certificate. +# Note that no password is obtained from the user. Every entry in the user +# file needs this password: `xxj31ZMTZzkVA'. +# o ExportCertData: +# This exports two additional environment variables: SSL_CLIENT_CERT and +# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the +# server (always existing) and the client (only existing when client +# authentication is used). This can be used to import the certificates +# into CGI scripts. +# o StdEnvVars: +# This exports the standard SSL/TLS related `SSL_*' environment variables. +# Per default this exportation is switched off for performance reasons, +# because the extraction step is an expensive operation and is usually +# useless for serving static content. So one usually enables the +# exportation for CGI and SSI requests only. +# o StrictRequire: +# This denies access when "SSLRequireSSL" or "SSLRequire" applied even +# under a "Satisfy any" situation, i.e. when it applies access is denied +# and no other module can change it. +# o OptRenegotiate: +# This enables optimized SSL connection renegotiation handling when SSL +# directives are used in per-directory context. +#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + +# SSL Protocol Adjustments: +# The safe and default but still SSL/TLS standard compliant shutdown +# approach is that mod_ssl sends the close notify alert but doesn't wait for +# the close notify alert from client. When you need a different shutdown +# approach you can use one of the following variables: +# o ssl-unclean-shutdown: +# This forces an unclean shutdown when the connection is closed, i.e. no +# SSL close notify alert is send or allowed to received. This violates +# the SSL/TLS standard but is needed for some brain-dead browsers. Use +# this when you receive I/O errors because of the standard approach where +# mod_ssl sends the close notify alert. +# o ssl-accurate-shutdown: +# This forces an accurate shutdown when the connection is closed, i.e. a +# SSL close notify alert is send and mod_ssl waits for the close notify +# alert of the client. This is 100% SSL/TLS standard compliant, but in +# practice often causes hanging connections with brain-dead browsers. Use +# this only for browsers where you know that their SSL implementation +# works correctly. +# Notice: Most problems of broken clients are also related to the HTTP +# keep-alive facility, so you usually additionally want to disable +# keep-alive for those clients, too. Use variable "nokeepalive" for this. +# Similarly, one has to force some clients to use HTTP/1.0 to workaround +# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and +# "force-response-1.0" for this. +BrowserMatch "MSIE [2-5]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0 + +# Per-Server Logging: +# The home of a custom SSL log file. Use this when you want a +# compact non-error SSL logfile on a virtual host basis. +CustomLog logs/ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" + + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf new file mode 100644 index 000000000..b5d7a49ef --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf @@ -0,0 +1,36 @@ +# +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. +# +# The path to the end user account 'public_html' directory must be +# accessible to the webserver userid. This usually means that ~userid +# must have permissions of 711, ~userid/public_html must have permissions +# of 755, and documents contained therein must be world-readable. +# Otherwise, the client will only receive a "403 Forbidden" message. +# + + # + # UserDir is disabled by default since it can confirm the presence + # of a username on the system (depending on home directory + # permissions). + # + UserDir disabled + + # + # To enable requests to /~user/ to serve the user's public_html + # directory, remove the "UserDir disabled" line above, and uncomment + # the following line instead: + # + #UserDir public_html + + +# +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. +# + + AllowOverride FileInfo AuthConfig Limit Indexes + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + Require method GET POST OPTIONS + + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf new file mode 100644 index 000000000..c1b6c11d9 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf @@ -0,0 +1,22 @@ +# +# This configuration file enables the default "Welcome" page if there +# is no default index page present for the root URL. To disable the +# Welcome page, comment out all the lines below. +# +# NOTE: if this file is removed, it will be restored on upgrades. +# + + Options -Indexes + ErrorDocument 403 /.noindex.html + + + + AllowOverride None + Require all granted + + +Alias /.noindex.html /usr/share/httpd/noindex/index.html +Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css +Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css +Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif +Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf new file mode 100644 index 000000000..31d979f20 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf @@ -0,0 +1,77 @@ +# +# This file loads most of the modules included with the Apache HTTP +# Server itself. +# + +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule allowmethods_module modules/mod_allowmethods.so +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_dbd_module modules/mod_authn_dbd.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule cache_module modules/mod_cache.so +LoadModule cache_disk_module modules/mod_cache_disk.so +LoadModule data_module modules/mod_data.so +LoadModule dbd_module modules/mod_dbd.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +LoadModule dumpio_module modules/mod_dumpio.so +LoadModule echo_module modules/mod_echo.so +LoadModule env_module modules/mod_env.so +LoadModule expires_module modules/mod_expires.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +LoadModule include_module modules/mod_include.so +LoadModule info_module modules/mod_info.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule mime_module modules/mod_mime.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule remoteip_module modules/mod_remoteip.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +LoadModule socache_dbm_module modules/mod_socache_dbm.so +LoadModule socache_memcache_module modules/mod_socache_memcache.so +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +LoadModule status_module modules/mod_status.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule unique_id_module modules/mod_unique_id.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule version_module modules/mod_version.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + +#LoadModule buffer_module modules/mod_buffer.so +#LoadModule watchdog_module modules/mod_watchdog.so +#LoadModule heartbeat_module modules/mod_heartbeat.so +#LoadModule heartmonitor_module modules/mod_heartmonitor.so +#LoadModule usertrack_module modules/mod_usertrack.so +#LoadModule dialup_module modules/mod_dialup.so +#LoadModule charset_lite_module modules/mod_charset_lite.so +#LoadModule log_debug_module modules/mod_log_debug.so +#LoadModule ratelimit_module modules/mod_ratelimit.so +#LoadModule reflector_module modules/mod_reflector.so +#LoadModule request_module modules/mod_request.so +#LoadModule sed_module modules/mod_sed.so +#LoadModule speling_module modules/mod_speling.so + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf new file mode 100644 index 000000000..e6af8decd --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf @@ -0,0 +1,3 @@ +LoadModule dav_module modules/mod_dav.so +LoadModule dav_fs_module modules/mod_dav_fs.so +LoadModule dav_lock_module modules/mod_dav_lock.so diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf new file mode 100644 index 000000000..9e0d0db6e --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf @@ -0,0 +1 @@ +LoadModule lua_module modules/mod_lua.so diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf new file mode 100644 index 000000000..7bfd1d413 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf @@ -0,0 +1,19 @@ +# Select the MPM module which should be used by uncommenting exactly +# one of the following LoadModule lines: + +# prefork MPM: Implements a non-threaded, pre-forking web server +# See: http://httpd.apache.org/docs/2.4/mod/prefork.html +LoadModule mpm_prefork_module modules/mod_mpm_prefork.so + +# worker MPM: Multi-Processing Module implementing a hybrid +# multi-threaded multi-process web server +# See: http://httpd.apache.org/docs/2.4/mod/worker.html +# +#LoadModule mpm_worker_module modules/mod_mpm_worker.so + +# event MPM: A variant of the worker MPM with the goal of consuming +# threads only for connections with active processing +# See: http://httpd.apache.org/docs/2.4/mod/event.html +# +#LoadModule mpm_event_module modules/mod_mpm_event.so + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf new file mode 100644 index 000000000..cc0bca077 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf @@ -0,0 +1,16 @@ +# This file configures all the proxy modules: +LoadModule proxy_module modules/mod_proxy.so +LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so +LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so +LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so +LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so +LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +LoadModule proxy_connect_module modules/mod_proxy_connect.so +LoadModule proxy_express_module modules/mod_proxy_express.so +LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so +LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so +LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule proxy_scgi_module modules/mod_proxy_scgi.so +LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf new file mode 100644 index 000000000..53235cd76 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf @@ -0,0 +1 @@ +LoadModule ssl_module modules/mod_ssl.so diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf new file mode 100644 index 000000000..b208c972d --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf @@ -0,0 +1,2 @@ +# This file configures systemd module: +LoadModule systemd_module modules/mod_systemd.so diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf new file mode 100644 index 000000000..5b8b9362e --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf @@ -0,0 +1,14 @@ +# This configuration file loads a CGI module appropriate to the MPM +# which has been configured in 00-mpm.conf. mod_cgid should be used +# with a threaded MPM; mod_cgi with the prefork MPM. + + + LoadModule cgid_module modules/mod_cgid.so + + + LoadModule cgid_module modules/mod_cgid.so + + + LoadModule cgi_module modules/mod_cgi.so + + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf new file mode 100644 index 000000000..a7af0dc1e --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf @@ -0,0 +1,353 @@ +# +# This is the main Apache HTTP server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so 'log/access_log' +# with ServerRoot set to '/www' will be interpreted by the +# server as '/www/log/access_log', where as '/log/access_log' will be +# interpreted as '/log/access_log'. + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# Do not add a slash at the end of the directory path. If you point +# ServerRoot at a non-local disk, be sure to specify a local disk on the +# Mutex directive, if file-based mutexes are used. If you wish to share the +# same ServerRoot for multiple httpd daemons, you will need to change at +# least PidFile. +# +ServerRoot "/etc/httpd" + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 +Listen 80 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +Include conf.modules.d/*.conf + +# +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# It is usually good practice to create a dedicated user and group for +# running httpd, as with most system services. +# +User apache +Group apache + +# 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# +ServerAdmin root@localhost + +# +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# +# If your host doesn't have a registered DNS name, enter its IP address here. +# +#ServerName www.example.com:80 + +# +# Deny access to the entirety of your server's filesystem. You must +# explicitly permit access to web content directories in other +# blocks below. +# + + AllowOverride none + Require all denied + + +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. +# + +# +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +DocumentRoot "/var/www/html" + +# +# Relax access to content within /var/www. +# + + AllowOverride None + # Allow open access: + Require all granted + + +# Further relax access to the default document root: + + # + # Possible values for the Options directive are "None", "All", + # or any combination of: + # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews + # + # Note that "MultiViews" must be named *explicitly* --- "Options All" + # doesn't give it to you. + # + # The Options directive is both complicated and important. Please see + # http://httpd.apache.org/docs/2.4/mod/core.html#options + # for more information. + # + Options Indexes FollowSymLinks + + # + # AllowOverride controls what directives may be placed in .htaccess files. + # It can be "All", "None", or any combination of the keywords: + # Options FileInfo AuthConfig Limit + # + AllowOverride None + + # + # Controls who can get stuff from this server. + # + Require all granted + + +# +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# + + DirectoryIndex index.html + + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog "logs/error_log" + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + + + # + # The following directives define some format nicknames for use with + # a CustomLog directive (see below). + # + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%h %l %u %t \"%r\" %>s %b" common + + + # You need to enable mod_logio.c to use %I and %O + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + + + # + # The location and format of the access logfile (Common Logfile Format). + # If you do not define any access logfiles within a + # container, they will be logged here. Contrariwise, if you *do* + # define per- access logfiles, transactions will be + # logged therein and *not* in this file. + # + #CustomLog "logs/access_log" common + + # + # If you prefer a logfile with access, agent, and referer information + # (Combined Logfile Format) you can use the following directive. + # + CustomLog "logs/access_log" combined + + + + # + # Redirect: Allows you to tell clients about documents that used to + # exist in your server's namespace, but do not anymore. The client + # will make a new request for the document at its new location. + # Example: + # Redirect permanent /foo http://www.example.com/bar + + # + # Alias: Maps web paths into filesystem paths and is used to + # access content that does not live under the DocumentRoot. + # Example: + # Alias /webpath /full/filesystem/path + # + # If you include a trailing / on /webpath then the server will + # require it to be present in the URL. You will also likely + # need to provide a section to allow access to + # the filesystem path. + + # + # ScriptAlias: This controls which directories contain server scripts. + # ScriptAliases are essentially the same as Aliases, except that + # documents in the target directory are treated as applications and + # run by the server when requested rather than as documents sent to the + # client. The same rules about trailing "/" apply to ScriptAlias + # directives as to Alias. + # + ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" + + + +# +# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. +# + + AllowOverride None + Options None + Require all granted + + + + # + # TypesConfig points to the file containing the list of mappings from + # filename extension to MIME-type. + # + TypesConfig /etc/mime.types + + # + # AddType allows you to add to or override the MIME configuration + # file specified in TypesConfig for specific file types. + # + #AddType application/x-gzip .tgz + # + # AddEncoding allows you to have certain browsers uncompress + # information on the fly. Note: Not all browsers support this. + # + #AddEncoding x-compress .Z + #AddEncoding x-gzip .gz .tgz + # + # If the AddEncoding directives above are commented-out, then you + # probably should define those extensions to indicate media types: + # + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + + # + # AddHandler allows you to map certain file extensions to "handlers": + # actions unrelated to filetype. These can be either built into the server + # or added with the Action directive (see below) + # + # To use CGI scripts outside of ScriptAliased directories: + # (You will also need to add "ExecCGI" to the "Options" directive.) + # + #AddHandler cgi-script .cgi + + # For type maps (negotiated resources): + #AddHandler type-map var + + # + # Filters allow you to process content before it is sent to the client. + # + # To parse .shtml files for server-side includes (SSI): + # (You will also need to add "Includes" to the "Options" directive.) + # + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + + +# +# Specify a default charset for all content served; this enables +# interpretation of all content as UTF-8 by default. To use the +# default browser choice (ISO-8859-1), or to allow the META tags +# in HTML content to override this choice, comment out this +# directive: +# +AddDefaultCharset UTF-8 + + + # + # The mod_mime_magic module allows the server to use various hints from the + # contents of the file itself to determine its type. The MIMEMagicFile + # directive tells the module where the hint definitions are located. + # + MIMEMagicFile conf/magic + + +# +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html +# + +# +# EnableMMAP and EnableSendfile: On systems that support it, +# memory-mapping or the sendfile syscall may be used to deliver +# files. This usually improves server performance, but must +# be turned off when serving from networked-mounted +# filesystems or if support for these functions is otherwise +# broken on your system. +# Defaults if commented: EnableMMAP On, EnableSendfile Off +# +#EnableMMAP off +EnableSendfile on + +# Supplemental configuration +# +# Load config files in the "/etc/httpd/conf.d" directory, if any. +IncludeOptional conf.d/*.conf diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic new file mode 100644 index 000000000..7c56119e9 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic @@ -0,0 +1,385 @@ +# Magic data for mod_mime_magic Apache module (originally for file(1) command) +# The module is described in /manual/mod/mod_mime_magic.html +# +# The format is 4-5 columns: +# Column #1: byte number to begin checking from, ">" indicates continuation +# Column #2: type of data to match +# Column #3: contents of data to match +# Column #4: MIME type of result +# Column #5: MIME encoding of result (optional) + +#------------------------------------------------------------------------------ +# Localstuff: file(1) magic for locally observed files +# Add any locally observed files here. + +#------------------------------------------------------------------------------ +# end local stuff +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Java + +0 short 0xcafe +>2 short 0xbabe application/java + +#------------------------------------------------------------------------------ +# audio: file(1) magic for sound formats +# +# from Jan Nicolai Langfeldt , +# + +# Sun/NeXT audio data +0 string .snd +>12 belong 1 audio/basic +>12 belong 2 audio/basic +>12 belong 3 audio/basic +>12 belong 4 audio/basic +>12 belong 5 audio/basic +>12 belong 6 audio/basic +>12 belong 7 audio/basic + +>12 belong 23 audio/x-adpcm + +# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format +# that uses little-endian encoding and has a different magic number +# (0x0064732E in little-endian encoding). +0 lelong 0x0064732E +>12 lelong 1 audio/x-dec-basic +>12 lelong 2 audio/x-dec-basic +>12 lelong 3 audio/x-dec-basic +>12 lelong 4 audio/x-dec-basic +>12 lelong 5 audio/x-dec-basic +>12 lelong 6 audio/x-dec-basic +>12 lelong 7 audio/x-dec-basic +# compressed (G.721 ADPCM) +>12 lelong 23 audio/x-dec-adpcm + +# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM" +# AIFF audio data +8 string AIFF audio/x-aiff +# AIFF-C audio data +8 string AIFC audio/x-aiff +# IFF/8SVX audio data +8 string 8SVX audio/x-aiff + +# Creative Labs AUDIO stuff +# Standard MIDI data +0 string MThd audio/unknown +#>9 byte >0 (format %d) +#>11 byte >1 using %d channels +# Creative Music (CMF) data +0 string CTMF audio/unknown +# SoundBlaster instrument data +0 string SBI audio/unknown +# Creative Labs voice data +0 string Creative\ Voice\ File audio/unknown +## is this next line right? it came this way... +#>19 byte 0x1A +#>23 byte >0 - version %d +#>22 byte >0 \b.%d + +# [GRR 950115: is this also Creative Labs? Guessing that first line +# should be string instead of unknown-endian long...] +#0 long 0x4e54524b MultiTrack sound data +#0 string NTRK MultiTrack sound data +#>4 long x - version %ld + +# Microsoft WAVE format (*.wav) +# [GRR 950115: probably all of the shorts and longs should be leshort/lelong] +# Microsoft RIFF +0 string RIFF audio/unknown +# - WAVE format +>8 string WAVE audio/x-wav +# MPEG audio. +0 beshort&0xfff0 0xfff0 audio/mpeg +# C64 SID Music files, from Linus Walleij +0 string PSID audio/prs.sid + +#------------------------------------------------------------------------------ +# c-lang: file(1) magic for C programs or various scripts +# + +# XPM icons (Greg Roelofs, newt@uchicago.edu) +# ideally should go into "images", but entries below would tag XPM as C source +0 string /*\ XPM image/x-xbm 7bit + +# this first will upset you if you're a PL/1 shop... (are there any left?) +# in which case rm it; ascmagic will catch real C programs +# C or REXX program text +0 string /* text/plain +# C++ program text +0 string // text/plain + +#------------------------------------------------------------------------------ +# compress: file(1) magic for pure-compression formats (no archives) +# +# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc. +# +# Formats for various forms of compressed data +# Formats for "compress" proper have been moved into "compress.c", +# because it tries to uncompress it to figure out what's inside. + +# standard unix compress +0 string \037\235 application/octet-stream x-compress + +# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver) +0 string \037\213 application/octet-stream x-gzip + +# According to gzip.h, this is the correct byte order for packed data. +0 string \037\036 application/octet-stream +# +# This magic number is byte-order-independent. +# +0 short 017437 application/octet-stream + +# XXX - why *two* entries for "compacted data", one of which is +# byte-order independent, and one of which is byte-order dependent? +# +# compacted data +0 short 0x1fff application/octet-stream +0 string \377\037 application/octet-stream +# huf output +0 short 0145405 application/octet-stream + +# Squeeze and Crunch... +# These numbers were gleaned from the Unix versions of the programs to +# handle these formats. Note that I can only uncrunch, not crunch, and +# I didn't have a crunched file handy, so the crunch number is untested. +# Keith Waclena +#0 leshort 0x76FF squeezed data (CP/M, DOS) +#0 leshort 0x76FE crunched data (CP/M, DOS) + +# Freeze +#0 string \037\237 Frozen file 2.1 +#0 string \037\236 Frozen file 1.0 (or gzip 0.5) + +# lzh? +#0 string \037\240 LZH compressed data + +#------------------------------------------------------------------------------ +# frame: file(1) magic for FrameMaker files +# +# This stuff came on a FrameMaker demo tape, most of which is +# copyright, but this file is "published" as witness the following: +# +0 string \ +# and Anna Shergold +# +0 string \ +0 string \14 byte 12 (OS/2 1.x format) +#>14 byte 64 (OS/2 2.x format) +#>14 byte 40 (Windows 3.x format) +#0 string IC icon +#0 string PI pointer +#0 string CI color icon +#0 string CP color pointer +#0 string BA bitmap array + +0 string \x89PNG image/png +0 string FWS application/x-shockwave-flash +0 string CWS application/x-shockwave-flash + +#------------------------------------------------------------------------------ +# lisp: file(1) magic for lisp programs +# +# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com) +0 string ;; text/plain 8bit +# Emacs 18 - this is always correct, but not very magical. +0 string \012( application/x-elc +# Emacs 19 +0 string ;ELC\023\000\000\000 application/x-elc + +#------------------------------------------------------------------------------ +# mail.news: file(1) magic for mail and news +# +# There are tests to ascmagic.c to cope with mail and news. +0 string Relay-Version: message/rfc822 7bit +0 string #!\ rnews message/rfc822 7bit +0 string N#!\ rnews message/rfc822 7bit +0 string Forward\ to message/rfc822 7bit +0 string Pipe\ to message/rfc822 7bit +0 string Return-Path: message/rfc822 7bit +0 string Path: message/news 8bit +0 string Xref: message/news 8bit +0 string From: message/rfc822 7bit +0 string Article message/news 8bit +#------------------------------------------------------------------------------ +# msword: file(1) magic for MS Word files +# +# Contributor claims: +# Reversed-engineered MS Word magic numbers +# + +0 string \376\067\0\043 application/msword +0 string \333\245-\0\0\0 application/msword + +# disable this one because it applies also to other +# Office/OLE documents for which msword is not correct. See PR#2608. +#0 string \320\317\021\340\241\261 application/msword + + + +#------------------------------------------------------------------------------ +# printer: file(1) magic for printer-formatted files +# + +# PostScript +0 string %! application/postscript +0 string \004%! application/postscript + +# Acrobat +# (due to clamen@cs.cmu.edu) +0 string %PDF- application/pdf + +#------------------------------------------------------------------------------ +# sc: file(1) magic for "sc" spreadsheet +# +38 string Spreadsheet application/x-sc + +#------------------------------------------------------------------------------ +# tex: file(1) magic for TeX files +# +# XXX - needs byte-endian stuff (big-endian and little-endian DVI?) +# +# From + +# Although we may know the offset of certain text fields in TeX DVI +# and font files, we can't use them reliably because they are not +# zero terminated. [but we do anyway, christos] +0 string \367\002 application/x-dvi +#0 string \367\203 TeX generic font data +#0 string \367\131 TeX packed font data +#0 string \367\312 TeX virtual font data +#0 string This\ is\ TeX, TeX transcript text +#0 string This\ is\ METAFONT, METAFONT transcript text + +# There is no way to detect TeX Font Metric (*.tfm) files without +# breaking them apart and reading the data. The following patterns +# match most *.tfm files generated by METAFONT or afm2tfm. +#2 string \000\021 TeX font metric data +#2 string \000\022 TeX font metric data +#>34 string >\0 (%s) + +# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com) +#0 string \\input\ texinfo Texinfo source text +#0 string This\ is\ Info\ file GNU Info text + +# correct TeX magic for Linux (and maybe more) +# from Peter Tobias (tobias@server.et-inf.fho-emden.de) +# +0 leshort 0x02f7 application/x-dvi + +# RTF - Rich Text Format +0 string {\\rtf application/rtf + +#------------------------------------------------------------------------------ +# animation: file(1) magic for animation/movie formats +# +# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8) +# MPEG file +0 string \000\000\001\263 video/mpeg +# +# The contributor claims: +# I couldn't find a real magic number for these, however, this +# -appears- to work. Note that it might catch other files, too, +# so BE CAREFUL! +# +# Note that title and author appear in the two 20-byte chunks +# at decimal offsets 2 and 22, respectively, but they are XOR'ed with +# 255 (hex FF)! DL format SUCKS BIG ROCKS. +# +# DL file version 1 , medium format (160x100, 4 images/screen) +0 byte 1 video/unknown +0 byte 2 video/unknown +# Quicktime video, from Linus Walleij +# from Apple quicktime file format documentation. +4 string moov video/quicktime +4 string mdat video/quicktime + diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/sites b/certbot-apache/tests/testdata/centos7_apache/apache/sites new file mode 100644 index 000000000..6af1f63fa --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/sites @@ -0,0 +1 @@ +conf.d/centos.example.com.conf, centos.example.com diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd b/certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd new file mode 100644 index 000000000..4bcb300c2 --- /dev/null +++ b/certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd @@ -0,0 +1,25 @@ +# +# This file can be used to set additional environment variables for +# the httpd process, or pass additional options to the httpd +# executable. +# +# Note: With previous versions of httpd, the MPM could be changed by +# editing an "HTTPD" variable here. With the current version, that +# variable is now ignored. The MPM is a loadable module, and the +# choice of MPM can be changed by editing the configuration file +# /etc/httpd/conf.modules.d/00-mpm.conf. +# + +# +# To pass additional options (for instance, -D definitions) to the +# httpd binary at startup, set OPTIONS here. +# +OPTIONS="-D mock_define -D mock_define_too -D mock_value=TRUE -DMOCK_NOSEP -DNOSEP_TWO=NOSEP_VAL" + +# +# This setting ensures the httpd process is started in the "C" locale +# by default. (Some modules will not behave correctly if +# case-sensitive string comparisons are performed in a different +# locale.) +# +LANG=C diff --git a/certbot-apache/tests/testdata/complex_parsing/apache2.conf b/certbot-apache/tests/testdata/complex_parsing/apache2.conf new file mode 100644 index 000000000..14cf95f9e --- /dev/null +++ b/certbot-apache/tests/testdata/complex_parsing/apache2.conf @@ -0,0 +1,55 @@ +# Global configuration + +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +ErrorLog ${APACHE_LOG_DIR}/error.log + +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# Include generic snippets of statements +IncludeOptional conf-enabled/ + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +Define COMPLEX + +Define tls_port 1234 +Define tls_port_str "1234" + +Define fnmatch_filename test_fnmatch.conf + + +Include test_variables.conf +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf b/certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf new file mode 100644 index 000000000..1e5307780 --- /dev/null +++ b/certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf @@ -0,0 +1,9 @@ +# 3 - one arg directives +# 2 - two arg directives +# 1 - three arg directives +TestArgsDirective one_arg +TestArgsDirective one_arg two_arg +TestArgsDirective one_arg +TestArgsDirective one_arg two_arg +TestArgsDirective one_arg two_arg three_arg +TestArgsDirective one_arg diff --git a/certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf b/certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf new file mode 100644 index 000000000..4e6b84edf --- /dev/null +++ b/certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf @@ -0,0 +1 @@ +FNMATCH_DIRECTIVE Success diff --git a/certbot-apache/tests/testdata/complex_parsing/test_variables.conf b/certbot-apache/tests/testdata/complex_parsing/test_variables.conf new file mode 100644 index 000000000..1a9edff74 --- /dev/null +++ b/certbot-apache/tests/testdata/complex_parsing/test_variables.conf @@ -0,0 +1,66 @@ +TestVariablePort ${tls_port} +TestVariablePortStr "${tls_port_str}" + +LoadModule status_module modules/mod_status.so + +# Basic IfDefine + + VAR_DIRECTIVE success + LoadModule ssl_module modules/mod_ssl.so + + + + INVALID_VAR_DIRECTIVE failure + + + + INVALID_VAR_DIRECTIVE failure + + + + VAR_DIRECTIVE failure + + + +# Basic IfModule + + MOD_DIRECTIVE Success + + + + INVALID_MOD_DIRECTIVE failure + + + + INVALID_MOD_DIRECTIVE failure + + + + MOD_DIRECTIVE Success + + +# Nested Tests + + + NESTED_DIRECTIVE success + + + NESTED_DIRECTIVE success + + + + INVALID_NESTED_DIRECTIVE failure + + + + + INVALID_NESTED_DIRECTIVE failure + + + INVALID_NESTED_DIRECTIVE failure + + + + NESTED_DIRECTIVE success + + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf new file mode 100644 index 000000000..2a5bb7be2 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf @@ -0,0 +1,196 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +Mutex file:${APACHE_LOCK_DIR} default + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf new file mode 100644 index 000000000..8e9178803 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf @@ -0,0 +1,3 @@ + + +ServerName invalid.net diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf new file mode 100644 index 000000000..5e9f5e9e7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf @@ -0,0 +1,4 @@ +# Define an access log for VirtualHosts that don't define their own logfile +CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf new file mode 100644 index 000000000..eccfcb1fd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf @@ -0,0 +1,35 @@ +# Changing the following options will not really affect the security of the +# server, but might make attacks slightly more difficult in some cases. + +# +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minimal | Minor | Major | Prod +# where Full conveys the most information, and Prod the least. +#ServerTokens Minimal +ServerTokens OS +#ServerTokens Full + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +#ServerSignature Off +ServerSignature On + +# +# Allow TRACE method +# +# Set to "extended" to also reflect the request body (only for testing and +# diagnostic purposes). +# +# Set to one of: On | Off | extended +TraceEnable Off +#TraceEnable On + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf new file mode 100644 index 000000000..b02782dab --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf @@ -0,0 +1,20 @@ + + + Define ENABLE_USR_LIB_CGI_BIN + + + + Define ENABLE_USR_LIB_CGI_BIN + + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Require all granted + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf new file mode 120000 index 000000000..8af91e530 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf @@ -0,0 +1 @@ +../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf new file mode 120000 index 000000000..036c97fa7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf @@ -0,0 +1 @@ +../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf new file mode 120000 index 000000000..d917f688e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf @@ -0,0 +1 @@ +../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars new file mode 100644 index 000000000..a13d9a89e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars @@ -0,0 +1,29 @@ +# envvars - default environment variables for apache2ctl + +# this won't be correct after changing uid +unset HOME + +# for supporting multiple apache2 instances +if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then + SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" +else + SUFFIX= +fi + +# Since there is no sane way to get the parsed apache2 config in scripts, some +# settings are defined via environment variables and then used in apache2ctl, +# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. +export APACHE_RUN_USER=www-data +export APACHE_RUN_GROUP=www-data +# temporary state file location. This might be changed to /run in Wheezy+1 +export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid +export APACHE_RUN_DIR=/var/run/apache2$SUFFIX +export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX +# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. +export APACHE_LOG_DIR=/var/log/apache2$SUFFIX + +## The locale used by some modules like mod_dav +export LANG=C + +export LANG + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load new file mode 100644 index 000000000..c6df2733b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load @@ -0,0 +1,5 @@ +# Depends: dav_svn + + Include mods-enabled/dav_svn.load + +LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load new file mode 100644 index 000000000..a5867fff3 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load @@ -0,0 +1,3 @@ + + LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf new file mode 100644 index 000000000..801cbd6bd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf @@ -0,0 +1,56 @@ +# dav_svn.conf - Example Subversion/Apache configuration +# +# For details and further options see the Apache user manual and +# the Subversion book. +# +# NOTE: for a setup with multiple vhosts, you will want to do this +# configuration in /etc/apache2/sites-available/*, not here. + +# ... +# URL controls how the repository appears to the outside world. +# In this example clients access the repository as http://hostname/svn/ +# Note, a literal /svn should NOT exist in your document root. +# + + # Uncomment this to enable the repository + #DAV svn + + # Set this to the path to your repository + #SVNPath /var/lib/svn + # Alternatively, use SVNParentPath if you have multiple repositories under + # under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...). + # You need either SVNPath and SVNParentPath, but not both. + #SVNParentPath /var/lib/svn + + # Access control is done at 3 levels: (1) Apache authentication, via + # any of several methods. A "Basic Auth" section is commented out + # below. (2) Apache and , also commented out + # below. (3) mod_authz_svn is a svn-specific authorization module + # which offers fine-grained read/write access control for paths + # within a repository. (The first two layers are coarse-grained; you + # can only enable/disable access to an entire repository.) Note that + # mod_authz_svn is noticeably slower than the other two layers, so if + # you don't need the fine-grained control, don't configure it. + + # Basic Authentication is repository-wide. It is not secure unless + # you are using https. See the 'htpasswd' command to create and + # manage the password file - and the documentation for the + # 'auth_basic' and 'authn_file' modules, which you will need for this + # (enable them with 'a2enmod'). + #AuthType Basic + #AuthName "Subversion Repository" + #AuthUserFile /etc/apache2/dav_svn.passwd + + # To enable authorization via mod_authz_svn (enable that module separately): + # + #AuthzSVNAccessFile /etc/apache2/dav_svn.authz + # + + # The following three lines allow anonymous read, but make + # committers authenticate themselves. It requires the 'authz_user' + # module (enable it with 'a2enmod'). + # + #Require valid-user + # + +# diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load new file mode 100644 index 000000000..e41e1581a --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load @@ -0,0 +1,7 @@ +# Depends: dav + + + Include mods-enabled/dav.load + + LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load new file mode 100644 index 000000000..b32f16264 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load @@ -0,0 +1 @@ +LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf new file mode 100644 index 000000000..e9fcf4f9b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf @@ -0,0 +1,89 @@ + + + # Pseudo Random Number Generator (PRNG): + # Configure one or more sources to seed the PRNG of the SSL library. + # The seed data should be of good random quality. + # WARNING! On some platforms /dev/random blocks if not enough entropy + # is available. This means you then cannot use the /dev/random device + # because it would lead to very long connection times (as long as + # it requires to make more entropy available). But usually those + # platforms additionally provide a /dev/urandom device which doesn't + # block. So, if available, use this one instead. Read the mod_ssl User + # Manual for more details. + # + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + ## + ## SSL Global Context + ## + ## All SSL configuration in this context applies both to + ## the main server and all SSL-enabled virtual hosts. + ## + + # + # Some MIME-types for downloading Certificates and CRLs + # + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + # Pass Phrase Dialog: + # Configure the pass phrase gathering process. + # The filtering dialog program (`builtin' is a internal + # terminal dialog) has to provide the pass phrase on stdout. + SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase + + # Inter-Process Session Cache: + # Configure the SSL Session Cache: First the mechanism + # to use and second the expiring timeout (in seconds). + # (The mechanism dbm has known memory leaks and should not be used). + #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache + SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) + SSLSessionCacheTimeout 300 + + # Semaphore: + # Configure the path to the mutual exclusion semaphore the + # SSL engine uses internally for inter-process synchronization. + # (Disabled by default, the global Mutex directive consolidates by default + # this) + #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache + + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. See the + # ciphers(1) man page from the openssl package for list of all available + # options. + # Enable only secure ciphers: + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 + + # Speed-optimized SSL Cipher configuration: + # If speed is your main concern (on busy HTTPS servers e.g.), + # you might want to force clients to specific, performance + # optimized ciphers. In this case, prepend those ciphers + # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. + # Caveat: by giving precedence to RC4-SHA and AES128-SHA + # (as in the example below), most connections will no longer + # have perfect forward secrecy - if the server's key is + # compromised, captures of past or future traffic must be + # considered compromised, too. + #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 + #SSLHonorCipherOrder on + + # The protocols to enable. + # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 + # SSL v2 is no longer supported + SSLProtocol all + + # Allow insecure renegotiation with clients which do not yet support the + # secure renegotiation protocol. Default: Off + #SSLInsecureRenegotiation on + + # Whether to forbid non-SNI clients to access name based virtual hosts. + # Default: Off + #SSLStrictSNIVHostCheck On + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load new file mode 100644 index 000000000..3d2336ae0 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load @@ -0,0 +1,2 @@ +# Depends: setenvif mime socache_shmcb +LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load new file mode 120000 index 000000000..7ac0725dd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load @@ -0,0 +1 @@ +../mods-available/authz_svn.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load new file mode 120000 index 000000000..9dcfef6da --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load @@ -0,0 +1 @@ +../mods-available/dav.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf new file mode 120000 index 000000000..964c7bb0b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf @@ -0,0 +1 @@ +../mods-available/dav_svn.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load new file mode 120000 index 000000000..4094e4173 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load @@ -0,0 +1 @@ +../mods-available/dav_svn.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf new file mode 100644 index 000000000..5daec58c1 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf @@ -0,0 +1,15 @@ +# If you just change the port or add more ports here, you will likely also +# have to change the VirtualHost statement in +# /etc/apache2/sites-enabled/000-default.conf + +Listen 80 + + + Listen 443 + + + + Listen 443 + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf new file mode 100644 index 000000000..1a5b7de47 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf @@ -0,0 +1,11 @@ + + ServerName wildcard.tld + ServerAlias ?.wildcard.tld + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf new file mode 100644 index 000000000..2bd4e1fe9 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf @@ -0,0 +1,12 @@ + + + ServerName ip-172-30-0-17 + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf new file mode 100644 index 000000000..b8046e6c9 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf @@ -0,0 +1,11 @@ + + ServerName example.net + ServerAlias ??.example.net *.other.example.net *another.example.net + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf new file mode 120000 index 000000000..95f52f002 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf @@ -0,0 +1 @@ +../sites-available/another_wildcard.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf new file mode 120000 index 000000000..f7fdf1bbe --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf @@ -0,0 +1 @@ +../sites-available/old-and-default.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf new file mode 120000 index 000000000..a87af2c93 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf @@ -0,0 +1 @@ +../sites-available/wildcard.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites new file mode 100644 index 000000000..ab518ee5b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites @@ -0,0 +1,3 @@ +sites-available/certbot.conf, certbot.demo +sites-available/encryption-example.conf, encryption-example.demo +sites-available/ocsp-ssl.conf, ocspvhost.com diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf new file mode 100644 index 000000000..4ed016e07 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf @@ -0,0 +1,198 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +Mutex file:${APACHE_LOCK_DIR} default + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + + +# +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf new file mode 100644 index 000000000..5e9f5e9e7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf @@ -0,0 +1,4 @@ +# Define an access log for VirtualHosts that don't define their own logfile +CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf new file mode 100644 index 000000000..1dfe33c60 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf @@ -0,0 +1,31 @@ +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minimal | Minor | Major | Prod +# where Full conveys the most information, and Prod the least. +#ServerTokens Minimal +ServerTokens OS +#ServerTokens Full + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +#ServerSignature Off +ServerSignature On + +# +# Allow TRACE method +# +# Set to "extended" to also reflect the request body (only for testing and +# diagnostic purposes). +# +# Set to one of: On | Off | extended +TraceEnable Off +#TraceEnable On + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf new file mode 100644 index 000000000..b02782dab --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf @@ -0,0 +1,20 @@ + + + Define ENABLE_USR_LIB_CGI_BIN + + + + Define ENABLE_USR_LIB_CGI_BIN + + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Require all granted + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf new file mode 120000 index 000000000..8af91e530 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf @@ -0,0 +1 @@ +../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf new file mode 120000 index 000000000..036c97fa7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf @@ -0,0 +1 @@ +../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf new file mode 120000 index 000000000..d917f688e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf @@ -0,0 +1 @@ +../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars new file mode 100644 index 000000000..8051c4544 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars @@ -0,0 +1,28 @@ +# envvars - default environment variables for apache2ctl + +# this won't be correct after changing uid +unset HOME + +# for supporting multiple apache2 instances +if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then + SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" +else + SUFFIX= +fi + +# Since there is no sane way to get the parsed apache2 config in scripts, some +# settings are defined via environment variables and then used in apache2ctl, +# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. +export APACHE_RUN_USER=www-data +export APACHE_RUN_GROUP=www-data +# temporary state file location. This might be changed to /run in Wheezy+1 +export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid +export APACHE_RUN_DIR=/var/run/apache2$SUFFIX +export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX +# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. +export APACHE_LOG_DIR=/var/log/apache2$SUFFIX + +## The locale used by some modules like mod_dav +export LANG=C + +export LANG diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf new file mode 100644 index 000000000..e9fcf4f9b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf @@ -0,0 +1,89 @@ + + + # Pseudo Random Number Generator (PRNG): + # Configure one or more sources to seed the PRNG of the SSL library. + # The seed data should be of good random quality. + # WARNING! On some platforms /dev/random blocks if not enough entropy + # is available. This means you then cannot use the /dev/random device + # because it would lead to very long connection times (as long as + # it requires to make more entropy available). But usually those + # platforms additionally provide a /dev/urandom device which doesn't + # block. So, if available, use this one instead. Read the mod_ssl User + # Manual for more details. + # + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + ## + ## SSL Global Context + ## + ## All SSL configuration in this context applies both to + ## the main server and all SSL-enabled virtual hosts. + ## + + # + # Some MIME-types for downloading Certificates and CRLs + # + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + # Pass Phrase Dialog: + # Configure the pass phrase gathering process. + # The filtering dialog program (`builtin' is a internal + # terminal dialog) has to provide the pass phrase on stdout. + SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase + + # Inter-Process Session Cache: + # Configure the SSL Session Cache: First the mechanism + # to use and second the expiring timeout (in seconds). + # (The mechanism dbm has known memory leaks and should not be used). + #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache + SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) + SSLSessionCacheTimeout 300 + + # Semaphore: + # Configure the path to the mutual exclusion semaphore the + # SSL engine uses internally for inter-process synchronization. + # (Disabled by default, the global Mutex directive consolidates by default + # this) + #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache + + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. See the + # ciphers(1) man page from the openssl package for list of all available + # options. + # Enable only secure ciphers: + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 + + # Speed-optimized SSL Cipher configuration: + # If speed is your main concern (on busy HTTPS servers e.g.), + # you might want to force clients to specific, performance + # optimized ciphers. In this case, prepend those ciphers + # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. + # Caveat: by giving precedence to RC4-SHA and AES128-SHA + # (as in the example below), most connections will no longer + # have perfect forward secrecy - if the server's key is + # compromised, captures of past or future traffic must be + # considered compromised, too. + #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 + #SSLHonorCipherOrder on + + # The protocols to enable. + # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 + # SSL v2 is no longer supported + SSLProtocol all + + # Allow insecure renegotiation with clients which do not yet support the + # secure renegotiation protocol. Default: Off + #SSLInsecureRenegotiation on + + # Whether to forbid non-SNI clients to access name based virtual hosts. + # Default: Off + #SSLStrictSNIVHostCheck On + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load new file mode 100644 index 000000000..3d2336ae0 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load @@ -0,0 +1,2 @@ +# Depends: setenvif mime socache_shmcb +LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf new file mode 100644 index 000000000..176b9d103 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf @@ -0,0 +1,20 @@ +# If you just change the port or add more ports here, you will likely also +# have to change the VirtualHost statement in +# /etc/apache2/sites-enabled/000-default.conf + +Listen 80 + +NameVirtualHost *:80 + + + Listen 443 + + + + Listen 443 + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet + +NameVirtualHost *:443 + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf new file mode 100644 index 000000000..d81fe132d --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf @@ -0,0 +1,11 @@ + + # How well does Certbot work without a ServerName/Alias? + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf new file mode 100644 index 000000000..e659d4b07 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf @@ -0,0 +1,38 @@ + + + ServerAdmin webmaster@localhost + + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem + SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem + + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf new file mode 120000 index 000000000..3c4632b73 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf @@ -0,0 +1 @@ +../sites-available/000-default.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites new file mode 100644 index 000000000..03d53dd61 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites @@ -0,0 +1 @@ +sites-available/000-default.conf, default.com diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf new file mode 100644 index 000000000..2a5bb7be2 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf @@ -0,0 +1,196 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +Mutex file:${APACHE_LOCK_DIR} default + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars new file mode 100644 index 000000000..a13d9a89e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars @@ -0,0 +1,29 @@ +# envvars - default environment variables for apache2ctl + +# this won't be correct after changing uid +unset HOME + +# for supporting multiple apache2 instances +if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then + SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" +else + SUFFIX= +fi + +# Since there is no sane way to get the parsed apache2 config in scripts, some +# settings are defined via environment variables and then used in apache2ctl, +# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. +export APACHE_RUN_USER=www-data +export APACHE_RUN_GROUP=www-data +# temporary state file location. This might be changed to /run in Wheezy+1 +export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid +export APACHE_RUN_DIR=/var/run/apache2$SUFFIX +export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX +# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. +export APACHE_LOG_DIR=/var/log/apache2$SUFFIX + +## The locale used by some modules like mod_dav +export LANG=C + +export LANG + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf new file mode 100644 index 000000000..5daec58c1 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf @@ -0,0 +1,15 @@ +# If you just change the port or add more ports here, you will likely also +# have to change the VirtualHost statement in +# /etc/apache2/sites-enabled/000-default.conf + +Listen 80 + + + Listen 443 + + + + Listen 443 + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf new file mode 100644 index 000000000..6ab206b2d --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf @@ -0,0 +1,22 @@ + + + ServerName banana.vomit.net + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + + + + ServerName banana.vomit.com + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf new file mode 100644 index 000000000..5f2b727bf --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf @@ -0,0 +1,38 @@ + + ServerName 1.multi.vhost.tld + ServerAlias first.multi.vhost.tld + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + + + ServerName 2.multi.vhost.tld + ServerAlias second.multi.vhost.tld + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined +RewriteEngine on +RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ b://u%{REQUEST_URI} [P,NE,L] +RewriteCond %{HTTPS} !=on +RewriteCond %{HTTPS} !^$ +RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,NE,R=permanent] + + + + + ServerName 3.multi.vhost.tld + ServerAlias third.multi.vhost.tld + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined +RewriteEngine on +RewriteRule "^/secrets/(.+)" "https://new.example.com/docs/$1" [R,L] +RewriteRule "^/docs/(.+)" "http://new.example.com/docs/$1" [R,L] + + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf new file mode 120000 index 000000000..032e6bcf0 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf @@ -0,0 +1 @@ +../sites-available/default.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf new file mode 120000 index 000000000..7f0910ff4 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf @@ -0,0 +1 @@ +../sites-available/multi-vhost.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf new file mode 100644 index 000000000..819a6bcb4 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf @@ -0,0 +1,207 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +Mutex file:${APACHE_LOCK_DIR} default + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + + + + ServerName vhost.in.rootconf + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf new file mode 100644 index 000000000..8e9178803 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf @@ -0,0 +1,3 @@ + + +ServerName invalid.net diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf new file mode 100644 index 000000000..5e9f5e9e7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf @@ -0,0 +1,4 @@ +# Define an access log for VirtualHosts that don't define their own logfile +CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf new file mode 100644 index 000000000..eccfcb1fd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf @@ -0,0 +1,35 @@ +# Changing the following options will not really affect the security of the +# server, but might make attacks slightly more difficult in some cases. + +# +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minimal | Minor | Major | Prod +# where Full conveys the most information, and Prod the least. +#ServerTokens Minimal +ServerTokens OS +#ServerTokens Full + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +#ServerSignature Off +ServerSignature On + +# +# Allow TRACE method +# +# Set to "extended" to also reflect the request body (only for testing and +# diagnostic purposes). +# +# Set to one of: On | Off | extended +TraceEnable Off +#TraceEnable On + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf new file mode 100644 index 000000000..b02782dab --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf @@ -0,0 +1,20 @@ + + + Define ENABLE_USR_LIB_CGI_BIN + + + + Define ENABLE_USR_LIB_CGI_BIN + + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Require all granted + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf new file mode 120000 index 000000000..8af91e530 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf @@ -0,0 +1 @@ +../conf-available/other-vhosts-access-log.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf new file mode 120000 index 000000000..036c97fa7 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf @@ -0,0 +1 @@ +../conf-available/security.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf new file mode 120000 index 000000000..d917f688e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf @@ -0,0 +1 @@ +../conf-available/serve-cgi-bin.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars new file mode 100644 index 000000000..a13d9a89e --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars @@ -0,0 +1,29 @@ +# envvars - default environment variables for apache2ctl + +# this won't be correct after changing uid +unset HOME + +# for supporting multiple apache2 instances +if [ "${APACHE_CONFDIR##/etc/apache2-}" != "${APACHE_CONFDIR}" ] ; then + SUFFIX="-${APACHE_CONFDIR##/etc/apache2-}" +else + SUFFIX= +fi + +# Since there is no sane way to get the parsed apache2 config in scripts, some +# settings are defined via environment variables and then used in apache2ctl, +# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc. +export APACHE_RUN_USER=www-data +export APACHE_RUN_GROUP=www-data +# temporary state file location. This might be changed to /run in Wheezy+1 +export APACHE_PID_FILE=/var/run/apache2/apache2$SUFFIX.pid +export APACHE_RUN_DIR=/var/run/apache2$SUFFIX +export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX +# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2. +export APACHE_LOG_DIR=/var/log/apache2$SUFFIX + +## The locale used by some modules like mod_dav +export LANG=C + +export LANG + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load new file mode 100644 index 000000000..c6df2733b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load @@ -0,0 +1,5 @@ +# Depends: dav_svn + + Include mods-enabled/dav_svn.load + +LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load new file mode 100644 index 000000000..a5867fff3 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load @@ -0,0 +1,3 @@ + + LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf new file mode 100644 index 000000000..801cbd6bd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf @@ -0,0 +1,56 @@ +# dav_svn.conf - Example Subversion/Apache configuration +# +# For details and further options see the Apache user manual and +# the Subversion book. +# +# NOTE: for a setup with multiple vhosts, you will want to do this +# configuration in /etc/apache2/sites-available/*, not here. + +# ... +# URL controls how the repository appears to the outside world. +# In this example clients access the repository as http://hostname/svn/ +# Note, a literal /svn should NOT exist in your document root. +# + + # Uncomment this to enable the repository + #DAV svn + + # Set this to the path to your repository + #SVNPath /var/lib/svn + # Alternatively, use SVNParentPath if you have multiple repositories under + # under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...). + # You need either SVNPath and SVNParentPath, but not both. + #SVNParentPath /var/lib/svn + + # Access control is done at 3 levels: (1) Apache authentication, via + # any of several methods. A "Basic Auth" section is commented out + # below. (2) Apache and , also commented out + # below. (3) mod_authz_svn is a svn-specific authorization module + # which offers fine-grained read/write access control for paths + # within a repository. (The first two layers are coarse-grained; you + # can only enable/disable access to an entire repository.) Note that + # mod_authz_svn is noticeably slower than the other two layers, so if + # you don't need the fine-grained control, don't configure it. + + # Basic Authentication is repository-wide. It is not secure unless + # you are using https. See the 'htpasswd' command to create and + # manage the password file - and the documentation for the + # 'auth_basic' and 'authn_file' modules, which you will need for this + # (enable them with 'a2enmod'). + #AuthType Basic + #AuthName "Subversion Repository" + #AuthUserFile /etc/apache2/dav_svn.passwd + + # To enable authorization via mod_authz_svn (enable that module separately): + # + #AuthzSVNAccessFile /etc/apache2/dav_svn.authz + # + + # The following three lines allow anonymous read, but make + # committers authenticate themselves. It requires the 'authz_user' + # module (enable it with 'a2enmod'). + # + #Require valid-user + # + +# diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load new file mode 100644 index 000000000..e41e1581a --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load @@ -0,0 +1,7 @@ +# Depends: dav + + + Include mods-enabled/dav.load + + LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load new file mode 100644 index 000000000..b32f16264 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load @@ -0,0 +1 @@ +LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf new file mode 100644 index 000000000..e9fcf4f9b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf @@ -0,0 +1,89 @@ + + + # Pseudo Random Number Generator (PRNG): + # Configure one or more sources to seed the PRNG of the SSL library. + # The seed data should be of good random quality. + # WARNING! On some platforms /dev/random blocks if not enough entropy + # is available. This means you then cannot use the /dev/random device + # because it would lead to very long connection times (as long as + # it requires to make more entropy available). But usually those + # platforms additionally provide a /dev/urandom device which doesn't + # block. So, if available, use this one instead. Read the mod_ssl User + # Manual for more details. + # + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + ## + ## SSL Global Context + ## + ## All SSL configuration in this context applies both to + ## the main server and all SSL-enabled virtual hosts. + ## + + # + # Some MIME-types for downloading Certificates and CRLs + # + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + # Pass Phrase Dialog: + # Configure the pass phrase gathering process. + # The filtering dialog program (`builtin' is a internal + # terminal dialog) has to provide the pass phrase on stdout. + SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase + + # Inter-Process Session Cache: + # Configure the SSL Session Cache: First the mechanism + # to use and second the expiring timeout (in seconds). + # (The mechanism dbm has known memory leaks and should not be used). + #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache + SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) + SSLSessionCacheTimeout 300 + + # Semaphore: + # Configure the path to the mutual exclusion semaphore the + # SSL engine uses internally for inter-process synchronization. + # (Disabled by default, the global Mutex directive consolidates by default + # this) + #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache + + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. See the + # ciphers(1) man page from the openssl package for list of all available + # options. + # Enable only secure ciphers: + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 + + # Speed-optimized SSL Cipher configuration: + # If speed is your main concern (on busy HTTPS servers e.g.), + # you might want to force clients to specific, performance + # optimized ciphers. In this case, prepend those ciphers + # to the SSLCipherSuite list, and enable SSLHonorCipherOrder. + # Caveat: by giving precedence to RC4-SHA and AES128-SHA + # (as in the example below), most connections will no longer + # have perfect forward secrecy - if the server's key is + # compromised, captures of past or future traffic must be + # considered compromised, too. + #SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5 + #SSLHonorCipherOrder on + + # The protocols to enable. + # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 + # SSL v2 is no longer supported + SSLProtocol all + + # Allow insecure renegotiation with clients which do not yet support the + # secure renegotiation protocol. Default: Off + #SSLInsecureRenegotiation on + + # Whether to forbid non-SNI clients to access name based virtual hosts. + # Default: Off + #SSLStrictSNIVHostCheck On + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load new file mode 100644 index 000000000..3d2336ae0 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load @@ -0,0 +1,2 @@ +# Depends: setenvif mime socache_shmcb +LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load new file mode 120000 index 000000000..7ac0725dd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load @@ -0,0 +1 @@ +../mods-available/authz_svn.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load new file mode 120000 index 000000000..9dcfef6da --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load @@ -0,0 +1 @@ +../mods-available/dav.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf new file mode 120000 index 000000000..964c7bb0b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf @@ -0,0 +1 @@ +../mods-available/dav_svn.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load new file mode 120000 index 000000000..4094e4173 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load @@ -0,0 +1 @@ +../mods-available/dav_svn.load \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf new file mode 100644 index 000000000..5daec58c1 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf @@ -0,0 +1,15 @@ +# If you just change the port or add more ports here, you will likely also +# have to change the VirtualHost statement in +# /etc/apache2/sites-enabled/000-default.conf + +Listen 80 + + + Listen 443 + + + + Listen 443 + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf new file mode 100644 index 000000000..2bd4e1fe9 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf @@ -0,0 +1,12 @@ + + + ServerName ip-172-30-0-17 + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf new file mode 100644 index 000000000..965ca2222 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf @@ -0,0 +1,43 @@ + +ServerName certbot.demo +ServerAlias www.certbot.demo +ServerAdmin webmaster@localhost + +DocumentRoot /var/www-certbot-reworld/static/ + +Options FollowSymLinks +AllowOverride None + + +Options Indexes FollowSymLinks MultiViews +AllowOverride None +Order allow,deny +allow from all + + +ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + +AllowOverride None +Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch +Order allow,deny +Allow from all + + +ErrorLog ${APACHE_LOG_DIR}/error.log + +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +LogLevel warn + +CustomLog ${APACHE_LOG_DIR}/access.log combined + +Alias /doc/ "/usr/share/doc/" + +Options Indexes MultiViews FollowSymLinks +AllowOverride None +Order deny,allow +Deny from all +Allow from 127.0.0.0/255.0.0.0 ::1/128 + + + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf new file mode 100644 index 000000000..849b42e9f --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf @@ -0,0 +1,36 @@ + + + ServerAdmin webmaster@localhost + + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem + SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem + + + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf new file mode 100644 index 000000000..a3025ae8a --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf @@ -0,0 +1,40 @@ + + + ServerAdmin webmaster@localhost + + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem + SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem + + + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf new file mode 100644 index 000000000..5684651fb --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf @@ -0,0 +1,9 @@ + + ServerName duplicate.example.com + + ServerAdmin webmaster@certbot.demo + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf new file mode 100644 index 000000000..e3ac21fac --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf @@ -0,0 +1,14 @@ + + + ServerName duplicate.example.com + + ServerAdmin webmaster@certbot.demo + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + +SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem +SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem + + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf new file mode 100644 index 000000000..862040fc1 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf @@ -0,0 +1,42 @@ + + ServerName encryption-example.demo + ServerAdmin webmaster@localhost + + DocumentRoot /var/www-encryption-example/static/ + + Options FollowSymLinks + AllowOverride None + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + ErrorLog ${APACHE_LOG_DIR}/error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/access.log combined + + Alias /doc/ "/usr/share/doc/" + + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order deny,allow + Deny from all + Allow from 127.0.0.0/255.0.0.0 ::1/128 + + + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf new file mode 100644 index 000000000..6a6579007 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf @@ -0,0 +1,15 @@ + + + ServerName $domain + ServerAlias www.$domain + DocumentRoot /var/www/html + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + +Use VHost macro1 test.com +Use VHost macro2 hostname.org +Use VHost macro3 apache.org + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf new file mode 100644 index 000000000..631cf16c8 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf @@ -0,0 +1,36 @@ + +SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000) + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName ocspvhost.com + + ServerAdmin webmaster@dumpbits.com + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf +SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem +SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem +SSLUseStapling on + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf new file mode 100644 index 000000000..33e30a63b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf @@ -0,0 +1,13 @@ + + + ServerName ip-172-30-0-17 + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ServerAlias *.blue.purple.com + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf new file mode 120000 index 000000000..3c4632b73 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf @@ -0,0 +1 @@ +../sites-available/000-default.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf new file mode 120000 index 000000000..4d08c763f --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf @@ -0,0 +1 @@ +../sites-available/certbot.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf new file mode 120000 index 000000000..103c1b68d --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf @@ -0,0 +1 @@ +../sites-available/default-ssl-port-only.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf new file mode 120000 index 000000000..d02890bbd --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf @@ -0,0 +1 @@ +../sites-available/default-ssl.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf new file mode 120000 index 000000000..a69ee3c1d --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf @@ -0,0 +1 @@ +../sites-available/duplicatehttp.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf new file mode 120000 index 000000000..a52ee1ccb --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf @@ -0,0 +1 @@ +../sites-available/duplicatehttps.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf new file mode 120000 index 000000000..417818069 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf @@ -0,0 +1 @@ +../sites-available/encryption-example.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf new file mode 120000 index 000000000..44f254304 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf @@ -0,0 +1 @@ +../sites-available/mod_macro-example.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf new file mode 100644 index 000000000..31cb6093c --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf @@ -0,0 +1,9 @@ + +ServerName nonsym.link +ServerAdmin webmaster@localhost + +DocumentRoot /var/www-certbot-reworld/static/ + +ErrorLog ${APACHE_LOG_DIR}/error.log +CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf new file mode 120000 index 000000000..b25ee0482 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf @@ -0,0 +1 @@ +../sites-available/ocsp-ssl.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf new file mode 120000 index 000000000..a87af2c93 --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf @@ -0,0 +1 @@ +../sites-available/wildcard.conf \ No newline at end of file diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites new file mode 100644 index 000000000..ab518ee5b --- /dev/null +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites @@ -0,0 +1,3 @@ +sites-available/certbot.conf, certbot.demo +sites-available/encryption-example.conf, encryption-example.demo +sites-available/ocsp-ssl.conf, ocspvhost.com diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf new file mode 100644 index 000000000..e5693ffff --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf @@ -0,0 +1,157 @@ +# This is a modification of the default Apache 2.4 configuration file +# for Gentoo Linux. +# +# Support: +# http://www.gentoo.org/main/en/lists.xml [mailing lists] +# http://forums.gentoo.org/ [web forums] +# irc://irc.freenode.net#gentoo-apache [irc chat] +# +# Bug Reports: +# http://bugs.gentoo.org [gentoo related bugs] +# http://httpd.apache.org/bug_report.html [apache httpd related bugs] +# +# +# This is the main Apache HTTP server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "var/log/apache2/foo_log" +# with ServerRoot set to "/usr" will be interpreted by the +# server as "/usr/var/log/apache2/foo.log". + +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# Do not add a slash at the end of the directory path. If you point +# ServerRoot at a non-local disk, be sure to point the LockFile directive +# at a local disk. If you wish to share the same ServerRoot for multiple +# httpd daemons, you will need to change at least LockFile and PidFile. +# Comment: The LockFile directive has been replaced by the Mutex directive +ServerRoot "/usr/lib64/apache2" + +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# +# GENTOO: Automatically defined based on APACHE2_MODULES USE_EXPAND variable. +# Do not change manually, it will be overwritten on upgrade. +# +# The following modules are considered as the default configuration. +# If you wish to disable one of them, you may have to alter other +# configuration directives. +# +# Change these at your own risk! + +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so + +LoadModule cache_module modules/mod_cache.so + +LoadModule cgi_module modules/mod_cgi.so +LoadModule cgid_module modules/mod_cgid.so + +LoadModule dav_module modules/mod_dav.so + + +LoadModule dav_fs_module modules/mod_dav_fs.so + + +LoadModule dav_lock_module modules/mod_dav_lock.so + +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +LoadModule env_module modules/mod_env.so +LoadModule expires_module modules/mod_expires.so +LoadModule ext_filter_module modules/mod_ext_filter.so + +LoadModule file_cache_module modules/mod_file_cache.so + +LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +LoadModule include_module modules/mod_include.so + +LoadModule info_module modules/mod_info.so + +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule mime_module modules/mod_mime.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule setenvif_module modules/mod_setenvif.so + +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so + +LoadModule speling_module modules/mod_speling.so + +LoadModule ssl_module modules/mod_ssl.so + + +LoadModule status_module modules/mod_status.so + +LoadModule unique_id_module modules/mod_unique_id.so +LoadModule unixd_module modules/mod_unixd.so + +LoadModule userdir_module modules/mod_userdir.so + +LoadModule usertrack_module modules/mod_usertrack.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + +# If you wish httpd to run as a different user or group, you must run +# httpd as root initially and it will switch. +# +# User/Group: The name (or #number) of the user/group to run httpd as. +# It is usually good practice to create a dedicated user and group for +# running httpd, as with most system services. +User apache +Group apache + +# Supplemental configuration +# +# Most of the configuration files in the /etc/apache2/modules.d/ directory can +# be turned on using APACHE2_OPTS in /etc/conf.d/apache2 to add extra features +# or to modify the default configuration of the server. +# +# To know which flag to add to APACHE2_OPTS, look at the first line of the +# the file, which will usually be an where OPTION is the +# flag to use. + +Include modules.d/*.conf + +# Virtual-host support +# +# Gentoo has made using virtual-hosts easy. In /etc/apache2/vhosts.d/ we +# include a default vhost (enabled by adding -D DEFAULT_VHOST to +# APACHE2_OPTS in /etc/conf.d/apache2). +Include vhosts.d/*.conf + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic new file mode 100644 index 000000000..7c56119e9 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic @@ -0,0 +1,385 @@ +# Magic data for mod_mime_magic Apache module (originally for file(1) command) +# The module is described in /manual/mod/mod_mime_magic.html +# +# The format is 4-5 columns: +# Column #1: byte number to begin checking from, ">" indicates continuation +# Column #2: type of data to match +# Column #3: contents of data to match +# Column #4: MIME type of result +# Column #5: MIME encoding of result (optional) + +#------------------------------------------------------------------------------ +# Localstuff: file(1) magic for locally observed files +# Add any locally observed files here. + +#------------------------------------------------------------------------------ +# end local stuff +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Java + +0 short 0xcafe +>2 short 0xbabe application/java + +#------------------------------------------------------------------------------ +# audio: file(1) magic for sound formats +# +# from Jan Nicolai Langfeldt , +# + +# Sun/NeXT audio data +0 string .snd +>12 belong 1 audio/basic +>12 belong 2 audio/basic +>12 belong 3 audio/basic +>12 belong 4 audio/basic +>12 belong 5 audio/basic +>12 belong 6 audio/basic +>12 belong 7 audio/basic + +>12 belong 23 audio/x-adpcm + +# DEC systems (e.g. DECstation 5000) use a variant of the Sun/NeXT format +# that uses little-endian encoding and has a different magic number +# (0x0064732E in little-endian encoding). +0 lelong 0x0064732E +>12 lelong 1 audio/x-dec-basic +>12 lelong 2 audio/x-dec-basic +>12 lelong 3 audio/x-dec-basic +>12 lelong 4 audio/x-dec-basic +>12 lelong 5 audio/x-dec-basic +>12 lelong 6 audio/x-dec-basic +>12 lelong 7 audio/x-dec-basic +# compressed (G.721 ADPCM) +>12 lelong 23 audio/x-dec-adpcm + +# Bytes 0-3 of AIFF, AIFF-C, & 8SVX audio files are "FORM" +# AIFF audio data +8 string AIFF audio/x-aiff +# AIFF-C audio data +8 string AIFC audio/x-aiff +# IFF/8SVX audio data +8 string 8SVX audio/x-aiff + +# Creative Labs AUDIO stuff +# Standard MIDI data +0 string MThd audio/unknown +#>9 byte >0 (format %d) +#>11 byte >1 using %d channels +# Creative Music (CMF) data +0 string CTMF audio/unknown +# SoundBlaster instrument data +0 string SBI audio/unknown +# Creative Labs voice data +0 string Creative\ Voice\ File audio/unknown +## is this next line right? it came this way... +#>19 byte 0x1A +#>23 byte >0 - version %d +#>22 byte >0 \b.%d + +# [GRR 950115: is this also Creative Labs? Guessing that first line +# should be string instead of unknown-endian long...] +#0 long 0x4e54524b MultiTrack sound data +#0 string NTRK MultiTrack sound data +#>4 long x - version %ld + +# Microsoft WAVE format (*.wav) +# [GRR 950115: probably all of the shorts and longs should be leshort/lelong] +# Microsoft RIFF +0 string RIFF audio/unknown +# - WAVE format +>8 string WAVE audio/x-wav +# MPEG audio. +0 beshort&0xfff0 0xfff0 audio/mpeg +# C64 SID Music files, from Linus Walleij +0 string PSID audio/prs.sid + +#------------------------------------------------------------------------------ +# c-lang: file(1) magic for C programs or various scripts +# + +# XPM icons (Greg Roelofs, newt@uchicago.edu) +# ideally should go into "images", but entries below would tag XPM as C source +0 string /*\ XPM image/x-xbm 7bit + +# this first will upset you if you're a PL/1 shop... (are there any left?) +# in which case rm it; ascmagic will catch real C programs +# C or REXX program text +0 string /* text/plain +# C++ program text +0 string // text/plain + +#------------------------------------------------------------------------------ +# compress: file(1) magic for pure-compression formats (no archives) +# +# compress, gzip, pack, compact, huf, squeeze, crunch, freeze, yabba, whap, etc. +# +# Formats for various forms of compressed data +# Formats for "compress" proper have been moved into "compress.c", +# because it tries to uncompress it to figure out what's inside. + +# standard unix compress +0 string \037\235 application/octet-stream x-compress + +# gzip (GNU zip, not to be confused with [Info-ZIP/PKWARE] zip archiver) +0 string \037\213 application/octet-stream x-gzip + +# According to gzip.h, this is the correct byte order for packed data. +0 string \037\036 application/octet-stream +# +# This magic number is byte-order-independent. +# +0 short 017437 application/octet-stream + +# XXX - why *two* entries for "compacted data", one of which is +# byte-order independent, and one of which is byte-order dependent? +# +# compacted data +0 short 0x1fff application/octet-stream +0 string \377\037 application/octet-stream +# huf output +0 short 0145405 application/octet-stream + +# Squeeze and Crunch... +# These numbers were gleaned from the Unix versions of the programs to +# handle these formats. Note that I can only uncrunch, not crunch, and +# I didn't have a crunched file handy, so the crunch number is untested. +# Keith Waclena +#0 leshort 0x76FF squeezed data (CP/M, DOS) +#0 leshort 0x76FE crunched data (CP/M, DOS) + +# Freeze +#0 string \037\237 Frozen file 2.1 +#0 string \037\236 Frozen file 1.0 (or gzip 0.5) + +# lzh? +#0 string \037\240 LZH compressed data + +#------------------------------------------------------------------------------ +# frame: file(1) magic for FrameMaker files +# +# This stuff came on a FrameMaker demo tape, most of which is +# copyright, but this file is "published" as witness the following: +# +0 string \ +# and Anna Shergold +# +0 string \ +0 string \14 byte 12 (OS/2 1.x format) +#>14 byte 64 (OS/2 2.x format) +#>14 byte 40 (Windows 3.x format) +#0 string IC icon +#0 string PI pointer +#0 string CI color icon +#0 string CP color pointer +#0 string BA bitmap array + +0 string \x89PNG image/png +0 string FWS application/x-shockwave-flash +0 string CWS application/x-shockwave-flash + +#------------------------------------------------------------------------------ +# lisp: file(1) magic for lisp programs +# +# various lisp types, from Daniel Quinlan (quinlan@yggdrasil.com) +0 string ;; text/plain 8bit +# Emacs 18 - this is always correct, but not very magical. +0 string \012( application/x-elc +# Emacs 19 +0 string ;ELC\023\000\000\000 application/x-elc + +#------------------------------------------------------------------------------ +# mail.news: file(1) magic for mail and news +# +# There are tests to ascmagic.c to cope with mail and news. +0 string Relay-Version: message/rfc822 7bit +0 string #!\ rnews message/rfc822 7bit +0 string N#!\ rnews message/rfc822 7bit +0 string Forward\ to message/rfc822 7bit +0 string Pipe\ to message/rfc822 7bit +0 string Return-Path: message/rfc822 7bit +0 string Path: message/news 8bit +0 string Xref: message/news 8bit +0 string From: message/rfc822 7bit +0 string Article message/news 8bit +#------------------------------------------------------------------------------ +# msword: file(1) magic for MS Word files +# +# Contributor claims: +# Reversed-engineered MS Word magic numbers +# + +0 string \376\067\0\043 application/msword +0 string \333\245-\0\0\0 application/msword + +# disable this one because it applies also to other +# Office/OLE documents for which msword is not correct. See PR#2608. +#0 string \320\317\021\340\241\261 application/msword + + + +#------------------------------------------------------------------------------ +# printer: file(1) magic for printer-formatted files +# + +# PostScript +0 string %! application/postscript +0 string \004%! application/postscript + +# Acrobat +# (due to clamen@cs.cmu.edu) +0 string %PDF- application/pdf + +#------------------------------------------------------------------------------ +# sc: file(1) magic for "sc" spreadsheet +# +38 string Spreadsheet application/x-sc + +#------------------------------------------------------------------------------ +# tex: file(1) magic for TeX files +# +# XXX - needs byte-endian stuff (big-endian and little-endian DVI?) +# +# From + +# Although we may know the offset of certain text fields in TeX DVI +# and font files, we can't use them reliably because they are not +# zero terminated. [but we do anyway, christos] +0 string \367\002 application/x-dvi +#0 string \367\203 TeX generic font data +#0 string \367\131 TeX packed font data +#0 string \367\312 TeX virtual font data +#0 string This\ is\ TeX, TeX transcript text +#0 string This\ is\ METAFONT, METAFONT transcript text + +# There is no way to detect TeX Font Metric (*.tfm) files without +# breaking them apart and reading the data. The following patterns +# match most *.tfm files generated by METAFONT or afm2tfm. +#2 string \000\021 TeX font metric data +#2 string \000\022 TeX font metric data +#>34 string >\0 (%s) + +# Texinfo and GNU Info, from Daniel Quinlan (quinlan@yggdrasil.com) +#0 string \\input\ texinfo Texinfo source text +#0 string This\ is\ Info\ file GNU Info text + +# correct TeX magic for Linux (and maybe more) +# from Peter Tobias (tobias@server.et-inf.fho-emden.de) +# +0 leshort 0x02f7 application/x-dvi + +# RTF - Rich Text Format +0 string {\\rtf application/rtf + +#------------------------------------------------------------------------------ +# animation: file(1) magic for animation/movie formats +# +# animation formats, originally from vax@ccwf.cc.utexas.edu (VaX#n8) +# MPEG file +0 string \000\000\001\263 video/mpeg +# +# The contributor claims: +# I couldn't find a real magic number for these, however, this +# -appears- to work. Note that it might catch other files, too, +# so BE CAREFUL! +# +# Note that title and author appear in the two 20-byte chunks +# at decimal offsets 2 and 22, respectively, but they are XOR'ed with +# 255 (hex FF)! DL format SUCKS BIG ROCKS. +# +# DL file version 1 , medium format (160x100, 4 images/screen) +0 byte 1 video/unknown +0 byte 2 video/unknown +# Quicktime video, from Linus Walleij +# from Apple quicktime file format documentation. +4 string moov video/quicktime +4 string mdat video/quicktime + diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf new file mode 100644 index 000000000..38635aa9d --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf @@ -0,0 +1,131 @@ +# This configuration file reflects default settings for Apache HTTP Server. +# You may change these, but chances are that you may not need to. + +# Timeout: The number of seconds before receives and sends time out. +Timeout 300 + +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +KeepAlive On + +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +MaxKeepAliveRequests 100 + +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +KeepAliveTimeout 15 + +# UseCanonicalName: Determines how Apache constructs self-referencing +# URLs and the SERVER_NAME and SERVER_PORT variables. +# When set "Off", Apache will use the Hostname and Port supplied +# by the client. When set "On", Apache will use the value of the +# ServerName directive. +UseCanonicalName Off + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +AccessFileName .htaccess + +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minor | Minimal | Major | Prod +# where Full conveys the most information, and Prod the least. +ServerTokens Prod + +# TraceEnable +# This directive overrides the behavior of TRACE for both the core server and +# mod_proxy. The default TraceEnable on permits TRACE requests per RFC 2616, +# which disallows any request body to accompany the request. TraceEnable off +# causes the core server and mod_proxy to return a 405 (Method not allowed) +# error to the client. +# For security reasons this is turned off by default. (bug #240680) +TraceEnable off + +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +ServerSignature On + +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +HostnameLookups Off + +# EnableMMAP and EnableSendfile: On systems that support it, +# memory-mapping or the sendfile syscall is used to deliver +# files. This usually improves server performance, but must +# be turned off when serving from networked-mounted +# filesystems or if support for these functions is otherwise +# broken on your system. +EnableMMAP On +EnableSendfile Off + +# FileETag: Configures the file attributes that are used to create +# the ETag (entity tag) response header field when the document is +# based on a static file. (The ETag value is used in cache management +# to save network bandwidth.) +FileETag MTime Size + +# ContentDigest: This directive enables the generation of Content-MD5 +# headers as defined in RFC1864 respectively RFC2616. +# The Content-MD5 header provides an end-to-end message integrity +# check (MIC) of the entity-body. A proxy or client may check this +# header for detecting accidental modification of the entity-body +# in transit. +# Note that this can cause performance problems on your server since +# the message digest is computed on every request (the values are +# not cached). +# Content-MD5 is only sent for documents served by the core, and not +# by any module. For example, SSI documents, output from CGI scripts, +# and byte range responses do not have this header. +ContentDigest Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +ErrorLog /var/log/apache2/error_log + +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +LogLevel warn + +# We configure the "default" to be a very restrictive set of features. + + Options FollowSymLinks + AllowOverride None + Require all denied + + +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# +# The index.html.var file (a type-map) is used to deliver content- +# negotiated documents. The MultiViews Options can be used for the +# same purpose, but it is much slower. +# +# Do not change this entry unless you know what you are doing. + + DirectoryIndex index.html index.html.var + + +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. + + Require all denied + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf new file mode 100644 index 000000000..61479fa53 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf @@ -0,0 +1,57 @@ +# The configuration below implements multi-language error documents through +# content-negotiation. + +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http://www.example.com/subscription_info.html + +# Required modules: mod_alias, mod_include, mod_negotiation +# We use Alias to redirect any /error/HTTP_.html.var response to +# our collection of by-error message multi-language collections. We use +# includes to substitute the appropriate text. +# You can modify the messages' appearance without changing any of the +# default HTTP_.html.var files by adding the line: +# Alias /error/include/ "/your/include/path/" +# which allows you to create your own set of files by starting with the +# /var/www/localhost/error/include/ files and copying them to /your/include/path/, +# even on a per-VirtualHost basis. The default include files will display +# your Apache version number and your ServerAdmin email address regardless +# of the setting of ServerSignature. + + +Alias /error/ "/usr/share/apache2/error/" + + + AllowOverride None + Options IncludesNoExec + AddOutputFilter Includes html + AddHandler type-map var + Require all granted + LanguagePriority en cs de es fr it ja ko nl pl pt-br ro sv tr + ForceLanguagePriority Prefer Fallback + + +ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var +ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var +ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var +ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var +ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var +ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var +ErrorDocument 410 /error/HTTP_GONE.html.var +ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var +ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var +ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var +ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var +ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var +ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var +ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var +ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var +ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var +ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf new file mode 100644 index 000000000..c429bf94c --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf @@ -0,0 +1,133 @@ +# Settings for hosting different languages. + +# DefaultLanguage and AddLanguage allows you to specify the language of +# a document. You can then use content negotiation to give a browser a +# file in a language the user can understand. +# +# Specify a default language. This means that all data +# going out without a specific language tag (see below) will +# be marked with this one. You probably do NOT want to set +# this unless you are sure it is correct for all cases. +# +# It is generally better to not mark a page as +# being a certain language than marking it with the wrong +# language! +# +# DefaultLanguage nl +# +# Note 1: The suffix does not have to be the same as the language +# keyword --- those with documents in Polish (whose net-standard +# language code is pl) may wish to use "AddLanguage pl .po" to +# avoid the ambiguity with the common suffix for perl scripts. +# +# Note 2: The example entries below illustrate that in some cases +# the two character 'Language' abbreviation is not identical to +# the two character 'Country' code for its country, +# E.g. 'Danmark/dk' versus 'Danish/da'. +# +# Note 3: In the case of 'ltz' we violate the RFC by using a three char +# specifier. There is 'work in progress' to fix this and get +# the reference data for rfc1766 cleaned up. +# +# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl) +# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) +# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) +# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) +# Norwegian (no) - Polish (pl) - Portugese (pt) +# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) +# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) +AddLanguage ca .ca +AddLanguage cs .cz .cs +AddLanguage da .dk +AddLanguage de .de +AddLanguage el .el +AddLanguage en .en +AddLanguage eo .eo +AddLanguage es .es +AddLanguage et .et +AddLanguage fr .fr +AddLanguage he .he +AddLanguage hr .hr +AddLanguage it .it +AddLanguage ja .ja +AddLanguage ko .ko +AddLanguage ltz .ltz +AddLanguage nl .nl +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pl .po +AddLanguage pt .pt +AddLanguage pt-BR .pt-br +AddLanguage ru .ru +AddLanguage sv .sv +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw + +# LanguagePriority allows you to give precedence to some languages +# in case of a tie during content negotiation. +# +# Just list the languages in decreasing order of preference. We have +# more or less alphabetized them here. You probably want to change this. +LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW + +# ForceLanguagePriority allows you to serve a result page rather than +# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) +# [in case no accepted languages matched the available variants] +ForceLanguagePriority Prefer Fallback + +# Commonly used filename extensions to character sets. You probably +# want to avoid clashes with the language extensions, unless you +# are good at carefully testing your setup after each change. +# See http://www.iana.org/assignments/character-sets for the +# official list of charset names and their respective RFCs. +AddCharset us-ascii.ascii .us-ascii +AddCharset ISO-8859-1 .iso8859-1 .latin1 +AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen +AddCharset ISO-8859-3 .iso8859-3 .latin3 +AddCharset ISO-8859-4 .iso8859-4 .latin4 +AddCharset ISO-8859-5 .iso8859-5 .cyr .iso-ru +AddCharset ISO-8859-6 .iso8859-6 .arb .arabic +AddCharset ISO-8859-7 .iso8859-7 .grk .greek +AddCharset ISO-8859-8 .iso8859-8 .heb .hebrew +AddCharset ISO-8859-9 .iso8859-9 .latin5 .trk +AddCharset ISO-8859-10 .iso8859-10 .latin6 +AddCharset ISO-8859-13 .iso8859-13 +AddCharset ISO-8859-14 .iso8859-14 .latin8 +AddCharset ISO-8859-15 .iso8859-15 .latin9 +AddCharset ISO-8859-16 .iso8859-16 .latin10 +AddCharset ISO-2022-JP .iso2022-jp .jis +AddCharset ISO-2022-KR .iso2022-kr .kis +AddCharset ISO-2022-CN .iso2022-cn .cis +AddCharset Big5.Big5 .big5 .b5 +AddCharset cn-Big5 .cn-big5 +# For russian, more than one charset is used (depends on client, mostly): +AddCharset WINDOWS-1251 .cp-1251 .win-1251 +AddCharset CP866 .cp866 +AddCharset KOI8 .koi8 +AddCharset KOI8-E .koi8-e +AddCharset KOI8-r .koi8-r .koi8-ru +AddCharset KOI8-U .koi8-u +AddCharset KOI8-ru .koi8-uk .ua +AddCharset ISO-10646-UCS-2 .ucs2 +AddCharset ISO-10646-UCS-4 .ucs4 +AddCharset UTF-7 .utf7 +AddCharset UTF-8 .utf8 +AddCharset UTF-16 .utf16 +AddCharset UTF-16BE .utf16be +AddCharset UTF-16LE .utf16le +AddCharset UTF-32 .utf32 +AddCharset UTF-32BE .utf32be +AddCharset UTF-32LE .utf32le +AddCharset euc-cn .euc-cn +AddCharset euc-gb .euc-gb +AddCharset euc-jp .euc-jp +AddCharset euc-kr .euc-kr +# Not sure how euc-tw got in - IANA doesn't list it??? +AddCharset EUC-TW .euc-tw +AddCharset gb2312 .gb2312 .gb +AddCharset iso-10646-ucs-2 .ucs-2 .iso-10646-ucs-2 +AddCharset iso-10646-ucs-4 .ucs-4 .iso-10646-ucs-4 +AddCharset shift_jis .shift_jis .sjis + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf new file mode 100644 index 000000000..10bf48317 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf @@ -0,0 +1,85 @@ + + + + +# We include the /icons/ alias for FancyIndexed directory listings. If +# you do not use FancyIndexing, you may comment this out. +Alias /icons/ "/usr/share/apache2/icons/" + + + Options Indexes MultiViews + AllowOverride None + Require all granted + + + +# Directives controlling the display of server-generated directory listings. +# +# To see the listing of a directory, the Options directive for the +# directory must include "Indexes", and the directory must not contain +# a file matching those listed in the DirectoryIndex directive. + +# IndexOptions: Controls the appearance of server-generated directory +# listings. +IndexOptions FancyIndexing VersionSort + +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif core + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. +DefaultIcon /icons/unknown.gif + +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename + +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. + +# HeaderName is the name of a file which should be prepended to +# directory indexes. +ReadmeName README.html +HeaderName HEADER.html + +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf new file mode 100644 index 000000000..2cd32c477 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf @@ -0,0 +1,10 @@ + +# Allow remote server configuration reports, with the URL of +# http://servername/server-info + + SetHandler server-info + Require local + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf new file mode 100644 index 000000000..ce0238eee --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf @@ -0,0 +1,35 @@ + +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common + +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-Agent}i" agent +LogFormat "%v %h %l %u %t \"%r\" %>s %b %T" script +LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" VLOG=%{VLOG}e" vhost + + +# You need to enable mod_logio.c to use %I and %O +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio +LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" vhostio + + +# The location and format of the access logfile (Common Logfile Format). +# If you do not define any access logfiles within a +# container, they will be logged here. Contrariwise, if you *do* +# define per- access logfiles, transactions will be +# logged therein and *not* in this file. +CustomLog /var/log/apache2/access_log common + +# If you would like to have agent and referer logfiles, +# uncomment the following directives. +#CustomLog /var/log/apache2/referer_log referer +#CustomLog /var/log/apache2/agent_logs agent + +# If you prefer a logfile with access, agent, and referer information +# (Combined Logfile Format) you can use the following directive. +#CustomLog /var/log/apache2/access_log combined + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf new file mode 100644 index 000000000..fb8a9a5d5 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf @@ -0,0 +1,46 @@ + +# TypesConfig points to the file containing the list of mappings from +# filename extension to MIME-type. +TypesConfig /etc/mime.types + +# AddType allows you to add to or override the MIME configuration +# file specified in TypesConfig for specific file types. +#AddType application/x-gzip .tgz + +# AddEncoding allows you to have certain browsers uncompress +# information on the fly. Note: Not all browsers support this. +#AddEncoding x-compress .Z +#AddEncoding x-gzip .gz .tgz + +# If the AddEncoding directives above are commented-out, then you +# probably should define those extensions to indicate media types: +AddType application/x-compress .Z +AddType application/x-gzip .gz .tgz + +# AddHandler allows you to map certain file extensions to "handlers": +# actions unrelated to filetype. These can be either built into the server +# or added with the Action directive (see below) + +# To use CGI scripts outside of ScriptAliased directories: +# (You will also need to add "ExecCGI" to the "Options" directive.) +#AddHandler cgi-script .cgi + +# For type maps (negotiated resources): +#AddHandler type-map var + +# Filters allow you to process content before it is sent to the client. +# +# To parse .shtml files for server-side includes (SSI): +# (You will also need to add "Includes" to the "Options" directive.) +#AddType text/html .shtml +#AddOutputFilter INCLUDES .shtml + + + +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. +MIMEMagicFile /etc/apache2/magic + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf new file mode 100644 index 000000000..ed8b3c7cb --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf @@ -0,0 +1,15 @@ + +# Allow server status reports generated by mod_status, +# with the URL of http://servername/server-status + + SetHandler server-status + Require local + + +# ExtendedStatus controls whether Apache will generate "full" status +# information (ExtendedStatus On) or just basic information (ExtendedStatus +# Off) when the "server-status" handler is called. +ExtendedStatus On + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf new file mode 100644 index 000000000..0087126c4 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf @@ -0,0 +1,32 @@ +# Settings for user home directories + +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. Note that you must also set +# the default access control for these directories, as in the example below. +UserDir public_html + +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. + + AllowOverride FileInfo AuthConfig Limit Indexes + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + + Require all granted + + + Require all denied + + + +# Suexec isn't really required to run cgi-scripts, but it's a really good +# idea if you have multiple users serving websites... + + + Options ExecCGI + SetHandler cgi-script + + + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf new file mode 100644 index 000000000..bcb9b6b47 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf @@ -0,0 +1,99 @@ +# Server-Pool Management (MPM specific) + +# PidFile: The file in which the server should record its process +# identification number when it starts. +# +# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING +PidFile /run/apache2.pid + +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# Mutex file:/run/apache_mpm_mutex + +# Only one of the below sections will be relevant on your +# installed httpd. Use "/usr/sbin/apache2 -l" to find out the +# active mpm. + +# common MPM configuration +# These configuration directives apply to all MPMs +# +# StartServers: Number of child server processes created at startup +# MaxRequestWorkers: Maximum number of child processes to serve requests +# MaxConnectionsPerChild: Limit on the number of connections that an individual +# child server will handle during its life + + +# prefork MPM +# This is the default MPM if USE=-threads +# +# MinSpareServers: Minimum number of idle child server processes +# MaxSpareServers: Maximum number of idle child server processes + + StartServers 5 + MinSpareServers 5 + MaxSpareServers 10 + MaxRequestWorkers 150 + MaxConnectionsPerChild 10000 + + +# worker MPM +# This is the default MPM if USE=threads +# +# MinSpareThreads: Minimum number of idle threads available to handle request spikes +# MaxSpareThreads: Maximum number of idle threads +# ThreadsPerChild: Number of threads created by each child process + + StartServers 2 + MinSpareThreads 25 + MaxSpareThreads 75 + ThreadsPerChild 25 + MaxRequestWorkers 150 + MaxConnectionsPerChild 10000 + + +# event MPM +# +# MinSpareThreads: Minimum number of idle threads available to handle request spikes +# MaxSpareThreads: Maximum number of idle threads +# ThreadsPerChild: Number of threads created by each child process + + StartServers 2 + MinSpareThreads 25 + MaxSpareThreads 75 + ThreadsPerChild 25 + MaxRequestWorkers 150 + MaxConnectionsPerChild 10000 + + +# peruser MPM +# +# MinSpareProcessors: Minimum number of idle child server processes +# MinProcessors: Minimum number of processors per virtual host +# MaxProcessors: Maximum number of processors per virtual host +# ExpireTimeout: Maximum idle time before a child is killed, 0 to disable +# Multiplexer: Specify a Multiplexer child configuration. +# Processor: Specify a user and group for a specific child process + + MinSpareProcessors 2 + MinProcessors 2 + MaxProcessors 10 + MaxRequestWorkers 150 + MaxConnectionsPerChild 1000 + ExpireTimeout 1800 + + Multiplexer nobody nobody + Processor apache apache + + +# itk MPM +# +# MinSpareServers: Minimum number of idle child server processes +# MaxSpareServers: Maximum number of idle child server processes + + StartServers 5 + MinSpareServers 5 + MaxSpareServers 10 + MaxRequestWorkers 150 + MaxConnectionsPerChild 10000 + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf new file mode 100644 index 000000000..520d9fd82 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf @@ -0,0 +1,10 @@ + +# 128MB cache for objects < 2MB +CacheEnable mem / +MCacheSize 131072 +MCacheMaxObjectCount 1000 +MCacheMinObjectSize 1 +MCacheMaxObjectSize 2097152 + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf new file mode 100644 index 000000000..f51de4641 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf @@ -0,0 +1,67 @@ +# Note: The following must must be present to support +# starting without SSL on platforms with no /dev/random equivalent +# but a statically compiled-in mod_ssl. + +SSLRandomSeed startup builtin +SSLRandomSeed connect builtin + + + +# This is the Apache server configuration file providing SSL support. +# It contains the configuration directives to instruct the server how to +# serve pages over an https connection. For detailing information about these +# directives see + +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. + +## Pseudo Random Number Generator (PRNG): +# Configure one or more sources to seed the PRNG of the SSL library. +# The seed data should be of good random quality. +# WARNING! On some platforms /dev/random blocks if not enough entropy +# is available. This means you then cannot use the /dev/random device +# because it would lead to very long connection times (as long as +# it requires to make more entropy available). But usually those +# platforms additionally provide a /dev/urandom device which doesn't +# block. So, if available, use this one instead. Read the mod_ssl User +# Manual for more details. +#SSLRandomSeed startup file:/dev/random 512 +#SSLRandomSeed startup file:/dev/urandom 512 +#SSLRandomSeed connect file:/dev/random 512 +#SSLRandomSeed connect file:/dev/urandom 512 + +## SSL Global Context: +# All SSL configuration in this context applies both to the main server and +# all SSL-enabled virtual hosts. + +# Some MIME-types for downloading Certificates and CRLs + + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + +## Pass Phrase Dialog: +# Configure the pass phrase gathering process. The filtering dialog program +# (`builtin' is a internal terminal dialog) has to provide the pass phrase on +# stdout. +SSLPassPhraseDialog builtin + +## Inter-Process Session Cache: +# Configure the SSL Session Cache: First the mechanism to use and second the +# expiring timeout (in seconds). +#SSLSessionCache dbm:/run/ssl_scache +SSLSessionCache shmcb:/run/ssl_scache(512000) +SSLSessionCacheTimeout 300 + +## Semaphore: +# Configure the path to the mutual exclusion semaphore the SSL engine uses +# internally for inter-process synchronization. +Mutex file:/run/apache_ssl_mutex ssl-cache + +## SSL Compression: +# Known to be vulnerable thus disabled by default (bug #507324). +SSLCompression off + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf new file mode 100644 index 000000000..e4c9454e0 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf @@ -0,0 +1,9 @@ + + + # enable debugging for this module + #LogLevel http2:info + + #Enable HTTP/2 support + Protocols h2 h2c http/1.1 + + diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf new file mode 100644 index 000000000..36f6b9cca --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf @@ -0,0 +1,19 @@ + +DavLockDB "/var/lib/dav/lockdb" + +# The following directives disable redirects on non-GET requests for +# a directory that does not include the trailing slash. This fixes a +# problem with several clients that do not appropriately handle +# redirects for folders with DAV methods. + +BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully +BrowserMatch "MS FrontPage" redirect-carefully +BrowserMatch "^WebDrive" redirect-carefully +BrowserMatch "^WebDAVFS/1.[012345678]" redirect-carefully +BrowserMatch "^gnome-vfs/1.0" redirect-carefully +BrowserMatch "^XML Spy" redirect-carefully +BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf new file mode 100644 index 000000000..883061fee --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf @@ -0,0 +1,18 @@ +# Examples below are taken from the online documentation +# Refer to: +# http://localhost/manual/mod/mod_ldap.html +# http://localhost/manual/mod/mod_auth_ldap.html + +LDAPSharedCacheSize 200000 +LDAPCacheEntries 1024 +LDAPCacheTTL 600 +LDAPOpCacheEntries 1024 +LDAPOpCacheTTL 600 + + + SetHandler ldap-status + Require local + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf new file mode 100644 index 000000000..bb395473c --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf @@ -0,0 +1,191 @@ + + + +# see bug #178966 why this is in here + +# When we also provide SSL we have to listen to the HTTPS port +# Note: Configurations that use IPv6 but not IPv4-mapped addresses need two +# Listen directives: "Listen [::]:443" and "Listen 0.0.0.0:443" +Listen 443 + + + ServerName localhost + Include /etc/apache2/vhosts.d/default_vhost.include + ErrorLog /var/log/apache2/ssl_error_log + + + TransferLog /var/log/apache2/ssl_access_log + + + ## SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + ## SSLProtocol: + # Don't use SSLv2 anymore as it's considered to be broken security-wise. + # Also disable SSLv3 as most modern browsers are capable of TLS. + SSLProtocol ALL -SSLv2 -SSLv3 + + ## SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. + # See the mod_ssl documentation for a complete list. + # This list of ciphers is recommended by mozilla and was stripped off + # its RC4 ciphers. (bug #506924) + SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:HIGH:!RC4:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK + + ## SSLHonorCipherOrder: + # Prefer the server's cipher preference order as the client may have a + # weak default order. + SSLHonorCipherOrder On + + ## Server Certificate: + # Point SSLCertificateFile at a PEM encoded certificate. If the certificate + # is encrypted, then you will be prompted for a pass phrase. Note that a + # kill -HUP will prompt again. Keep in mind that if you have both an RSA + # and a DSA certificate you can configure both in parallel (to also allow + # the use of DSA ciphers, etc.) + SSLCertificateFile /etc/ssl/apache2/server.crt + + ## Server Private Key: + # If the key is not combined with the certificate, use this directive to + # point at the key file. Keep in mind that if you've both a RSA and a DSA + # private key you can configure both in parallel (to also allow the use of + # DSA ciphers, etc.) + SSLCertificateKeyFile /etc/ssl/apache2/server.key + + ## Server Certificate Chain: + # Point SSLCertificateChainFile at a file containing the concatenation of + # PEM encoded CA certificates which form the certificate chain for the + # server certificate. Alternatively the referenced file can be the same as + # SSLCertificateFile when the CA certificates are directly appended to the + # server certificate for convinience. + #SSLCertificateChainFile /etc/ssl/apache2/ca.crt + + ## Certificate Authority (CA): + # Set the CA certificate verification path where to find CA certificates + # for client authentication or alternatively one huge file containing all + # of them (file must be PEM encoded). + # Note: Inside SSLCACertificatePath you need hash symlinks to point to the + # certificate files. Use the provided Makefile to update the hash symlinks + # after changes. + #SSLCACertificatePath /etc/ssl/apache2/ssl.crt + #SSLCACertificateFile /etc/ssl/apache2/ca-bundle.crt + + ## Certificate Revocation Lists (CRL): + # Set the CA revocation path where to find CA CRLs for client authentication + # or alternatively one huge file containing all of them (file must be PEM + # encoded). + # Note: Inside SSLCARevocationPath you need hash symlinks to point to the + # certificate files. Use the provided Makefile to update the hash symlinks + # after changes. + #SSLCARevocationPath /etc/ssl/apache2/ssl.crl + #SSLCARevocationFile /etc/ssl/apache2/ca-bundle.crl + + ## Client Authentication (Type): + # Client certificate verification type and depth. Types are none, optional, + # require and optional_no_ca. Depth is a number which specifies how deeply + # to verify the certificate issuer chain before deciding the certificate is + # not valid. + #SSLVerifyClient require + #SSLVerifyDepth 10 + + ## Access Control: + # With SSLRequire you can do per-directory access control based on arbitrary + # complex boolean expressions containing server variable checks and other + # lookup directives. The syntax is a mixture between C and Perl. See the + # mod_ssl documentation for more details. + # + # #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ + # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ + # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ + # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ + # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ + # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ + # + + ## SSL Engine Options: + # Set various options for the SSL engine. + + ## FakeBasicAuth: + # Translate the client X.509 into a Basic Authorisation. This means that the + # standard Auth/DBMAuth methods can be used for access control. The user + # name is the `one line' version of the client's X.509 certificate. + # Note that no password is obtained from the user. Every entry in the user + # file needs this password: `xxj31ZMTZzkVA'. + + ## ExportCertData: + # This exports two additional environment variables: SSL_CLIENT_CERT and + # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the server + # (always existing) and the client (only existing when client + # authentication is used). This can be used to import the certificates into + # CGI scripts. + + ## StdEnvVars: + # This exports the standard SSL/TLS related `SSL_*' environment variables. + # Per default this exportation is switched off for performance reasons, + # because the extraction step is an expensive operation and is usually + # useless for serving static content. So one usually enables the exportation + # for CGI and SSI requests only. + + ## StrictRequire: + # This denies access when "SSLRequireSSL" or "SSLRequire" applied even under + # a "Satisfy any" situation, i.e. when it applies access is denied and no + # other module can change it. + + ## OptRenegotiate: + # This enables optimized SSL connection renegotiation handling when SSL + # directives are used in per-directory context. + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + + SSLOptions +StdEnvVars + + + ## SSL Protocol Adjustments: + # The safe and default but still SSL/TLS standard compliant shutdown + # approach is that mod_ssl sends the close notify alert but doesn't wait + # for the close notify alert from client. When you need a different + # shutdown approach you can use one of the following variables: + + ## ssl-unclean-shutdown: + # This forces an unclean shutdown when the connection is closed, i.e. no + # SSL close notify alert is send or allowed to received. This violates the + # SSL/TLS standard but is needed for some brain-dead browsers. Use this when + # you receive I/O errors because of the standard approach where mod_ssl + # sends the close notify alert. + + ## ssl-accurate-shutdown: + # This forces an accurate shutdown when the connection is closed, i.e. a + # SSL close notify alert is send and mod_ssl waits for the close notify + # alert of the client. This is 100% SSL/TLS standard compliant, but in + # practice often causes hanging connections with brain-dead browsers. Use + # this only for browsers where you know that their SSL implementation works + # correctly. + # Notice: Most problems of broken clients are also related to the HTTP + # keep-alive facility, so you usually additionally want to disable + # keep-alive for those clients, too. Use variable "nokeepalive" for this. + # Similarly, one has to force some clients to use HTTP/1.0 to workaround + # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and + # "force-response-1.0" for this. + + BrowserMatch ".*MSIE.*" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + + + ## Per-Server Logging: + # The home of a custom SSL log file. Use this when you want a compact + # non-error SSL logfile on a virtual host basis. + + CustomLog /var/log/apache2/ssl_request_log \ + "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" + + + + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf new file mode 100644 index 000000000..b9766b5f1 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf @@ -0,0 +1,45 @@ +# Virtual Hosts +# +# If you want to maintain multiple domains/hostnames on your +# machine you can setup VirtualHost containers for them. Most configurations +# use only name-based virtual hosts so the server doesn't need to worry about +# IP addresses. This is indicated by the asterisks in the directives below. +# +# Please see the documentation at +# +# for further details before you try to setup virtual hosts. +# +# You may use the command line option '-S' to verify your virtual host +# configuration. + + +# see bug #178966 why this is in here + +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 +Listen 80 + +# When virtual hosts are enabled, the main host defined in the default +# httpd.conf configuration will go away. We redefine it here so that it is +# still available. +# +# If you disable this vhost by removing -D DEFAULT_VHOST from +# /etc/conf.d/apache2, the first defined virtual host elsewhere will be +# the default. + + ServerName localhost + Include /etc/apache2/vhosts.d/default_vhost.include + + + ServerEnvironment apache apache + + + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include new file mode 100644 index 000000000..af6ece85b --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include @@ -0,0 +1,71 @@ +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +ServerAdmin root@localhost + +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. +# +# If you change this to something that isn't under /var/www then suexec +# will no longer work. +DocumentRoot "/var/www/localhost/htdocs" + +# This should be changed to whatever you set DocumentRoot to. + + # Possible values for the Options directive are "None", "All", + # or any combination of: + # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews + # + # Note that "MultiViews" must be named *explicitly* --- "Options All" + # doesn't give it to you. + # + # The Options directive is both complicated and important. Please see + # http://httpd.apache.org/docs/2.4/mod/core.html#options + # for more information. + Options Indexes FollowSymLinks + + # AllowOverride controls what directives may be placed in .htaccess files. + # It can be "All", "None", or any combination of the keywords: + # Options FileInfo AuthConfig Limit + AllowOverride All + + # Controls who can get stuff from this server. + Require all granted + + + + # Redirect: Allows you to tell clients about documents that used to + # exist in your server's namespace, but do not anymore. The client + # will make a new request for the document at its new location. + # Example: + # Redirect permanent /foo http://www.example.com/bar + + # Alias: Maps web paths into filesystem paths and is used to + # access content that does not live under the DocumentRoot. + # Example: + # Alias /webpath /full/filesystem/path + # + # If you include a trailing / on /webpath then the server will + # require it to be present in the URL. You will also likely + # need to provide a section to allow access to + # the filesystem path. + + # ScriptAlias: This controls which directories contain server scripts. + # ScriptAliases are essentially the same as Aliases, except that + # documents in the target directory are treated as applications and + # run by the server when requested rather than as documents sent to the + # client. The same rules about trailing "/" apply to ScriptAlias + # directives as to Alias. + ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/" + + +# "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. + + AllowOverride None + Options None + Require all granted + + +# vim: ts=4 filetype=apache diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf new file mode 100644 index 000000000..41de4d236 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf @@ -0,0 +1,7 @@ + + ServerName gentoo.example.com + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 b/certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 new file mode 100644 index 000000000..b7ecb4f2a --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 @@ -0,0 +1,74 @@ +# /etc/conf.d/apache2: config file for /etc/init.d/apache2 + +# When you install a module it is easy to activate or deactivate the modules +# and other features of apache using the APACHE2_OPTS line. Every module should +# install a configuration in /etc/apache2/modules.d. In that file will have an +# directive where NNN is the option to enable that module. +# +# Here are the options available in the default configuration: +# +# AUTH_DIGEST Enables mod_auth_digest +# AUTHNZ_LDAP Enables authentication through mod_ldap (available if USE=ldap) +# CACHE Enables mod_cache +# DAV Enables mod_dav +# ERRORDOCS Enables default error documents for many languages. +# INFO Enables mod_info, a useful module for debugging +# LANGUAGE Enables content-negotiation based on language and charset. +# LDAP Enables mod_ldap (available if USE=ldap) +# MANUAL Enables /manual/ to be the apache manual (available if USE=docs) +# MEM_CACHE Enables default configuration mod_mem_cache +# PROXY Enables mod_proxy +# SSL Enables SSL (available if USE=ssl) +# STATUS Enabled mod_status, a useful module for statistics +# SUEXEC Enables running CGI scripts (in USERDIR) through suexec. +# USERDIR Enables /~username mapping to /home/username/public_html +# +# +# The following two options provide the default virtual host for the HTTP and +# HTTPS protocol. YOU NEED TO ENABLE AT LEAST ONE OF THEM, otherwise apache +# will not listen for incomming connections on the approriate port. +# +# DEFAULT_VHOST Enables name-based virtual hosts, with the default +# virtual host being in /var/www/localhost/htdocs +# SSL_DEFAULT_VHOST Enables default vhost for SSL (you should enable this +# when you enable SSL) +# +APACHE2_OPTS="-D DEFAULT_VHOST -D INFO -D SSL -D SSL_DEFAULT_VHOST -D LANGUAGE" + +# Extended options for advanced uses of Apache ONLY +# You don't need to edit these unless you are doing crazy Apache stuff +# As not having them set correctly, or feeding in an incorrect configuration +# via them will result in Apache failing to start +# YOU HAVE BEEN WARNED. + +# PID file +#PIDFILE=/var/run/apache2.pid + +# timeout for startup/shutdown checks +#TIMEOUT=10 + +# ServerRoot setting +#SERVERROOT=/usr/lib64/apache2 + +# Configuration file location +# - If this does NOT start with a '/', then it is treated relative to +# $SERVERROOT by Apache +#CONFIGFILE=/etc/apache2/httpd.conf + +# Location to log startup errors to +# They are normally dumped to your terminal. +#STARTUPERRORLOG="/var/log/apache2/startuperror.log" + +# A command that outputs a formatted text version of the HTML at the URL +# of the command line. Designed for lynx, however other programs may work. +#LYNX="lynx -dump" + +# The URL to your server's mod_status status page. +# Required for status and fullstatus +#STATUSURL="http://localhost/server-status" + +# Method to use when reloading the server +# Valid options are 'restart' and 'graceful' +# See http://httpd.apache.org/docs/2.2/stopping.html for information on +# what they do and how they differ. +#RELOAD_TYPE="graceful" diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/sites b/certbot-apache/tests/testdata/gentoo_apache/apache/sites new file mode 100644 index 000000000..7f0b3a8b3 --- /dev/null +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/sites @@ -0,0 +1,3 @@ +vhosts.d/gentoo.example.com.conf, gentoo.example.com +vhosts.d/00_default_vhost.conf, localhost +vhosts.d/00_default_ssl_vhost.conf, localhost diff --git a/certbot-apache/tests/util.py b/certbot-apache/tests/util.py new file mode 100644 index 000000000..f5b07ed8c --- /dev/null +++ b/certbot-apache/tests/util.py @@ -0,0 +1,239 @@ +"""Common utilities for certbot_apache.""" +import shutil +import sys +import unittest + +import augeas +import josepy as jose +import mock +import zope.component + +from certbot.compat import os +from certbot.display import util as display_util +from certbot.plugins import common +from certbot.tests import util as test_util + +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint +from certbot_apache._internal import obj + + +class ApacheTest(unittest.TestCase): + + def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", + config_root="debian_apache_2_4/multiple_vhosts/apache2", + vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): + # pylint: disable=arguments-differ + super(ApacheTest, self).setUp() + + self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( + test_dir=test_dir, + pkg=__name__) + + self.config_path = os.path.join(self.temp_dir, config_root) + self.vhost_path = os.path.join(self.temp_dir, vhost_root) + + self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( + "rsa512_key.pem")) + + self.config = get_apache_configurator(self.config_path, vhost_root, + self.config_dir, self.work_dir) + + # Make sure all vhosts in sites-enabled are symlinks (Python packaging + # does not preserve symlinks) + sites_enabled = os.path.join(self.config_path, "sites-enabled") + if not os.path.exists(sites_enabled): + return + + for vhost_basename in os.listdir(sites_enabled): + # Keep the one non-symlink test vhost in place + if vhost_basename == "non-symlink.conf": + continue + vhost = os.path.join(sites_enabled, vhost_basename) + if not os.path.islink(vhost): # pragma: no cover + os.remove(vhost) + target = os.path.join( + os.path.pardir, "sites-available", vhost_basename) + os.symlink(target, vhost) + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + +class ParserTest(ApacheTest): + + def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", + config_root="debian_apache_2_4/multiple_vhosts/apache2", + vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"): + super(ParserTest, self).setUp(test_dir, config_root, vhost_root) + + zope.component.provideUtility(display_util.FileDisplay(sys.stdout, + False)) + + from certbot_apache._internal.parser import ApacheParser + self.aug = augeas.Augeas( + flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) + with mock.patch("certbot_apache._internal.parser.ApacheParser." + "update_runtime_variables"): + self.parser = ApacheParser( + self.config_path, self.vhost_path, configurator=self.config) + + +def get_apache_configurator( + config_path, vhost_path, + config_dir, work_dir, version=(2, 4, 7), + os_info="generic", + conf_vhost_path=None): + """Create an Apache Configurator with the specified options. + + :param conf: Function that returns binary paths. self.conf in Configurator + + """ + backups = os.path.join(work_dir, "backups") + mock_le_config = mock.MagicMock( + apache_server_root=config_path, + apache_vhost_root=None, + apache_le_vhost_ext="-le-ssl.conf", + apache_challenge_location=config_path, + apache_enmod=None, + backup_dir=backups, + config_dir=config_dir, + http01_port=80, + temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), + in_progress_dir=os.path.join(backups, "IN_PROGRESS"), + work_dir=work_dir) + + with mock.patch("certbot_apache._internal.configurator.util.run_script"): + with mock.patch("certbot_apache._internal.configurator.util." + "exe_exists") as mock_exe_exists: + mock_exe_exists.return_value = True + with mock.patch("certbot_apache._internal.parser.ApacheParser." + "update_runtime_variables"): + try: + config_class = entrypoint.OVERRIDE_CLASSES[os_info] + except KeyError: + config_class = configurator.ApacheConfigurator + config = config_class(config=mock_le_config, name="apache", + version=version) + if not conf_vhost_path: + config_class.OS_DEFAULTS["vhost_root"] = vhost_path + else: + # Custom virtualhost path was requested + config.config.apache_vhost_root = conf_vhost_path + config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"] + config.prepare() + return config + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + if config_name == "debian_apache_2_4/multiple_vhosts": + prefix = os.path.join( + temp_dir, config_name, "apache2/sites-enabled") + + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "encryption-example.conf"), + os.path.join(aug_pre, "encryption-example.conf/Virtualhost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "encryption-example.demo"), + obj.VirtualHost( + os.path.join(prefix, "default-ssl.conf"), + os.path.join(aug_pre, + "default-ssl.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), True, True), + obj.VirtualHost( + os.path.join(prefix, "000-default.conf"), + os.path.join(aug_pre, "000-default.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80"), + obj.Addr.fromstring("[::]:80")]), + False, True, "ip-172-30-0-17"), + obj.VirtualHost( + os.path.join(prefix, "certbot.conf"), + os.path.join(aug_pre, "certbot.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + "certbot.demo", aliases=["www.certbot.demo"]), + obj.VirtualHost( + os.path.join(prefix, "mod_macro-example.conf"), + os.path.join(aug_pre, + "mod_macro-example.conf/Macro/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + modmacro=True), + obj.VirtualHost( + os.path.join(prefix, "default-ssl-port-only.conf"), + os.path.join(aug_pre, ("default-ssl-port-only.conf/" + "IfModule/VirtualHost")), + set([obj.Addr.fromstring("_default_:443")]), True, True), + obj.VirtualHost( + os.path.join(prefix, "wildcard.conf"), + os.path.join(aug_pre, "wildcard.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + "ip-172-30-0-17", aliases=["*.blue.purple.com"]), + obj.VirtualHost( + os.path.join(prefix, "ocsp-ssl.conf"), + os.path.join(aug_pre, "ocsp-ssl.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("10.2.3.4:443")]), True, True, + "ocspvhost.com"), + obj.VirtualHost( + os.path.join(prefix, "non-symlink.conf"), + os.path.join(aug_pre, "non-symlink.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + "nonsym.link"), + obj.VirtualHost( + os.path.join(prefix, "default-ssl-port-only.conf"), + os.path.join(aug_pre, + "default-ssl-port-only.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), True, True, ""), + obj.VirtualHost( + os.path.join(temp_dir, config_name, + "apache2/apache2.conf"), + "/files" + os.path.join(temp_dir, config_name, + "apache2/apache2.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + "vhost.in.rootconf"), + obj.VirtualHost( + os.path.join(prefix, "duplicatehttp.conf"), + os.path.join(aug_pre, "duplicatehttp.conf/VirtualHost"), + set([obj.Addr.fromstring("10.2.3.4:80")]), False, True, + "duplicate.example.com"), + obj.VirtualHost( + os.path.join(prefix, "duplicatehttps.conf"), + os.path.join(aug_pre, "duplicatehttps.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("10.2.3.4:443")]), True, True, + "duplicate.example.com")] + return vh_truth + if config_name == "debian_apache_2_4/multi_vhosts": + prefix = os.path.join( + temp_dir, config_name, "apache2/sites-available") + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "default.conf"), + os.path.join(aug_pre, "default.conf/VirtualHost[1]"), + set([obj.Addr.fromstring("*:80")]), + False, True, "ip-172-30-0-17"), + obj.VirtualHost( + os.path.join(prefix, "default.conf"), + os.path.join(aug_pre, "default.conf/VirtualHost[2]"), + set([obj.Addr.fromstring("*:80")]), + False, True, "banana.vomit.com"), + obj.VirtualHost( + os.path.join(prefix, "multi-vhost.conf"), + os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[1]"), + set([obj.Addr.fromstring("*:80")]), + False, True, "1.multi.vhost.tld"), + obj.VirtualHost( + os.path.join(prefix, "multi-vhost.conf"), + os.path.join(aug_pre, "multi-vhost.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, "2.multi.vhost.tld"), + obj.VirtualHost( + os.path.join(prefix, "multi-vhost.conf"), + os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[2]"), + set([obj.Addr.fromstring("*:80")]), + False, True, "3.multi.vhost.tld")] + return vh_truth + return None # pragma: no cover diff --git a/tox.ini b/tox.ini index f83f40891..10f8ec914 100644 --- a/tox.ini +++ b/tox.ini @@ -140,14 +140,14 @@ commands = [testenv:apacheconftest] commands = {[base]pip_install} acme certbot certbot-apache certbot-compatibility-test - {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules + {toxinidir}/certbot-apache/tests/apache-conf-files/apache-conf-test --debian-modules passenv = SERVER [testenv:apacheconftest-with-pebble] commands = {[base]pip_install} acme certbot certbot-apache certbot-ci certbot-compatibility-test - {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules + {toxinidir}/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules [testenv:nginxroundtrip] commands = -- cgit v1.2.3 From 84b770b56eb293aa1b026c45d2151e4420b393d9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 27 Nov 2019 11:32:00 -0800 Subject: Defines the RenewableCert API (#7603) This is my proposed fix for #7540. I would ideally like this to be included in our 1.0 release. I came up with this design by adding all attributes used either in our own plugins, 3rd party plugins listed at https://certbot.eff.org/docs/using.html#third-party-plugins, or our public API code. Despite me thinking that zope is unneeded nowadays, I initially tried to use it to define this interface since we have it and it gives us a way to define expected attributes, but it doesn't work because zope interface objects also have a method called `names` which conflict with the API. I talked about this with Adrien out of band and did some of my own research and there are some minor benefits with this new approach of using properties: 1. It's more conventional. 2. If you also change the implementation to inherit from the class, Python will error if all properties aren't defined. 3. The PEP 526 style type annotations with mypy seem to (currently) only be used to validate code using the class, not the class implementation itself. You can add a type annotation saying the class needs to have this attribute, never define it, and mypy won't complain. With this new approach, I had to fix `names` because pylint was complaining that the arguments differed, however, we never used the optional parameter to `names` outside of tests so I just deleted the code altogether. * fixes #7540 * move to properties --- certbot/certbot/_internal/storage.py | 24 +++++++------ certbot/certbot/crypto_util.py | 25 ++++++++------ certbot/certbot/interfaces.py | 60 +++++++++++++++++++++++++++++++-- certbot/certbot/plugins/enhancements.py | 8 ++--- certbot/tests/crypto_util_test.py | 22 ++++++------ certbot/tests/storage_test.py | 14 -------- 6 files changed, 101 insertions(+), 52 deletions(-) diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index bb36f462a..72eb3de85 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -17,6 +17,7 @@ from certbot._internal import constants from certbot import crypto_util from certbot._internal import error_handler from certbot import errors +from certbot import interfaces from certbot import util from certbot.compat import os from certbot.compat import filesystem @@ -376,7 +377,7 @@ def delete_files(config, certname): logger.debug("Unable to remove %s", archive_path) -class RenewableCert(object): +class RenewableCert(interfaces.RenewableCert): """Renewable certificate. Represents a lineage of certificates that is under the management of @@ -423,7 +424,7 @@ class RenewableCert(object): """ self.cli_config = cli_config - self.lineagename = lineagename_for_filename(config_filename) + self._lineagename = lineagename_for_filename(config_filename) # self.configuration should be used to read parameters that # may have been chosen based on default values from the @@ -483,6 +484,15 @@ class RenewableCert(object): """Duck type for self.fullchain""" return self.fullchain + @property + def lineagename(self): + """Name given to the certificate lineage. + + :rtype: str + + """ + return self._lineagename + @property def target_expiry(self): """The current target certificate's expiration datetime @@ -858,21 +868,15 @@ class RenewableCert(object): for _, link in previous_links: os.unlink(link) - def names(self, version=None): + def names(self): """What are the subject names of this certificate? - (If no version is specified, use the current version.) - - :param int version: the desired version number :returns: the subject names :rtype: `list` of `str` :raises .CertStorageError: if could not find cert file. """ - if version is None: - target = self.current_target("cert") - else: - target = self.version("cert", version) + target = self.current_target("cert") if target is None: raise errors.CertStorageError("could not find cert file") with open(target) as f: diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 12291af38..5c375cc55 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -213,26 +213,28 @@ def verify_renewable_cert(renewable_cert): 2. That fullchain matches cert and chain when concatenated. 3. Check that the private key matches the certificate. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If verification fails. """ verify_renewable_cert_sig(renewable_cert) verify_fullchain(renewable_cert) - verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey) + verify_cert_matches_priv_key(renewable_cert.cert_path, renewable_cert.key_path) def verify_renewable_cert_sig(renewable_cert): - """Verifies the signature of a `.storage.RenewableCert` object. + """Verifies the signature of a RenewableCert object. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If signature verification fails. """ try: - with open(renewable_cert.chain, 'rb') as chain_file: # type: IO[bytes] + with open(renewable_cert.chain_path, 'rb') as chain_file: # type: IO[bytes] chain = x509.load_pem_x509_certificate(chain_file.read(), default_backend()) - with open(renewable_cert.cert, 'rb') as cert_file: # type: IO[bytes] + with open(renewable_cert.cert_path, 'rb') as cert_file: # type: IO[bytes] cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() with warnings.catch_warnings(): @@ -240,7 +242,7 @@ def verify_renewable_cert_sig(renewable_cert): cert.signature_hash_algorithm) except (IOError, ValueError, InvalidSignature) as e: error_str = "verifying the signature of the cert located at {0} has failed. \ - Details: {1}".format(renewable_cert.cert, e) + Details: {1}".format(renewable_cert.cert_path, e) logger.exception(error_str) raise errors.Error(error_str) @@ -301,16 +303,17 @@ def verify_cert_matches_priv_key(cert_path, key_path): def verify_fullchain(renewable_cert): """ Verifies that fullchain is indeed cert concatenated with chain. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If cert and chain do not combine to fullchain. """ try: - with open(renewable_cert.chain) as chain_file: # type: IO[str] + with open(renewable_cert.chain_path) as chain_file: # type: IO[str] chain = chain_file.read() - with open(renewable_cert.cert) as cert_file: # type: IO[str] + with open(renewable_cert.cert_path) as cert_file: # type: IO[str] cert = cert_file.read() - with open(renewable_cert.fullchain) as fullchain_file: # type: IO[str] + with open(renewable_cert.fullchain_path) as fullchain_file: # type: IO[str] fullchain = fullchain_file.read() if (cert + chain) != fullchain: error_str = "fullchain does not match cert + chain for {0}!" diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py index edf71e63f..cf993a55b 100644 --- a/certbot/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -532,6 +532,62 @@ class IReporter(zope.interface.Interface): """Prints messages to the user and clears the message queue.""" +@six.add_metaclass(abc.ABCMeta) +class RenewableCert(object): + """Interface to a certificate lineage.""" + + @abc.abstractproperty + def cert_path(self): + """Path to the certificate file. + + :rtype: str + + """ + + @abc.abstractproperty + def key_path(self): + """Path to the private key file. + + :rtype: str + + """ + + @abc.abstractproperty + def chain_path(self): + """Path to the certificate chain file. + + :rtype: str + + """ + + @abc.abstractproperty + def fullchain_path(self): + """Path to the full chain file. + + The full chain is the certificate file plus the chain file. + + :rtype: str + + """ + + @abc.abstractproperty + def lineagename(self): + """Name given to the certificate lineage. + + :rtype: str + + """ + + @abc.abstractmethod + def names(self): + """What are the subject names of this certificate? + + :returns: the subject names + :rtype: `list` of `str` + :raises .CertStorageError: if could not find cert file. + + """ + # Updater interfaces # # When "certbot renew" is run, Certbot will iterate over each lineage and check @@ -570,7 +626,7 @@ class GenericUpdater(object): This method is called once for each lineage. :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert + :type lineage: RenewableCert """ @@ -599,6 +655,6 @@ class RenewDeployer(object): This method is called once for each lineage renewed :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert + :type lineage: RenewableCert """ diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index d917b0ea4..44638e91d 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -62,7 +62,7 @@ def enable(lineage, domains, installer, config): Run enable method for each requested enhancement that is supported. :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str @@ -123,7 +123,7 @@ class AutoHSTSEnhancement(object): Implementation of this method should increase the max-age value. :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert .. note:: prepare() method inherited from `interfaces.IPlugin` might need to be called manually within implementation of this interface method @@ -137,7 +137,7 @@ class AutoHSTSEnhancement(object): Long max-age value should be set in implementation of this method. :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert """ @abc.abstractmethod @@ -148,7 +148,7 @@ class AutoHSTSEnhancement(object): over the subsequent runs of Certbot renew. :param lineage: Certificate lineage object - :type lineage: certbot._internal.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 666e4c082..7438fed5a 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -181,15 +181,15 @@ class VerifyCertSetup(unittest.TestCase): super(VerifyCertSetup, self).setUp() self.renewable_cert = mock.MagicMock() - self.renewable_cert.cert = SS_CERT_PATH - self.renewable_cert.chain = SS_CERT_PATH - self.renewable_cert.privkey = RSA2048_KEY_PATH - self.renewable_cert.fullchain = test_util.vector_path('cert_fullchain_2048.pem') + self.renewable_cert.cert_path = SS_CERT_PATH + self.renewable_cert.chain_path = SS_CERT_PATH + self.renewable_cert.key_path = RSA2048_KEY_PATH + self.renewable_cert.fullchain_path = test_util.vector_path('cert_fullchain_2048.pem') self.bad_renewable_cert = mock.MagicMock() - self.bad_renewable_cert.chain = SS_CERT_PATH - self.bad_renewable_cert.cert = SS_CERT_PATH - self.bad_renewable_cert.fullchain = SS_CERT_PATH + self.bad_renewable_cert.chain_path = SS_CERT_PATH + self.bad_renewable_cert.cert_path = SS_CERT_PATH + self.bad_renewable_cert.fullchain_path = SS_CERT_PATH class VerifyRenewableCertTest(VerifyCertSetup): @@ -219,13 +219,13 @@ class VerifyRenewableCertSigTest(VerifyCertSetup): def test_cert_sig_match_ec(self): renewable_cert = mock.MagicMock() - renewable_cert.cert = P256_CERT_PATH - renewable_cert.chain = P256_CERT_PATH - renewable_cert.privkey = P256_KEY + renewable_cert.cert_path = P256_CERT_PATH + renewable_cert.chain_path = P256_CERT_PATH + renewable_cert.key_path = P256_KEY self.assertEqual(None, self._call(renewable_cert)) def test_cert_sig_mismatch(self): - self.bad_renewable_cert.cert = test_util.vector_path('cert_512_bad.pem') + self.bad_renewable_cert.cert_path = test_util.vector_path('cert_512_bad.pem') self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 35f10656e..06c881a87 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -404,20 +404,6 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual(self.test_rc.names(), ["example.com", "www.example.com"]) - # Trying a non-current version - self._write_out_kind("cert", 15, test_util.load_vector("cert_512.pem")) - - self.assertEqual(self.test_rc.names(12), - ["example.com", "www.example.com"]) - - # Testing common name is listed first - self._write_out_kind( - "cert", 12, test_util.load_vector("cert-5sans_512.pem")) - - self.assertEqual( - self.test_rc.names(12), - ["example.com"] + ["{0}.example.com".format(c) for c in "abcd"]) - # Trying missing cert os.unlink(self.test_rc.cert) self.assertRaises(errors.CertStorageError, self.test_rc.names) -- cgit v1.2.3 From 4c652b9c824ce1601309b82e085094243b30e501 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 2 Dec 2019 22:39:31 +0100 Subject: Upgrade to pywin32>=227 (#7615) Current version of pywin32 used in certbot (225) does not have wheels available for Python 3.8. Installing certbot for development in this case requires to build from source. On Windows, this implies a Visual Studio C++ environment up and ready, which is absolutely not fun. Let's upgrade to pywin32 227, that provides these wheels for all Python versions from 3.5 up to current dev status of 3.9. --- certbot/setup.py | 2 +- tools/dev_constraints.txt | 2 +- windows-installer/construct.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot/setup.py b/certbot/setup.py index 752b5e39c..c1bf91410 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -59,7 +59,7 @@ install_requires = [ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. -pywin32_req = 'pywin32>=225' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py +pywin32_req = 'pywin32>=227' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py if StrictVersion(setuptools_version) >= StrictVersion('36.2'): install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 6854be466..dd5a97600 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -70,7 +70,7 @@ pytest-sugar==0.9.2 pytest-rerunfailures==4.2 python-dateutil==2.6.1 python-digitalocean==1.11 -pywin32==225 +pywin32==227 PyYAML==3.13 repoze.sphinx.autointerface==0.8 requests-file==1.4.2 diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 699786411..192906d79 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -11,7 +11,7 @@ import time PYTHON_VERSION = (3, 7, 4) PYTHON_BITNESS = 32 -PYWIN32_VERSION = 225 # do not forget to edit pywin32 dependency accordingly in setup.py +PYWIN32_VERSION = 227 # do not forget to edit pywin32 dependency accordingly in setup.py NSIS_VERSION = '3.04' -- cgit v1.2.3 From 2008e3cc775fd8f6fe14c8900f6d0014e893a078 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 3 Dec 2019 01:16:41 +0100 Subject: acme/setup.py: comment refers to "PyOpenSSL" not "mock" (#7619) --- acme/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/setup.py b/acme/setup.py index 6d3dcdf84..fcca333f9 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -14,8 +14,8 @@ install_requires = [ # 1.1.0+ is required to avoid the warnings described at # https://github.com/certbot/josepy/issues/13. 'josepy>=1.1.0', - # Connection.set_tlsext_host_name (>=0.13) 'mock', + # Connection.set_tlsext_host_name (>=0.13) 'PyOpenSSL>=0.13.1', 'pyrfc3339', 'pytz', -- cgit v1.2.3 From bc80195a586618b69fa1798b5e3c7e980e6173b6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:20:30 -0800 Subject: Update changelog for 1.0.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index b5794c2dd..709b01b58 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.0.0 - master +## 1.0.0 - 2019-12-03 ### Added -- cgit v1.2.3 From 6102cc440bdb36a3f238548cb753bc23d19b2f23 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:27:28 -0800 Subject: Release 1.0.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 228 ++++++++++++--------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/local-oldest-requirements.txt | 4 +- certbot-nginx/setup.py | 6 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 12 +- letsencrypt-auto | 228 ++++++++++++--------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 27 files changed, 324 insertions(+), 256 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index fcca333f9..8551821dd 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 5b62f37d7..49b67f65c 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 9f3d2af08..24c007e03 100755 --- a/certbot-auto +++ b/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.40.1" +LE_AUTO_VERSION="1.0.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1145,63 +1145,70 @@ if [ "$1" = "--le-auto-phase2" ]; then # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.6.16 \ - --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ - --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 -cffi==1.12.3 \ - --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ - --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ - --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ - --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ - --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ - --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ - --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ - --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ - --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ - --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ - --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ - --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ - --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ - --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ - --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ - --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ - --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ - --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ - --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ - --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ - --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ - --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ - --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ - --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ - --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ - --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ - --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ - --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.7 \ - --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ - --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ - --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ - --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ - --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ - --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ - --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ - --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ - --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ - --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ - --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ - --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ - --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ - --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ - --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ - --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 distro==1.4.0 \ --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 @@ -1213,14 +1220,14 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 josepy==1.2.0 \ --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 @@ -1230,9 +1237,9 @@ mock==1.3.0 \ parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.2 \ - --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ - --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1241,29 +1248,28 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.2 \ - --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ - --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2019.2 \ - --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ - --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 urllib3==1.24.3 \ --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a @@ -1314,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.2 \ - --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ - --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ - --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ - --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ - --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ - --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ - --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ - --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ - --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ - --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ - --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # @@ -1338,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.1 \ - --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ - --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 -acme==0.40.1 \ - --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ - --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc -certbot-apache==0.40.1 \ - --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ - --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c -certbot-nginx==0.40.1 \ - --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ - --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 3e5c24600..cc08d96b6 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '1.0.0.dev0' +version = '1.0.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 05a85bb66..f721e52dc 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 189af0a55..69325ced9 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index d5e500be0..2dbc05c35 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 09c90ff0d..0504276d4 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 8c9c73319..8d30bd412 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index cb7768a29..b44c049f5 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 21ea2eb38..09f1ac850 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index b6e20ec74..a2e6c3bf3 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 2350638b5..399b36d1d 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 09a5b7a93..41b177751 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 99b6e365b..66cd51bae 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d767caa1f..86e4a292f 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index eb66a2d43..088a3f798 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index b9584234b..51b294e24 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 1782f15ba..0e30f44eb 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. --e acme[dev] --e certbot[dev] +acme[dev]==1.0.0 +certbot[dev]==1.0.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index f7ee46a6a..256ee2699 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0.dev0' +version = '1.0.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=1.0.0.dev0', - 'certbot>=1.0.0.dev0', + 'acme>=1.0.0', + 'certbot>=1.0.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 30b52be1a..dfe498135 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.0.0.dev0' +__version__ = '1.0.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index efade498c..b46206b87 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,12 +113,12 @@ 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.40.1 - (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX - Installer/YYY (SUBCOMMAND; flags: FLAGS) - Py/major.minor.patchlevel). The flags encoded in the - user agent are: --duplicate, --force-renew, --allow- - subset-of-names, -n, and whether any hooks are set. + "". (default: CertbotACMEClient/1.0.0 (certbot(-auto); + OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY + (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). + The flags encoded in the user agent are: --duplicate, + --force-renew, --allow-subset-of-names, -n, and + whether any hooks are set. --user-agent-comment USER_AGENT_COMMENT Add a comment to the default user agent string. May be used when repackaging Certbot or calling it from diff --git a/letsencrypt-auto b/letsencrypt-auto index 9f3d2af08..24c007e03 100755 --- a/letsencrypt-auto +++ b/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.40.1" +LE_AUTO_VERSION="1.0.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1145,63 +1145,70 @@ if [ "$1" = "--le-auto-phase2" ]; then # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.6.16 \ - --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 \ - --hash=sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695 -cffi==1.12.3 \ - --hash=sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774 \ - --hash=sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d \ - --hash=sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90 \ - --hash=sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b \ - --hash=sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63 \ - --hash=sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45 \ - --hash=sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25 \ - --hash=sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3 \ - --hash=sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b \ - --hash=sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647 \ - --hash=sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016 \ - --hash=sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4 \ - --hash=sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb \ - --hash=sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753 \ - --hash=sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7 \ - --hash=sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9 \ - --hash=sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f \ - --hash=sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8 \ - --hash=sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f \ - --hash=sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc \ - --hash=sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42 \ - --hash=sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3 \ - --hash=sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909 \ - --hash=sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45 \ - --hash=sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d \ - --hash=sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512 \ - --hash=sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff \ - --hash=sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.7 \ - --hash=sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c \ - --hash=sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643 \ - --hash=sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216 \ - --hash=sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799 \ - --hash=sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a \ - --hash=sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9 \ - --hash=sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc \ - --hash=sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8 \ - --hash=sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53 \ - --hash=sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1 \ - --hash=sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609 \ - --hash=sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292 \ - --hash=sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e \ - --hash=sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6 \ - --hash=sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed \ - --hash=sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 distro==1.4.0 \ --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 @@ -1213,14 +1220,14 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 josepy==1.2.0 \ --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 @@ -1230,9 +1237,9 @@ mock==1.3.0 \ parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.2 \ - --hash=sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc \ - --hash=sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1241,29 +1248,28 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.2 \ - --hash=sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80 \ - --hash=sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2019.2 \ - --hash=sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32 \ - --hash=sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7 +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 urllib3==1.24.3 \ --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a @@ -1314,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.2 \ - --hash=sha256:320a7619992e42142549ebf61e14ce27683b4d14b0cbc45f7c037ba64edb560c \ - --hash=sha256:824d4dbabbb7deb84f25fdb96ea1eeca436a1802c3c8d323b3eb4ac9d527d41c \ - --hash=sha256:8a32eb9c94908f3544da2dae3f4a9e6961d78819b88ac6b6f4a51cee2d65f4a0 \ - --hash=sha256:96265fd3bc3ea646f98482e16307a69de21402eeaaaaf4b841c1161ac2f71bb0 \ - --hash=sha256:ab6d6975d9c51c13cac828ff03168de21fb562b0664c59bcdc4a4b10f39a5b17 \ - --hash=sha256:af10cb772391772463f65a58348e2de5ecc06693c16d2078be276dc068bcbb54 \ - --hash=sha256:b8fd3a3de3f7b6452775e92af22af5977b17b69ac86a38a3ddfe870e40a0d05f \ - --hash=sha256:bb7088f1bed3b8214284a5e425dc23da56f2f28e8815b7580bfed9e245b6c0b6 \ - --hash=sha256:bc29b3665eac34f14c4aef5224bef045efcfb1a7d12d78c8685858de5fbf21c0 \ - --hash=sha256:c39fa6a159affeae5fe31b49d9f5b12bd674fe77271a9a324408b271440c50a7 \ - --hash=sha256:e946a036ac5b9f897e986ac9dc950a34cffc857d88eae6727b8434fbc4752366 +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # @@ -1338,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.1 \ - --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ - --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 -acme==0.40.1 \ - --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ - --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc -certbot-apache==0.40.1 \ - --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ - --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c -certbot-nginx==0.40.1 \ - --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ - --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 2c7986ea9..aea28117c 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3CMH0ACgkQTRfJlc2X -dfJ4DwgAljOxkQ4uhiF1R8Mw3r5+lwSezh7seA01QVUcuLArZM8B+IJM0FbqrSca -ToYCrUXXTVL6aPn/1yxuNMMmVlJbIl5tMc3tfm//lbXTpLkiVASZl5+3HKtR5o6w -GP2v6apjvkM1oaT2jL0VKcMGheVYaLfUQ3MGhMHbgOZKALNYwMGP9WOwgn0GGa1l -3SIYJOqgEjDQTSr1tK+Ki+UkuoMsL4HamfvgNWeXbTpChdjth46UTj+N60rafIA2 -HAkXoP4eav6XbDlzDulG2cTBtnxvr6yiK76YF68P7BHNNYWbybFW8k6TVwexGgDF -2di01Av0nXsa4O8QkYdtVQcCgniaUw== -=E9YT +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3mmvMACgkQTRfJlc2X +dfKUbQf/aW8ZWRH36WhTHmZjJmBumSUYclFdDAR4c6Ym+MBTeYT0iQq/dqfqTklB +7jPHTcxWbyMJCjOqtMEDRt+aVF0A91OA1bSRt1MJCm7o8Oa1h4XVVPL2UZYCPNlu +46UEBGDOkd6DlrRvD0X2BrQ4EsktLe1d+EoDbDPebwfip9OYnEYMD7EQB9O3N8eo +aYRkaSJMc2HalI5u0oLEhnZGucNw6K7uvuW0LkwmRWpN8Lc8e9ELZ3FOCE6qD9yh +giAkvZNklwhAxkk9spFkEilvEOPVtKgiSS6jZIL5G1NlAhp8n6+vhatY5Aotw8nO +QrqmPvzBd+2Gy2nrrGuSMC146m0x/g== +=3A0n -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 09a7c998e..24c007e03 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.0.0.dev0" +LE_AUTO_VERSION="1.0.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1372,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.40.1 \ - --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ - --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 -acme==0.40.1 \ - --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ - --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc -certbot-apache==0.40.1 \ - --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ - --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c -certbot-nginx==0.40.1 \ - --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ - --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index ce698e84d..705f30e3f 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 5029feb9d..d4bdfd49e 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.40.1 \ - --hash=sha256:afe4d7edc61d4cab8b6f7ab7611d66aaba67d9f0404fa2760bd1cc430b2ec9ec \ - --hash=sha256:8dc81b3044cf401c55fa36c30893887fcb92657df1d76a8848059683df3b10d1 -acme==0.40.1 \ - --hash=sha256:a85387c26fb4fc24511b2579b8c61177b3175fd25e130c5be95a840d9f67d54f \ - --hash=sha256:33bf8686408d5b6b79886a9a43aee691ca754408deaec4fb2bb17b9af48a5ffc -certbot-apache==0.40.1 \ - --hash=sha256:6c4a4e21a17bd8120056595e5670c9a0f86756da0ad269196e8d56fc1b9fec82 \ - --hash=sha256:16404e9e404f0b98c18bd752fad812a45ef7f9b0efa9bbc8589f24a94e67de7c -certbot-nginx==0.40.1 \ - --hash=sha256:acdbfc4ab75ebc810264ee1248332f8e857c5e7776717b7cd53c4ceceb2b4d34 \ - --hash=sha256:014fdda80647ad9e67019b16c7cdaee5d21a750b393bcb98e1300615cc930f4f +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 -- cgit v1.2.3 From 5debf7af7e3981f2b6558dcaf5e7c26e01fb0a3d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:27:30 -0800 Subject: Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 709b01b58..0e8c20a50 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.1.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.0.0 - 2019-12-03 ### Added -- cgit v1.2.3 From d2bad803f3dc4a471183ac2237bb090c526dcc6d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:27:30 -0800 Subject: Bump version to 1.1.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 8551821dd..5397cf2ae 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 49b67f65c..821e23014 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index cc08d96b6..cea364290 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '1.0.0' +version = '1.1.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index f721e52dc..db483b0ee 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 69325ced9..03a905b4c 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 2dbc05c35..1ff8d9f5b 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 0504276d4..c0db2bf4d 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 8d30bd412..07ae111b8 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index b44c049f5..053150276 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 09f1ac850..26be2ac1c 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index a2e6c3bf3..924153992 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 399b36d1d..e42b85848 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 41b177751..9efb4a06b 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 66cd51bae..7e3d3b426 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 86e4a292f..a46d06f04 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 088a3f798..ce325c612 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 51b294e24..849099770 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 256ee2699..8cd7e24f4 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools.command.test import test as TestCommand import sys -version = '1.0.0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index dfe498135..71c7e4e87 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.0.0' +__version__ = '1.1.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 24c007e03..2f48751f2 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.0.0" +LE_AUTO_VERSION="1.1.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From e32033f1ec6fa910007d2454f6210e27fe7f98d7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:51:43 -0800 Subject: document main (#7610) I deleted the exceptions because I think it's not feasible to document the possible exceptions raised by all of Certbot. --- certbot/certbot/_internal/main.py | 9 +++++---- certbot/certbot/main.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index c674efd79..3fc858711 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -1304,12 +1304,13 @@ def set_displayer(config): def main(cli_args=None): - """Command line argument parsing and main script execution. + """Run Certbot. - :returns: result of requested command + :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]`` + :type cli_args: `list` of `str` - :raises errors.Error: OS errors triggered by wrong permissions - :raises errors.Error: error if plugin command is not supported + :returns: value for `sys.exit` about the exit status of Certbot + :rtype: `str` or `int` or `None` """ if not cli_args: diff --git a/certbot/certbot/main.py b/certbot/certbot/main.py index b329f15c5..b2fb1dbb7 100644 --- a/certbot/certbot/main.py +++ b/certbot/certbot/main.py @@ -3,12 +3,13 @@ from certbot._internal import main as internal_main def main(cli_args=None): - """Command line argument parsing and main script execution. + """Run Certbot. - :returns: result of requested command + :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]`` + :type cli_args: `list` of `str` - :raises errors.Error: OS errors triggered by wrong permissions - :raises errors.Error: error if plugin command is not supported + :returns: value for `sys.exit` about the exit status of Certbot + :rtype: `str` or `int` or `None` """ return internal_main.main(cli_args) -- cgit v1.2.3 From 27d6f62a96995ac183fe2f5bdd0fa841daac4b47 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:52:05 -0800 Subject: update external plugin (#7604) The old plugin at https://github.com/marcan/certbot-external says it's obsolete and points people to https://github.com/EnigmaBridge/certbot-external-auth. The new plugin is also an installer. I also removed the reference to #2782 about us adding similar functionality since that's been done for a long time. We could reference our manual plugin instead, but I think that devalues their plugin a bit which I don't think is necessary or correct as it has different features. --- certbot/docs/using.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index a67d28c89..fdf878a99 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -275,7 +275,7 @@ haproxy_ Y Y Integration with the HAProxy load balancer s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets gandi_ Y N Obtain certificates via the Gandi LiveDNS API varnish_ Y N Obtain certificates via a Varnish server -external_ Y N A plugin for convenient scripting (See also ticket 2782_) +external-auth_ Y Y A plugin for convenient scripting pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers proxmox_ N Y Install certificates in Proxmox Virtualization servers dns-standalone_ Y N Obtain certificates via an integrated DNS server @@ -286,10 +286,9 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server .. _s3front: https://github.com/dlapiduz/letsencrypt-s3front .. _gandi: https://github.com/obynio/certbot-plugin-gandi .. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin -.. _2782: https://github.com/certbot/certbot/issues/2782 .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox -.. _external: https://github.com/marcan/letsencrypt-external +.. _external: https://github.com/EnigmaBridge/certbot-external-auth .. _dns-standalone: https://github.com/siilike/certbot-dns-standalone .. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig -- cgit v1.2.3 From 3cfa63483d4735b06c87ed647c77f32f784568d2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 09:54:37 -0800 Subject: Add full API documentation (#7614) A lot of Certbot's files don't have API documentation which is fixed by this PR. To do this, from the top level certbot directory I ran: ``` sphinx-apidoc -Me -o docs/api certbot ``` I then merged the resulting `modules.rst` file with `docs/api.rst`. --- certbot/docs/api.rst | 4 +-- certbot/docs/api/achallenges.rst | 5 ---- certbot/docs/api/certbot.achallenges.rst | 7 +++++ certbot/docs/api/certbot.compat.filesystem.rst | 7 +++++ certbot/docs/api/certbot.compat.misc.rst | 7 +++++ certbot/docs/api/certbot.compat.os.rst | 7 +++++ certbot/docs/api/certbot.compat.rst | 17 ++++++++++++ certbot/docs/api/certbot.crypto_util.rst | 7 +++++ certbot/docs/api/certbot.display.ops.rst | 7 +++++ certbot/docs/api/certbot.display.rst | 16 +++++++++++ certbot/docs/api/certbot.display.util.rst | 7 +++++ certbot/docs/api/certbot.errors.rst | 7 +++++ certbot/docs/api/certbot.interfaces.rst | 7 +++++ certbot/docs/api/certbot.main.rst | 7 +++++ certbot/docs/api/certbot.plugins.common.rst | 7 +++++ certbot/docs/api/certbot.plugins.dns_common.rst | 7 +++++ .../api/certbot.plugins.dns_common_lexicon.rst | 7 +++++ .../docs/api/certbot.plugins.dns_test_common.rst | 7 +++++ .../certbot.plugins.dns_test_common_lexicon.rst | 7 +++++ certbot/docs/api/certbot.plugins.enhancements.rst | 7 +++++ certbot/docs/api/certbot.plugins.rst | 22 +++++++++++++++ certbot/docs/api/certbot.plugins.storage.rst | 7 +++++ certbot/docs/api/certbot.plugins.util.rst | 7 +++++ certbot/docs/api/certbot.reverter.rst | 7 +++++ certbot/docs/api/certbot.rst | 31 ++++++++++++++++++++++ certbot/docs/api/certbot.tests.acme_util.rst | 7 +++++ certbot/docs/api/certbot.tests.rst | 16 +++++++++++ certbot/docs/api/certbot.tests.util.rst | 7 +++++ certbot/docs/api/certbot.util.rst | 7 +++++ certbot/docs/api/crypto_util.rst | 5 ---- certbot/docs/api/display.rst | 17 ------------ certbot/docs/api/errors.rst | 5 ---- certbot/docs/api/index.rst | 5 ---- certbot/docs/api/interfaces.rst | 5 ---- certbot/docs/api/main.rst | 5 ---- certbot/docs/api/plugins/common.rst | 5 ---- certbot/docs/api/plugins/dns_common.rst | 5 ---- certbot/docs/api/plugins/dns_common_lexicon.rst | 5 ---- certbot/docs/api/plugins/util.rst | 5 ---- certbot/docs/api/reverter.rst | 5 ---- certbot/docs/api/util.rst | 5 ---- 41 files changed, 258 insertions(+), 79 deletions(-) delete mode 100644 certbot/docs/api/achallenges.rst create mode 100644 certbot/docs/api/certbot.achallenges.rst create mode 100644 certbot/docs/api/certbot.compat.filesystem.rst create mode 100644 certbot/docs/api/certbot.compat.misc.rst create mode 100644 certbot/docs/api/certbot.compat.os.rst create mode 100644 certbot/docs/api/certbot.compat.rst create mode 100644 certbot/docs/api/certbot.crypto_util.rst create mode 100644 certbot/docs/api/certbot.display.ops.rst create mode 100644 certbot/docs/api/certbot.display.rst create mode 100644 certbot/docs/api/certbot.display.util.rst create mode 100644 certbot/docs/api/certbot.errors.rst create mode 100644 certbot/docs/api/certbot.interfaces.rst create mode 100644 certbot/docs/api/certbot.main.rst create mode 100644 certbot/docs/api/certbot.plugins.common.rst create mode 100644 certbot/docs/api/certbot.plugins.dns_common.rst create mode 100644 certbot/docs/api/certbot.plugins.dns_common_lexicon.rst create mode 100644 certbot/docs/api/certbot.plugins.dns_test_common.rst create mode 100644 certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst create mode 100644 certbot/docs/api/certbot.plugins.enhancements.rst create mode 100644 certbot/docs/api/certbot.plugins.rst create mode 100644 certbot/docs/api/certbot.plugins.storage.rst create mode 100644 certbot/docs/api/certbot.plugins.util.rst create mode 100644 certbot/docs/api/certbot.reverter.rst create mode 100644 certbot/docs/api/certbot.rst create mode 100644 certbot/docs/api/certbot.tests.acme_util.rst create mode 100644 certbot/docs/api/certbot.tests.rst create mode 100644 certbot/docs/api/certbot.tests.util.rst create mode 100644 certbot/docs/api/certbot.util.rst delete mode 100644 certbot/docs/api/crypto_util.rst delete mode 100644 certbot/docs/api/display.rst delete mode 100644 certbot/docs/api/errors.rst delete mode 100644 certbot/docs/api/index.rst delete mode 100644 certbot/docs/api/interfaces.rst delete mode 100644 certbot/docs/api/main.rst delete mode 100644 certbot/docs/api/plugins/common.rst delete mode 100644 certbot/docs/api/plugins/dns_common.rst delete mode 100644 certbot/docs/api/plugins/dns_common_lexicon.rst delete mode 100644 certbot/docs/api/plugins/util.rst delete mode 100644 certbot/docs/api/reverter.rst delete mode 100644 certbot/docs/api/util.rst diff --git a/certbot/docs/api.rst b/certbot/docs/api.rst index 8668ec5d8..9c8b2f1fe 100644 --- a/certbot/docs/api.rst +++ b/certbot/docs/api.rst @@ -3,6 +3,6 @@ API Documentation ================= .. toctree:: - :glob: + :maxdepth: 4 - api/** + api/certbot diff --git a/certbot/docs/api/achallenges.rst b/certbot/docs/api/achallenges.rst deleted file mode 100644 index 90dda3f06..000000000 --- a/certbot/docs/api/achallenges.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.achallenges` ------------------------------- - -.. automodule:: certbot.achallenges - :members: diff --git a/certbot/docs/api/certbot.achallenges.rst b/certbot/docs/api/certbot.achallenges.rst new file mode 100644 index 000000000..3fd2f2a42 --- /dev/null +++ b/certbot/docs/api/certbot.achallenges.rst @@ -0,0 +1,7 @@ +certbot.achallenges module +========================== + +.. automodule:: certbot.achallenges + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.filesystem.rst b/certbot/docs/api/certbot.compat.filesystem.rst new file mode 100644 index 000000000..d4f1e2fe0 --- /dev/null +++ b/certbot/docs/api/certbot.compat.filesystem.rst @@ -0,0 +1,7 @@ +certbot.compat.filesystem module +================================ + +.. automodule:: certbot.compat.filesystem + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.misc.rst b/certbot/docs/api/certbot.compat.misc.rst new file mode 100644 index 000000000..35c2913e7 --- /dev/null +++ b/certbot/docs/api/certbot.compat.misc.rst @@ -0,0 +1,7 @@ +certbot.compat.misc module +========================== + +.. automodule:: certbot.compat.misc + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.os.rst b/certbot/docs/api/certbot.compat.os.rst new file mode 100644 index 000000000..3a4c9fe47 --- /dev/null +++ b/certbot/docs/api/certbot.compat.os.rst @@ -0,0 +1,7 @@ +certbot.compat.os module +======================== + +.. automodule:: certbot.compat.os + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.rst b/certbot/docs/api/certbot.compat.rst new file mode 100644 index 000000000..f6f2b3739 --- /dev/null +++ b/certbot/docs/api/certbot.compat.rst @@ -0,0 +1,17 @@ +certbot.compat package +====================== + +.. automodule:: certbot.compat + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.compat.filesystem + certbot.compat.misc + certbot.compat.os + diff --git a/certbot/docs/api/certbot.crypto_util.rst b/certbot/docs/api/certbot.crypto_util.rst new file mode 100644 index 000000000..34aa665b9 --- /dev/null +++ b/certbot/docs/api/certbot.crypto_util.rst @@ -0,0 +1,7 @@ +certbot.crypto\_util module +=========================== + +.. automodule:: certbot.crypto_util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.display.ops.rst b/certbot/docs/api/certbot.display.ops.rst new file mode 100644 index 000000000..544b0dad3 --- /dev/null +++ b/certbot/docs/api/certbot.display.ops.rst @@ -0,0 +1,7 @@ +certbot.display.ops module +========================== + +.. automodule:: certbot.display.ops + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.display.rst b/certbot/docs/api/certbot.display.rst new file mode 100644 index 000000000..04bc68b07 --- /dev/null +++ b/certbot/docs/api/certbot.display.rst @@ -0,0 +1,16 @@ +certbot.display package +======================= + +.. automodule:: certbot.display + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.display.ops + certbot.display.util + diff --git a/certbot/docs/api/certbot.display.util.rst b/certbot/docs/api/certbot.display.util.rst new file mode 100644 index 000000000..22b59dc98 --- /dev/null +++ b/certbot/docs/api/certbot.display.util.rst @@ -0,0 +1,7 @@ +certbot.display.util module +=========================== + +.. automodule:: certbot.display.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.errors.rst b/certbot/docs/api/certbot.errors.rst new file mode 100644 index 000000000..731b7695d --- /dev/null +++ b/certbot/docs/api/certbot.errors.rst @@ -0,0 +1,7 @@ +certbot.errors module +===================== + +.. automodule:: certbot.errors + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.interfaces.rst b/certbot/docs/api/certbot.interfaces.rst new file mode 100644 index 000000000..2665aaa01 --- /dev/null +++ b/certbot/docs/api/certbot.interfaces.rst @@ -0,0 +1,7 @@ +certbot.interfaces module +========================= + +.. automodule:: certbot.interfaces + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.main.rst b/certbot/docs/api/certbot.main.rst new file mode 100644 index 000000000..ce0539f5c --- /dev/null +++ b/certbot/docs/api/certbot.main.rst @@ -0,0 +1,7 @@ +certbot.main module +=================== + +.. automodule:: certbot.main + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.common.rst b/certbot/docs/api/certbot.plugins.common.rst new file mode 100644 index 000000000..e94b2d12e --- /dev/null +++ b/certbot/docs/api/certbot.plugins.common.rst @@ -0,0 +1,7 @@ +certbot.plugins.common module +============================= + +.. automodule:: certbot.plugins.common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_common.rst b/certbot/docs/api/certbot.plugins.dns_common.rst new file mode 100644 index 000000000..36c7a6428 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_common.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_common module +================================== + +.. automodule:: certbot.plugins.dns_common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst b/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst new file mode 100644 index 000000000..1a961accd --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_common\_lexicon module +=========================================== + +.. automodule:: certbot.plugins.dns_common_lexicon + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_test_common.rst b/certbot/docs/api/certbot.plugins.dns_test_common.rst new file mode 100644 index 000000000..69e672f0a --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_test_common.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_test\_common module +======================================== + +.. automodule:: certbot.plugins.dns_test_common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst b/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst new file mode 100644 index 000000000..92d516c99 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_test\_common\_lexicon module +================================================= + +.. automodule:: certbot.plugins.dns_test_common_lexicon + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.enhancements.rst b/certbot/docs/api/certbot.plugins.enhancements.rst new file mode 100644 index 000000000..16db737c7 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.enhancements.rst @@ -0,0 +1,7 @@ +certbot.plugins.enhancements module +=================================== + +.. automodule:: certbot.plugins.enhancements + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.rst b/certbot/docs/api/certbot.plugins.rst new file mode 100644 index 000000000..517a209e6 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.rst @@ -0,0 +1,22 @@ +certbot.plugins package +======================= + +.. automodule:: certbot.plugins + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.plugins.common + certbot.plugins.dns_common + certbot.plugins.dns_common_lexicon + certbot.plugins.dns_test_common + certbot.plugins.dns_test_common_lexicon + certbot.plugins.enhancements + certbot.plugins.storage + certbot.plugins.util + diff --git a/certbot/docs/api/certbot.plugins.storage.rst b/certbot/docs/api/certbot.plugins.storage.rst new file mode 100644 index 000000000..9ed0fe724 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.storage.rst @@ -0,0 +1,7 @@ +certbot.plugins.storage module +============================== + +.. automodule:: certbot.plugins.storage + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.util.rst b/certbot/docs/api/certbot.plugins.util.rst new file mode 100644 index 000000000..c5453564e --- /dev/null +++ b/certbot/docs/api/certbot.plugins.util.rst @@ -0,0 +1,7 @@ +certbot.plugins.util module +=========================== + +.. automodule:: certbot.plugins.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.reverter.rst b/certbot/docs/api/certbot.reverter.rst new file mode 100644 index 000000000..002b75360 --- /dev/null +++ b/certbot/docs/api/certbot.reverter.rst @@ -0,0 +1,7 @@ +certbot.reverter module +======================= + +.. automodule:: certbot.reverter + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.rst b/certbot/docs/api/certbot.rst new file mode 100644 index 000000000..6f5b4b403 --- /dev/null +++ b/certbot/docs/api/certbot.rst @@ -0,0 +1,31 @@ +certbot package +=============== + +.. automodule:: certbot + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + certbot.compat + certbot.display + certbot.plugins + certbot.tests + +Submodules +---------- + +.. toctree:: + + certbot.achallenges + certbot.crypto_util + certbot.errors + certbot.interfaces + certbot.main + certbot.reverter + certbot.util + diff --git a/certbot/docs/api/certbot.tests.acme_util.rst b/certbot/docs/api/certbot.tests.acme_util.rst new file mode 100644 index 000000000..908397596 --- /dev/null +++ b/certbot/docs/api/certbot.tests.acme_util.rst @@ -0,0 +1,7 @@ +certbot.tests.acme\_util module +=============================== + +.. automodule:: certbot.tests.acme_util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.tests.rst b/certbot/docs/api/certbot.tests.rst new file mode 100644 index 000000000..336f0eabc --- /dev/null +++ b/certbot/docs/api/certbot.tests.rst @@ -0,0 +1,16 @@ +certbot.tests package +===================== + +.. automodule:: certbot.tests + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.tests.acme_util + certbot.tests.util + diff --git a/certbot/docs/api/certbot.tests.util.rst b/certbot/docs/api/certbot.tests.util.rst new file mode 100644 index 000000000..3f0335849 --- /dev/null +++ b/certbot/docs/api/certbot.tests.util.rst @@ -0,0 +1,7 @@ +certbot.tests.util module +========================= + +.. automodule:: certbot.tests.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.util.rst b/certbot/docs/api/certbot.util.rst new file mode 100644 index 000000000..11cb33b09 --- /dev/null +++ b/certbot/docs/api/certbot.util.rst @@ -0,0 +1,7 @@ +certbot.util module +=================== + +.. automodule:: certbot.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/crypto_util.rst b/certbot/docs/api/crypto_util.rst deleted file mode 100644 index 2f473944c..000000000 --- a/certbot/docs/api/crypto_util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.crypto_util` ------------------------------- - -.. automodule:: certbot.crypto_util - :members: diff --git a/certbot/docs/api/display.rst b/certbot/docs/api/display.rst deleted file mode 100644 index 70038786c..000000000 --- a/certbot/docs/api/display.rst +++ /dev/null @@ -1,17 +0,0 @@ -:mod:`certbot.display` --------------------------- - -.. automodule:: certbot.display - :members: - -:mod:`certbot.display.util` -=============================== - -.. automodule:: certbot.display.util - :members: - -:mod:`certbot.display.ops` -============================== - -.. automodule:: certbot.display.ops - :members: diff --git a/certbot/docs/api/errors.rst b/certbot/docs/api/errors.rst deleted file mode 100644 index a9324765b..000000000 --- a/certbot/docs/api/errors.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.errors` -------------------------- - -.. automodule:: certbot.errors - :members: diff --git a/certbot/docs/api/index.rst b/certbot/docs/api/index.rst deleted file mode 100644 index be94214c9..000000000 --- a/certbot/docs/api/index.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot` ------------------- - -.. automodule:: certbot - :members: diff --git a/certbot/docs/api/interfaces.rst b/certbot/docs/api/interfaces.rst deleted file mode 100644 index 2988b3b87..000000000 --- a/certbot/docs/api/interfaces.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.interfaces` ------------------------------ - -.. automodule:: certbot.interfaces - :members: diff --git a/certbot/docs/api/main.rst b/certbot/docs/api/main.rst deleted file mode 100644 index d9dda841d..000000000 --- a/certbot/docs/api/main.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.main` ------------------------------- - -.. automodule:: certbot.main - :members: diff --git a/certbot/docs/api/plugins/common.rst b/certbot/docs/api/plugins/common.rst deleted file mode 100644 index 7cfaf8d70..000000000 --- a/certbot/docs/api/plugins/common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.common` ---------------------------------- - -.. automodule:: certbot.plugins.common - :members: diff --git a/certbot/docs/api/plugins/dns_common.rst b/certbot/docs/api/plugins/dns_common.rst deleted file mode 100644 index ee3945e74..000000000 --- a/certbot/docs/api/plugins/dns_common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common` ---------------------------------- - -.. automodule:: certbot.plugins.dns_common - :members: diff --git a/certbot/docs/api/plugins/dns_common_lexicon.rst b/certbot/docs/api/plugins/dns_common_lexicon.rst deleted file mode 100644 index a48166828..000000000 --- a/certbot/docs/api/plugins/dns_common_lexicon.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common_lexicon` ------------------------------------------ - -.. automodule:: certbot.plugins.dns_common_lexicon - :members: diff --git a/certbot/docs/api/plugins/util.rst b/certbot/docs/api/plugins/util.rst deleted file mode 100644 index 30ab3d49f..000000000 --- a/certbot/docs/api/plugins/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.util` -------------------------------- - -.. automodule:: certbot.plugins.util - :members: diff --git a/certbot/docs/api/reverter.rst b/certbot/docs/api/reverter.rst deleted file mode 100644 index 3e0ac750b..000000000 --- a/certbot/docs/api/reverter.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.reverter` ---------------------------- - -.. automodule:: certbot.reverter - :members: diff --git a/certbot/docs/api/util.rst b/certbot/docs/api/util.rst deleted file mode 100644 index 7d0e33501..000000000 --- a/certbot/docs/api/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.util` --------------------------- - -.. automodule:: certbot.util - :members: -- cgit v1.2.3 From b45f79d0ab5b8f0a84580880e8db9f28ae6b1f80 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 11:05:23 -0800 Subject: fix bad links in docs (#7623) This PR fixes the failures at https://travis-ci.com/certbot/website/builds/139193502#L1316. Once this PR lands, I'll update certbot/website#508 to include this commit. --- certbot/certbot/display/ops.py | 2 +- certbot/docs/using.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index 1a36744a5..185c21b58 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -292,7 +292,7 @@ def _gen_ssl_lab_urls(domains): def _gen_https_names(domains): """Returns a string of the https domains. - Domains are formatted nicely with https:// prepended to each. + Domains are formatted nicely with ``https://`` prepended to each. :param list domains: Each domain is a 'str' diff --git a/certbot/docs/using.rst b/certbot/docs/using.rst index fdf878a99..27ae826bd 100644 --- a/certbot/docs/using.rst +++ b/certbot/docs/using.rst @@ -288,7 +288,7 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server .. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox -.. _external: https://github.com/EnigmaBridge/certbot-external-auth +.. _external-auth: https://github.com/EnigmaBridge/certbot-external-auth .. _dns-standalone: https://github.com/siilike/certbot-dns-standalone .. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig -- cgit v1.2.3 From 5da61564d97113b596385c98a655ede783227bd4 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 3 Dec 2019 19:56:16 -0800 Subject: Don't list DNS plugins as alpha quality. (#7624) They should be considered production quality like our other packaged code. --- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 05a85bb66..05cbc1cd6 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 189af0a55..304628953 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index d5e500be0..6d43a4bd6 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -47,7 +47,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 09c90ff0d..7aa4132fd 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -58,7 +58,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 8c9c73319..ea43e731e 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index cb7768a29..076cdbc13 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -45,7 +45,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 21ea2eb38..327d644e0 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -49,7 +49,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index b6e20ec74..5019c79cb 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -44,7 +44,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 2350638b5..508155e50 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 09a5b7a93..eb80ff3a6 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 99b6e365b..2d95ca436 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index d767caa1f..b1a55cc39 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -46,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index eb66a2d43..b52eb5c71 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -40,7 +40,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index b9584234b..778dc08bc 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -44,7 +44,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', -- cgit v1.2.3 From 34b568f36648dd5c4103c3a444e81b5662e6be81 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 4 Dec 2019 11:22:10 -0800 Subject: Don't list adding type annotations as a PR req. (#7627) --- pull_request_template.md | 1 - 1 file changed, 1 deletion(-) diff --git a/pull_request_template.md b/pull_request_template.md index 227dda49c..c806d33e8 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,5 +1,4 @@ ## Pull Request Checklist - [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made. -- [ ] Add [mypy type annotations](https://certbot.eff.org/docs/contributing.html#mypy-type-annotations) for any functions that were added or modified. - [ ] Include your name in `AUTHORS.md` if you like. -- cgit v1.2.3 From e048da1e389ede7a52bd518ab4ebf9a0b18bfafc Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 9 Dec 2019 21:50:20 +0100 Subject: Reorganize imports (#7616) * Isort execution * Fix pylint, adapt coverage * New isort * Fix magic_typing lint * Second round * Fix pylint * Third round. Store isort configuration * Fix latest mistakes * Other fixes * Add newline * Fix lint errors --- .isort.cfg | 7 +++++ acme/acme/__init__.py | 1 - acme/acme/client.py | 13 +++++---- acme/acme/crypto_util.py | 12 ++++---- acme/acme/fields.py | 1 - acme/acme/magic_typing.py | 1 + acme/acme/messages.py | 15 ++++++---- acme/acme/standalone.py | 3 +- acme/docs/conf.py | 3 +- acme/examples/http01_example.py | 3 +- acme/setup.py | 5 ++-- acme/tests/challenges_test.py | 1 - acme/tests/client_test.py | 6 ++-- acme/tests/crypto_util_test.py | 9 +++--- acme/tests/jose_test.py | 1 + acme/tests/jws_test.py | 1 - acme/tests/messages_test.py | 3 +- acme/tests/standalone_test.py | 9 +++--- acme/tests/test_util.py | 2 +- .../certbot_apache/_internal/configurator.py | 15 +++++----- .../certbot_apache/_internal/constants.py | 1 - .../certbot_apache/_internal/display_ops.py | 2 +- .../certbot_apache/_internal/entrypoint.py | 5 ++-- certbot-apache/certbot_apache/_internal/http_01.py | 7 ++--- certbot-apache/certbot_apache/_internal/obj.py | 2 +- .../certbot_apache/_internal/override_arch.py | 3 +- .../certbot_apache/_internal/override_centos.py | 5 +--- .../certbot_apache/_internal/override_darwin.py | 3 +- .../certbot_apache/_internal/override_debian.py | 1 - .../certbot_apache/_internal/override_fedora.py | 1 - .../certbot_apache/_internal/override_gentoo.py | 3 +- .../certbot_apache/_internal/override_suse.py | 3 +- certbot-apache/certbot_apache/_internal/parser.py | 6 ++-- certbot-apache/setup.py | 6 ++-- certbot-apache/tests/autohsts_test.py | 5 ++-- certbot-apache/tests/centos6_test.py | 2 -- certbot-apache/tests/centos_test.py | 2 -- certbot-apache/tests/complex_parsing_test.py | 1 - certbot-apache/tests/configurator_reverter_test.py | 1 - certbot-apache/tests/configurator_test.py | 8 ++--- certbot-apache/tests/debian_test.py | 2 -- certbot-apache/tests/display_ops_test.py | 5 ---- certbot-apache/tests/fedora_test.py | 2 -- certbot-apache/tests/gentoo_test.py | 2 -- certbot-apache/tests/http_01_test.py | 5 +--- certbot-apache/tests/parser_test.py | 1 - certbot-apache/tests/util.py | 1 - .../certbot_integration_tests/assets/hook.py | 2 +- .../certbot_tests/assertions.py | 1 + .../certbot_tests/test_main.py | 20 ++++++++----- certbot-ci/certbot_integration_tests/conftest.py | 2 +- .../nginx_tests/context.py | 3 +- .../certbot_integration_tests/utils/acme_server.py | 13 +++++---- .../utils/certbot_call.py | 3 +- certbot-ci/certbot_integration_tests/utils/misc.py | 16 +++++----- .../utils/pebble_ocsp_server.py | 13 +++++---- .../certbot_integration_tests/utils/proxy.py | 2 +- certbot-ci/setup.py | 7 +++-- .../configurators/apache/common.py | 2 +- .../configurators/common.py | 1 - .../configurators/nginx/common.py | 5 ++-- .../certbot_compatibility_test/test_driver.py | 11 +++---- .../certbot_compatibility_test/util.py | 4 +-- .../certbot_compatibility_test/validator.py | 5 ++-- .../certbot_compatibility_test/validator_test.py | 2 +- certbot-compatibility-test/nginx/roundtrip.py | 1 + certbot-compatibility-test/setup.py | 3 +- certbot-dns-cloudflare/docs/conf.py | 1 + certbot-dns-cloudflare/setup.py | 6 ++-- .../certbot_dns_cloudxns/_internal/dns_cloudxns.py | 2 +- certbot-dns-cloudxns/docs/conf.py | 1 + certbot-dns-cloudxns/setup.py | 6 ++-- certbot-dns-cloudxns/tests/dns_cloudxns_test.py | 3 +- certbot-dns-digitalocean/docs/conf.py | 1 + certbot-dns-digitalocean/setup.py | 6 ++-- .../certbot_dns_dnsimple/_internal/dns_dnsimple.py | 2 +- certbot-dns-dnsimple/docs/conf.py | 1 + certbot-dns-dnsimple/setup.py | 6 ++-- .../_internal/dns_dnsmadeeasy.py | 2 +- certbot-dns-dnsmadeeasy/docs/conf.py | 1 + certbot-dns-dnsmadeeasy/setup.py | 6 ++-- .../certbot_dns_gehirn/_internal/dns_gehirn.py | 2 +- certbot-dns-gehirn/docs/conf.py | 1 + certbot-dns-gehirn/setup.py | 6 ++-- .../certbot_dns_google/_internal/dns_google.py | 4 +-- certbot-dns-google/docs/conf.py | 1 + certbot-dns-google/setup.py | 6 ++-- certbot-dns-google/tests/dns_google_test.py | 2 +- .../certbot_dns_linode/_internal/dns_linode.py | 2 +- certbot-dns-linode/docs/conf.py | 1 + certbot-dns-linode/setup.py | 5 ++-- .../certbot_dns_luadns/_internal/dns_luadns.py | 2 +- certbot-dns-luadns/docs/conf.py | 1 + certbot-dns-luadns/setup.py | 6 ++-- .../certbot_dns_nsone/_internal/dns_nsone.py | 2 +- certbot-dns-nsone/docs/conf.py | 1 + certbot-dns-nsone/setup.py | 6 ++-- .../certbot_dns_ovh/_internal/dns_ovh.py | 2 +- certbot-dns-ovh/docs/conf.py | 1 + certbot-dns-ovh/setup.py | 6 ++-- certbot-dns-rfc2136/docs/conf.py | 1 + certbot-dns-rfc2136/setup.py | 6 ++-- .../certbot_dns_route53/_internal/dns_route53.py | 9 ++++-- certbot-dns-route53/docs/conf.py | 1 + certbot-dns-route53/setup.py | 5 ++-- certbot-dns-route53/tests/dns_route53_test.py | 3 +- .../_internal/dns_sakuracloud.py | 2 +- certbot-dns-sakuracloud/docs/conf.py | 1 + certbot-dns-sakuracloud/setup.py | 5 ++-- .../certbot_nginx/_internal/configurator.py | 11 ++++--- .../certbot_nginx/_internal/display_ops.py | 2 -- certbot-nginx/certbot_nginx/_internal/http_01.py | 7 ++--- .../certbot_nginx/_internal/nginxparser.py | 14 +++++++-- certbot-nginx/certbot_nginx/_internal/parser.py | 11 ++++--- .../certbot_nginx/_internal/parser_obj.py | 4 +-- certbot-nginx/setup.py | 6 ++-- certbot-nginx/tests/configurator_test.py | 6 ++-- certbot-nginx/tests/display_ops_test.py | 4 --- certbot-nginx/tests/http_01_test.py | 4 --- certbot-nginx/tests/nginxparser_test.py | 10 ++++--- certbot-nginx/tests/obj_test.py | 2 +- certbot-nginx/tests/parser_obj_test.py | 4 ++- certbot-nginx/tests/parser_test.py | 3 -- certbot-nginx/tests/test_util.py | 1 - certbot/certbot/_internal/account.py | 5 ++-- certbot/certbot/_internal/auth_handler.py | 12 ++++---- certbot/certbot/_internal/cert_manager.py | 3 +- certbot/certbot/_internal/cli.py | 17 +++++------ certbot/certbot/_internal/client.py | 25 ++++++++-------- certbot/certbot/_internal/configuration.py | 4 +-- certbot/certbot/_internal/constants.py | 1 - certbot/certbot/_internal/display/completer.py | 1 + certbot/certbot/_internal/display/enhancements.py | 1 - certbot/certbot/_internal/eff.py | 3 +- certbot/certbot/_internal/error_handler.py | 9 +++--- certbot/certbot/_internal/hooks.py | 7 +++-- certbot/certbot/_internal/lock.py | 10 ++++--- certbot/certbot/_internal/log.py | 3 +- certbot/certbot/_internal/main.py | 16 +++++----- certbot/certbot/_internal/ocsp.py | 34 +++++++++++++--------- certbot/certbot/_internal/plugins/disco.py | 4 +-- certbot/certbot/_internal/plugins/manual.py | 3 +- certbot/certbot/_internal/plugins/null.py | 1 - certbot/certbot/_internal/plugins/standalone.py | 9 +++--- certbot/certbot/_internal/plugins/webroot.py | 12 ++++---- certbot/certbot/_internal/renewal.py | 9 +++--- certbot/certbot/_internal/reporter.py | 1 - certbot/certbot/_internal/storage.py | 10 +++---- certbot/certbot/_internal/updater.py | 1 - certbot/certbot/achallenges.py | 1 - certbot/certbot/compat/_path.py | 6 +++- certbot/certbot/compat/filesystem.py | 6 ++-- certbot/certbot/compat/misc.py | 5 ++-- certbot/certbot/compat/os.py | 2 ++ certbot/certbot/crypto_util.py | 13 ++++----- certbot/certbot/display/util.py | 4 +-- certbot/certbot/interfaces.py | 1 + certbot/certbot/plugins/common.py | 8 ++--- certbot/certbot/plugins/dns_common.py | 1 - certbot/certbot/plugins/dns_common_lexicon.py | 7 +++-- certbot/certbot/plugins/dns_test_common.py | 1 - certbot/certbot/plugins/dns_test_common_lexicon.py | 3 +- certbot/certbot/plugins/enhancements.py | 6 ++-- certbot/certbot/plugins/storage.py | 6 ++-- certbot/certbot/reverter.py | 4 +-- certbot/certbot/tests/acme_util.py | 3 -- certbot/certbot/tests/util.py | 15 +++++----- certbot/certbot/util.py | 12 ++++---- certbot/docs/conf.py | 1 - certbot/docs/contributing.rst | 2 +- certbot/examples/plugins/setup.py | 1 - certbot/setup.py | 6 ++-- certbot/tests/account_test.py | 3 +- certbot/tests/auth_handler_test.py | 4 +-- certbot/tests/cert_manager_test.py | 7 ++--- certbot/tests/cli_test.py | 9 +++--- certbot/tests/client_test.py | 11 ++++--- certbot/tests/compat/filesystem_test.py | 13 +++++---- certbot/tests/configuration_test.py | 2 +- certbot/tests/crypto_util_test.py | 6 ++-- certbot/tests/display/completer_test.py | 5 ++-- certbot/tests/display/ops_test.py | 7 ++--- certbot/tests/error_handler_test.py | 7 ++--- certbot/tests/errors_test.py | 1 - certbot/tests/hook_test.py | 4 +-- certbot/tests/lock_test.py | 11 ++++--- certbot/tests/log_test.py | 3 +- certbot/tests/main_test.py | 17 +++++------ certbot/tests/ocsp_test.py | 18 +++++++----- certbot/tests/plugins/common_test.py | 3 +- certbot/tests/plugins/disco_test.py | 1 - certbot/tests/plugins/enhancements_test.py | 4 +-- certbot/tests/plugins/manual_test.py | 5 ++-- certbot/tests/plugins/null_test.py | 2 +- certbot/tests/plugins/selection_test.py | 3 +- certbot/tests/plugins/standalone_test.py | 11 ++++--- certbot/tests/plugins/storage_test.py | 4 +-- certbot/tests/plugins/webroot_test.py | 3 +- certbot/tests/renewal_test.py | 5 ++-- certbot/tests/renewupdater_test.py | 3 +- certbot/tests/reporter_test.py | 2 +- certbot/tests/storage_test.py | 6 ++-- certbot/tests/util_test.py | 4 +-- letshelp-certbot/docs/conf.py | 3 +- letshelp-certbot/letshelp_certbot/apache_test.py | 5 ++-- letshelp-certbot/letshelp_certbot/magic_typing.py | 1 + letshelp-certbot/setup.py | 3 +- linter_plugin.py | 13 +++++---- tests/letstest/multitester.py | 22 +++++++++++--- tests/lock_test.py | 9 +++--- tests/modification-check.py | 3 +- tools/_venv_common.py | 6 ++-- tools/deactivate.py | 2 +- tools/extract_changelog.py | 4 +-- tools/install_and_test.py | 4 +-- tools/pip_install.py | 7 +++-- tools/pip_install_editable.py | 1 + tools/readlink.py | 1 + tools/simple_http_server.py | 1 + tools/venv.py | 1 + tox.cover.py | 4 +-- windows-installer/construct.py | 4 +-- 222 files changed, 563 insertions(+), 565 deletions(-) create mode 100644 .isort.cfg diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 000000000..11c895f4d --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,7 @@ +[settings] +skip_glob=venv* +skip=letsencrypt-auto-source +force_sort_within_sections=True +force_single_line=True +order_by_type=False +line_length=400 diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index 8a034470a..d1679fcad 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -13,7 +13,6 @@ import warnings # # It is based on # https://github.com/requests/requests/blob/1278ecdf71a312dc2268f3bfc0aabfab3c006dcf/requests/packages.py - import josepy as jose for mod in list(sys.modules): diff --git a/acme/acme/client.py b/acme/acme/client.py index 4f5be0176..928b86d03 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -5,25 +5,26 @@ import datetime from email.utils import parsedate_tz import heapq import logging -import time import re import sys +import time -import six -from six.moves import http_client # pylint: disable=import-error import josepy as jose import OpenSSL import requests from requests.adapters import HTTPAdapter from requests_toolbelt.adapters.source import SourceAddressAdapter +import six +from six.moves import http_client # pylint: disable=import-error from acme import crypto_util from acme import errors from acme import jws from acme import messages -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Set, Text - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Text # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 0be3cc896..66dfc738c 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -6,15 +6,15 @@ import os import re import socket -from OpenSSL import crypto -from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 import josepy as jose +from OpenSSL import crypto +from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 from acme import errors -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Union, Tuple, Optional -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) diff --git a/acme/acme/fields.py b/acme/acme/fields.py index d7ec78403..3b5672283 100644 --- a/acme/acme/fields.py +++ b/acme/acme/fields.py @@ -4,7 +4,6 @@ import logging import josepy as jose import pyrfc3339 - logger = logging.getLogger(__name__) diff --git a/acme/acme/magic_typing.py b/acme/acme/magic_typing.py index 471b8dfa9..5a6358c69 100644 --- a/acme/acme/magic_typing.py +++ b/acme/acme/magic_typing.py @@ -1,6 +1,7 @@ """Shim class to not have to depend on typing module in prod.""" import sys + class TypingClass(object): """Ignore import errors by getting anything""" def __getattr__(self, name): diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 2bfe688d2..a87abdb91 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -1,18 +1,21 @@ """ACME protocol messages.""" import json -import six -try: - from collections.abc import Hashable # pylint: disable=no-name-in-module -except ImportError: # pragma: no cover - from collections import Hashable import josepy as jose +import six from acme import challenges from acme import errors from acme import fields -from acme import util from acme import jws +from acme import util + +try: + from collections.abc import Hashable # pylint: disable=no-name-in-module +except ImportError: # pragma: no cover + from collections import Hashable + + OLD_ERROR_PREFIX = "urn:acme:error:" ERROR_PREFIX = "urn:ietf:params:acme:error:" diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index e355dca38..0b66976e4 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -11,8 +11,7 @@ from six.moves import socketserver # type: ignore # pylint: disable=import-err from acme import challenges from acme import crypto_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) diff --git a/acme/docs/conf.py b/acme/docs/conf.py index e70651648..01029a81f 100644 --- a/acme/docs/conf.py +++ b/acme/docs/conf.py @@ -12,10 +12,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os import shlex - +import sys here = os.path.abspath(os.path.dirname(__file__)) diff --git a/acme/examples/http01_example.py b/acme/examples/http01_example.py index 79508f1b4..2dc197d09 100644 --- a/acme/examples/http01_example.py +++ b/acme/examples/http01_example.py @@ -26,8 +26,10 @@ Workflow: - Deactivate Account """ from contextlib import contextmanager + from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa +import josepy as jose import OpenSSL from acme import challenges @@ -36,7 +38,6 @@ from acme import crypto_util from acme import errors from acme import messages from acme import standalone -import josepy as jose # Constants: diff --git a/acme/setup.py b/acme/setup.py index 5397cf2ae..6da5fe519 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand -import sys version = '1.1.0.dev0' diff --git a/acme/tests/challenges_test.py b/acme/tests/challenges_test.py index 819ba9261..f8a503b3c 100644 --- a/acme/tests/challenges_test.py +++ b/acme/tests/challenges_test.py @@ -4,7 +4,6 @@ import unittest import josepy as jose import mock import requests - from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import import test_util diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 22eb3fc45..7c1d9f68e 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -5,19 +5,17 @@ import datetime import json import unittest -from six.moves import http_client # pylint: disable=import-error - import josepy as jose import mock import OpenSSL import requests +from six.moves import http_client # pylint: disable=import-error from acme import challenges from acme import errors from acme import jws as acme_jws from acme import messages -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module import messages_test import test_util diff --git a/acme/tests/crypto_util_test.py b/acme/tests/crypto_util_test.py index d351c1a3d..a7ce51f92 100644 --- a/acme/tests/crypto_util_test.py +++ b/acme/tests/crypto_util_test.py @@ -5,17 +5,16 @@ import threading import time import unittest -import six -from six.moves import socketserver #type: ignore # pylint: disable=import-error - import josepy as jose import OpenSSL +import six +from six.moves import socketserver # type: ignore # pylint: disable=import-error from acme import errors -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module import test_util + class SSLSocketAndProbeSNITest(unittest.TestCase): """Tests for acme.crypto_util.SSLSocket/probe_sni.""" diff --git a/acme/tests/jose_test.py b/acme/tests/jose_test.py index 340624a4f..899ad7074 100644 --- a/acme/tests/jose_test.py +++ b/acme/tests/jose_test.py @@ -2,6 +2,7 @@ import importlib import unittest + class JoseTest(unittest.TestCase): """Tests for acme.jose shim.""" diff --git a/acme/tests/jws_test.py b/acme/tests/jws_test.py index e43ed55e6..2e6ad72dd 100644 --- a/acme/tests/jws_test.py +++ b/acme/tests/jws_test.py @@ -5,7 +5,6 @@ import josepy as jose import test_util - KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py index 269970b1c..2700fc23b 100644 --- a/acme/tests/messages_test.py +++ b/acme/tests/messages_test.py @@ -5,8 +5,7 @@ import josepy as jose import mock from acme import challenges -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module import test_util CERT = test_util.load_comparable_cert('cert.der') diff --git a/acme/tests/standalone_test.py b/acme/tests/standalone_test.py index 0be57bad4..83ced12b0 100644 --- a/acme/tests/standalone_test.py +++ b/acme/tests/standalone_test.py @@ -3,18 +3,17 @@ import socket import threading import unittest -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error - import josepy as jose import mock import requests +from six.moves import http_client # pylint: disable=import-error +from six.moves import socketserver # type: ignore # pylint: disable=import-error from acme import challenges -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module import test_util + class TLSServerTest(unittest.TestCase): """Tests for acme.standalone.TLSServer.""" diff --git a/acme/tests/test_util.py b/acme/tests/test_util.py index 6737bff4e..6525f3af5 100644 --- a/acme/tests/test_util.py +++ b/acme/tests/test_util.py @@ -4,12 +4,12 @@ """ import os -import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import josepy as jose from OpenSSL import crypto +import pkg_resources def load_vector(*names): diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 5df61ecdc..f1035aa33 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -1,5 +1,6 @@ """Apache Configurator.""" # pylint: disable=too-many-lines +from collections import defaultdict import copy import fnmatch import logging @@ -7,28 +8,26 @@ import re import socket import time -from collections import defaultdict - import pkg_resources import six - import zope.component import zope.interface from acme import challenges -from acme.magic_typing import DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util - from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common -from certbot.plugins.util import path_surgery from certbot.plugins.enhancements import AutoHSTSEnhancement - +from certbot.plugins.util import path_surgery from certbot_apache._internal import apache_util from certbot_apache._internal import constants from certbot_apache._internal import display_ops diff --git a/certbot-apache/certbot_apache/_internal/constants.py b/certbot-apache/certbot_apache/_internal/constants.py index a0f455a02..47e3be856 100644 --- a/certbot-apache/certbot_apache/_internal/constants.py +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -3,7 +3,6 @@ import pkg_resources from certbot.compat import os - MOD_SSL_CONF_DEST = "options-ssl-apache.conf" """Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" diff --git a/certbot-apache/certbot_apache/_internal/display_ops.py b/certbot-apache/certbot_apache/_internal/display_ops.py index 4e746f5b8..1ae32bb47 100644 --- a/certbot-apache/certbot_apache/_internal/display_ops.py +++ b/certbot-apache/certbot_apache/_internal/display_ops.py @@ -3,10 +3,10 @@ import logging import zope.component -import certbot.display.util as display_util from certbot import errors from certbot import interfaces from certbot.compat import os +import certbot.display.util as display_util logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/_internal/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py index 96ffee1b3..d43094976 100644 --- a/certbot-apache/certbot_apache/_internal/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -4,13 +4,12 @@ from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error from certbot import util - from certbot_apache._internal import configurator from certbot_apache._internal import override_arch -from certbot_apache._internal import override_fedora +from certbot_apache._internal import override_centos from certbot_apache._internal import override_darwin from certbot_apache._internal import override_debian -from certbot_apache._internal import override_centos +from certbot_apache._internal import override_fedora from certbot_apache._internal import override_gentoo from certbot_apache._internal import override_suse diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index a4be4853e..62c6db272 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,13 +1,12 @@ """A class that performs HTTP-01 challenges for Apache""" import logging -from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins import common - from certbot_apache._internal.obj import VirtualHost # pylint: disable=unused-import from certbot_apache._internal.parser import get_aug_path diff --git a/certbot-apache/certbot_apache/_internal/obj.py b/certbot-apache/certbot_apache/_internal/obj.py index dd4018155..8b3aeb376 100644 --- a/certbot-apache/certbot_apache/_internal/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -1,7 +1,7 @@ """Module contains classes used by the Apache Configurator.""" import re -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot.plugins import common diff --git a/certbot-apache/certbot_apache/_internal/override_arch.py b/certbot-apache/certbot_apache/_internal/override_arch.py index c4cc1ce03..2765bd238 100644 --- a/certbot-apache/certbot_apache/_internal/override_arch.py +++ b/certbot-apache/certbot_apache/_internal/override_arch.py @@ -1,13 +1,12 @@ """ Distribution specific override class for Arch Linux """ import pkg_resources - import zope.interface from certbot import interfaces from certbot.compat import os - from certbot_apache._internal import configurator + @zope.interface.provider(interfaces.IPluginFactory) class ArchConfigurator(configurator.ApacheConfigurator): """Arch Linux specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index 9de91e7a7..b3576e083 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -4,19 +4,16 @@ import logging import pkg_resources import zope.interface +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import os from certbot.errors import MisconfigurationError - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser - logger = logging.getLogger(__name__) diff --git a/certbot-apache/certbot_apache/_internal/override_darwin.py b/certbot-apache/certbot_apache/_internal/override_darwin.py index 254d738bb..00faff623 100644 --- a/certbot-apache/certbot_apache/_internal/override_darwin.py +++ b/certbot-apache/certbot_apache/_internal/override_darwin.py @@ -1,13 +1,12 @@ """ Distribution specific override class for macOS """ import pkg_resources - import zope.interface from certbot import interfaces from certbot.compat import os - from certbot_apache._internal import configurator + @zope.interface.provider(interfaces.IPluginFactory) class DarwinConfigurator(configurator.ApacheConfigurator): """macOS specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/_internal/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py index 37906808e..aefc4c6d4 100644 --- a/certbot-apache/certbot_apache/_internal/override_debian.py +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -9,7 +9,6 @@ from certbot import interfaces from certbot import util from certbot.compat import filesystem from certbot.compat import os - from certbot_apache._internal import apache_util from certbot_apache._internal import configurator diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py index e6045a634..a9607a60f 100644 --- a/certbot-apache/certbot_apache/_internal/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -6,7 +6,6 @@ from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import os - from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser diff --git a/certbot-apache/certbot_apache/_internal/override_gentoo.py b/certbot-apache/certbot_apache/_internal/override_gentoo.py index 845530b31..38f8aebe9 100644 --- a/certbot-apache/certbot_apache/_internal/override_gentoo.py +++ b/certbot-apache/certbot_apache/_internal/override_gentoo.py @@ -1,15 +1,14 @@ """ Distribution specific override class for Gentoo Linux """ import pkg_resources - import zope.interface from certbot import interfaces from certbot.compat import os - from certbot_apache._internal import apache_util from certbot_apache._internal import configurator from certbot_apache._internal import parser + @zope.interface.provider(interfaces.IPluginFactory) class GentooConfigurator(configurator.ApacheConfigurator): """Gentoo specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/_internal/override_suse.py b/certbot-apache/certbot_apache/_internal/override_suse.py index ab217bc0f..0c9219e6d 100644 --- a/certbot-apache/certbot_apache/_internal/override_suse.py +++ b/certbot-apache/certbot_apache/_internal/override_suse.py @@ -1,13 +1,12 @@ """ Distribution specific override class for OpenSUSE """ import pkg_resources - import zope.interface from certbot import interfaces from certbot.compat import os - from certbot_apache._internal import configurator + @zope.interface.provider(interfaces.IPluginFactory) class OpenSUSEConfigurator(configurator.ApacheConfigurator): """OpenSUSE specific ApacheConfigurator override class""" diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index 759518a2c..5c447ed27 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -8,11 +8,11 @@ import sys import six -from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os - from certbot_apache._internal import constants logger = logging.getLogger(__name__) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 821e23014..f043e8857 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-apache/tests/autohsts_test.py b/certbot-apache/tests/autohsts_test.py index 5911f2b88..c9901ecdb 100644 --- a/certbot-apache/tests/autohsts_test.py +++ b/certbot-apache/tests/autohsts_test.py @@ -2,13 +2,12 @@ """Test for certbot_apache._internal.configurator AutoHSTS functionality""" import re import unittest + import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import +import six # pylint: disable=unused-import # six is used in mock.patch() from certbot import errors from certbot_apache._internal import constants - import util diff --git a/certbot-apache/tests/centos6_test.py b/certbot-apache/tests/centos6_test.py index 6c0fae24c..15d086600 100644 --- a/certbot-apache/tests/centos6_test.py +++ b/certbot-apache/tests/centos6_test.py @@ -3,11 +3,9 @@ import unittest from certbot.compat import os from certbot.errors import MisconfigurationError - from certbot_apache._internal import obj from certbot_apache._internal import override_centos from certbot_apache._internal import parser - import util diff --git a/certbot-apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py index 2f7ab9b54..8959d73b8 100644 --- a/certbot-apache/tests/centos_test.py +++ b/certbot-apache/tests/centos_test.py @@ -6,10 +6,8 @@ import mock from certbot import errors from certbot.compat import filesystem from certbot.compat import os - from certbot_apache._internal import obj from certbot_apache._internal import override_centos - import util diff --git a/certbot-apache/tests/complex_parsing_test.py b/certbot-apache/tests/complex_parsing_test.py index b42e82996..8b795b0b6 100644 --- a/certbot-apache/tests/complex_parsing_test.py +++ b/certbot-apache/tests/complex_parsing_test.py @@ -4,7 +4,6 @@ import unittest from certbot import errors from certbot.compat import os - import util diff --git a/certbot-apache/tests/configurator_reverter_test.py b/certbot-apache/tests/configurator_reverter_test.py index 5718d782f..ad8e73347 100644 --- a/certbot-apache/tests/configurator_reverter_test.py +++ b/certbot-apache/tests/configurator_reverter_test.py @@ -5,7 +5,6 @@ import unittest import mock from certbot import errors - import util diff --git a/certbot-apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py index 4a75eaedc..9fab5ea5d 100644 --- a/certbot-apache/tests/configurator_test.py +++ b/certbot-apache/tests/configurator_test.py @@ -7,24 +7,20 @@ import tempfile import unittest import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import +import six # pylint: disable=unused-import # six is used in mock.patch() from acme import challenges - from certbot import achallenges from certbot import crypto_util from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as certbot_util - from certbot_apache._internal import apache_util from certbot_apache._internal import constants from certbot_apache._internal import obj from certbot_apache._internal import parser - import util diff --git a/certbot-apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py index 1c6e3bf71..6e63a9bd3 100644 --- a/certbot-apache/tests/debian_test.py +++ b/certbot-apache/tests/debian_test.py @@ -6,10 +6,8 @@ import mock from certbot import errors from certbot.compat import os - from certbot_apache._internal import apache_util from certbot_apache._internal import obj - import util diff --git a/certbot-apache/tests/display_ops_test.py b/certbot-apache/tests/display_ops_test.py index 7463071e4..50bdc03cf 100644 --- a/certbot-apache/tests/display_ops_test.py +++ b/certbot-apache/tests/display_ops_test.py @@ -4,15 +4,10 @@ import unittest import mock from certbot import errors - from certbot.display import util as display_util - from certbot.tests import util as certbot_util - from certbot_apache._internal import obj - from certbot_apache._internal.display_ops import select_vhost_multiple - import util diff --git a/certbot-apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py index 38435edfb..2bfd6babb 100644 --- a/certbot-apache/tests/fedora_test.py +++ b/certbot-apache/tests/fedora_test.py @@ -6,10 +6,8 @@ import mock from certbot import errors from certbot.compat import filesystem from certbot.compat import os - from certbot_apache._internal import obj from certbot_apache._internal import override_fedora - import util diff --git a/certbot-apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py index 152c36ba9..90a163fd3 100644 --- a/certbot-apache/tests/gentoo_test.py +++ b/certbot-apache/tests/gentoo_test.py @@ -6,10 +6,8 @@ import mock from certbot import errors from certbot.compat import filesystem from certbot.compat import os - from certbot_apache._internal import obj from certbot_apache._internal import override_gentoo - import util diff --git a/certbot-apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py index 99f5e7186..643a6bdd5 100644 --- a/certbot-apache/tests/http_01_test.py +++ b/certbot-apache/tests/http_01_test.py @@ -1,21 +1,18 @@ """Test for certbot_apache._internal.http_01.""" import unittest + import mock from acme import challenges from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges from certbot import errors from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util - from certbot_apache._internal.parser import get_aug_path - import util - NUM_ACHALLS = 3 diff --git a/certbot-apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py index 36d02292e..b334ce52e 100644 --- a/certbot-apache/tests/parser_test.py +++ b/certbot-apache/tests/parser_test.py @@ -6,7 +6,6 @@ import mock from certbot import errors from certbot.compat import os - import util diff --git a/certbot-apache/tests/util.py b/certbot-apache/tests/util.py index f5b07ed8c..57b20dc9d 100644 --- a/certbot-apache/tests/util.py +++ b/certbot-apache/tests/util.py @@ -12,7 +12,6 @@ from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import common from certbot.tests import util as test_util - from certbot_apache._internal import configurator from certbot_apache._internal import entrypoint from certbot_apache._internal import obj diff --git a/certbot-ci/certbot_integration_tests/assets/hook.py b/certbot-ci/certbot_integration_tests/assets/hook.py index ff735a216..39aa72ac5 100755 --- a/certbot-ci/certbot_integration_tests/assets/hook.py +++ b/certbot-ci/certbot_integration_tests/assets/hook.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import sys import os +import sys hook_script_type = os.path.basename(os.path.dirname(sys.argv[1])) if hook_script_type == 'deploy' and ('RENEWED_DOMAINS' not in os.environ or 'RENEWED_LINEAGE' not in os.environ): diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py index 5177ffbd2..1b5914d1a 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -1,5 +1,6 @@ """This module contains advanced assertions for the certbot integration tests.""" import os + try: import grp POSIX_MODE = True diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index cd4c316d2..94e76cf79 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -2,21 +2,25 @@ from __future__ import print_function import os +from os.path import exists +from os.path import join import re import shutil import subprocess import time -from os.path import join, exists import pytest + from certbot_integration_tests.certbot_tests import context as certbot_context -from certbot_integration_tests.certbot_tests.assertions import ( - assert_hook_execution, assert_saved_renew_hook, - assert_cert_count_for_lineage, - assert_world_no_permissions, assert_world_read_permissions, - assert_equals_group_owner, assert_equals_group_permissions, assert_equals_world_read_permissions, - EVERYBODY_SID -) +from certbot_integration_tests.certbot_tests.assertions import assert_cert_count_for_lineage +from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_owner +from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_equals_world_read_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_hook_execution +from certbot_integration_tests.certbot_tests.assertions import assert_saved_renew_hook +from certbot_integration_tests.certbot_tests.assertions import assert_world_no_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_world_read_permissions +from certbot_integration_tests.certbot_tests.assertions import EVERYBODY_SID from certbot_integration_tests.utils import misc diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index d52e4fb58..0879c829c 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -7,8 +7,8 @@ for a directory a specific configuration using built-in pytest hooks. See https://docs.pytest.org/en/latest/reference.html#hook-reference """ import contextlib -import sys import subprocess +import sys from certbot_integration_tests.utils import acme_server as acme_lib diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py index 61facc6af..3a769840c 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/context.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -2,8 +2,9 @@ import os import subprocess from certbot_integration_tests.certbot_tests import context as certbot_context -from certbot_integration_tests.utils import misc, certbot_call from certbot_integration_tests.nginx_tests import nginx_config as config +from certbot_integration_tests.utils import certbot_call +from certbot_integration_tests.utils import misc class IntegrationTestsContext(certbot_context.IntegrationTestsContext): diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index 9e7ead916..fbf97fef1 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -1,19 +1,22 @@ #!/usr/bin/env python """Module to setup an ACME CA server environment able to run multiple tests in parallel""" from __future__ import print_function + import errno import json -import tempfile -import time import os -import subprocess +from os.path import join import shutil +import subprocess import sys -from os.path import join +import tempfile +import time import requests -from certbot_integration_tests.utils import misc, proxy, pebble_artifacts +from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils import pebble_artifacts +from certbot_integration_tests.utils import proxy from certbot_integration_tests.utils.constants import * diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index 949852c0a..2ddaa41c8 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -1,10 +1,11 @@ #!/usr/bin/env python """Module to call certbot in test mode""" from __future__ import absolute_import + from distutils.version import LooseVersion +import os import subprocess import sys -import os import certbot_integration_tests from certbot_integration_tests.utils.constants import * diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index db910b9ec..b08f11e89 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -3,27 +3,27 @@ Misc module contains stateless functions that could be used during pytest execut or outside during setup/teardown of the integration tests environment. """ import contextlib -import logging import errno import multiprocessing import os import re import shutil import stat -import subprocess import sys import tempfile import time import warnings -from distutils.version import LooseVersion -import pkg_resources -import requests -from OpenSSL import crypto from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption -from six.moves import socketserver, SimpleHTTPServer +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.serialization import NoEncryption +from cryptography.hazmat.primitives.serialization import PrivateFormat +from OpenSSL import crypto +import pkg_resources +import requests +from six.moves import SimpleHTTPServer +from six.moves import socketserver RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py index 2c5dea4a2..9458560e8 100755 --- a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py +++ b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py @@ -6,17 +6,18 @@ to serve a mock OCSP responder during integration tests against Pebble. import datetime import re -import requests -from dateutil import parser - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization, hashes from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization from cryptography.x509 import ocsp +from dateutil import parser +import requests from six.moves import BaseHTTPServer +from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT +from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL from certbot_integration_tests.utils.misc import GracefulTCPServer -from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT, PEBBLE_MANAGEMENT_URL class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): diff --git a/certbot-ci/certbot_integration_tests/utils/proxy.py b/certbot-ci/certbot_integration_tests/utils/proxy.py index 69248c771..3a16adebf 100644 --- a/certbot-ci/certbot_integration_tests/utils/proxy.py +++ b/certbot-ci/certbot_integration_tests/utils/proxy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import json -import sys import re +import sys import requests from six.moves import BaseHTTPServer diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 025bb3c81..71052bd3e 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -1,8 +1,9 @@ -import sys - from distutils.version import StrictVersion -from setuptools import setup, find_packages, __version__ as setuptools_version +import sys +from setuptools import __version__ as setuptools_version +from setuptools import find_packages +from setuptools import setup version = '0.32.0.dev0' diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 67cbc7ad9..a9b1ce87e 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -6,9 +6,9 @@ import subprocess import mock import zope.interface -from certbot._internal import configuration from certbot import errors as le_errors from certbot import util as certbot_util +from certbot._internal import configuration from certbot_apache._internal import entrypoint from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index 47ebac741..c35aa4ba5 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -8,7 +8,6 @@ from certbot._internal import constants from certbot_compatibility_test import errors from certbot_compatibility_test import util - logger = logging.getLogger(__name__) diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 5185b8a5d..3011b9823 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -6,14 +6,13 @@ import subprocess import zope.interface from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module - from certbot._internal import configuration -from certbot_nginx._internal import configurator -from certbot_nginx._internal import constants from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces from certbot_compatibility_test import util from certbot_compatibility_test.configurators import common as configurators_common +from certbot_nginx._internal import configurator +from certbot_nginx._internal import constants @zope.interface.implementer(interfaces.IConfiguratorProxy) diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 72204367e..03e1283c6 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -5,31 +5,28 @@ import filecmp import logging import os import shutil +import sys import tempfile import time -import sys -from urllib3.util import connection import OpenSSL - from six.moves import xrange # pylint: disable=import-error,redefined-builtin +from urllib3.util import connection from acme import challenges from acme import crypto_util from acme import messages -from acme.magic_typing import List, Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors as le_errors from certbot.tests import acme_util - from certbot_compatibility_test import errors from certbot_compatibility_test import util from certbot_compatibility_test import validator - from certbot_compatibility_test.configurators.apache import common as a_common from certbot_compatibility_test.configurators.nginx import common as n_common - DESCRIPTION = """ Tests Certbot plugins against different server configurations. It is assumed that Docker is already installed. If no test type is specified, all diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index 4f93e5561..3465b7143 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -8,12 +8,10 @@ import tarfile import josepy as jose -from certbot.tests import util as test_util from certbot._internal import constants - +from certbot.tests import util as test_util from certbot_compatibility_test import errors - _KEY_BASE = "rsa2048_key.pem" KEY_PATH = test_util.vector_path(_KEY_BASE) KEY = test_util.load_pyopenssl_private_key(_KEY_BASE) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator.py b/certbot-compatibility-test/certbot_compatibility_test/validator.py index 3455ce82d..796ebbe9d 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator.py @@ -1,15 +1,14 @@ """Validators to determine the current webserver configuration""" import logging import socket -import requests +import requests import six -from six.moves import xrange # pylint: disable=import-error,redefined-builtin +from six.moves import xrange # pylint: disable=import-error, redefined-builtin from acme import crypto_util from acme import errors as acme_errors - logger = logging.getLogger(__name__) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py index c4a668c5e..86edbdb55 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py @@ -1,9 +1,9 @@ """Tests for certbot_compatibility_test.validator.""" import unittest -import requests import mock import OpenSSL +import requests from acme import errors as acme_errors from certbot_compatibility_test import validator diff --git a/certbot-compatibility-test/nginx/roundtrip.py b/certbot-compatibility-test/nginx/roundtrip.py index e2d518e00..afc68647d 100644 --- a/certbot-compatibility-test/nginx/roundtrip.py +++ b/certbot-compatibility-test/nginx/roundtrip.py @@ -5,6 +5,7 @@ import sys from certbot_nginx._internal import nginxparser + def roundtrip(stuff): success = True for t in stuff: diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index cea364290..f26fb0706 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -1,8 +1,7 @@ import sys -from setuptools import setup from setuptools import find_packages - +from setuptools import setup version = '1.1.0.dev0' diff --git a/certbot-dns-cloudflare/docs/conf.py b/certbot-dns-cloudflare/docs/conf.py index aa7809246..488268577 100644 --- a/certbot-dns-cloudflare/docs/conf.py +++ b/certbot-dns-cloudflare/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 04f9ce75a..d1c761d19 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py index 5132137f8..2a0f12ea7 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -1,8 +1,8 @@ """DNS Authenticator for CloudXNS DNS.""" import logging -import zope.interface from lexicon.providers import cloudxns +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-cloudxns/docs/conf.py b/certbot-dns-cloudxns/docs/conf.py index 9e2f4c0e6..16ccd1d62 100644 --- a/certbot-dns-cloudxns/docs/conf.py +++ b/certbot-dns-cloudxns/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index f7d751644..33afdfb60 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-cloudxns/tests/dns_cloudxns_test.py b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py index 7b8d0944d..a1e3cde89 100644 --- a/certbot-dns-cloudxns/tests/dns_cloudxns_test.py +++ b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py @@ -3,7 +3,8 @@ import unittest import mock -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException from certbot.compat import os from certbot.plugins import dns_test_common diff --git a/certbot-dns-digitalocean/docs/conf.py b/certbot-dns-digitalocean/docs/conf.py index e223b1535..9c493a220 100644 --- a/certbot-dns-digitalocean/docs/conf.py +++ b/certbot-dns-digitalocean/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 4e41323a2..21c31d803 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py index ad2a3fa30..8c48d31e7 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -1,8 +1,8 @@ """DNS Authenticator for DNSimple DNS.""" import logging -import zope.interface from lexicon.providers import dnsimple +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-dnsimple/docs/conf.py b/certbot-dns-dnsimple/docs/conf.py index da692fb9e..b5cb24e2f 100644 --- a/certbot-dns-dnsimple/docs/conf.py +++ b/certbot-dns-dnsimple/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 664160d53..fbdcea142 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -1,9 +1,9 @@ import os -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py index 4cd8721ce..ed3146dce 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -1,8 +1,8 @@ """DNS Authenticator for DNS Made Easy DNS.""" import logging -import zope.interface from lexicon.providers import dnsmadeeasy +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-dnsmadeeasy/docs/conf.py b/certbot-dns-dnsmadeeasy/docs/conf.py index 7d26f9742..60e0163bd 100644 --- a/certbot-dns-dnsmadeeasy/docs/conf.py +++ b/certbot-dns-dnsmadeeasy/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 87a50d934..c25a49c59 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index e64e62da9..18090c95a 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -1,8 +1,8 @@ """DNS Authenticator for Gehirn Infrastracture Service DNS.""" import logging -import zope.interface from lexicon.providers import gehirn +import zope.interface from certbot import interfaces from certbot.plugins import dns_common diff --git a/certbot-dns-gehirn/docs/conf.py b/certbot-dns-gehirn/docs/conf.py index a1b2799fb..67aafa3b4 100644 --- a/certbot-dns-gehirn/docs/conf.py +++ b/certbot-dns-gehirn/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 0dbbda4e9..746df1b62 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py index b722a38cf..f603a2dc3 100644 --- a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -2,11 +2,11 @@ import json import logging -import httplib2 -import zope.interface from googleapiclient import discovery from googleapiclient import errors as googleapiclient_errors +import httplib2 from oauth2client.service_account import ServiceAccountCredentials +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-google/docs/conf.py b/certbot-dns-google/docs/conf.py index bbb343ee8..8f045cf3f 100644 --- a/certbot-dns-google/docs/conf.py +++ b/certbot-dns-google/docs/conf.py @@ -18,6 +18,7 @@ # import os import sys + sys.path.insert(0, os.path.abspath('_ext')) diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index b823a7f8f..f42e4d13d 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-google/tests/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py index e91db58ab..ddb0da813 100644 --- a/certbot-dns-google/tests/dns_google_test.py +++ b/certbot-dns-google/tests/dns_google_test.py @@ -2,11 +2,11 @@ import unittest -import mock from googleapiclient import discovery from googleapiclient.errors import Error from googleapiclient.http import HttpMock from httplib2 import ServerNotFoundError +import mock from certbot import errors from certbot.compat import os diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py index 507ad5e53..ea6046849 100644 --- a/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -2,9 +2,9 @@ import logging import re -import zope.interface from lexicon.providers import linode from lexicon.providers import linode4 +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-linode/docs/conf.py b/certbot-dns-linode/docs/conf.py index 1fb721400..f23d65023 100644 --- a/certbot-dns-linode/docs/conf.py +++ b/certbot-dns-linode/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 9aa45f366..8bbcfd3af 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand -import sys version = '1.1.0.dev0' diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py index 7cdd4c8e1..7c18c7131 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -1,8 +1,8 @@ """DNS Authenticator for LuaDNS DNS.""" import logging -import zope.interface from lexicon.providers import luadns +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-luadns/docs/conf.py b/certbot-dns-luadns/docs/conf.py index bd81d5a5f..899480f66 100644 --- a/certbot-dns-luadns/docs/conf.py +++ b/certbot-dns-luadns/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index adc6edd17..43a865b93 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py index b585ddb7a..f5af37389 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -1,8 +1,8 @@ """DNS Authenticator for NS1 DNS.""" import logging -import zope.interface from lexicon.providers import nsone +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py index cffe2a25c..aec0771a2 100644 --- a/certbot-dns-nsone/docs/conf.py +++ b/certbot-dns-nsone/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 4b30c7055..bb8cbe5b0 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py index 84771b0a8..a495983f2 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -1,8 +1,8 @@ """DNS Authenticator for OVH DNS.""" import logging -import zope.interface from lexicon.providers import ovh +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-ovh/docs/conf.py b/certbot-dns-ovh/docs/conf.py index 57194666e..a4985edee 100644 --- a/certbot-dns-ovh/docs/conf.py +++ b/certbot-dns-ovh/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 012399ded..3d0df5026 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-rfc2136/docs/conf.py b/certbot-dns-rfc2136/docs/conf.py index 8cc5d595f..e4df84594 100644 --- a/certbot-dns-rfc2136/docs/conf.py +++ b/certbot-dns-rfc2136/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 9385b2a98..c17610366 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py index e32017b34..637558304 100644 --- a/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -4,15 +4,17 @@ import logging import time import boto3 +from botocore.exceptions import ClientError +from botocore.exceptions import NoCredentialsError import zope.interface -from botocore.exceptions import NoCredentialsError, ClientError +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot.plugins import dns_common -from acme.magic_typing import DefaultDict, List, Dict # pylint: disable=unused-import, no-name-in-module - logger = logging.getLogger(__name__) INSTRUCTIONS = ( @@ -20,6 +22,7 @@ INSTRUCTIONS = ( "https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials " # pylint: disable=line-too-long "and add the necessary permissions for Route53 access.") + @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_common.DNSAuthenticator): diff --git a/certbot-dns-route53/docs/conf.py b/certbot-dns-route53/docs/conf.py index 25a7c6e4d..cb8aae0b6 100644 --- a/certbot-dns-route53/docs/conf.py +++ b/certbot-dns-route53/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 79780de97..c0f69fb4f 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand -import sys version = '1.1.0.dev0' diff --git a/certbot-dns-route53/tests/dns_route53_test.py b/certbot-dns-route53/tests/dns_route53_test.py index 180ebdf6b..85ec259b1 100644 --- a/certbot-dns-route53/tests/dns_route53_test.py +++ b/certbot-dns-route53/tests/dns_route53_test.py @@ -2,8 +2,9 @@ import unittest +from botocore.exceptions import ClientError +from botocore.exceptions import NoCredentialsError import mock -from botocore.exceptions import NoCredentialsError, ClientError from certbot import errors from certbot.compat import os diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py index d6e20894d..25042bfc6 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -1,8 +1,8 @@ """DNS Authenticator for Sakura Cloud DNS.""" import logging -import zope.interface from lexicon.providers import sakuracloud +import zope.interface from certbot import interfaces from certbot.plugins import dns_common diff --git a/certbot-dns-sakuracloud/docs/conf.py b/certbot-dns-sakuracloud/docs/conf.py index e14fe1d4c..f973779ab 100644 --- a/certbot-dns-sakuracloud/docs/conf.py +++ b/certbot-dns-sakuracloud/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 1bfb1ba86..68795cd2b 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand -import sys version = '1.1.0.dev0' diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index e212ba40d..6802df421 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,6 +1,6 @@ """Nginx Configuration""" # https://github.com/PyCQA/pylint/issues/73 -from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error +from distutils.version import LooseVersion # pylint: disable=no-name-in-module, import-error import logging import re import socket @@ -8,22 +8,21 @@ import subprocess import tempfile import time -import pkg_resources - import OpenSSL +import pkg_resources import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util -from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import util from certbot.compat import os from certbot.plugins import common - from certbot_nginx._internal import constants from certbot_nginx._internal import display_ops from certbot_nginx._internal import http_01 diff --git a/certbot-nginx/certbot_nginx/_internal/display_ops.py b/certbot-nginx/certbot_nginx/_internal/display_ops.py index 9b973d8d3..bbb47f98a 100644 --- a/certbot-nginx/certbot_nginx/_internal/display_ops.py +++ b/certbot-nginx/certbot_nginx/_internal/display_ops.py @@ -4,10 +4,8 @@ import logging import zope.component from certbot import interfaces - import certbot.display.util as display_util - logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py index 7223548d9..97b111576 100644 --- a/certbot-nginx/certbot_nginx/_internal/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -3,15 +3,12 @@ import logging from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os from certbot.plugins import common - -from certbot_nginx._internal import obj from certbot_nginx._internal import nginxparser - +from certbot_nginx._internal import obj logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index f4603dcde..04b1ffa01 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -3,10 +3,18 @@ import copy import logging -from pyparsing import ( - Literal, White, Forward, Group, Optional, OneOrMore, QuotedString, Regex, ZeroOrMore, Combine) -from pyparsing import stringEnd +from pyparsing import Combine +from pyparsing import Forward +from pyparsing import Group +from pyparsing import Literal +from pyparsing import OneOrMore +from pyparsing import Optional +from pyparsing import QuotedString +from pyparsing import Regex from pyparsing import restOfLine +from pyparsing import stringEnd +from pyparsing import White +from pyparsing import ZeroOrMore import six logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py index a0d375437..0b1eb1682 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -4,16 +4,19 @@ import functools import glob import logging import re -import pyparsing +import pyparsing import six +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os - -from certbot_nginx._internal import obj from certbot_nginx._internal import nginxparser -from acme.magic_typing import Union, Dict, Set, Any, List, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot_nginx._internal import obj logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/_internal/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py index 71e8c6088..e03913887 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser_obj.py +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -3,12 +3,12 @@ raw lists of tokens from pyparsing. """ import abc import logging + import six +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - logger = logging.getLogger(__name__) COMMENT = " managed by Certbot" COMMENT_BLOCK = ["#", COMMENT] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 8cd7e24f4..c844c85f5 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -1,8 +1,8 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand version = '1.1.0.dev0' diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index 9204d464d..afa28befb 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -1,22 +1,20 @@ """Test for certbot_nginx._internal.configurator.""" import unittest -import OpenSSL import mock +import OpenSSL + from acme import challenges from acme import messages - from certbot import achallenges from certbot import crypto_util from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_test_util - from certbot_nginx._internal import obj from certbot_nginx._internal import parser from certbot_nginx._internal.configurator import _redirect_block_for_domain from certbot_nginx._internal.nginxparser import UnspacedList - import test_util as util diff --git a/certbot-nginx/tests/display_ops_test.py b/certbot-nginx/tests/display_ops_test.py index e8a3c81d3..377255441 100644 --- a/certbot-nginx/tests/display_ops_test.py +++ b/certbot-nginx/tests/display_ops_test.py @@ -2,13 +2,9 @@ import unittest from certbot.display import util as display_util - from certbot.tests import util as certbot_util - from certbot_nginx._internal import parser - from certbot_nginx._internal.display_ops import select_vhost_multiple - import test_util as util diff --git a/certbot-nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py index 8473d2038..6418a8841 100644 --- a/certbot-nginx/tests/http_01_test.py +++ b/certbot-nginx/tests/http_01_test.py @@ -6,14 +6,10 @@ import mock import six from acme import challenges - from certbot import achallenges - from certbot.tests import acme_util from certbot.tests import util as test_util - from certbot_nginx._internal.obj import Addr - import test_util as util AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) diff --git a/certbot-nginx/tests/nginxparser_test.py b/certbot-nginx/tests/nginxparser_test.py index 77fb8a1b2..a5212078f 100644 --- a/certbot-nginx/tests/nginxparser_test.py +++ b/certbot-nginx/tests/nginxparser_test.py @@ -6,12 +6,14 @@ import unittest from pyparsing import ParseException -from certbot_nginx._internal.nginxparser import ( - RawNginxParser, loads, load, dumps, dump, UnspacedList) - +from certbot_nginx._internal.nginxparser import dump +from certbot_nginx._internal.nginxparser import dumps +from certbot_nginx._internal.nginxparser import load +from certbot_nginx._internal.nginxparser import loads +from certbot_nginx._internal.nginxparser import RawNginxParser +from certbot_nginx._internal.nginxparser import UnspacedList import test_util as util - FIRST = operator.itemgetter(0) diff --git a/certbot-nginx/tests/obj_test.py b/certbot-nginx/tests/obj_test.py index 351b33e82..db808229f 100644 --- a/certbot-nginx/tests/obj_test.py +++ b/certbot-nginx/tests/obj_test.py @@ -1,6 +1,6 @@ """Test the helper objects in certbot_nginx._internal.obj.""" -import unittest import itertools +import unittest class AddrTest(unittest.TestCase): diff --git a/certbot-nginx/tests/parser_obj_test.py b/certbot-nginx/tests/parser_obj_test.py index 084b17303..132f83771 100644 --- a/certbot-nginx/tests/parser_obj_test.py +++ b/certbot-nginx/tests/parser_obj_test.py @@ -1,10 +1,12 @@ """ Tests for functions and classes in parser_obj.py """ import unittest + import mock -from certbot_nginx._internal.parser_obj import parse_raw from certbot_nginx._internal.parser_obj import COMMENT_BLOCK +from certbot_nginx._internal.parser_obj import parse_raw + class CommentHelpersTest(unittest.TestCase): def test_is_comment(self): diff --git a/certbot-nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py index fd5a57582..5beee1111 100644 --- a/certbot-nginx/tests/parser_test.py +++ b/certbot-nginx/tests/parser_test.py @@ -5,14 +5,11 @@ import shutil import unittest from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot.compat import os - from certbot_nginx._internal import nginxparser from certbot_nginx._internal import obj from certbot_nginx._internal import parser - import test_util as util diff --git a/certbot-nginx/tests/test_util.py b/certbot-nginx/tests/test_util.py index 2ee0bb738..8dfd18637 100644 --- a/certbot-nginx/tests/test_util.py +++ b/certbot-nginx/tests/test_util.py @@ -12,7 +12,6 @@ from certbot import util from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util - from certbot_nginx._internal import configurator from certbot_nginx._internal import nginxparser diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index 6060cbd71..d33aa6d0c 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -6,20 +6,19 @@ import logging import shutil import socket +from cryptography.hazmat.primitives import serialization import josepy as jose import pyrfc3339 import pytz import six import zope.component -from cryptography.hazmat.primitives import serialization from acme import fields as acme_fields from acme import messages - -from certbot._internal import constants from certbot import errors from certbot import interfaces from certbot import util +from certbot._internal import constants from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py index 5c037e8dc..55415b46d 100644 --- a/certbot/certbot/_internal/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -1,20 +1,20 @@ """ACME AuthHandler.""" +import datetime import logging import time -import datetime import zope.component from acme import challenges -from acme import messages from acme import errors as acme_errors -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Tuple -# pylint: enable=unused-import, no-name-in-module +from acme import messages +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors -from certbot._internal import error_handler from certbot import interfaces +from certbot._internal import error_handler logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index 329b6cdff..da7b7a190 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -8,13 +8,12 @@ import pytz import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import crypto_util from certbot import errors from certbot import interfaces +from certbot import util from certbot._internal import ocsp from certbot._internal import storage -from certbot import util from certbot.compat import os from certbot.display import util as display_util diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index 7eabeeee6..886904c0a 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -15,22 +15,21 @@ import zope.interface from zope.interface import interfaces as zope_interfaces from acme import challenges -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Dict, Optional -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module import certbot -import certbot.plugins.enhancements as enhancements -import certbot._internal.plugins.selection as plugin_selection -from certbot._internal import constants from certbot import crypto_util from certbot import errors -from certbot._internal import hooks from certbot import interfaces from certbot import util +from certbot._internal import constants +from certbot._internal import hooks +from certbot._internal.plugins import disco as plugins_disco +import certbot._internal.plugins.selection as plugin_selection from certbot.compat import os from certbot.display import util as display_util -from certbot._internal.plugins import disco as plugins_disco +import certbot.plugins.enhancements as enhancements logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index 2a9a52e73..c9977d4d7 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -3,36 +3,35 @@ import datetime import logging import platform -import OpenSSL -import josepy as jose -import zope.component from cryptography.hazmat.backends import default_backend -# https://github.com/python/typeshed/blob/master/third_party/ -# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi +# See https://github.com/pyca/cryptography/issues/4275 from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore +import josepy as jose +import OpenSSL +import zope.component from acme import client as acme_client from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Optional, List # pylint: disable=unused-import,no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module import certbot +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util from certbot._internal import account from certbot._internal import auth_handler from certbot._internal import cli from certbot._internal import constants -from certbot import crypto_util from certbot._internal import eff from certbot._internal import error_handler -from certbot import errors -from certbot import interfaces from certbot._internal import storage -from certbot import util -from certbot.compat import os from certbot._internal.display import enhancements -from certbot.display import ops as display_ops from certbot._internal.plugins import selection as plugin_selection +from certbot.compat import os +from certbot.display import ops as display_ops logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/configuration.py b/certbot/certbot/_internal/configuration.py index 48579eb1c..6e9c185f4 100644 --- a/certbot/certbot/_internal/configuration.py +++ b/certbot/certbot/_internal/configuration.py @@ -1,13 +1,13 @@ """Certbot user-supplied configuration.""" import copy -import zope.interface from six.moves.urllib import parse # pylint: disable=relative-import +import zope.interface -from certbot._internal import constants from certbot import errors from certbot import interfaces from certbot import util +from certbot._internal import constants from certbot.compat import misc from certbot.compat import os diff --git a/certbot/certbot/_internal/constants.py b/certbot/certbot/_internal/constants.py index 5ac7ee72d..9a2220e0b 100644 --- a/certbot/certbot/_internal/constants.py +++ b/certbot/certbot/_internal/constants.py @@ -4,7 +4,6 @@ import logging import pkg_resources from acme import challenges - from certbot.compat import misc from certbot.compat import os diff --git a/certbot/certbot/_internal/display/completer.py b/certbot/certbot/_internal/display/completer.py index 3be06bec1..03719862b 100644 --- a/certbot/certbot/_internal/display/completer.py +++ b/certbot/certbot/_internal/display/completer.py @@ -1,5 +1,6 @@ """Provides Tab completion when prompting users for a path.""" import glob + # readline module is not available on all systems try: import readline diff --git a/certbot/certbot/_internal/display/enhancements.py b/certbot/certbot/_internal/display/enhancements.py index 0529f53c6..ce6470708 100644 --- a/certbot/certbot/_internal/display/enhancements.py +++ b/certbot/certbot/_internal/display/enhancements.py @@ -7,7 +7,6 @@ from certbot import errors from certbot import interfaces from certbot.display import util as display_util - logger = logging.getLogger(__name__) # Define a helper function to avoid verbose code diff --git a/certbot/certbot/_internal/eff.py b/certbot/certbot/_internal/eff.py index a0692009f..586697dbb 100644 --- a/certbot/certbot/_internal/eff.py +++ b/certbot/certbot/_internal/eff.py @@ -4,9 +4,8 @@ import logging import requests import zope.component -from certbot._internal import constants from certbot import interfaces - +from certbot._internal import constants logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py index 1a570e48e..a2f9a3dc6 100644 --- a/certbot/certbot/_internal/error_handler.py +++ b/certbot/certbot/_internal/error_handler.py @@ -4,10 +4,11 @@ import logging import signal import traceback -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Callable, Dict, List, Union -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py index 1bb3a2eab..25addd915 100644 --- a/certbot/certbot/_internal/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -2,10 +2,11 @@ from __future__ import print_function import logging -from subprocess import Popen, PIPE - -from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module +from subprocess import PIPE +from subprocess import Popen +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import util from certbot.compat import filesystem diff --git a/certbot/certbot/_internal/lock.py b/certbot/certbot/_internal/lock.py index eda2a72a1..7823eaac3 100644 --- a/certbot/certbot/_internal/lock.py +++ b/certbot/certbot/_internal/lock.py @@ -1,6 +1,12 @@ """Implements file locks compatible with Linux and Windows for locking files and directories.""" import errno import logging + +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os + try: import fcntl # pylint: disable=import-error except ImportError: @@ -9,11 +15,7 @@ except ImportError: else: POSIX_MODE = True -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/log.py b/certbot/certbot/_internal/log.py index 2109e0427..56ac2c3fe 100644 --- a/certbot/certbot/_internal/log.py +++ b/certbot/certbot/_internal/log.py @@ -23,10 +23,9 @@ import tempfile import traceback from acme import messages - -from certbot._internal import constants from certbot import errors from certbot import util +from certbot._internal import constants from certbot.compat import os # Logging format diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 3fc858711..f87b7c571 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -12,32 +12,32 @@ import zope.component from acme import errors as acme_errors from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module - import certbot +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util from certbot._internal import account from certbot._internal import cert_manager from certbot._internal import cli from certbot._internal import client from certbot._internal import configuration from certbot._internal import constants -from certbot import crypto_util from certbot._internal import eff -from certbot import errors from certbot._internal import hooks -from certbot import interfaces from certbot._internal import log from certbot._internal import renewal from certbot._internal import reporter from certbot._internal import storage from certbot._internal import updater -from certbot import util +from certbot._internal.plugins import disco as plugins_disco +from certbot._internal.plugins import selection as plug_sel from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os -from certbot.display import util as display_util, ops as display_ops -from certbot._internal.plugins import disco as plugins_disco +from certbot.display import ops as display_ops +from certbot.display import util as display_util from certbot.plugins import enhancements -from certbot._internal.plugins import selection as plug_sel USER_CANCELLED = ("User chose to cancel the operation and may " "reinvoke the client.") diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/_internal/ocsp.py index 2a63412a0..8312d7fe0 100644 --- a/certbot/certbot/_internal/ocsp.py +++ b/certbot/certbot/_internal/ocsp.py @@ -1,29 +1,37 @@ """Tools for checking certificate revocation.""" +from datetime import datetime +from datetime import timedelta import logging import re -from datetime import datetime, timedelta -from subprocess import Popen, PIPE +from subprocess import PIPE +from subprocess import Popen -try: - # Only cryptography>=2.5 has ocsp module - # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error - getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') -except (ImportError, AttributeError): # pragma: no cover - ocsp = None # type: ignore from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization +# See https://github.com/pyca/cryptography/issues/4275 from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature +from cryptography.hazmat.primitives import serialization import pytz import requests -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import crypto_util from certbot import errors -from certbot._internal.storage import RenewableCert # pylint: disable=unused-import from certbot import util +from certbot._internal.storage import RenewableCert # pylint: disable=unused-import + +try: + # Only cryptography>=2.5 has ocsp module + # and signature_hash_algorithm attribute in OCSPResponse class + from cryptography.x509 import ocsp # pylint: disable=import-error, ungrouped-imports + getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') +except (ImportError, AttributeError): # pragma: no cover + ocsp = None # type: ignore + + logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 0bee88ae1..360597474 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -5,15 +5,13 @@ import logging import pkg_resources import six - import zope.interface import zope.interface.verify from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from certbot._internal import constants from certbot import errors from certbot import interfaces - +from certbot._internal import constants logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index 43f70d650..be6abaad4 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -4,12 +4,11 @@ import zope.interface from acme import challenges from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges # pylint: disable=unused-import from certbot import errors -from certbot._internal import hooks from certbot import interfaces from certbot import reverter +from certbot._internal import hooks from certbot.compat import os from certbot.plugins import common diff --git a/certbot/certbot/_internal/plugins/null.py b/certbot/certbot/_internal/plugins/null.py index 6deb358f1..bf4615497 100644 --- a/certbot/certbot/_internal/plugins/null.py +++ b/certbot/certbot/_internal/plugins/null.py @@ -7,7 +7,6 @@ import zope.interface from certbot import interfaces from certbot.plugins import common - logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index 9723116c1..bb816cd46 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -11,13 +11,14 @@ import zope.interface from acme import challenges from acme import standalone as acme_standalone -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import TYPE_CHECKING # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces - from certbot.plugins import common logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index b87b3092a..837918345 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -10,16 +10,16 @@ import zope.component import zope.interface from acme import challenges # pylint: disable=unused-import -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Set, DefaultDict, List -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import -from certbot._internal import cli from certbot import errors from certbot import interfaces -from certbot.compat import os +from certbot._internal import cli from certbot.compat import filesystem +from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util from certbot.plugins import common diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index f96cd004f..4947ca067 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -14,17 +14,16 @@ import six import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot._internal import cli from certbot import crypto_util from certbot import errors -from certbot._internal import hooks from certbot import interfaces +from certbot import util +from certbot._internal import cli +from certbot._internal import hooks from certbot._internal import storage from certbot._internal import updater -from certbot import util -from certbot.compat import os from certbot._internal.plugins import disco as plugins_disco +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/reporter.py b/certbot/certbot/_internal/reporter.py index e0063d8e5..947f343d4 100644 --- a/certbot/certbot/_internal/reporter.py +++ b/certbot/certbot/_internal/reporter.py @@ -12,7 +12,6 @@ import zope.interface from certbot import interfaces from certbot import util - logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index 72eb3de85..b632e9d52 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -12,17 +12,17 @@ import pytz import six import certbot -from certbot._internal import cli -from certbot._internal import constants from certbot import crypto_util -from certbot._internal import error_handler from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import os +from certbot._internal import cli +from certbot._internal import constants +from certbot._internal import error_handler +from certbot._internal.plugins import disco as plugins_disco from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins import common as plugins_common -from certbot._internal.plugins import disco as plugins_disco logger = logging.getLogger(__name__) diff --git a/certbot/certbot/_internal/updater.py b/certbot/certbot/_internal/updater.py index 50db0e21c..961436ca5 100644 --- a/certbot/certbot/_internal/updater.py +++ b/certbot/certbot/_internal/updater.py @@ -3,7 +3,6 @@ import logging from certbot import errors from certbot import interfaces - from certbot._internal.plugins import selection as plug_sel import certbot.plugins.enhancements as enhancements diff --git a/certbot/certbot/achallenges.py b/certbot/certbot/achallenges.py index 2f2e1f3bd..70588683d 100644 --- a/certbot/certbot/achallenges.py +++ b/certbot/certbot/achallenges.py @@ -23,7 +23,6 @@ import josepy as jose from acme import challenges - logger = logging.getLogger(__name__) diff --git a/certbot/certbot/compat/_path.py b/certbot/certbot/compat/_path.py index fe2d2d1d2..5c5fe460e 100644 --- a/certbot/certbot/compat/_path.py +++ b/certbot/certbot/compat/_path.py @@ -1,4 +1,8 @@ -"""This compat module wraps os.path to forbid some functions.""" +""" +This compat module wraps os.path to forbid some functions. + +isort:skip_file +""" # pylint: disable=function-redefined from __future__ import absolute_import diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index 5fba440cc..b7a4683df 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -5,6 +5,10 @@ import errno import os # pylint: disable=os-module-forbidden import stat +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module + try: # pylint: disable=import-error import ntsecuritycon @@ -20,8 +24,6 @@ except ImportError: else: POSIX_MODE = False -from acme.magic_typing import List, Union, Tuple # pylint: disable=unused-import, no-name-in-module - def chmod(file_path, mode): # type: (str, int) -> None diff --git a/certbot/certbot/compat/misc.py b/certbot/certbot/compat/misc.py index a8fbf2c96..ffe611edb 100644 --- a/certbot/certbot/compat/misc.py +++ b/certbot/certbot/compat/misc.py @@ -7,14 +7,15 @@ from __future__ import absolute_import import select import sys +from certbot import errors +from certbot.compat import os + try: from win32com.shell import shell as shellwin32 # pylint: disable=import-error POSIX_MODE = False except ImportError: # pragma: no cover POSIX_MODE = True -from certbot import errors -from certbot.compat import os # For Linux: define OS specific standard binary directories diff --git a/certbot/certbot/compat/os.py b/certbot/certbot/compat/os.py index e5438f365..0231dd51a 100644 --- a/certbot/certbot/compat/os.py +++ b/certbot/certbot/compat/os.py @@ -2,6 +2,8 @@ This compat modules is a wrapper of the core os module that forbids usage of specific operations (e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. This module is intended to replace standard os module throughout certbot projects (except acme). + +isort:skip_file """ # pylint: disable=function-redefined from __future__ import absolute_import diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py index 5c375cc55..9aae75991 100644 --- a/certbot/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -8,12 +8,7 @@ import hashlib import logging import warnings -import pyrfc3339 -import six -import zope.component -from OpenSSL import SSL # type: ignore -from OpenSSL import crypto -# https://github.com/python/typeshed/tree/master/third_party/2/cryptography +# See https://github.com/pyca/cryptography/issues/4275 from cryptography import x509 # type: ignore from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend @@ -21,10 +16,14 @@ from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from OpenSSL import crypto +from OpenSSL import SSL # type: ignore +import pyrfc3339 +import six +import zope.component from acme import crypto_util as acme_crypto_util from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot import interfaces from certbot import util diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py index b79ba338f..05b69e539 100644 --- a/certbot/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -5,12 +5,12 @@ import textwrap import zope.interface -from certbot._internal import constants from certbot import errors from certbot import interfaces +from certbot._internal import constants +from certbot._internal.display import completer from certbot.compat import misc from certbot.compat import os -from certbot._internal.display import completer logger = logging.getLogger(__name__) diff --git a/certbot/certbot/interfaces.py b/certbot/certbot/interfaces.py index cf993a55b..e96712d23 100644 --- a/certbot/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -1,5 +1,6 @@ """Certbot client interfaces.""" import abc + import six import zope.interface diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index 843e27a1b..b56559c0e 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -6,21 +6,19 @@ import sys import tempfile import warnings +from josepy import util as jose_util import pkg_resources import zope.interface -from josepy import util as jose_util - from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges # pylint: disable=unused-import -from certbot._internal import constants from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import reverter -from certbot.compat import os +from certbot._internal import constants from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins.storage import PluginStorage logger = logging.getLogger(__name__) diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py index 931778b07..10d5010f2 100644 --- a/certbot/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -8,7 +8,6 @@ import configobj import zope.interface from acme import challenges - from certbot import errors from certbot import interfaces from certbot.compat import filesystem diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py index 2c82db030..71bba76d8 100644 --- a/certbot/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -1,9 +1,12 @@ """Common code for DNS Authenticator Plugins built on Lexicon.""" import logging -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException -from acme.magic_typing import Union, Dict, Any # pylint: disable=unused-import,no-name-in-module +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.plugins import dns_common diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py index 0fc0c9a71..3f269c99c 100644 --- a/certbot/certbot/plugins/dns_test_common.py +++ b/certbot/certbot/plugins/dns_test_common.py @@ -6,7 +6,6 @@ import mock import six from acme import challenges - from certbot import achallenges from certbot.compat import filesystem from certbot.tests import acme_util diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py index a221cf1bf..c77d6da9e 100644 --- a/certbot/certbot/plugins/dns_test_common_lexicon.py +++ b/certbot/certbot/plugins/dns_test_common_lexicon.py @@ -2,7 +2,8 @@ import josepy as jose import mock -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException from certbot import errors from certbot.plugins import dns_test_common diff --git a/certbot/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py index 44638e91d..f8d9db7dc 100644 --- a/certbot/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -1,11 +1,13 @@ """New interface style Certbot enhancements""" import abc + import six +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot._internal import constants -from acme.magic_typing import Dict, List, Any # pylint: disable=unused-import, no-name-in-module - ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] """List of possible :class:`certbot.interfaces.IInstaller` enhancements. diff --git a/certbot/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py index 294dfa0e8..7956295d2 100644 --- a/certbot/certbot/plugins/storage.py +++ b/certbot/certbot/plugins/storage.py @@ -2,11 +2,11 @@ import json import logging -from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/certbot/reverter.py b/certbot/certbot/reverter.py index 9118fef94..47a77c80a 100644 --- a/certbot/certbot/reverter.py +++ b/certbot/certbot/reverter.py @@ -9,11 +9,11 @@ import traceback import six -from certbot._internal import constants from certbot import errors from certbot import util -from certbot.compat import os +from certbot._internal import constants from certbot.compat import filesystem +from certbot.compat import os logger = logging.getLogger(__name__) diff --git a/certbot/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py index c88fcd706..3d560dcbc 100644 --- a/certbot/certbot/tests/acme_util.py +++ b/certbot/certbot/tests/acme_util.py @@ -6,12 +6,9 @@ import six from acme import challenges from acme import messages - from certbot._internal import auth_handler - from certbot.tests import util - JWK = jose.JWK.load(util.load_vector('rsa512_key.pem')) KEY = util.load_rsa_private_key('rsa512_key.pem') diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index d9ff18f1c..d757ab05a 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -4,29 +4,30 @@ """ import logging +from multiprocessing import Event +from multiprocessing import Process import shutil import sys import tempfile import unittest -from multiprocessing import Process, Event -import OpenSSL +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization import josepy as jose import mock +import OpenSSL import pkg_resources import six from six.moves import reload_module # pylint: disable=import-error -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization +from certbot import interfaces +from certbot import util from certbot._internal import configuration from certbot._internal import constants -from certbot import interfaces from certbot._internal import lock from certbot._internal import storage -from certbot import util -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.display import util as display_util diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index 5d8aa8f22..d56fe6845 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -1,10 +1,10 @@ """Utilities for all Certbot.""" +# distutils.version under virtualenv confuses pylint +# For more info, see: https://github.com/PyCQA/pylint/issues/73 import argparse import atexit import collections from collections import OrderedDict -# distutils.version under virtualenv confuses pylint -# For more info, see: https://github.com/PyCQA/pylint/issues/73 import distutils.version # pylint: disable=import-error,no-name-in-module import errno import logging @@ -17,13 +17,13 @@ import sys import configargparse import six -from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module - -from certbot._internal import constants +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors +from certbot._internal import constants from certbot._internal import lock -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os if sys.platform.startswith('linux'): import distro diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index c72d1c1cf..6b7c1c2c0 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -19,7 +19,6 @@ import sys import sphinx - here = os.path.abspath(os.path.dirname(__file__)) # read version number (and other metadata) from package init diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index d38dfc121..da0ddc9d1 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -409,7 +409,7 @@ Note that instead of just importing ``typing``, due to packaging issues, in Cert .. code-block:: python - from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + from acme.magic_typing import Dict Also note that OpenSSL, which we rely on, has type definitions for crypto but not SSL. We use both. Those imports should look like this: diff --git a/certbot/examples/plugins/setup.py b/certbot/examples/plugins/setup.py index 4538e83b8..ba2b5e4e2 100644 --- a/certbot/examples/plugins/setup.py +++ b/certbot/examples/plugins/setup.py @@ -1,6 +1,5 @@ from setuptools import setup - setup( name='certbot-example-plugins', package='certbot_example_plugins.py', diff --git a/certbot/setup.py b/certbot/setup.py index c1bf91410..d76494a84 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -1,10 +1,12 @@ import codecs +from distutils.version import StrictVersion import os import re import sys -from distutils.version import StrictVersion -from setuptools import find_packages, setup, __version__ as setuptools_version +from setuptools import __version__ as setuptools_version +from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand # Workaround for http://bugs.python.org/issue8876, see diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 80f381028..4a6ed3e01 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -8,12 +8,11 @@ import mock import pytz from acme import messages - -import certbot.tests.util as test_util from certbot import errors from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os +import certbot.tests.util as test_util KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index 308154aad..b38618daf 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -8,14 +8,12 @@ import zope.component from acme import challenges from acme import client as acme_client -from acme import messages from acme import errors as acme_errors - +from acme import messages from certbot import achallenges from certbot import errors from certbot import interfaces from certbot import util - from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 4f0837723..2c838214e 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -9,14 +9,13 @@ import unittest import configobj import mock -from certbot._internal import configuration from certbot import errors -from certbot.compat import os +from certbot._internal import configuration +from certbot._internal.storage import ALL_FOUR from certbot.compat import filesystem +from certbot.compat import os from certbot.display import util as display_util -from certbot._internal.storage import ALL_FOUR from certbot.tests import util as test_util - import storage_test diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index fbfaea333..05da1da4e 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -9,14 +9,13 @@ import six from six.moves import reload_module # pylint: disable=import-error from acme import challenges - -import certbot.tests.util as test_util +from certbot import errors from certbot._internal import cli from certbot._internal import constants -from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem from certbot._internal.plugins import disco +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util from certbot.tests.util import TempDirTestCase PLUGINS = disco.PluginsRegistry.find_all() diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index cac716854..bc3727043 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -4,16 +4,15 @@ import shutil import tempfile import unittest -import mock - from josepy import interfaces +import mock -import certbot.tests.util as test_util -from certbot._internal import account from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem from certbot import util +from certbot._internal import account +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util KEY = test_util.load_vector("rsa512_key.pem") CSR_SAN = test_util.load_vector("csr-san_512.pem") diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 7a8b89974..f700e0908 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -5,6 +5,13 @@ import unittest import mock +from certbot import util +from certbot._internal import lock +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util +from certbot.tests.util import TempDirTestCase + try: # pylint: disable=import-error import win32api @@ -15,12 +22,6 @@ try: except ImportError: POSIX_MODE = True -import certbot.tests.util as test_util -from certbot._internal import lock -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem -from certbot.tests.util import TempDirTestCase EVERYBODY_SID = 'S-1-1-0' diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index 11dd1b967..d748b9bfb 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -3,8 +3,8 @@ import unittest import mock -from certbot._internal import constants from certbot import errors +from certbot._internal import constants from certbot.compat import misc from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 7438fed5a..1d642ae9e 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -2,16 +2,16 @@ import logging import unittest -import OpenSSL import mock +import OpenSSL import zope.component -import certbot.tests.util as test_util from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util RSA256_KEY = test_util.load_vector('rsa256_key.pem') RSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem') diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index 262e0b344..5ddf69266 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -10,10 +10,9 @@ import unittest import mock from six.moves import reload_module # pylint: disable=import-error -from acme.magic_typing import List # pylint: disable=unused-import,no-name-in-module - -from certbot.compat import os # pylint: disable=ungrouped-imports +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot.compat import filesystem # pylint: disable=ungrouped-imports +from certbot.compat import os # pylint: disable=ungrouped-imports import certbot.tests.util as test_util # pylint: disable=ungrouped-imports diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index c19941a4b..5df7bfcf8 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -8,14 +8,13 @@ import mock import zope.component from acme import messages - -import certbot.tests.util as test_util -from certbot._internal import account from certbot import errors -from certbot.compat import os +from certbot._internal import account from certbot.compat import filesystem +from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util +import certbot.tests.util as test_util KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index 3803b70e3..45fec7f39 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -6,10 +6,9 @@ import unittest import mock -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Dict, Union -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot.compat import os diff --git a/certbot/tests/errors_test.py b/certbot/tests/errors_test.py index c8a6c4ac5..b7951284f 100644 --- a/certbot/tests/errors_test.py +++ b/certbot/tests/errors_test.py @@ -4,7 +4,6 @@ import unittest import mock from acme import messages - from certbot import achallenges from certbot.tests import acme_util diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index 2e403d8f3..a3bba57d2 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -2,12 +2,12 @@ import unittest import mock -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import util -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index dae643269..5a48009fd 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -2,6 +2,13 @@ import functools import multiprocessing import unittest + +import mock + +from certbot import errors +from certbot.compat import os +from certbot.tests import util as test_util + try: import fcntl # pylint: disable=import-error,unused-import except ImportError: @@ -9,11 +16,7 @@ except ImportError: else: POSIX_MODE = True -import mock -from certbot import errors -from certbot.compat import os -from certbot.tests import util as test_util class LockDirTest(test_util.TempDirTestCase): diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index 36c473bd2..3b9adbbf2 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -10,10 +10,9 @@ import six from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module - -from certbot._internal import constants from certbot import errors from certbot import util +from certbot._internal import constants from certbot.compat import filesystem from certbot.compat import os from certbot.tests import util as test_util diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index c60a79292..7b22c81d6 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -19,24 +19,23 @@ import six from six.moves import reload_module # pylint: disable=import-error from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -import certbot.tests.util as test_util +from certbot import crypto_util +from certbot import errors +from certbot import interfaces # pylint: disable=unused-import +from certbot import util from certbot._internal import account from certbot._internal import cli from certbot._internal import configuration from certbot._internal import constants -from certbot import crypto_util -from certbot import errors -from certbot import interfaces # pylint: disable=unused-import from certbot._internal import main from certbot._internal import updater -from certbot import util -from certbot.compat import os -from certbot.compat import filesystem from certbot._internal.plugins import disco -from certbot.plugins import enhancements from certbot._internal.plugins import manual from certbot._internal.plugins import null +from certbot.compat import filesystem +from certbot.compat import os +from certbot.plugins import enhancements +import certbot.tests.util as test_util CERT_PATH = test_util.vector_path('cert_512.pem') CERT = test_util.vector_path('cert_512.pem') diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index 500d5960b..6e4ab52b8 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,13 +1,21 @@ """Tests for ocsp.py""" # pylint: disable=protected-access import contextlib +from datetime import datetime +from datetime import timedelta import unittest -from datetime import datetime, timedelta +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -from cryptography import x509 +import mock +import pytz + +from certbot import errors +from certbot.tests import util as test_util + try: # Only cryptography>=2.5 has ocsp module # and signature_hash_algorithm attribute in OCSPResponse class @@ -15,11 +23,7 @@ try: getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm') except (ImportError, AttributeError): # pragma: no cover ocsp_lib = None # type: ignore -import mock -import pytz -from certbot import errors -from certbot.tests import util as test_util out = """Missing = in header key=value ocsp: Use -help for summary. diff --git a/certbot/tests/plugins/common_test.py b/certbot/tests/plugins/common_test.py index 977500f86..915a3ae6c 100644 --- a/certbot/tests/plugins/common_test.py +++ b/certbot/tests/plugins/common_test.py @@ -7,12 +7,11 @@ import josepy as jose import mock from acme import challenges - from certbot import achallenges from certbot import crypto_util from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/tests/plugins/disco_test.py b/certbot/tests/plugins/disco_test.py index f739512f0..6d3c7d97e 100644 --- a/certbot/tests/plugins/disco_test.py +++ b/certbot/tests/plugins/disco_test.py @@ -11,7 +11,6 @@ import zope.interface from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces - from certbot._internal.plugins import standalone from certbot._internal.plugins import webroot diff --git a/certbot/tests/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py index 86482184e..3ecda2019 100644 --- a/certbot/tests/plugins/enhancements_test.py +++ b/certbot/tests/plugins/enhancements_test.py @@ -1,10 +1,10 @@ """Tests for new style enhancements""" import unittest + import mock -from certbot.plugins import enhancements from certbot._internal.plugins import null - +from certbot.plugins import enhancements import certbot.tests.util as test_util diff --git a/certbot/tests/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py index 8796c30f1..bd11a9538 100644 --- a/certbot/tests/plugins/manual_test.py +++ b/certbot/tests/plugins/manual_test.py @@ -1,15 +1,14 @@ """Tests for certbot._internal.plugins.manual""" -import unittest import sys +import unittest import mock import six from acme import challenges - from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/tests/plugins/null_test.py b/certbot/tests/plugins/null_test.py index 41cd45a93..db0213813 100644 --- a/certbot/tests/plugins/null_test.py +++ b/certbot/tests/plugins/null_test.py @@ -1,8 +1,8 @@ """Tests for certbot._internal.plugins.null.""" import unittest -import six import mock +import six class InstallerTest(unittest.TestCase): diff --git a/certbot/tests/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py index 9de7f7941..ac846af7b 100644 --- a/certbot/tests/plugins/selection_test.py +++ b/certbot/tests/plugins/selection_test.py @@ -6,12 +6,11 @@ import mock import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot import interfaces +from certbot._internal.plugins.disco import PluginsRegistry from certbot.compat import os from certbot.display import util as display_util -from certbot._internal.plugins.disco import PluginsRegistry from certbot.tests import util as test_util diff --git a/certbot/tests/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py index c9dabb8b4..afca48bd9 100644 --- a/certbot/tests/plugins/standalone_test.py +++ b/certbot/tests/plugins/standalone_test.py @@ -1,22 +1,21 @@ """Tests for certbot._internal.plugins.standalone.""" -import socket # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +import socket from socket import errno as socket_errors # type: ignore import unittest import josepy as jose import mock -import six - import OpenSSL.crypto # pylint: disable=unused-import +import six from acme import challenges from acme import standalone as acme_standalone # pylint: disable=unused-import -from acme.magic_typing import Dict, Tuple, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors - from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/tests/plugins/storage_test.py b/certbot/tests/plugins/storage_test.py index 9d08cc7ef..e9ca2007f 100644 --- a/certbot/tests/plugins/storage_test.py +++ b/certbot/tests/plugins/storage_test.py @@ -1,12 +1,12 @@ """Tests for certbot.plugins.storage.PluginStorage""" import json import unittest + import mock from certbot import errors - -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util diff --git a/certbot/tests/plugins/webroot_test.py b/certbot/tests/plugins/webroot_test.py index 70501f812..fade12bb1 100644 --- a/certbot/tests/plugins/webroot_test.py +++ b/certbot/tests/plugins/webroot_test.py @@ -14,11 +14,10 @@ import mock import six from acme import challenges - from certbot import achallenges from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.display import util as display_util from certbot.tests import acme_util from certbot.tests import util as test_util diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index 9b36c8b83..e92211ea2 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -1,13 +1,12 @@ """Tests for certbot._internal.renewal""" import unittest + import mock from acme import challenges - -from certbot._internal import configuration from certbot import errors +from certbot._internal import configuration from certbot._internal import storage - import certbot.tests.util as test_util diff --git a/certbot/tests/renewupdater_test.py b/certbot/tests/renewupdater_test.py index 42b723c94..c6f8f3713 100644 --- a/certbot/tests/renewupdater_test.py +++ b/certbot/tests/renewupdater_test.py @@ -1,13 +1,12 @@ """Tests for renewal updater interfaces""" import unittest + import mock from certbot import interfaces from certbot._internal import main from certbot._internal import updater - from certbot.plugins import enhancements - import certbot.tests.util as test_util diff --git a/certbot/tests/reporter_test.py b/certbot/tests/reporter_test.py index a30aaeb8c..3d7c80172 100644 --- a/certbot/tests/reporter_test.py +++ b/certbot/tests/reporter_test.py @@ -1,8 +1,8 @@ """Tests for certbot._internal.reporter.""" import sys import unittest -import mock +import mock import six diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 06c881a87..8e127b21d 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -11,11 +11,11 @@ import pytz import six import certbot -import certbot.tests.util as test_util from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem from certbot._internal.storage import ALL_FOUR +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util CERT = test_util.load_cert('cert_512.pem') diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 5ced9f78e..ae061de65 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -8,10 +8,10 @@ import mock import six from six.moves import reload_module # pylint: disable=import-error -import certbot.tests.util as test_util from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util class RunScriptTest(unittest.TestCase): diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py index 17d8b3ea9..fcff25d55 100644 --- a/letshelp-certbot/docs/conf.py +++ b/letshelp-certbot/docs/conf.py @@ -12,10 +12,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os import shlex - +import sys here = os.path.abspath(os.path.dirname(__file__)) diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py index a84641bfe..0853046b4 100644 --- a/letshelp-certbot/letshelp_certbot/apache_test.py +++ b/letshelp-certbot/letshelp_certbot/apache_test.py @@ -6,15 +6,14 @@ import subprocess import tarfile import tempfile import unittest -import pkg_resources -import mock # six is used in mock.patch() +import mock +import pkg_resources import six # pylint: disable=unused-import import letshelp_certbot.apache as letshelp_le_apache - _PARTIAL_CONF_PATH = os.path.join("mods-available", "ssl.load") _PARTIAL_LINK_PATH = os.path.join("mods-enabled", "ssl.load") _CONFIG_FILE = pkg_resources.resource_filename( diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py index 471b8dfa9..5a6358c69 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing.py @@ -1,6 +1,7 @@ """Shim class to not have to depend on typing module in prod.""" import sys + class TypingClass(object): """Ignore import errors by getting anything""" def __getattr__(self, name): diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py index cb5171b72..af992de16 100644 --- a/letshelp-certbot/setup.py +++ b/letshelp-certbot/setup.py @@ -1,6 +1,5 @@ -from setuptools import setup from setuptools import find_packages - +from setuptools import setup version = '0.7.0.dev0' diff --git a/linter_plugin.py b/linter_plugin.py index e870fda3a..6be8c2414 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -1,13 +1,14 @@ -"""Certbot PyLint plugin. -http://docs.pylint.org/plugins.html """ -# The built-in ImportChecker of Pylint does a similar job to ForbidStandardOsModule to detect -# deprecated modules. You can check its behavior as a reference to what is coded here. -# See https://github.com/PyCQA/pylint/blob/b20a2984c94e2946669d727dbda78735882bf50a/pylint/checkers/imports.py#L287 +Certbot PyLint plugin. + +The built-in ImportChecker of Pylint does a similar job to ForbidStandardOsModule to detect +deprecated modules. You can check its behavior as a reference to what is coded here. +See https://github.com/PyCQA/pylint/blob/b20a2984c94e2946669d727dbda78735882bf50a/pylint/checkers/imports.py#L287 +See http://docs.pylint.org/plugins.html +""" from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker - # Modules in theses packages can import the os module. WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', 'lock_test'] diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index cfa53df7e..9ea9fe76b 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -32,17 +32,31 @@ see: from __future__ import print_function from __future__ import with_statement -import sys, os, time, argparse, socket, traceback +import argparse import multiprocessing as mp from multiprocessing import Manager +import os +import socket +import sys +import time +import traceback import urllib2 -import yaml + import boto3 from botocore.exceptions import ClientError +import yaml + import fabric -from fabric.api import run, execute, local, env, sudo, cd, lcd -from fabric.operations import get, put +from fabric.api import cd +from fabric.api import env +from fabric.api import execute +from fabric.api import lcd +from fabric.api import local +from fabric.api import run +from fabric.api import sudo from fabric.context_managers import shell_env +from fabric.operations import get +from fabric.operations import put # Command line parser #------------------------------------------------------------------------------- diff --git a/tests/lock_test.py b/tests/lock_test.py index c3eebe3d2..e6481941f 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -15,17 +15,16 @@ import tempfile from cryptography import x509 from cryptography.hazmat.backends import default_backend # TODO: once mypy has cryptography types bundled, type: ignore can be removed. -# See https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography.hazmat.primitives import serialization, hashes # type: ignore +# See https://github.com/pyca/cryptography/issues/4275 +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -from certbot._internal import lock from certbot import util +from certbot._internal import lock from certbot.compat import filesystem - from certbot.tests import util as test_util - logger = logging.getLogger(__name__) diff --git a/tests/modification-check.py b/tests/modification-check.py index 8abc0fbfe..811f369d4 100755 --- a/tests/modification-check.py +++ b/tests/modification-check.py @@ -3,10 +3,11 @@ from __future__ import print_function import os +import shutil import subprocess import sys import tempfile -import shutil + try: from urllib.request import urlretrieve except ImportError: diff --git a/tools/_venv_common.py b/tools/_venv_common.py index ec6a0ef7a..c61385054 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -12,13 +12,13 @@ VENV_NAME. from __future__ import print_function +import glob import os +import re import shutil -import glob -import time import subprocess import sys -import re +import time REQUIREMENTS = [ '-e acme[dev]', diff --git a/tools/deactivate.py b/tools/deactivate.py index d43b84552..10c9ecd35 100644 --- a/tools/deactivate.py +++ b/tools/deactivate.py @@ -16,8 +16,8 @@ import os import sys from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa import josepy as jose from acme import client as acme_client diff --git a/tools/extract_changelog.py b/tools/extract_changelog.py index 695870278..fb0b849aa 100755 --- a/tools/extract_changelog.py +++ b/tools/extract_changelog.py @@ -1,9 +1,9 @@ #!/usr/bin/env python from __future__ import print_function -import sys + import os import re - +import sys CERTBOT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) diff --git a/tools/install_and_test.py b/tools/install_and_test.py index f8d7a2e3d..192708957 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -8,9 +8,9 @@ from __future__ import print_function import os -import sys -import subprocess import re +import subprocess +import sys SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] diff --git a/tools/pip_install.py b/tools/pip_install.py index 76355a1e6..0a3961384 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -8,13 +8,14 @@ # CERTBOT_OLDEST is set, this script must be run with `-e ` and # no other arguments. -from __future__ import print_function, absolute_import +from __future__ import absolute_import +from __future__ import print_function -import subprocess import os -import sys import re import shutil +import subprocess +import sys import tempfile import merge_requirements as merge_module diff --git a/tools/pip_install_editable.py b/tools/pip_install_editable.py index 8eaf3a9fa..3f7c02ba9 100755 --- a/tools/pip_install_editable.py +++ b/tools/pip_install_editable.py @@ -8,6 +8,7 @@ import sys import pip_install + def main(args): new_args = [] for arg in args: diff --git a/tools/readlink.py b/tools/readlink.py index 0199ce184..446c8ebdc 100755 --- a/tools/readlink.py +++ b/tools/readlink.py @@ -11,6 +11,7 @@ from __future__ import print_function import os import sys + def main(link): return os.path.realpath(link) diff --git a/tools/simple_http_server.py b/tools/simple_http_server.py index 233aa6bd3..24c55962d 100755 --- a/tools/simple_http_server.py +++ b/tools/simple_http_server.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """A version of Python's SimpleHTTPServer that flushes its output.""" import sys + try: from http.server import HTTPServer, SimpleHTTPRequestHandler except ImportError: diff --git a/tools/venv.py b/tools/venv.py index 6cd38cfc0..f99386eff 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -5,6 +5,7 @@ import sys import _venv_common + def create_venv(venv_path): """Create a Python 2 virtual environment at venv_path. diff --git a/tox.cover.py b/tox.cover.py index 85e929567..0ef5c0d07 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import argparse -import subprocess import os +import subprocess import sys DEFAULT_PACKAGES = [ @@ -16,7 +16,7 @@ COVER_THRESHOLDS = { 'acme': {'linux': 100, 'windows': 99}, 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, - 'certbot_dns_cloudxns': {'linux': 99, 'windows': 99}, + 'certbot_dns_cloudxns': {'linux': 98, 'windows': 98}, 'certbot_dns_digitalocean': {'linux': 98, 'windows': 98}, 'certbot_dns_dnsimple': {'linux': 98, 'windows': 98}, 'certbot_dns_dnsmadeeasy': {'linux': 99, 'windows': 99}, diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 192906d79..77ca67e65 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import contextlib import ctypes +import os +import shutil import struct import subprocess -import os import sys -import shutil import tempfile import time -- cgit v1.2.3 From 9e5bca4bbf3c4fd198382dd9d6e05b54014aca30 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 10 Dec 2019 23:12:50 +0100 Subject: Lint certbot code on Python 3, and update Pylint to the latest version (#7551) Part of #7550 This PR makes appropriate corrections to run pylint on Python 3. Why not keeping the dependencies unchanged and just run pylint on Python 3? Because the old version of pylint breaks horribly on Python 3 because of unsupported version of astroid. Why updating pylint + astroid to the latest version ? Because this version only fixes some internal errors occuring during the lint of Certbot code, and is also ready to run gracefully on Python 3.8. Why upgrading mypy ? Because the old version does not support the new version of astroid required to run pylint correctly. Why not upgrading mypy to its latest version ? Because this latest version includes a new typshed version, that adds a lot of new type definitions, and brings dozens of new errors on the Certbot codebase. I would like to fix that in a future PR. That said so, the work has been to find the correct set of new dependency versions, then configure pylint for sane configuration errors in our situation, disable irrelevant lintings errors, then fixing (or ignoring for good reason) the remaining mypy errors. I also made PyLint and MyPy checks run correctly on Windows. * Start configuration * Reconfigure travis * Suspend a check specific to python 3. Start fixing code. * Repair call_args * Fix return + elif lints * Reconfigure development to run mainly on python3 * Remove incompatible Python 3.4 jobs * Suspend pylint in some assertions * Remove pylint in dev * Take first mypy that supports typed-ast>=1.4.0 to limit the migration path * Various return + else lint errors * Find a set of deps that is working with current mypy version * Update local oldest requirements * Remove all current pylint errors * Rebuild letsencrypt-auto * Update mypy to fix pylint with new astroid version, and fix mypy issues * Explain type: ignore * Reconfigure tox, fix none path * Simplify pinning * Remove useless directive * Remove debugging code * Remove continue * Update requirements * Disable unsubscriptable-object check * Disable one check, enabling two more * Plug certbot dev version for oldest requirements * Remove useless disable directives * Remove useless no-member disable * Remove no-else-* checks. Use elif in symetric branches. * Add back assertion * Add new line * Remove unused pylint disable * Remove other pylint disable --- .pylintrc | 17 ++++-- .travis.yml | 5 +- Dockerfile-dev | 9 ++-- acme/acme/challenges.py | 5 +- acme/acme/client.py | 16 ++---- acme/acme/errors.py | 16 ++++-- acme/acme/jws.py | 2 +- acme/acme/messages.py | 2 +- acme/acme/standalone.py | 3 +- acme/tests/challenges_test.py | 3 +- acme/tests/client_test.py | 14 ++--- acme/tests/crypto_util_test.py | 3 +- acme/tests/jose_test.py | 9 ++-- acme/tests/messages_test.py | 20 +++---- acme/tests/test_util.py | 3 +- .../certbot_apache/_internal/configurator.py | 20 +++---- certbot-apache/certbot_apache/_internal/http_01.py | 4 +- .../certbot_apache/_internal/override_debian.py | 17 +++--- certbot-apache/certbot_apache/_internal/parser.py | 9 ++-- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 2 +- .../configurators/common.py | 6 +-- .../certbot_compatibility_test/test_driver.py | 23 ++++---- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 2 +- .../certbot_dns_google/_internal/dns_google.py | 2 +- certbot-dns-google/local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-google/tests/dns_google_test.py | 1 - certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- .../certbot_nginx/_internal/configurator.py | 2 +- .../certbot_nginx/_internal/nginxparser.py | 5 +- certbot-nginx/certbot_nginx/_internal/parser.py | 15 +++--- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 2 +- certbot-nginx/tests/configurator_test.py | 2 +- certbot-nginx/tests/parser_test.py | 20 +++---- certbot/certbot/_internal/account.py | 5 +- certbot/certbot/_internal/auth_handler.py | 5 +- certbot/certbot/_internal/cert_manager.py | 4 +- certbot/certbot/_internal/cli.py | 13 +++-- certbot/certbot/_internal/client.py | 11 ++-- certbot/certbot/_internal/configuration.py | 2 +- certbot/certbot/_internal/error_handler.py | 2 +- certbot/certbot/_internal/log.py | 6 +-- certbot/certbot/_internal/main.py | 62 ++++++++++------------ certbot/certbot/_internal/ocsp.py | 2 +- certbot/certbot/_internal/plugins/selection.py | 7 ++- certbot/certbot/_internal/plugins/standalone.py | 3 +- certbot/certbot/_internal/plugins/webroot.py | 13 ++--- certbot/certbot/_internal/renewal.py | 3 +- certbot/certbot/_internal/storage.py | 12 ++--- certbot/certbot/compat/filesystem.py | 8 ++- certbot/certbot/display/ops.py | 7 ++- certbot/certbot/display/util.py | 22 ++++---- certbot/certbot/plugins/common.py | 7 +-- certbot/certbot/plugins/dns_common.py | 6 +-- certbot/certbot/plugins/dns_common_lexicon.py | 2 +- certbot/certbot/plugins/dns_test_common.py | 10 ++-- certbot/certbot/tests/util.py | 10 ++-- certbot/certbot/util.py | 13 ++--- certbot/setup.py | 6 +-- certbot/tests/auth_handler_test.py | 2 +- certbot/tests/cert_manager_test.py | 6 +-- certbot/tests/client_test.py | 2 +- certbot/tests/compat/filesystem_test.py | 2 + certbot/tests/display/util_test.py | 14 ++--- certbot/tests/eff_test.py | 10 ++-- certbot/tests/errors_test.py | 14 ++--- certbot/tests/plugins/enhancements_test.py | 6 +-- certbot/tests/plugins/standalone_test.py | 4 +- certbot/tests/storage_test.py | 6 +-- letshelp-certbot/letshelp_certbot/apache.py | 4 +- tests/lock_test.py | 2 +- tools/dev_constraints.txt | 15 +++--- tox.ini | 3 +- 99 files changed, 304 insertions(+), 344 deletions(-) diff --git a/.pylintrc b/.pylintrc index c2d8ff85e..0e78828bd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -24,6 +24,11 @@ persistent=yes # usually to register additional checkers. load-plugins=linter_plugin +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist=pywintypes,win32api,win32file,win32security + [MESSAGES CONTROL] @@ -41,10 +46,14 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,locally-enabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design -# abstract-class-not-used cannot be disabled locally (at least in -# pylint 1.4.1), same for abstract-class-little-used - +# CERTBOT COMMENT +# 1) Once certbot codebase is claimed to be compatible exclusively with Python 3, +# the useless-object-inheritance check can be enabled again, and code fixed accordingly. +# 2) Check unsubscriptable-object tends to create a lot of false positives. Let's disable it. +# See https://github.com/PyCQA/pylint/issues/1498. +# 3) Same as point 2 for no-value-for-parameter. +# See https://github.com/PyCQA/pylint/issues/2820. +disable=fixme,locally-disabled,locally-enabled,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue [REPORTS] diff --git a/.travis.yml b/.travis.yml index 22391c84f..59cc8630a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,12 +46,9 @@ matrix: - python: "2.7" env: TOXENV=py27-cover FYI="py27 tests + code coverage" - - python: "2.7" + - python: "3.7" env: TOXENV=lint <<: *not-on-master - - python: "3.4" - env: TOXENV=mypy - <<: *not-on-master - python: "3.5" env: TOXENV=mypy <<: *not-on-master diff --git a/Dockerfile-dev b/Dockerfile-dev index 86f5f04e7..ae197b1cb 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -6,16 +6,15 @@ EXPOSE 80 443 WORKDIR /opt/certbot/src -# TODO: Install Apache/Nginx for plugin development. COPY . . RUN apt-get update && \ - apt-get install apache2 git nginx-light -y && \ - letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \ + apt-get install apache2 git python3-dev python3-venv gcc libaugeas0 \ + libssl-dev libffi-dev ca-certificates openssl nginx-light -y && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* \ /tmp/* \ /var/tmp/* -RUN VENV_NAME="../venv" python tools/venv.py +RUN VENV_NAME="../venv3" python3 tools/venv3.py -ENV PATH /opt/certbot/venv/bin:$PATH +ENV PATH /opt/certbot/venv3/bin:$PATH diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index d9d529810..8a0366301 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -54,8 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - # pylint: disable=no-member - return self.jobj + return self.jobj # pylint: disable=no-member @classmethod def from_json(cls, jobj): @@ -113,7 +112,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): :rtype: bool """ - parts = self.key_authorization.split('.') # pylint: disable=no-member + parts = self.key_authorization.split('.') if len(parts) != 2: logger.debug("Key authorization (%r) is not well formed", self.key_authorization) diff --git a/acme/acme/client.py b/acme/acme/client.py index 928b86d03..aabcbe312 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -34,7 +34,6 @@ 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 @@ -280,7 +279,6 @@ class Client(ClientBase): assert response.status_code == http_client.CREATED # "Instance of 'Field' has no key/contact member" bug: - # pylint: disable=no-member return self._regr_from_response(response) def query_registration(self, regr): @@ -465,7 +463,6 @@ class Client(ClientBase): updated[authzr] = updated_authzr attempts[authzr] += 1 - # pylint: disable=no-member if updated_authzr.body.status not in ( messages.STATUS_VALID, messages.STATUS_INVALID): if attempts[authzr] < max_attempts: @@ -606,7 +603,6 @@ class ClientV2(ClientBase): if response.status_code == 200 and 'Location' in response.headers: raise errors.ConflictError(response.headers.get('Location')) # "Instance of 'Field' has no key/contact member" bug: - # pylint: disable=no-member regr = self._regr_from_response(response) self.net.account = regr return regr @@ -730,7 +726,7 @@ class ClientV2(ClientBase): for authzr in responses: if authzr.body.status != messages.STATUS_VALID: for chall in authzr.body.challenges: - if chall.error != None: + if chall.error is not None: failed.append(authzr) if failed: raise errors.ValidationError(failed) @@ -1125,10 +1121,9 @@ class ClientNetwork(object): err_regex = r".*host='(\S*)'.*Max retries exceeded with url\: (\/\w*).*(\[Errno \d+\])([A-Za-z ]*)" m = re.match(err_regex, str(e)) if m is None: - raise # pragma: no cover - else: - host, path, _err_no, err_msg = m.groups() - raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg)) + raise # pragma: no cover + host, path, _err_no, err_msg = m.groups() + raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg)) # If content is DER, log the base64 of it instead of raw bytes, to keep # binary data out of the logs. @@ -1194,8 +1189,7 @@ class ClientNetwork(object): if error.code == 'badNonce': logger.debug('Retrying request after error:\n%s', error) return self._post_once(*args, **kwargs) - else: - raise + raise def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, acme_version=1, **kwargs): diff --git a/acme/acme/errors.py b/acme/acme/errors.py index 3a0f8c596..806657940 100644 --- a/acme/acme/errors.py +++ b/acme/acme/errors.py @@ -29,7 +29,12 @@ class NonceError(ClientError): class BadNonce(NonceError): """Bad nonce error.""" def __init__(self, nonce, error, *args, **kwargs): - super(BadNonce, self).__init__(*args, **kwargs) + # MyPy complains here that there is too many arguments for BaseException constructor. + # This is an error fixed in typeshed, see https://github.com/python/mypy/issues/4183 + # The fix is included in MyPy>=0.740, but upgrading it would bring dozen of errors due to + # new types definitions. So we ignore the error until the code base is fixed to match + # with MyPy>=0.740 referential. + super(BadNonce, self).__init__(*args, **kwargs) # type: ignore self.nonce = nonce self.error = error @@ -48,7 +53,8 @@ class MissingNonce(NonceError): """ def __init__(self, response, *args, **kwargs): - super(MissingNonce, self).__init__(*args, **kwargs) + # See comment in BadNonce constructor above for an explanation of type: ignore here. + super(MissingNonce, self).__init__(*args, **kwargs) # type: ignore self.response = response def __str__(self): @@ -83,6 +89,7 @@ class PollError(ClientError): return '{0}(exhausted={1!r}, updated={2!r})'.format( self.__class__.__name__, self.exhausted, self.updated) + class ValidationError(Error): """Error for authorization failures. Contains a list of authorization resources, each of which is invalid and should have an error field. @@ -91,9 +98,11 @@ class ValidationError(Error): self.failed_authzrs = failed_authzrs super(ValidationError, self).__init__() -class TimeoutError(Error): + +class TimeoutError(Error): # pylint: disable=redefined-builtin """Error for when polling an authorization or an order times out.""" + class IssuanceError(Error): """Error sent by the server after requesting issuance of a certificate.""" @@ -105,6 +114,7 @@ class IssuanceError(Error): self.error = error super(IssuanceError, self).__init__() + class ConflictError(ClientError): """Error for when the server returns a 409 (Conflict) HTTP status. diff --git a/acme/acme/jws.py b/acme/acme/jws.py index ff8070d0b..894e69f3d 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -40,7 +40,7 @@ class Signature(jose.Signature): class JWS(jose.JWS): """ACME-specific JWS. Includes none, url, and kid in protected header.""" signature_cls = Signature - __slots__ = jose.JWS._orig_slots # pylint: disable=no-member + __slots__ = jose.JWS._orig_slots @classmethod # pylint: disable=arguments-differ diff --git a/acme/acme/messages.py b/acme/acme/messages.py index a87abdb91..96a1ed7c0 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -146,7 +146,7 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test raise jose.DeserializationError( '{0} not recognized'.format(cls.__name__)) - return cls.POSSIBLE_NAMES[jobj] # pylint: disable=unsubscriptable-object + return cls.POSSIBLE_NAMES[jobj] def __repr__(self): return '{0}({1})'.format(self.__class__.__name__, self.name) diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 0b66976e4..cf0da4e86 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -44,7 +44,7 @@ class TLSServer(socketserver.TCPServer): return socketserver.TCPServer.server_bind(self) -class ACMEServerMixin: # pylint: disable=old-style-class +class ACMEServerMixin: """ACME server common settings mixin.""" # TODO: c.f. #858 server_version = "ACME client standalone challenge solver" @@ -105,7 +105,6 @@ class BaseDualNetworkedServers(object): """Wraps socketserver.TCPServer.serve_forever""" for server in self.servers: thread = threading.Thread( - # pylint: disable=no-member target=server.serve_forever) thread.start() self.threads.append(thread) diff --git a/acme/tests/challenges_test.py b/acme/tests/challenges_test.py index f8a503b3c..490caadc2 100644 --- a/acme/tests/challenges_test.py +++ b/acme/tests/challenges_test.py @@ -4,7 +4,7 @@ import unittest import josepy as jose import mock import requests -from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import +from six.moves.urllib import parse as urllib_parse import test_util @@ -18,7 +18,6 @@ class ChallengeTest(unittest.TestCase): from acme.challenges import Challenge from acme.challenges import UnrecognizedChallenge chall = UnrecognizedChallenge({"type": "foo"}) - # pylint: disable=no-member self.assertEqual(chall, Challenge.from_json(chall.jobj)) diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 7c1d9f68e..192cd2949 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -61,7 +61,7 @@ class ClientTestBase(unittest.TestCase): self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') reg = messages.Registration( contact=self.contact, key=KEY.public_key()) - the_arg = dict(reg) # type: Dict + the_arg = dict(reg) # type: Dict self.new_reg = messages.NewRegistration(**the_arg) self.regr = messages.RegistrationResource( body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') @@ -963,8 +963,8 @@ class ClientNetworkTest(unittest.TestCase): def test_check_response_not_ok_jobj_error(self): self.response.ok = False - self.response.json.return_value = messages.Error( - detail='foo', typ='serverInternal', title='some title').to_json() + self.response.json.return_value = messages.Error.with_code( + 'serverInternal', detail='foo', title='some title').to_json() # pylint: disable=protected-access self.assertRaises( messages.Error, self.net._check_response, self.response) @@ -989,7 +989,7 @@ class ClientNetworkTest(unittest.TestCase): self.response.json.side_effect = ValueError for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter + # pylint: disable=protected-access self.assertEqual( self.response, self.net._check_response(self.response)) @@ -1003,7 +1003,7 @@ class ClientNetworkTest(unittest.TestCase): self.response.json.return_value = {} for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter + # pylint: disable=protected-access self.assertEqual( self.response, self.net._check_response(self.response)) @@ -1128,8 +1128,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): self.response.headers = {} self.response.links = {} self.response.checked = False - self.acmev1_nonce_response = mock.MagicMock(ok=False, - status_code=http_client.METHOD_NOT_ALLOWED) + self.acmev1_nonce_response = mock.MagicMock( + ok=False, status_code=http_client.METHOD_NOT_ALLOWED) self.acmev1_nonce_response.headers = {} self.obj = mock.MagicMock() self.wrapped_obj = mock.MagicMock() diff --git a/acme/tests/crypto_util_test.py b/acme/tests/crypto_util_test.py index a7ce51f92..41640ed60 100644 --- a/acme/tests/crypto_util_test.py +++ b/acme/tests/crypto_util_test.py @@ -38,7 +38,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) self.port = self.server.socket.getsockname()[1] self.server_thread = threading.Thread( - # pylint: disable=no-member target=self.server.handle_request) def tearDown(self): @@ -65,7 +64,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): def test_probe_connection_error(self): # pylint has a hard time with six - self.server.server_close() # pylint: disable=no-member + self.server.server_close() original_timeout = socket.getdefaulttimeout() try: socket.setdefaulttimeout(1) diff --git a/acme/tests/jose_test.py b/acme/tests/jose_test.py index 899ad7074..e008cb6fc 100644 --- a/acme/tests/jose_test.py +++ b/acme/tests/jose_test.py @@ -21,11 +21,10 @@ class JoseTest(unittest.TestCase): # 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) + import acme # pylint: disable=unused-import + import josepy # pylint: disable=unused-import + acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used + josepy_mod = eval(josepy_path) # pylint: disable=eval-used self.assertIs(acme_jose_mod, josepy_mod) self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py index 2700fc23b..b9b70266b 100644 --- a/acme/tests/messages_test.py +++ b/acme/tests/messages_test.py @@ -18,8 +18,7 @@ class ErrorTest(unittest.TestCase): def setUp(self): from acme.messages import Error, ERROR_PREFIX - self.error = Error( - detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') + self.error = Error.with_code('malformed', detail='foo', title='title') self.jobj = { 'detail': 'foo', 'title': 'some title', @@ -27,7 +26,6 @@ class ErrorTest(unittest.TestCase): } self.error_custom = Error(typ='custom', detail='bar') self.empty_error = Error() - self.jobj_custom = {'type': 'custom', 'detail': 'bar'} def test_default_typ(self): from acme.messages import Error @@ -42,8 +40,7 @@ class ErrorTest(unittest.TestCase): hash(Error.from_json(self.error.to_json())) def test_description(self): - self.assertEqual( - 'The request message was malformed', self.error.description) + self.assertEqual('The request message was malformed', self.error.description) self.assertTrue(self.error_custom.description is None) def test_code(self): @@ -53,17 +50,17 @@ class ErrorTest(unittest.TestCase): self.assertEqual(None, Error().code) def test_is_acme_error(self): - from acme.messages import is_acme_error + from acme.messages import is_acme_error, Error self.assertTrue(is_acme_error(self.error)) self.assertFalse(is_acme_error(self.error_custom)) + self.assertFalse(is_acme_error(Error())) self.assertFalse(is_acme_error(self.empty_error)) self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) def test_unicode_error(self): - from acme.messages import Error, ERROR_PREFIX, is_acme_error - arabic_error = Error( - detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', - title='title') + from acme.messages import Error, is_acme_error + arabic_error = Error.with_code( + 'malformed', detail=u'\u0639\u062f\u0627\u0644\u0629', title='title') self.assertTrue(is_acme_error(arabic_error)) def test_with_code(self): @@ -304,8 +301,7 @@ class ChallengeBodyTest(unittest.TestCase): from acme.messages import Error from acme.messages import STATUS_INVALID self.status = STATUS_INVALID - error = Error(typ='urn:ietf:params:acme:error:serverInternal', - detail='Unable to communicate with DNS server') + error = Error.with_code('serverInternal', detail='Unable to communicate with DNS server') self.challb = ChallengeBody( uri='http://challb', chall=self.chall, status=self.status, error=error) diff --git a/acme/tests/test_util.py b/acme/tests/test_util.py index 6525f3af5..d4a45272d 100644 --- a/acme/tests/test_util.py +++ b/acme/tests/test_util.py @@ -25,8 +25,7 @@ def _guess_loader(filename, loader_pem, loader_der): return loader_pem elif ext.lower() == '.der': return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") + raise ValueError("Loader could not be recognized based on extension") # pragma: no cover def load_cert(*names): diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index f1035aa33..20c225e04 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -449,7 +449,7 @@ class ApacheConfigurator(common.Installer): filtered_vhosts[name] = vhost # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + dialog_input = set(filtered_vhosts.values()) # Ask the user which of names to enable, expect list of names back dialog_output = display_ops.select_vhost_multiple(list(dialog_input)) @@ -600,9 +600,9 @@ class ApacheConfigurator(common.Installer): "in the Apache config.", target_name) raise errors.PluginError("No vhost selected") - elif temp: + if temp: return vhost - elif not vhost.ssl: + if not vhost.ssl: addrs = self._get_proposed_addrs(vhost, "443") # TODO: Conflicts is too conservative if not any(vhost.enabled and vhost.conflicts(addrs) for @@ -951,13 +951,12 @@ class ApacheConfigurator(common.Installer): loc = parser.get_aug_path(self.parser.loc["name"]) if addr.get_port() == "443": - path = self.parser.add_dir_to_ifmodssl( + self.parser.add_dir_to_ifmodssl( loc, "NameVirtualHost", [str(addr)]) else: - path = self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) + self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) - msg = ("Setting %s to be NameBasedVirtualHost\n" - "\tDirective added to %s\n" % (addr, path)) + msg = "Setting {0} to be NameBasedVirtualHost\n".format(addr) logger.debug(msg) self.save_notes += msg @@ -1365,12 +1364,9 @@ class ApacheConfigurator(common.Installer): result.append(comment) sift = True - result.append('\n'.join( - ['# ' + l for l in chunk])) - continue + result.append('\n'.join(['# ' + l for l in chunk])) else: result.append('\n'.join(chunk)) - continue return result, sift def _get_vhost_block(self, vhost): @@ -2513,4 +2509,4 @@ class ApacheConfigurator(common.Installer): self._autohsts_save_state() -AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member +AutoHSTSEnhancement.register(ApacheConfigurator) diff --git a/certbot-apache/certbot_apache/_internal/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py index 62c6db272..c34abc2b4 100644 --- a/certbot-apache/certbot_apache/_internal/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -194,8 +194,8 @@ class ApacheHttp01(common.ChallengePerformer): if vhost not in self.moded_vhosts: logger.debug( - "Adding a temporary challenge validation Include for name: %s " + - "in: %s", vhost.name, vhost.filep) + "Adding a temporary challenge validation Include for name: %s in: %s", + vhost.name, vhost.filep) self.configurator.parser.add_dir_beginning( vhost.path, "Include", self.challenge_conf_pre) self.configurator.parser.add_dir( diff --git a/certbot-apache/certbot_apache/_internal/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py index aefc4c6d4..77ced6a3f 100644 --- a/certbot-apache/certbot_apache/_internal/override_debian.py +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -70,15 +70,14 @@ class DebianConfigurator(configurator.ApacheConfigurator): # Already in shape vhost.enabled = True return None - else: - logger.warning( - "Could not symlink %s to %s, got error: %s", enabled_path, - vhost.filep, err.strerror) - errstring = ("Encountered error while trying to enable a " + - "newly created VirtualHost located at {0} by " + - "linking to it from {1}") - raise errors.NotSupportedError(errstring.format(vhost.filep, - enabled_path)) + logger.warning( + "Could not symlink %s to %s, got error: %s", enabled_path, + vhost.filep, err.strerror) + errstring = ("Encountered error while trying to enable a " + + "newly created VirtualHost located at {0} by " + + "linking to it from {1}") + raise errors.NotSupportedError(errstring.format(vhost.filep, + enabled_path)) vhost.enabled = True logger.info("Enabling available site: %s", vhost.filep) self.save_notes += "Enabled site %s\n" % vhost.filep diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index 5c447ed27..e4073ee16 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -284,8 +284,8 @@ class ApacheParser(object): mods.add(mod_name) mods.add(os.path.basename(mod_filename)[:-2] + "c") else: - logger.debug("Could not read LoadModule directive from " + - "Augeas path: %s", match_name[6:]) + logger.debug("Could not read LoadModule directive from Augeas path: %s", + match_name[6:]) self.modules.update(mods) def update_runtime_variables(self): @@ -625,7 +625,7 @@ class ApacheParser(object): # https://httpd.apache.org/docs/2.4/mod/core.html#include for match in matches: dir_ = self.aug.get(match).lower() - if dir_ == "include" or dir_ == "includeoptional": + if dir_ in ("include", "includeoptional"): ordered_matches.extend(self.find_dir( directive, arg, self._get_include_path(self.get_arg(match + "/arg")), @@ -665,8 +665,7 @@ class ApacheParser(object): # e.g. strip now, not later if not value: return None - else: - value = value.strip("'\"") + value = value.strip("'\"") variables = ApacheParser.arg_var_interpreter.findall(value) diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index f043e8857..204d01620 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index c35aa4ba5..b592d6288 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -43,8 +43,7 @@ class Proxy(object): method = getattr(self._configurator, name, None) if callable(method): return method - else: - raise AttributeError() + raise AttributeError() def has_more_configs(self): """Returns true if there are more configs to test""" @@ -82,8 +81,7 @@ class Proxy(object): """Returns the set of domain names that the plugin should find""" if self._all_names: return self._all_names - else: - raise errors.Error("No configuration file loaded") + raise errors.Error("No configuration file loaded") def get_testable_domain_names(self): """Returns the set of domain names that can be tested against""" diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 03e1283c6..2c3f880e0 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -10,7 +10,6 @@ import tempfile import time import OpenSSL -from six.moves import xrange # pylint: disable=import-error,redefined-builtin from urllib3.util import connection from acme import challenges @@ -57,26 +56,27 @@ def test_authenticator(plugin, config, temp_dir): return False success = True - for i in xrange(len(responses)): - if not responses[i]: + for i, response in enumerate(responses): + achall = achalls[i] + if not response: logger.error( "Plugin failed to complete %s for %s in %s", - type(achalls[i]), achalls[i].domain, config) + type(achall), achall.domain, config) success = False - elif isinstance(responses[i], challenges.HTTP01Response): + elif isinstance(response, challenges.HTTP01Response): # We fake the DNS resolution to ensure that any domain is resolved # to the local HTTP server setup for the compatibility tests with _fake_dns_resolution("127.0.0.1"): - verified = responses[i].simple_verify( - achalls[i].chall, achalls[i].domain, + verified = response.simple_verify( + achall.chall, achall.domain, util.JWK.public_key(), port=plugin.http_port) if verified: logger.info( - "http-01 verification for %s succeeded", achalls[i].domain) + "http-01 verification for %s succeeded", achall.domain) else: logger.error( "**** http-01 verification for %s in %s failed", - achalls[i].domain, config) + achall.domain, config) success = False if success: @@ -89,8 +89,7 @@ def test_authenticator(plugin, config, temp_dir): if _dirs_are_unequal(config, backup): logger.error("Challenge cleanup failed for %s", config) return False - else: - logger.info("Challenge cleanup succeeded") + logger.info("Challenge cleanup succeeded") return success @@ -305,7 +304,7 @@ def get_args(): "-e", "--enhance", action="store_true", help="tests the enhancements " "the plugin supports (implicitly includes installer tests)") - for plugin in PLUGINS.itervalues(): + for plugin in PLUGINS.values(): plugin.add_parser_arguments(parser) args = parser.parse_args() diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index d1c761d19..b3fd81223 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 33afdfb60..288a6d115 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 21c31d803..ba3190567 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index fbdcea142..5729bd789 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -11,7 +11,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index c25a49c59..6fc756389 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 746df1b62..7c4da556d 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -9,7 +9,7 @@ version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py index f603a2dc3..3aa910b52 100644 --- a/certbot-dns-google/certbot_dns_google/_internal/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -235,7 +235,7 @@ class _GoogleClient(object): :rtype: `list` of `string` or `None` """ - rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member + rrs_request = self.dns.resourceRecordSets() request = rrs_request.list(managedZone=zone_id, project=self.project_id) # Add dot as the API returns absolute domains record_name += "." diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f42e4d13d..a0dc1c386 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'google-api-python-client>=1.5.5', 'mock', 'oauth2client>=4.0', diff --git a/certbot-dns-google/tests/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py index ddb0da813..647a75b05 100644 --- a/certbot-dns-google/tests/dns_google_test.py +++ b/certbot-dns-google/tests/dns_google_test.py @@ -288,7 +288,6 @@ class GoogleClientTest(unittest.TestCase): def test_get_existing_fallback(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}]) - # pylint: disable=no-member mock_execute = client.dns.resourceRecordSets.return_value.list.return_value.execute mock_execute.side_effect = API_ERROR diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 838e70c69..1829f7eb2 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 8bbcfd3af..f772dc26a 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -9,7 +9,7 @@ version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 43a865b93..18ba8cacc 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index bb8cbe5b0..3894f01cd 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 1116b6dfc..2e11550d6 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 3d0df5026..2fccf17c2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index c17610366..47167fa2b 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 1ee716cd6..3fce6f83b 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index c0f69fb4f..b4dcc58c1 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index aefe03f90..67d4cc53b 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.39.0 +-e certbot[dev] diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 68795cd2b..56c209a90 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -9,7 +9,7 @@ version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.39.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 6802df421..70d9d87f8 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -283,7 +283,7 @@ class NginxConfigurator(common.Installer): filtered_vhosts[name] = vhost # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + dialog_input = set(filtered_vhosts.values()) # Ask the user which of names to enable, expect list of names back return_vhosts = display_ops.select_vhost_multiple(list(dialog_input)) diff --git a/certbot-nginx/certbot_nginx/_internal/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py index 04b1ffa01..4fa1362a0 100644 --- a/certbot-nginx/certbot_nginx/_internal/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -186,12 +186,11 @@ class UnspacedList(list): """ if not isinstance(inbound, list): # str or None - return (inbound, inbound) + return inbound, inbound else: if not hasattr(inbound, "spaced"): inbound = UnspacedList(inbound) - return (inbound, inbound.spaced) - + return inbound, inbound.spaced def insert(self, i, x): item, spaced_item = self._coerce(x) diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py index 0b1eb1682..edb77a1c1 100644 --- a/certbot-nginx/certbot_nginx/_internal/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -275,7 +275,7 @@ class NginxParser(object): for directive in server: if not directive: continue - elif _is_ssl_on_directive(directive): + if _is_ssl_on_directive(directive): return True return False @@ -489,7 +489,7 @@ def get_best_match(target_name, names): def _exact_match(target_name, name): - return target_name == name or '.' + target_name == name + return name in (target_name, '.' + target_name) def _wildcard_match(target_name, name, start): @@ -507,7 +507,7 @@ def _wildcard_match(target_name, name, start): # The first part must be a wildcard or blank, e.g. '.eff.org' first = match_parts.pop(0) - if first != '*' and first != '': + if first not in ('*', ''): return False target_name = '.'.join(parts) @@ -585,7 +585,7 @@ def comment_directive(block, location): if isinstance(next_entry, list) and next_entry: if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: return - elif isinstance(next_entry, nginxparser.UnspacedList): + if isinstance(next_entry, nginxparser.UnspacedList): next_entry = next_entry.spaced[0] else: next_entry = next_entry[0] @@ -661,13 +661,12 @@ def _add_directive(block, directive, insert_at_top): for included_directive in included_directives: included_dir_loc = _find_location(block, included_directive[0]) included_dir_name = included_directive[0] - if not _is_whitespace_or_comment(included_directive) \ - and not can_append(included_dir_loc, included_dir_name): + if (not _is_whitespace_or_comment(included_directive) + and not can_append(included_dir_loc, included_dir_name)): if block[included_dir_loc] != included_directive: raise errors.MisconfigurationError(err_fmt.format(included_directive, block[included_dir_loc])) - else: - _comment_out_directive(block, included_dir_loc, directive[1]) + _comment_out_directive(block, included_dir_loc, directive[1]) if can_append(location, directive_name): if insert_at_top: diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 0e30f44eb..37532aabf 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==1.0.0 -certbot[dev]==1.0.0 +-e certbot[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index c844c85f5..96bf32d3e 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '1.1.0.dev0' # acme/certbot version. install_requires = [ 'acme>=1.0.0', - 'certbot>=1.0.0', + 'certbot>=1.0.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py index afa28befb..ef5593395 100644 --- a/certbot-nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -1091,7 +1091,7 @@ class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): self.assertIn("/usr/local/etc/nginx", server_root) self.assertIn("/etc/nginx", server_root) else: - self.assertTrue(server_root == "/etc/nginx" or server_root == "/usr/local/etc/nginx") + self.assertTrue(server_root in ("/etc/nginx", "/usr/local/etc/nginx")) if __name__ == "__main__": diff --git a/certbot-nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py index 5beee1111..2f3b260ca 100644 --- a/certbot-nginx/tests/parser_test.py +++ b/certbot-nginx/tests/parser_test.py @@ -49,16 +49,16 @@ class NginxParserTest(util.NginxTest): """ nparser = parser.NginxParser(self.config_path) nparser.load() - self.assertEqual(set([nparser.abs_path(x) for x in - ['foo.conf', 'nginx.conf', 'server.conf', - 'sites-enabled/default', - 'sites-enabled/example.com', - 'sites-enabled/headers.com', - 'sites-enabled/migration.com', - 'sites-enabled/sslon.com', - 'sites-enabled/globalssl.com', - 'sites-enabled/ipv6.com', - 'sites-enabled/ipv6ssl.com']]), + self.assertEqual({nparser.abs_path(x) for x in + ['foo.conf', 'nginx.conf', 'server.conf', + 'sites-enabled/default', + 'sites-enabled/example.com', + 'sites-enabled/headers.com', + 'sites-enabled/migration.com', + 'sites-enabled/sslon.com', + 'sites-enabled/globalssl.com', + 'sites-enabled/ipv6.com', + 'sites-enabled/ipv6ssl.com']}, set(nparser.parsed.keys())) self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], nparser.parsed[nparser.abs_path('server.conf')]) diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index d33aa6d0c..c4ea6ef35 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -216,9 +216,8 @@ class AccountFileStorage(interfaces.AccountStorage): else: self._symlink_to_accounts_dir(prev_server_path, server_path) return prev_loaded_account - else: - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) try: with open(self._regr_path(account_dir_path)) as regr_file: diff --git a/certbot/certbot/_internal/auth_handler.py b/certbot/certbot/_internal/auth_handler.py index 55415b46d..e4ad91247 100644 --- a/certbot/certbot/_internal/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -285,9 +285,8 @@ def challb_to_achall(challb, account_key, domain): challb=challb, domain=domain, account_key=account_key) elif isinstance(chall, challenges.DNS): return achallenges.DNS(challb=challb, domain=domain) - else: - raise errors.Error( - "Received unsupported challenge of type: {0}".format(chall.typ)) + raise errors.Error( + "Received unsupported challenge of type: {0}".format(chall.typ)) def gen_challenge_path(challbs, preferences, combinations): diff --git a/certbot/certbot/_internal/cert_manager.py b/certbot/certbot/_internal/cert_manager.py index da7b7a190..1def76a3d 100644 --- a/certbot/certbot/_internal/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -242,8 +242,8 @@ def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) elif len(matched) > 1: raise errors.OverlappingMatchFound() - else: - return matched + return matched + def human_readable_cert_info(config, cert, skip_filter_checks=False): """ Returns a human readable description of info about a RenewableCert object""" diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index 886904c0a..fb3010a4e 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -287,10 +287,9 @@ def flag_default(name): def config_help(name, hidden=False): """Extract the help message for an `.IConfig` attribute.""" - # pylint: disable=no-member if hidden: return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute return field.__doc__ @@ -674,7 +673,7 @@ class HelpfulArgumentParser(object): parsed_args.actual_csr = (csr, typ) - csr_domains = set([d.lower() for d in domains]) + csr_domains = {d.lower() for d in domains} config_domains = set(parsed_args.domains) if csr_domains != config_domains: raise errors.ConfigurationError( @@ -847,11 +846,11 @@ class HelpfulArgumentParser(object): chosen_topic = "run" if chosen_topic == "all": # 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]) + return {t: t != 'certbot-route53:auth' for t in self.help_topics} elif not chosen_topic: - return dict([(t, False) for t in self.help_topics]) - return dict([(t, t == chosen_topic) for t in self.help_topics]) + return {t: False for t in self.help_topics} + return {t: t == chosen_topic for t in self.help_topics} + def _add_all_groups(helpful): helpful.add_group("automation", description="Flags for automating execution & other tweaks") diff --git a/certbot/certbot/_internal/client.py b/certbot/certbot/_internal/client.py index c9977d4d7..9ce741e38 100644 --- a/certbot/certbot/_internal/client.py +++ b/certbot/certbot/_internal/client.py @@ -224,11 +224,9 @@ def perform_registration(acme, config, tos_cb): "Please ensure it is a valid email and attempt " "registration again." % config.email) raise errors.Error(msg) - else: - config.email = display_ops.get_email(invalid=True) - return perform_registration(acme, config, tos_cb) - else: - raise + config.email = display_ops.get_email(invalid=True) + return perform_registration(acme, config, tos_cb) + raise class Client(object): @@ -360,7 +358,6 @@ class Client(object): return self.obtain_certificate(successful_domains) else: cert, chain = self.obtain_certificate_from_csr(csr, orderr) - return cert, chain, key, csr def _get_order_and_authorizations(self, csr_pem, best_effort): @@ -393,8 +390,6 @@ class Client(object): authzr = self.auth_handler.handle_authorizations(orderr, best_effort) return orderr.update(authorizations=authzr) - - # pylint: disable=no-member def obtain_and_enroll_certificate(self, domains, certname): """Obtain and enroll certificate. diff --git a/certbot/certbot/_internal/configuration.py b/certbot/certbot/_internal/configuration.py index 6e9c185f4..f3db207db 100644 --- a/certbot/certbot/_internal/configuration.py +++ b/certbot/certbot/_internal/configuration.py @@ -1,7 +1,7 @@ """Certbot user-supplied configuration.""" import copy -from six.moves.urllib import parse # pylint: disable=relative-import +from six.moves.urllib import parse import zope.interface from certbot import errors diff --git a/certbot/certbot/_internal/error_handler.py b/certbot/certbot/_internal/error_handler.py index a2f9a3dc6..5ca3cc57e 100644 --- a/certbot/certbot/_internal/error_handler.py +++ b/certbot/certbot/_internal/error_handler.py @@ -93,7 +93,7 @@ class ErrorHandler(object): # SystemExit is ignored to properly handle forks that don't exec if exec_type is SystemExit: return retval - elif exec_type is None: + if exec_type is None: if not self.call_on_regular_exit: return retval elif exec_type is errors.SignalExit: diff --git a/certbot/certbot/_internal/log.py b/certbot/certbot/_internal/log.py index 56ac2c3fe..0a492ba55 100644 --- a/certbot/certbot/_internal/log.py +++ b/certbot/certbot/_internal/log.py @@ -102,9 +102,9 @@ def post_arg_parse_setup(config): root_logger.addHandler(file_handler) root_logger.removeHandler(memory_handler) - temp_handler = memory_handler.target - memory_handler.setTarget(file_handler) - memory_handler.flush(force=True) + temp_handler = memory_handler.target # pylint: disable=no-member + memory_handler.setTarget(file_handler) # pylint: disable=no-member + memory_handler.flush(force=True) # pylint: disable=unexpected-keyword-arg memory_handler.close() temp_handler.close() diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index f87b7c571..72fcfca71 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -121,7 +121,7 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N lineage = le_client.obtain_and_enroll_certificate(domains, certname) if lineage is False: raise errors.Error("Certificate could not be obtained") - elif lineage is not None: + if lineage is not None: hooks.deploy_hook(config, lineage.names(), lineage.live_dir) finally: hooks.post_hook(config) @@ -162,19 +162,18 @@ def _handle_subset_cert_request(config, domains, cert): cli_flag="--expand", force_interactive=True): return "renew", cert - else: - reporter_util = zope.component.getUtility(interfaces.IReporter) - reporter_util.add_message( - "To obtain a new certificate that contains these names without " - "replacing your existing certificate for {0}, you must use the " - "--duplicate option.{br}{br}" - "For example:{br}{br}{1} --duplicate {2}".format( - existing, - sys.argv[0], " ".join(sys.argv[1:]), - br=os.linesep - ), - reporter_util.HIGH_PRIORITY) - raise errors.Error(USER_CANCELLED) + reporter_util = zope.component.getUtility(interfaces.IReporter) + reporter_util.add_message( + "To obtain a new certificate that contains these names without " + "replacing your existing certificate for {0}, you must use the " + "--duplicate option.{br}{br}" + "For example:{br}{br}{1} --duplicate {2}".format( + existing, + sys.argv[0], " ".join(sys.argv[1:]), + br=os.linesep + ), + reporter_util.HIGH_PRIORITY) + raise errors.Error(USER_CANCELLED) def _handle_identical_cert_request(config, lineage): @@ -220,7 +219,7 @@ def _handle_identical_cert_request(config, lineage): # skipping the menu for this case. raise errors.Error( "Operation canceled. You may re-run the client.") - elif response[1] == 0: + if response[1] == 0: return "reinstall", lineage elif response[1] == 1: return "renew", lineage @@ -312,23 +311,20 @@ def _find_lineage_for_domains_and_certname(config, domains, certname): """ if not certname: return _find_lineage_for_domains(config, domains) - else: - lineage = cert_manager.lineage_for_certname(config, certname) - if lineage: - if domains: - if set(cert_manager.domains_for_certname(config, certname)) != set(domains): - _ask_user_to_confirm_new_names(config, domains, certname, - lineage.names()) # raises if no - return "renew", lineage - # unnecessarily specified domains or no domains specified - return _handle_identical_cert_request(config, lineage) - else: - if domains: - return "newcert", None - else: - raise errors.ConfigurationError("No certificate with name {0} found. " - "Use -d to specify domains, or run certbot certificates to see " - "possible certificate names.".format(certname)) + lineage = cert_manager.lineage_for_certname(config, certname) + if lineage: + if domains: + if set(cert_manager.domains_for_certname(config, certname)) != set(domains): + _ask_user_to_confirm_new_names(config, domains, certname, + lineage.names()) # raises if no + return "renew", lineage + # unnecessarily specified domains or no domains specified + return _handle_identical_cert_request(config, lineage) + elif domains: + return "newcert", None + raise errors.ConfigurationError("No certificate with name {0} found. " + "Use -d to specify domains, or run certbot certificates to see " + "possible certificate names.".format(certname)) def _get_added_removed(after, before): """Get lists of items removed from `before` @@ -1338,7 +1334,7 @@ def main(cli_args=None): make_or_verify_needed_dirs(config) except errors.Error: # Let plugins_cmd be run as un-privileged user. - if config.func != plugins_cmd: + if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise set_displayer(config) diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/_internal/ocsp.py index 8312d7fe0..65a6d5c17 100644 --- a/certbot/certbot/_internal/ocsp.py +++ b/certbot/certbot/_internal/ocsp.py @@ -296,5 +296,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False diff --git a/certbot/certbot/_internal/plugins/selection.py b/certbot/certbot/_internal/plugins/selection.py index de1d27227..6d87e4b07 100644 --- a/certbot/certbot/_internal/plugins/selection.py +++ b/certbot/certbot/_internal/plugins/selection.py @@ -64,9 +64,8 @@ def get_unprepared_installer(config, plugins): inst = list(installers.values())[0] logger.debug("Selecting plugin: %s", inst) return inst.init(config) - else: - raise errors.PluginSelectionError( - "Could not select or initialize the requested installer %s." % req_inst) + raise errors.PluginSelectionError( + "Could not select or initialize the requested installer %s." % req_inst) def pick_plugin(config, default, plugins, question, ifaces): """Pick plugin. @@ -209,7 +208,7 @@ def choose_configurator_plugins(config, plugins, verb): need_inst = need_auth = False if verb == "certonly": need_auth = True - if verb == "install" or verb == "enhance": + elif verb in ("install", "enhance"): need_inst = True if config.authenticator: logger.warning("Specifying an authenticator doesn't make sense when " diff --git a/certbot/certbot/_internal/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py index bb816cd46..80421299e 100644 --- a/certbot/certbot/_internal/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -76,7 +76,6 @@ class ServerManager(object): servers.serve_forever() # if port == 0, then random free port on OS is taken - # pylint: disable=no-member # both servers, if they exist, have the same port real_port = servers.getsocknames()[0][1] self._instances[real_port] = servers @@ -196,7 +195,7 @@ def _handle_perform_error(error): "the appropriate permissions (for example, you " "aren't running this program as " "root).".format(error.port)) - elif error.socket_error.errno == socket_errors.EADDRINUSE: + if error.socket_error.errno == socket_errors.EADDRINUSE: display = zope.component.getUtility(interfaces.IDisplay) msg = ( "Could not bind TCP port {0} because it is already in " diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py index 837918345..c7737e0d1 100644 --- a/certbot/certbot/_internal/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -135,8 +135,7 @@ to serve all files under specified web root ({0}).""" raise errors.PluginError( "Every requested domain must have a " "webroot when using the webroot plugin.") - else: # code == display_util.OK - return None if index == 0 else known_webroots[index - 1] + return None if index == 0 else known_webroots[index - 1] # code == display_util.OK def _prompt_for_new_webroot(self, domain, allowraise=False): code, webroot = ops.validated_directory( @@ -146,12 +145,10 @@ to serve all files under specified web root ({0}).""" if code == display_util.CANCEL: if not allowraise: return None - else: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return _validate_webroot(webroot) + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + return _validate_webroot(webroot) # code == display_util.OK def _create_challenge_dirs(self): path_map = self.conf("map") diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 4947ca067..0426b2e2d 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -471,5 +471,4 @@ def handle_renewal_request(config): if renew_failures or parse_failures: raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( len(renew_failures), len(parse_failures))) - else: - logger.debug("no renewal failures") + logger.debug("no renewal failures") diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index b632e9d52..964515eee 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -1014,10 +1014,8 @@ class RenewableCert(interfaces.RenewableCert): "directory %s created.", archive, live_dir) # 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]) + target = {kind: os.path.join(live_dir, kind + ".pem") for kind in ALL_FOUR} + archive_target = {kind: os.path.join(archive, kind + "1.pem") for kind in ALL_FOUR} for kind in ALL_FOUR: os.symlink(_relpath_from_file(archive_target[kind], target[kind]), target[kind]) with open(target["cert"], "wb") as f: @@ -1082,10 +1080,8 @@ class RenewableCert(interfaces.RenewableCert): self.cli_config = cli_config target_version = self.next_free_version() - target = dict( - [(kind, - os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version))) - for kind in ALL_FOUR]) + target = {kind: 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)) diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index b7a4683df..ba4a155e8 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -71,6 +71,9 @@ def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): stats = os.stat(src) user_id = stats.st_uid if copy_user else -1 group_id = stats.st_gid if copy_group else -1 + # On Windows, os.chown does not exist. This is checked through POSIX_MODE value, + # but MyPy/PyLint does not know it and raises an error here on Windows. + # We disable specifically the check to fix the issue. os.chown(dst, user_id, group_id) elif copy_user: # There is no group handling in Windows @@ -105,7 +108,10 @@ def check_owner(file_path): :return: True if given file is owned by current user, False otherwise. """ if POSIX_MODE: - return os.stat(file_path).st_uid == os.getuid() + # On Windows, os.getuid does not exist. This is checked through POSIX_MODE value, + # but MyPy/PyLint does not know it and raises an error here on Windows. + # We disable specifically the check to fix the issue. + return os.stat(file_path).st_uid == os.getuid() # type: ignore # Get owner sid of the file security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index 185c21b58..92b09d6a1 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -60,11 +60,10 @@ def get_email(invalid=False, optional=True): raise errors.Error( "An e-mail address or " "--register-unsafely-without-email must be provided.") - else: - raise errors.Error("An e-mail address must be provided.") - elif util.safe_email(email): + raise errors.Error("An e-mail address must be provided.") + if util.safe_email(email): return email - elif suggest_unsafe: + if suggest_unsafe: msg += unsafe_suggestion suggest_unsafe = False # add this message at most once diff --git a/certbot/certbot/display/util.py b/certbot/certbot/display/util.py index 05b69e539..ba2dd4ecf 100644 --- a/certbot/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -177,7 +177,7 @@ class FileDisplay(object): message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " " ans = input_with_timeout(message) - if ans == "c" or ans == "C": + if ans in ("c", "C"): return CANCEL, "-1" return OK, ans @@ -258,10 +258,9 @@ class FileDisplay(object): selected_tags = self._scrub_checklist_input(indices, tags) if selected_tags: return code, selected_tags - else: - self.outfile.write( - "** Error - Invalid selection **%s" % os.linesep) - self.outfile.flush() + self.outfile.write( + "** Error - Invalid selection **%s" % os.linesep) + self.outfile.flush() else: return code, [] @@ -282,18 +281,17 @@ class FileDisplay(object): # assert_valid_call(prompt, default, cli_flag, force_interactive) if self._can_interact(force_interactive): return False - elif default is None: + if default is None: msg = "Unable to get an answer for the question:\n{0}".format(prompt) if cli_flag: msg += ( "\nYou can provide an answer on the " "command line with the {0} flag.".format(cli_flag)) raise errors.Error(msg) - else: - logger.debug( - "Falling back to default %s for the prompt:\n%s", - default, prompt) - return True + logger.debug( + "Falling back to default %s for the prompt:\n%s", + default, prompt) + return True def _can_interact(self, force_interactive): """Can we safely interact with the user? @@ -308,7 +306,7 @@ class FileDisplay(object): if (self.force_interactive or force_interactive or sys.stdin.isatty() and self.outfile.isatty()): return True - elif not self.skipped_interaction: + if not self.skipped_interaction: logger.warning( "Skipped user interaction because Certbot doesn't appear to " "be running in a terminal. You should probably include " diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index b56559c0e..6fa1e76f8 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -33,6 +33,7 @@ def dest_namespace(name): """ArgumentParser dest namespace (prefix of all destinations).""" return name.replace("-", "_") + "_" + private_ips_regex = re.compile( r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|" r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)") @@ -294,7 +295,7 @@ class Addr(object): # appended to the end append_to_end = True continue - elif len(block) > 1: + if len(block) > 1: # remove leading zeros block = block.lstrip("0") if not append_to_end: @@ -373,9 +374,9 @@ def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes active_file_digest = crypto_util.sha256sum(dest_path) if active_file_digest == current_hash: # already up to date return - elif active_file_digest in all_hashes: # safe to update + if active_file_digest in all_hashes: # safe to update _install_current_file() - else: # has been manually modified, not safe to update + else: # has been manually modified, not safe to update # did they modify the current version or an old version? if os.path.isfile(digest_path): with open(digest_path, "r") as f: diff --git a/certbot/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py index 10d5010f2..d31266434 100644 --- a/certbot/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -197,8 +197,7 @@ class DNSAuthenticator(common.Plugin): if code == display_util.OK: return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) + raise errors.PluginError('{0} required to proceed.'.format(label)) @staticmethod def _prompt_for_file(label, validator=None): @@ -231,8 +230,7 @@ class DNSAuthenticator(common.Plugin): if code == display_util.OK: return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) + raise errors.PluginError('{0} required to proceed.'.format(label)) class CredentialsConfiguration(object): diff --git a/certbot/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py index 71bba76d8..3e28a291b 100644 --- a/certbot/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -100,7 +100,7 @@ class LexiconClient(object): result = self._handle_general_error(e, domain_name) if result: - raise result + raise result # pylint: disable=raising-bad-type raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}' .format(domain, domain_name_guesses)) diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py index 3f269c99c..9ef76c2c3 100644 --- a/certbot/certbot/plugins/dns_test_common.py +++ b/certbot/certbot/plugins/dns_test_common.py @@ -28,18 +28,14 @@ class BaseAuthenticatorTest(object): challb=acme_util.DNS01, domain=DOMAIN, account_key=KEY) def test_more_info(self): - # pylint: disable=no-member - self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) + self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) # pylint: disable=no-member def test_get_chall_pref(self): - # pylint: disable=no-member - self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) + self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) # pylint: disable=no-member def test_parser_arguments(self): m = mock.MagicMock() - - # pylint: disable=no-member - self.auth.add_parser_arguments(m) + self.auth.add_parser_arguments(m) # pylint: disable=no-member m.assert_any_call('propagation-seconds', type=int, default=mock.ANY, help=mock.ANY) diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index d757ab05a..02abe0a31 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -57,8 +57,7 @@ def _guess_loader(filename, loader_pem, loader_der): return loader_pem elif ext.lower() == '.der': return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") + raise ValueError("Loader could not be recognized based on extension") # pragma: no cover def load_cert(*names): @@ -235,8 +234,7 @@ class FreezableMock(object): if self._frozen: if name in self._frozen_set: raise AttributeError('Cannot change frozen attribute ' + name) - else: - return setattr(self._mock, name, value) + return setattr(self._mock, name, value) if name != '_frozen_set': self._frozen_set.add(name) @@ -250,7 +248,7 @@ class FreezableMock(object): def _create_get_utility_mock(): display = FreezableMock() # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: E1120 if name != 'notification': frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call) setattr(display, name, frozen_mock) @@ -275,7 +273,7 @@ def _create_get_utility_mock_with_stdout(stdout): display = FreezableMock() # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: E1120 if name == 'notification': frozen_mock = FreezableMock(frozen=True, func=_write_msg) diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index d56fe6845..0a47cd87a 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -26,7 +26,7 @@ from certbot.compat import filesystem from certbot.compat import os if sys.platform.startswith('linux'): - import distro + import distro # pylint: disable=import-error _USE_DISTRO = True else: _USE_DISTRO = False @@ -105,10 +105,9 @@ def exe_exists(exe): path, _ = os.path.split(exe) if path: return filesystem.is_executable(exe) - else: - for path in os.environ["PATH"].split(os.pathsep): - if filesystem.is_executable(os.path.join(path, exe)): - return True + for path in os.environ["PATH"].split(os.pathsep): + if filesystem.is_executable(os.path.join(path, exe)): + return True return False @@ -436,7 +435,6 @@ def add_deprecated_argument(add_argument, argument_name, nargs): # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was # changed from a set to a tuple. if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set): - # pylint: disable=no-member configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add( _ShowWarning) else: @@ -537,7 +535,7 @@ def enforce_domain_sanity(domain): for l in labels: if not l: raise errors.ConfigurationError("{0} it contains an empty label.".format(msg)) - elif len(l) > 63: + if len(l) > 63: raise errors.ConfigurationError("{0} label {1} is too long.".format(msg, l)) return domain @@ -571,7 +569,6 @@ def get_strict_version(normalized): """ # strict version ending with "a" and a number designates a pre-release - # pylint: disable=no-member return distutils.version.StrictVersion(normalized.replace(".dev", "a")) diff --git a/certbot/setup.py b/certbot/setup.py index d76494a84..0026ef8e9 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -74,21 +74,21 @@ elif os.name == 'nt': install_requires.append(pywin32_req) dev_extras = [ - 'astroid==1.6.5', 'coverage', 'ipdb', 'pytest', 'pytest-cov', 'pytest-xdist', - 'pylint==1.9.4', 'tox', 'twine', 'wheel', ] dev3_extras = [ + 'astroid', 'mypy', - 'typing', # for python3.4 + 'pylint', + 'typing', # for python3.4 ] docs_extras = [ diff --git a/certbot/tests/auth_handler_test.py b/certbot/tests/auth_handler_test.py index b38618daf..7ab3a2baa 100644 --- a/certbot/tests/auth_handler_test.py +++ b/certbot/tests/auth_handler_test.py @@ -494,7 +494,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase): self.authzr1.body.identifier.value = 'example.com' self.authzr1.body.challenges = [http_01, http_01] - kwargs["error"] = messages.Error(typ="dnssec", detail="detail") + kwargs["error"] = messages.Error.with_code("dnssec", detail="detail") http_01_diff = messages.ChallengeBody(**kwargs) self.authzr2 = mock.MagicMock() diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 2c838214e..81134f02f 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -178,7 +178,7 @@ class CertificatesTest(BaseCertManagerTest): mock_verifier.return_value = None mock_report.return_value = "" self._certificates(self.config) - self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member + self.assertFalse(mock_logger.warning.called) self.assertTrue(mock_report.called) self.assertTrue(mock_utility.called) self.assertTrue(mock_renewable_cert.called) @@ -196,7 +196,7 @@ class CertificatesTest(BaseCertManagerTest): filesystem.makedirs(empty_config.renewal_configs_dir) self._certificates(empty_config) - self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member + self.assertFalse(mock_logger.warning.called) self.assertTrue(mock_utility.called) shutil.rmtree(empty_tempdir) @@ -240,7 +240,7 @@ class CertificatesTest(BaseCertManagerTest): # pylint: disable=protected-access out = get_report() self.assertTrue('3 days' in out) - self.assertTrue('VALID' in out and 'INVALID' not in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.is_test_cert = True mock_revoked.return_value = True diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index bc3727043..7232ed84b 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -205,7 +205,7 @@ class RegisterTest(test_util.ConfigTestCase): def test_unsupported_error(self): from acme import messages msg = "Test" - mx_err = messages.Error(detail=msg, typ="malformed", title="title") + mx_err = messages.Error.with_code("malformed", detail=msg, title="title") with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().client.directory.__getitem__ = mock.Mock( side_effect=self._new_acct_dir_mock diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index f700e0908..e721bbd48 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -363,6 +363,8 @@ class CheckPermissionsTest(test_util.TempDirTestCase): self.assertTrue(filesystem.check_owner(self.probe_path)) import os as std_os # pylint: disable=os-module-forbidden + # See related inline comment in certbot.compat.filesystem.check_owner method + # that explains why MyPy/PyLint check disable is needed here. uid = std_os.getuid() with mock.patch('os.getuid') as mock_uid: diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index ca9f1e382..615f33406 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -311,12 +311,12 @@ class FileOutputDisplayTest(unittest.TestCase): def test_methods_take_force_interactive(self): # Every IDisplay method implemented by FileDisplay must take # force_interactive to prevent workflow regressions. - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,no-value-for-parameter + for name in interfaces.IDisplay.names(): if six.PY2: - getargspec = inspect.getargspec # pylint: disable=no-member + getargspec = inspect.getargspec else: - getargspec = inspect.getfullargspec # pylint: disable=no-member - arg_spec = getargspec(getattr(self.displayer, name)) + getargspec = inspect.getfullargspec + arg_spec = getargspec(getattr(self.displayer, name)) # pylint: disable=deprecated-method self.assertTrue("force_interactive" in arg_spec.args) @@ -372,14 +372,14 @@ class NoninteractiveDisplayTest(unittest.TestCase): # NoninteractiveDisplay. # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: disable=E1120 method = getattr(self.displayer, name) # asserts method accepts arbitrary keyword arguments if six.PY2: - result = inspect.getargspec(method).keywords # pylint: disable=no-member + result = inspect.getargspec(method).keywords # pylint:deprecated-method self.assertFalse(result is None) else: - result = inspect.getfullargspec(method).varkw # pylint: disable=no-member + result = inspect.getfullargspec(method).varkw self.assertFalse(result is None) diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index b53187f47..cdd7908a3 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -111,7 +111,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_bad_status(self, mock_get_utility): self.json['status'] = False - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'because your e-mail address appears to be invalid.' self.assertTrue(expected_part in actual) @@ -120,7 +120,7 @@ class SubscribeTest(unittest.TestCase): def test_not_ok(self, mock_get_utility): self.response.ok = False self.response.raise_for_status.side_effect = requests.exceptions.HTTPError - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) unexpected_part = 'because' self.assertFalse(unexpected_part in actual) @@ -128,7 +128,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_response_not_json(self, mock_get_utility): self.response.json.side_effect = ValueError() - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'problem' self.assertTrue(expected_part in actual) @@ -136,7 +136,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_response_json_missing_status_element(self, mock_get_utility): self.json.clear() - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'problem' self.assertTrue(expected_part in actual) @@ -147,7 +147,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_subscribe(self, mock_get_utility): - self._call() # pylint: disable=no-value-for-parameter + self._call() self.assertFalse(mock_get_utility.called) diff --git a/certbot/tests/errors_test.py b/certbot/tests/errors_test.py index b7951284f..d6c829322 100644 --- a/certbot/tests/errors_test.py +++ b/certbot/tests/errors_test.py @@ -13,25 +13,27 @@ class FailedChallengesTest(unittest.TestCase): def setUp(self): from certbot.errors import FailedChallenges - self.error = FailedChallenges(set([achallenges.DNS( + self.error = FailedChallenges({achallenges.DNS( domain="example.com", challb=messages.ChallengeBody( chall=acme_util.DNS01, uri=None, - error=messages.Error(typ="tls", detail="detail")))])) + error=messages.Error.with_code("tls", detail="detail")))}) def test_str(self): self.assertTrue(str(self.error).startswith( - "Failed authorization procedure. example.com (dns-01): tls")) + "Failed authorization procedure. example.com (dns-01): " + "urn:ietf:params:acme:error:tls")) def test_unicode(self): from certbot.errors import FailedChallenges arabic_detail = u'\u0639\u062f\u0627\u0644\u0629' - arabic_error = FailedChallenges(set([achallenges.DNS( + arabic_error = FailedChallenges({achallenges.DNS( domain="example.com", challb=messages.ChallengeBody( chall=acme_util.DNS01, uri=None, - error=messages.Error(typ="tls", detail=arabic_detail)))])) + error=messages.Error.with_code("tls", detail=arabic_detail)))}) self.assertTrue(str(arabic_error).startswith( - "Failed authorization procedure. example.com (dns-01): tls")) + "Failed authorization procedure. example.com (dns-01): " + "urn:ietf:params:acme:error:tls")) class StandaloneBindErrorTest(unittest.TestCase): diff --git a/certbot/tests/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py index 3ecda2019..05fbc5028 100644 --- a/certbot/tests/plugins/enhancements_test.py +++ b/certbot/tests/plugins/enhancements_test.py @@ -37,12 +37,10 @@ class EnhancementTest(test_util.ConfigTestCase): self.assertTrue([i for i in enabled if i["name"] == "somethingelse"]) def test_are_requested(self): - self.assertEqual( - len([i for i in enhancements.enabled_enhancements(self.config)]), 0) + self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 0) self.assertFalse(enhancements.are_requested(self.config)) self.config.auto_hsts = True - self.assertEqual( - len([i for i in enhancements.enabled_enhancements(self.config)]), 1) + self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 1) self.assertTrue(enhancements.are_requested(self.config)) def test_are_supported(self): diff --git a/certbot/tests/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py index afca48bd9..5d9ff5244 100644 --- a/certbot/tests/plugins/standalone_test.py +++ b/certbot/tests/plugins/standalone_test.py @@ -37,7 +37,7 @@ class ServerManagerTest(unittest.TestCase): def _test_run_stop(self, challenge_type): server = self.mgr.run(port=0, challenge_type=challenge_type) - port = server.getsocknames()[0][1] # pylint: disable=no-member + port = server.getsocknames()[0][1] self.assertEqual(self.mgr.running(), {port: server}) self.mgr.stop(port=port) self.assertEqual(self.mgr.running(), {}) @@ -47,7 +47,7 @@ class ServerManagerTest(unittest.TestCase): def test_run_idempotent(self): server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) - port = server.getsocknames()[0][1] # pylint: disable=no-member + port = server.getsocknames()[0][1] server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) self.assertEqual(self.mgr.running(), {port: server}) self.assertTrue(server is server2) diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 8e127b21d..6208974ec 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -356,8 +356,7 @@ class RenewableCertTests(BaseRenewableCertTest): basename = os.path.basename(path) if "fullchain" in basename and basename.startswith("prev"): raise ValueError - else: - real_unlink(path) + real_unlink(path) self._write_out_ex_kinds() with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: @@ -372,8 +371,7 @@ class RenewableCertTests(BaseRenewableCertTest): # pylint: disable=missing-docstring if "fullchain" in os.path.basename(path): raise ValueError - else: - real_unlink(path) + real_unlink(path) self._write_out_ex_kinds() with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py index 50f3c5ef6..ebe4e3671 100755 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ b/letshelp-certbot/letshelp_certbot/apache.py @@ -74,7 +74,7 @@ def make_and_verify_selection(server_root, temp_dir): ans = six.moves.input("(Y)es/(N)o: ").lower() if ans.startswith("y"): return - elif ans.startswith("n"): + if ans.startswith("n"): sys.exit("Your files were not submitted") @@ -159,7 +159,7 @@ def safe_config_file(config_file): empty_or_all_comments = False if line.startswith("-----BEGIN"): return False - elif ":" not in line: + if ":" not in line: possible_password_file = False # If file isn't empty or commented out and could be a password file, # don't include it in selection. It is safe to include the file if diff --git a/tests/lock_test.py b/tests/lock_test.py index e6481941f..29a77ae17 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -173,7 +173,7 @@ def setup_certificate(workspace): key_path = os.path.join(workspace, 'cert.key') with open(key_path, 'wb') as file_handle: - file_handle.write(private_key.private_bytes( + file_handle.write(private_key.private_bytes( # type: ignore encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index dd5a97600..a4c877ba8 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -6,7 +6,7 @@ alabaster==0.7.10 apipkg==1.4 appnope==0.1.0 asn1crypto==0.22.0 -astroid==1.6.5 +astroid==2.3.3 attrs==17.3.0 Babel==2.5.1 backports.functools-lru-cache==1.5 @@ -33,17 +33,18 @@ importlib-metadata==0.23 ipdb==0.10.2 ipython==5.5.0 ipython-genutils==0.2.0 -isort==4.2.5 +isort==4.3.21 Jinja2==2.9.6 jmespath==0.9.3 josepy==1.1.0 -lazy-object-proxy==1.3.1 +lazy-object-proxy==1.4.3 logger==1.4 logilab-common==1.4.1 MarkupSafe==1.0 mccabe==0.6.1 more-itertools==5.0.0 -mypy==0.600 +mypy==0.710 +mypy-extensions==0.4.3 ndg-httpsclient==0.3.2 oauth2client==4.0.0 packaging==19.2 @@ -58,7 +59,7 @@ py==1.8.0 pyasn1==0.1.9 pyasn1-modules==0.0.10 Pygments==2.2.0 -pylint==1.9.4 +pylint==2.4.3 # If pynsist version is upgraded, our NSIS template windows-installer/template.nsi # must be upgraded if necessary using the new built-in one from pynsist. pynsist==2.4 @@ -90,10 +91,10 @@ tox==3.14.0 tqdm==4.19.4 traitlets==4.3.2 twine==1.11.0 -typed-ast==1.1.0 +typed-ast==1.4.0 typing==3.6.4 uritemplate==3.0.0 virtualenv==16.6.2 wcwidth==0.1.7 -wrapt==1.11.1 +wrapt==1.11.2 zipp==0.6.0 diff --git a/tox.ini b/tox.ini index 10f8ec914..5f1a9a426 100644 --- a/tox.ini +++ b/tox.ini @@ -122,12 +122,13 @@ commands = python tox.cover.py [testenv:lint] -basepython = python2.7 +basepython = python3 # separating into multiple invocations disables cross package # duplicate code checking; if one of the commands fails, others will # continue, but tox return code will reflect previous error commands = {[base]install_packages} + {[base]pip_install} certbot[dev3] python -m pylint --reports=n --rcfile=.pylintrc {[base]source_paths} [testenv:mypy] -- cgit v1.2.3 From 4a906484eeec0748f480dc4dca9ed7557d11ae56 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Tue, 17 Dec 2019 01:03:39 +0100 Subject: Execute Windows installer integration tests on several Windows versions (#7641) This PRs extends the installer tests on Azure Pipeline, in order to run the integration tests on a certbot instance installed with the Windows installer for several Windows versions, corresponding to the scope of supported versions on Certbot: * Windows Server 2012 R2 * Windows Server 2016 * Windows Server 2019 One can see the result on: https://dev.azure.com/adferrand/certbot/_build/results?buildId=311 * Try specific installer-build step * Install Python manually * Add tests on windows 2019 --- .azure-pipelines/templates/installer-tests.yml | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index b1c5ea6b1..d4550aad0 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -1,5 +1,5 @@ jobs: - - job: installer + - job: installer_build pool: vmImage: vs2017-win2016 steps: @@ -20,10 +20,32 @@ jobs: path: $(Build.ArtifactStagingDirectory) artifact: windows-installer displayName: Publish Windows installer - - script: $(Build.ArtifactStagingDirectory)\certbot-beta-installer-win32.exe /S + - job: installer_run + dependsOn: installer_build + strategy: + matrix: + win2019: + imageName: windows-2019 + win2016: + imageName: vs2017-win2016 + win2012r2: + imageName: vs2015-win2012r2 + pool: + vmImage: $(imageName) + steps: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: windows-installer + path: $(Build.SourcesDirectory)/bin + displayName: Retrieve Windows installer + - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S displayName: Install Certbot + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe -OutFile C:\py3-setup.exe + displayName: Get Python + - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=0 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 + displayName: Install Python - script: | - python -m venv venv + py -3 -m venv venv venv\Scripts\python tools\pip_install.py -e certbot-ci displayName: Prepare Certbot-CI - script: | -- cgit v1.2.3 From 24fdea5fd8a8319af6513a2053283f7ca65fd9ef Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 Dec 2019 11:13:57 -0800 Subject: discourage dns plugins (#7639) --- certbot/docs/contributing.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index da0ddc9d1..0807cbf24 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -300,6 +300,16 @@ configuration checkpoints and rollback. Writing your own plugin ~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: The Certbot team is not currently accepting any new DNS plugins + because we want to rethink our approach to the challenge and resolve some + issues like `#6464 `_, + `#6503 `_, and `#6504 + `_ first. + + In the meantime, you're welcome to release it as a third-party plugin. See + `certbot-dns-ispconfig `_ + for one example of that. + Certbot client supports dynamic discovery of plugins through the `setuptools entry points`_ using the `certbot.plugins` group. This way you can, for example, create a custom implementation of -- cgit v1.2.3 From 6ac7aabaf7e49fc9f076884f981ba91d696fb093 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 Dec 2019 11:14:58 -0800 Subject: Remove warning about dev preview (#7640) --- certbot/docs/contributing.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 0807cbf24..e1289c849 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -338,12 +338,6 @@ plugins. It's technically possible to install third-party plugins into the virtualenv used by `certbot-auto`, but they will be wiped away when `certbot-auto` upgrades. -.. warning:: Please be aware though that as this client is still in a - developer-preview stage, the API may undergo a few changes. If you - believe the plugin will be beneficial to the community, please - consider submitting a pull request to the repo and we will update - it with any necessary API changes. - .. _`setuptools entry points`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points -- cgit v1.2.3 From b5a31bec036ad9a6770353537227c34e9494b270 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 18 Dec 2019 22:21:54 +0100 Subject: Add docker-compose as a requirement of certbot-ci (#7120) Fixes #7110 This PR declares docker-compose as a requirement for certbot-ci. This way, a recent version of docker-compose is installed in the standard virtual environment set up by `tools/venv.py` and `tools/venv3.py`, and so is available to pytest integration tests from `tox` or in the virtual environment enabled. * Add docker-compose as a dev dependency and declares it in certbot-ci requirements * Update docker-compose 1.25.0 --- certbot-ci/certbot_integration_tests/conftest.py | 1 + certbot-ci/setup.py | 1 + tools/dev_constraints.txt | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index 0879c829c..6eb9ee865 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -6,6 +6,7 @@ for a directory a specific configuration using built-in pytest hooks. See https://docs.pytest.org/en/latest/reference.html#hook-reference """ +from __future__ import print_function import contextlib import subprocess import sys diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 71052bd3e..fb82b6ca5 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -10,6 +10,7 @@ version = '0.32.0.dev0' install_requires = [ 'coverage', 'cryptography', + 'docker-compose', 'pyopenssl', 'pytest', 'pytest-cov', diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index a4c877ba8..d5d78c96a 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -11,8 +11,11 @@ attrs==17.3.0 Babel==2.5.1 backports.functools-lru-cache==1.5 backports.shutil-get-terminal-size==1.0.0 +backports.ssl-match-hostname==3.7.0.1 +bcrypt==3.1.6 boto3==1.9.36 botocore==1.12.36 +cached-property==1.5.1 cloudflare==1.5.1 codecov==2.0.15 configparser==3.7.4 @@ -21,8 +24,14 @@ coverage==4.5.4 decorator==4.1.2 dns-lexicon==3.2.1 dnspython==1.15.0 +docker==3.7.2 +docker-compose==1.25.0 +docker-pycreds==0.4.0 +dockerpty==0.4.1 +docopt==0.6.2 docutils==0.12 execnet==1.5.0 +functools32==3.2.3.post2 future==0.16.0 futures==3.1.1 filelock==3.0.12 @@ -37,6 +46,7 @@ isort==4.3.21 Jinja2==2.9.6 jmespath==0.9.3 josepy==1.1.0 +jsonschema==2.6.0 lazy-object-proxy==1.4.3 logger==1.4 logilab-common==1.4.1 @@ -48,6 +58,7 @@ mypy-extensions==0.4.3 ndg-httpsclient==0.3.2 oauth2client==4.0.0 packaging==19.2 +paramiko==2.4.2 pathlib2==2.3.0 pexpect==4.7.0 pickleshare==0.7.4 @@ -62,6 +73,7 @@ Pygments==2.2.0 pylint==2.4.3 # If pynsist version is upgraded, our NSIS template windows-installer/template.nsi # must be upgraded if necessary using the new built-in one from pynsist. +pynacl==1.3.0 pynsist==2.4 pytest==3.2.5 pytest-cov==2.5.1 @@ -85,6 +97,7 @@ snowballstemmer==1.2.1 Sphinx==1.7.5 sphinx-rtd-theme==0.2.4 sphinxcontrib-websupport==1.0.1 +texttable==0.9.1 tldextract==2.2.0 toml==0.10.0 tox==3.14.0 @@ -96,5 +109,6 @@ typing==3.6.4 uritemplate==3.0.0 virtualenv==16.6.2 wcwidth==0.1.7 +websocket-client==0.56.0 wrapt==1.11.2 zipp==0.6.0 -- cgit v1.2.3 From f520d482fd7c19ed9dd83ba514b4aadc4a6aa2cf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 Dec 2019 14:00:49 -0800 Subject: Remove other 3.8-dev references. (#7646) --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59cc8630a..63129c9b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -175,7 +175,7 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *extended-test-suite - - python: "3.8-dev" + - python: "3.8" env: TOXENV=py38 <<: *extended-test-suite - python: "3.4" @@ -218,10 +218,10 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "3.8-dev" + - python: "3.8" env: ACME_SERVER=boulder-v1 TOXENV=integration <<: *extended-test-suite - - python: "3.8-dev" + - python: "3.8" env: ACME_SERVER=boulder-v2 TOXENV=integration <<: *extended-test-suite - sudo: required -- cgit v1.2.3 From 6ca80b7ce88a50f86736dadf45676b9a27cb4919 Mon Sep 17 00:00:00 2001 From: Barbz Date: Thu, 19 Dec 2019 22:30:13 +0100 Subject: How to uninstall certbot-auto (#7648) --- certbot/docs/install.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/certbot/docs/install.rst b/certbot/docs/install.rst index 42d46c33e..d21242367 100644 --- a/certbot/docs/install.rst +++ b/certbot/docs/install.rst @@ -70,11 +70,13 @@ The ``certbot-auto`` wrapper script installs Certbot, obtaining some dependencie from your web server OS and putting others in a python virtual environment. You can download and run it as follows:: - user@webserver:~$ wget https://dl.eff.org/certbot-auto - user@webserver:~$ sudo mv certbot-auto /usr/local/bin/certbot-auto - user@webserver:~$ sudo chown root /usr/local/bin/certbot-auto - user@webserver:~$ chmod 0755 /usr/local/bin/certbot-auto - user@webserver:~$ /usr/local/bin/certbot-auto --help + wget https://dl.eff.org/certbot-auto + sudo mv certbot-auto /usr/local/bin/certbot-auto + sudo chown root /usr/local/bin/certbot-auto + sudo chmod 0755 /usr/local/bin/certbot-auto + /usr/local/bin/certbot-auto --help + +To remove certbot-auto, just delete it and the files it places under /opt/eff.org, along with any cronjob or systemd timer you may have created. To check the integrity of the ``certbot-auto`` script, you can use these steps:: -- cgit v1.2.3 From 6d527bcc426111912fb9faf37ee8467cb75fb55b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 19 Dec 2019 14:02:24 -0800 Subject: Include header files for compilation. (#7650) --- .azure-pipelines/templates/installer-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index d4550aad0..e3a005334 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -42,7 +42,7 @@ jobs: displayName: Install Certbot - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe -OutFile C:\py3-setup.exe displayName: Get Python - - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=0 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 + - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 displayName: Install Python - script: | py -3 -m venv venv -- cgit v1.2.3 From 887d72fd5d9b77d3cea914ffed23b9d62e93c5e7 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 Jan 2020 21:48:55 +0100 Subject: Remove POST-as-GET fallback to GET (#6994) --- acme/acme/client.py | 22 +++------------------- acme/tests/client_test.py | 13 ------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/acme/acme/client.py b/acme/acme/client.py index aabcbe312..527430120 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -776,29 +776,13 @@ class ClientV2(ClientBase): 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. + Send GET request using the POST-as-GET protocol. :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) - except messages.Error as error: - if error.code == 'malformed': - logger.debug('Error during a POST-as-GET request, ' - 'your ACME CA server 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) + new_args = args[:1] + (None,) + args[1:] + return self._post(*new_args, **kwargs) class BackwardsCompatibleClientV2(object): diff --git a/acme/tests/client_test.py b/acme/tests/client_test.py index 192cd2949..a38fedbd6 100644 --- a/acme/tests/client_test.py +++ b/acme/tests/client_test.py @@ -885,19 +885,6 @@ class ClientV2Test(ClientTestBase): new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce') self.client.net.get.assert_not_called() - class FakeError(messages.Error): - """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 -- cgit v1.2.3 From fda655370a564a42ce3b6fc85e91d263a794aaa4 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 2 Jan 2020 23:44:16 +0100 Subject: Update CHANGELOG.md (#7659) --- certbot/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 0e8c20a50..f2671a7b4 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -10,7 +10,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Removed the fallback introduced with 0.34.0 in `acme` to retry a POST-as-GET + request as a GET request when the targeted ACME CA server seems to not support + POST-as-GET requests. ### Fixed -- cgit v1.2.3 From 84c1b912d9ce82c40f868c24200ed2e4c7d28cfc Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 8 Jan 2020 16:36:34 +0100 Subject: Implement a sunset mechanism in certbot-auto for systems not supported anymore (#7587) * Sunset mechanism * Simplify code * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update template * Deprecate for all RHEL/CentOS 6 32bits flavors * Add a wrapper to uname to do tests on fake 32 bits versions * Replace all occurences * Add some tests about sunset mechanism * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * Various corrections * Recreate script * Update comment position * Test also install only * Fix docker * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Brad Warren * What error command is doing here ? * Fix permissions * Rebuild script * Add changelog * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Update changelog * Trigger CI * Handle old venv path * Modify test * Fix test error detection from subpaths * Edit echo * Use set -e * Update letsencrypt-auto-source/letsencrypt-auto.template Co-Authored-By: Brad Warren * Corrections Co-authored-by: Brad Warren --- certbot/CHANGELOG.md | 4 + letsencrypt-auto-source/Dockerfile.centos6 | 6 ++ letsencrypt-auto-source/letsencrypt-auto | 90 +++++++++++++++++------ letsencrypt-auto-source/letsencrypt-auto.template | 90 +++++++++++++++++------ letsencrypt-auto-source/tests/centos6_tests.sh | 86 ++++++++++++++++------ letsencrypt-auto-source/tests/uname_wrapper.sh | 10 +++ 6 files changed, 214 insertions(+), 72 deletions(-) create mode 100644 letsencrypt-auto-source/tests/uname_wrapper.sh diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index f2671a7b4..5209de607 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,6 +13,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Removed the fallback introduced with 0.34.0 in `acme` to retry a POST-as-GET request as a GET request when the targeted ACME CA server seems to not support POST-as-GET requests. +* certbot-auto no longer supports architectures other than x86_64 on RHEL 6 + based systems. Existing certbot-auto installations affected by this will + continue to work, but they will no longer receive updates. To install a + newer version of Certbot on these systems, you should update your OS. ### Fixed diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 index 09aa52dcd..f152e1bac 100644 --- a/letsencrypt-auto-source/Dockerfile.centos6 +++ b/letsencrypt-auto-source/Dockerfile.centos6 @@ -30,6 +30,12 @@ RUN update-ca-trust # Copy code: COPY . /home/lea/certbot/letsencrypt-auto-source +# Tweak uname binary for tests on fake 32bits +COPY tests/uname_wrapper.sh /bin +RUN mv /bin/uname /bin/uname_orig \ + && mv /bin/uname_wrapper.sh /bin/uname \ + && chmod +x /bin/uname + USER lea WORKDIR /home/lea diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2f48751f2..2e658f642 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -758,6 +758,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -870,6 +875,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1079,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1617,6 +1651,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1865,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." 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 - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" - - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + 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 + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index 31c5bb134..e481fd6f3 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -333,6 +333,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -445,6 +450,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -534,6 +546,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -657,6 +691,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -720,30 +757,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." 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 - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" - - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + 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 + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 2c6dcf734..713e83b16 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # Start by making sure your system is up-to-date: yum update -y > /dev/null yum install -y centos-release-scl > /dev/null @@ -6,46 +7,51 @@ yum install -y python27 > /dev/null 2> /dev/null LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" +echo "" + +# we're going to modify env variables, so do this in a subshell +( +# ensure CentOS6 32bits is not supported anymore, and so certbot is not installed +export UNAME_FAKE_32BITS=true +if ! "$LE_AUTO" 2>&1 | grep -q "Certbot cannot be installed."; then + echo "On CentOS 32 bits, certbot-auto installed certbot." + exit 1 +fi +) + +echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install certbot." + # we're going to modify env variables, so do this in a subshell ( source /opt/rh/python27/enable # ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "Python3 is already installed." +if python3 --version 2> /dev/null; then + echo "Python3 is already installed." exit 1 fi # ensure python2.7 is available -python2.7 --version 2> /dev/null -RESULT=$? -if [ $RESULT -ne 0 ]; then - error "Python3 is not available." +if ! python2.7 --version 2> /dev/null; then + echo "Python2.7 is not available." exit 1 fi # bootstrap, but don't install python 3. -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null +"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null # ensure python 3 isn't installed -python3 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "letsencrypt-auto installed Python3 even though Python2.7 is present." +if python3 --version 2> /dev/null; then + echo "letsencrypt-auto installed Python3 even though Python2.7 is present." exit 1 fi +) -echo "" echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." -) # ensure python2.7 isn't available -python2.7 --version 2> /dev/null -RESULT=$? -if [ $RESULT -eq 0 ]; then - error "Python2.7 is still available." +if python2.7 --version 2> /dev/null; then + echo "Python2.7 is still available." exit 1 fi @@ -56,13 +62,11 @@ if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then fi # bootstrap, this time installing python3 -"$LE_AUTO" --no-self-upgrade -n > /dev/null 2> /dev/null +"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null # ensure python 3 is installed -python3 --version > /dev/null -RESULT=$? -if [ $RESULT -ne 0 ]; then - error "letsencrypt-auto failed to install Python3 when only Python2.6 is present." +if ! python3 --version > /dev/null; then + echo "letsencrypt-auto failed to install Python3 when only Python2.6 is present." exit 1 fi @@ -77,5 +81,39 @@ if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; th fi unset VENV_PATH +# we're going to modify env variables, so do this in a subshell +( +# ensure CentOS6 32bits is not supported anymore, and so certbot +# is not upgraded nor reinstalled. +export UNAME_FAKE_32BITS=true +set -o pipefail +if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then + echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot instance." + exit 1 +fi +set +o pipefail +if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then + echo "On CentOS 6 32 bits, certbot-auto installed certbot again." + exit 1 +fi +) + +# we're going to modify env variables, so do this in a subshell +( +# Prepare a certbot installation in the old venv path +rm -rf /opt/eff.org +VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null +# fake 32 bits mode +export UNAME_FAKE_32BITS=true +set -o pipefail +if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then + echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot in the old venv path." + exit 1 +fi +set +o pipefail +) + +echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install/upgrade certbot." + # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/uname_wrapper.sh b/letsencrypt-auto-source/tests/uname_wrapper.sh new file mode 100644 index 000000000..df1f568c6 --- /dev/null +++ b/letsencrypt-auto-source/tests/uname_wrapper.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +uname_output=$(/bin/uname_orig "$@") + +if [ "$UNAME_FAKE_32BITS" = true ]; then + uname_output="${uname_output//x86_64/i686}" +fi + +echo "$uname_output" -- cgit v1.2.3 From 456122e342db13cda50b9ae59a1be9521b6547d8 Mon Sep 17 00:00:00 2001 From: Vladimir Varlamov Date: Thu, 9 Jan 2020 22:34:04 +0300 Subject: improve help about supply selecting in delete command (#7673) for #6625 --- certbot/certbot/_internal/cli.py | 4 ++-- certbot/docs/cli-help.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index fb3010a4e..9853d9b53 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -92,8 +92,8 @@ obtain, install, and renew certificates: manage certificates: certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) manage your account: register Create an ACME account diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index b46206b87..de12cefda 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -24,8 +24,8 @@ obtain, install, and renew certificates: manage certificates: certificates Display information about certificates you have from Certbot - revoke Revoke a certificate (supply --cert-path or --cert-name) - delete Delete a certificate + revoke Revoke a certificate (supply --cert-name or --cert-path) + delete Delete a certificate (supply --cert-name) manage your account: register Create an ACME account -- cgit v1.2.3 From ceea41c1e2c03ea3d8c9976a6a3ff87f248cedd7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 10 Jan 2020 16:48:01 -0800 Subject: Do not document private members (#7675) It looks like we're currently documenting functions that are marked private (prefixed with an underscore) such as https://certbot.eff.org/docs/api/certbot.crypto_util.html#certbot.crypto_util._load_cert_or_req. I do not think we should do this because the functionality is private, should not be used, and including it in our docs just adds visual noise. This PR stops us from documenting private code and fixes up `tools/sphinx-quickstart.sh` so we don't document it in future modules. * Do not document private code. * Don't document private members in the future. --- acme/docs/conf.py | 2 +- certbot-dns-cloudflare/docs/conf.py | 2 +- certbot-dns-cloudxns/docs/conf.py | 2 +- certbot-dns-digitalocean/docs/conf.py | 2 +- certbot-dns-dnsimple/docs/conf.py | 2 +- certbot-dns-dnsmadeeasy/docs/conf.py | 2 +- certbot-dns-gehirn/docs/conf.py | 2 +- certbot-dns-google/docs/conf.py | 2 +- certbot-dns-linode/docs/conf.py | 2 +- certbot-dns-luadns/docs/conf.py | 2 +- certbot-dns-nsone/docs/conf.py | 2 +- certbot-dns-ovh/docs/conf.py | 2 +- certbot-dns-rfc2136/docs/conf.py | 2 +- certbot-dns-route53/docs/conf.py | 2 +- certbot-dns-sakuracloud/docs/conf.py | 2 +- certbot/docs/conf.py | 2 +- letshelp-certbot/docs/conf.py | 2 +- tools/sphinx-quickstart.sh | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/acme/docs/conf.py b/acme/docs/conf.py index 01029a81f..8c1689128 100644 --- a/acme/docs/conf.py +++ b/acme/docs/conf.py @@ -41,7 +41,7 @@ extensions = [ ] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-cloudflare/docs/conf.py b/certbot-dns-cloudflare/docs/conf.py index 488268577..97e54421e 100644 --- a/certbot-dns-cloudflare/docs/conf.py +++ b/certbot-dns-cloudflare/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-cloudxns/docs/conf.py b/certbot-dns-cloudxns/docs/conf.py index 16ccd1d62..1fc05c94c 100644 --- a/certbot-dns-cloudxns/docs/conf.py +++ b/certbot-dns-cloudxns/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-digitalocean/docs/conf.py b/certbot-dns-digitalocean/docs/conf.py index 9c493a220..0741e4cea 100644 --- a/certbot-dns-digitalocean/docs/conf.py +++ b/certbot-dns-digitalocean/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-dnsimple/docs/conf.py b/certbot-dns-dnsimple/docs/conf.py index b5cb24e2f..99cc93135 100644 --- a/certbot-dns-dnsimple/docs/conf.py +++ b/certbot-dns-dnsimple/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-dnsmadeeasy/docs/conf.py b/certbot-dns-dnsmadeeasy/docs/conf.py index 60e0163bd..1f0c57812 100644 --- a/certbot-dns-dnsmadeeasy/docs/conf.py +++ b/certbot-dns-dnsmadeeasy/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-gehirn/docs/conf.py b/certbot-dns-gehirn/docs/conf.py index 67aafa3b4..527bc3d55 100644 --- a/certbot-dns-gehirn/docs/conf.py +++ b/certbot-dns-gehirn/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-google/docs/conf.py b/certbot-dns-google/docs/conf.py index 8f045cf3f..b2ddcfb34 100644 --- a/certbot-dns-google/docs/conf.py +++ b/certbot-dns-google/docs/conf.py @@ -39,7 +39,7 @@ extensions = ['sphinx.ext.autodoc', 'jsonlexer'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-linode/docs/conf.py b/certbot-dns-linode/docs/conf.py index f23d65023..c6d564b7a 100644 --- a/certbot-dns-linode/docs/conf.py +++ b/certbot-dns-linode/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-luadns/docs/conf.py b/certbot-dns-luadns/docs/conf.py index 899480f66..8e9d49988 100644 --- a/certbot-dns-luadns/docs/conf.py +++ b/certbot-dns-luadns/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py index aec0771a2..5531959ed 100644 --- a/certbot-dns-nsone/docs/conf.py +++ b/certbot-dns-nsone/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-ovh/docs/conf.py b/certbot-dns-ovh/docs/conf.py index a4985edee..56e24a920 100644 --- a/certbot-dns-ovh/docs/conf.py +++ b/certbot-dns-ovh/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-rfc2136/docs/conf.py b/certbot-dns-rfc2136/docs/conf.py index e4df84594..c0d55078e 100644 --- a/certbot-dns-rfc2136/docs/conf.py +++ b/certbot-dns-rfc2136/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-route53/docs/conf.py b/certbot-dns-route53/docs/conf.py index cb8aae0b6..c2eb880ac 100644 --- a/certbot-dns-route53/docs/conf.py +++ b/certbot-dns-route53/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot-dns-sakuracloud/docs/conf.py b/certbot-dns-sakuracloud/docs/conf.py index f973779ab..70a4d7434 100644 --- a/certbot-dns-sakuracloud/docs/conf.py +++ b/certbot-dns-sakuracloud/docs/conf.py @@ -38,7 +38,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/certbot/docs/conf.py b/certbot/docs/conf.py index 6b7c1c2c0..1e57bc224 100644 --- a/certbot/docs/conf.py +++ b/certbot/docs/conf.py @@ -52,7 +52,7 @@ if sphinx.version_info >= (1, 6): extensions.append('sphinx.ext.imgconverter') autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py index fcff25d55..fc482a348 100644 --- a/letshelp-certbot/docs/conf.py +++ b/letshelp-certbot/docs/conf.py @@ -40,7 +40,7 @@ extensions = [ ] autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] +autodoc_default_flags = ['show-inheritance'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/tools/sphinx-quickstart.sh b/tools/sphinx-quickstart.sh index 72dc9e200..35a7f7fad 100755 --- a/tools/sphinx-quickstart.sh +++ b/tools/sphinx-quickstart.sh @@ -14,7 +14,7 @@ sed -i -e "s|\# import os|import os|" conf.py sed -i -e "s|\# needs_sphinx = '1.0'|needs_sphinx = '1.0'|" conf.py sed -i -e "s|intersphinx_mapping = {'https://docs.python.org/': None}|intersphinx_mapping = {\n 'python': ('https://docs.python.org/', None),\n 'acme': ('https://acme-python.readthedocs.org/en/latest/', None),\n 'certbot': ('https://certbot.eff.org/docs/', None),\n}|" conf.py sed -i -e "s|html_theme = 'alabaster'|\n# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs\n# on_rtd is whether we are on readthedocs.org\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\nif not on_rtd: # only import and set the theme if we're building docs locally\n import sphinx_rtd_theme\n html_theme = 'sphinx_rtd_theme'\n html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n# otherwise, readthedocs.org uses their theme by default, so no need to specify it|" conf.py -sed -i -e "s|# Add any paths that contain templates here, relative to this directory.|autodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance', 'private-members']\n\n# Add any paths that contain templates here, relative to this directory.|" conf.py +sed -i -e "s|# Add any paths that contain templates here, relative to this directory.|autodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance']\n\n# Add any paths that contain templates here, relative to this directory.|" conf.py sed -i -e "s|# The name of the Pygments (syntax highlighting) style to use.|default_role = 'py:obj'\n\n# The name of the Pygments (syntax highlighting) style to use.|" conf.py echo "/_build/" >> .gitignore echo "================= -- cgit v1.2.3 From e84ed49c56d7f0b90a8161cea8bff82efa8f4257 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Mon, 13 Jan 2020 09:24:41 +0100 Subject: Fix certbot-auto regarding python 3.4 -> python 3.6 migration for CentOS 6 users (#7519) * Revert "Add back Python 3.4 support (#7510)" This reverts commit 9b848b1d65783000a13ef3f94ac5fe0e8c3879e7. * Fix certbot-auto * Use a more consistent way to enable rh-python36 * Avoid to call CompareVersions unecessarily * Control rh-python36 exit code * Fix travis config * Remove vscode config * Ignore vscode * Fix merge conflicts regarding #7587 (#70) * Add changelog entry * Finish sentence * Update certbot/CHANGELOG.md Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update letsencrypt-auto-source/tests/centos6_tests.sh Co-Authored-By: Joona Hoikkala * Update comments * Improve warning message * Update changelog Co-authored-by: Joona Hoikkala --- .gitignore | 1 + .travis.yml | 4 + certbot/CHANGELOG.md | 5 + certbot/certbot/_internal/main.py | 4 + letsencrypt-auto-source/Dockerfile.centos6 | 43 ----- letsencrypt-auto-source/Dockerfile.redhat6 | 54 ++++++ letsencrypt-auto-source/letsencrypt-auto | 215 +++++++++++++++------ letsencrypt-auto-source/letsencrypt-auto.template | 106 +++++++--- .../pieces/bootstrappers/rpm_common_base.sh | 24 +-- .../pieces/bootstrappers/rpm_python3.sh | 7 - .../pieces/bootstrappers/rpm_python3_legacy.sh | 78 ++++++++ letsencrypt-auto-source/tests/centos6_tests.sh | 186 +++++++++++------- .../tests/oraclelinux6_tests.sh | 85 ++++++++ tests/letstest/scripts/test_leauto_upgrades.sh | 2 + tests/letstest/scripts/test_sdists.sh | 13 ++ tox.ini | 12 +- 16 files changed, 611 insertions(+), 228 deletions(-) delete mode 100644 letsencrypt-auto-source/Dockerfile.centos6 create mode 100644 letsencrypt-auto-source/Dockerfile.redhat6 create mode 100644 letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh create mode 100644 letsencrypt-auto-source/tests/oraclelinux6_tests.sh diff --git a/.gitignore b/.gitignore index 68762da6b..6dd422187 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tags \#*# .idea .ropeproject +.vscode # auth --cert-path --chain-path /*.pem diff --git a/.travis.yml b/.travis.yml index 63129c9b1..fdb692ac1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -232,6 +232,10 @@ matrix: env: TOXENV=le_auto_centos6 services: docker <<: *extended-test-suite + - sudo: required + env: TOXENV=le_auto_oraclelinux6 + services: docker + <<: *extended-test-suite - sudo: required env: TOXENV=docker_dev services: docker diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 5209de607..b3b8ec9d9 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -17,6 +17,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). based systems. Existing certbot-auto installations affected by this will continue to work, but they will no longer receive updates. To install a newer version of Certbot on these systems, you should update your OS. +* Support for Python 3.4 in Certbot and its ACME library is deprecated and will be + removed in the next release of Certbot. certbot-auto users on x86_64 systems running + RHEL 6 or derivatives will be asked to enable Software Collections (SCL) repository + so Python 3.6 can be installed. certbot-auto can enable the SCL repo for you on CentOS 6 + while users on other RHEL 6 based systems will be asked to do this manually. ### Fixed diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 72fcfca71..509b5b981 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -1337,6 +1337,10 @@ def main(cli_args=None): if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise + if sys.version_info[:2] == (3, 4): + logger.warning("Python 3.4 support will be dropped in the next release " + "of Certbot - please upgrade your Python version to 3.5+.") + set_displayer(config) # Reporter diff --git a/letsencrypt-auto-source/Dockerfile.centos6 b/letsencrypt-auto-source/Dockerfile.centos6 deleted file mode 100644 index f152e1bac..000000000 --- a/letsencrypt-auto-source/Dockerfile.centos6 +++ /dev/null @@ -1,43 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -FROM centos:6 - -RUN yum install -y epel-release - -# Install pip and sudo: -RUN yum install -y python-pip sudo -# Update to a stable and tested version of pip. -# We do not use pipstrap here because it no longer supports Python 2.6. -RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ - /etc/sudoers - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-trust - -# Copy code: -COPY . /home/lea/certbot/letsencrypt-auto-source - -# Tweak uname binary for tests on fake 32bits -COPY tests/uname_wrapper.sh /bin -RUN mv /bin/uname /bin/uname_orig \ - && mv /bin/uname_wrapper.sh /bin/uname \ - && chmod +x /bin/uname - -USER lea -WORKDIR /home/lea - -RUN sudo chmod +x certbot/letsencrypt-auto-source/tests/centos6_tests.sh -CMD sudo certbot/letsencrypt-auto-source/tests/centos6_tests.sh diff --git a/letsencrypt-auto-source/Dockerfile.redhat6 b/letsencrypt-auto-source/Dockerfile.redhat6 new file mode 100644 index 000000000..66f21bc14 --- /dev/null +++ b/letsencrypt-auto-source/Dockerfile.redhat6 @@ -0,0 +1,54 @@ +# For running tests, build a docker image with a passwordless sudo and a trust +# store we can manipulate. + +ARG REDHAT_DIST_FLAVOR +FROM ${REDHAT_DIST_FLAVOR}:6 + +ARG REDHAT_DIST_FLAVOR + +RUN curl -O https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm \ + && rpm -ivh epel-release-latest-6.noarch.rpm + +# Install pip and sudo: +RUN yum install -y python-pip sudo +# Update to a stable and tested version of pip. +# We do not use pipstrap here because it no longer supports Python 2.6. +RUN pip install pip==9.0.1 setuptools==29.0.1 wheel==0.29.0 +# Pin pytest version for increased stability +RUN pip install pytest==3.2.5 six==1.10.0 + +# Add an unprivileged user: +RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea + +# Let that user sudo: +RUN sed -i.bkp -e \ + 's/# %wheel\(NOPASSWD: ALL\)\?/%wheel/g' \ + /etc/sudoers + +RUN mkdir -p /home/lea/certbot + +# Install fake testing CA: +COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ +RUN update-ca-trust + +# Copy current letsencrypt-auto: +COPY . /home/lea/certbot/letsencrypt-auto-source + +# Tweak uname binary for tests on fake 32bits +COPY tests/uname_wrapper.sh /bin +RUN mv /bin/uname /bin/uname_orig \ + && mv /bin/uname_wrapper.sh /bin/uname \ + && chmod +x /bin/uname + +# Fetch previous letsencrypt-auto that was installing python 3.4 +RUN curl https://raw.githubusercontent.com/certbot/certbot/v0.38.0/letsencrypt-auto-source/letsencrypt-auto \ + -o /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/letsencrypt-auto_py_34 + +RUN cp /home/lea/certbot/letsencrypt-auto-source/tests/${REDHAT_DIST_FLAVOR}6_tests.sh /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh \ + && chmod +x /home/lea/certbot/letsencrypt-auto-source/tests/redhat6_tests.sh + +USER lea +WORKDIR /home/lea + +CMD ["sudo", "certbot/letsencrypt-auto-source/tests/redhat6_tests.sh"] diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2e658f642..78e449444 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -774,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -1112,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1124,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index e481fd6f3..53e57a498 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -298,6 +306,7 @@ DeterminePythonVersion() { {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common_base.sh }} {{ bootstrappers/rpm_common.sh }} +{{ bootstrappers/rpm_python3_legacy.sh }} {{ bootstrappers/rpm_python3.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} @@ -349,31 +358,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -579,8 +607,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -591,12 +626,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh index 326ad8b3f..2b00b199b 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_common_base.sh @@ -3,7 +3,9 @@ # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -23,26 +25,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh index f33b07ca9..ac0553db5 100644 --- a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3.sh @@ -4,7 +4,6 @@ BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -15,12 +14,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 diff --git a/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh new file mode 100644 index 000000000..febfc7a83 --- /dev/null +++ b/letsencrypt-auto-source/pieces/bootstrappers/rpm_python3_legacy.sh @@ -0,0 +1,78 @@ +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 713e83b16..86b307ad2 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -1,20 +1,22 @@ #!/bin/bash set -e # Start by making sure your system is up-to-date: -yum update -y > /dev/null -yum install -y centos-release-scl > /dev/null -yum install -y python27 > /dev/null 2> /dev/null +yum update -y >/dev/null +yum install -y centos-release-scl >/dev/null +yum install -y python27 >/dev/null 2>/dev/null +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" -echo "" +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" # we're going to modify env variables, so do this in a subshell ( # ensure CentOS6 32bits is not supported anymore, and so certbot is not installed export UNAME_FAKE_32BITS=true if ! "$LE_AUTO" 2>&1 | grep -q "Certbot cannot be installed."; then - echo "On CentOS 32 bits, certbot-auto installed certbot." + echo "ERROR: certbot-auto installed certbot on 32-bit CentOS." exit 1 fi ) @@ -23,97 +25,149 @@ echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install certbot." # we're going to modify env variables, so do this in a subshell ( -source /opt/rh/python27/enable + . /opt/rh/python27/enable + + # ensure python 3 isn't installed + if python3 --version 2> /dev/null; then + echo "ERROR: Python3 is already installed." + exit 1 + fi + + # ensure python2.7 is available + if ! python2.7 --version 2> /dev/null; then + echo "ERROR: Python2.7 is not available." + exit 1 + fi + + # bootstrap, but don't install python 3. + "$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null + + # ensure python 3 isn't installed + if python3 --version 2> /dev/null; then + echo "ERROR: letsencrypt-auto installed Python3 even though Python2.7 is present." + exit 1 + fi + + echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." +) -# ensure python 3 isn't installed -if python3 --version 2> /dev/null; then - echo "Python3 is already installed." +# ensure python2.7 isn't available +if python2.7 --version 2> /dev/null; then + echo "ERROR: Python2.7 is still available." exit 1 fi -# ensure python2.7 is available -if ! python2.7 --version 2> /dev/null; then - echo "Python2.7 is not available." +# Skip self upgrade due to Python 3 not being available. +if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then + echo "ERROR: Python upgrade failure warning not printed!" exit 1 fi -# bootstrap, but don't install python 3. -"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null +# bootstrap from the old letsencrypt-auto, this time installing python3.4 +"$LE_AUTO_PY_34" --no-self-upgrade -n --version >/dev/null 2>/dev/null -# ensure python 3 isn't installed -if python3 --version 2> /dev/null; then - echo "letsencrypt-auto installed Python3 even though Python2.7 is present." +# ensure python 3.4 is installed +if ! python3.4 --version >/dev/null 2>/dev/null; then + echo "ERROR: letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." exit 1 fi -) -echo "PASSED: Did not upgrade to Python3 when Python2.7 is present." +echo "PASSED: Successfully upgraded to Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." -# ensure python2.7 isn't available -if python2.7 --version 2> /dev/null; then - echo "Python2.7 is still available." - exit 1 -fi +# As "certbot-auto" (so without implicit --non-interactive flag set), check that the script +# refuses to install SCL Python 3.6 when run in a non interactive shell (simulated here +# using | tee /dev/null) if --non-interactive flag is not provided. +cp "$LE_AUTO" /tmp/certbot-auto +# NB: Readline has an issue on all Python versions for CentOS 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$(/tmp/certbot-auto --version 2>/dev/null | tee /dev/null | tail -2 | head -1) -# Skip self upgrade due to Python 3 not being available. -if ! "$LE_AUTO" 2>&1 | grep -q "WARNING: couldn't find Python"; then - echo "Python upgrade failure warning not printed!" +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: certbot-auto upgraded certbot in a non-interactive shell with --non-interactive flag not set." exit 1 fi -# bootstrap, this time installing python3 -"$LE_AUTO" --no-self-upgrade -n --version > /dev/null 2> /dev/null +echo "PASSED: certbot-auto did not upgrade certbot in a non-interactive shell with --non-interactive flag not set." -# ensure python 3 is installed -if ! python3 --version > /dev/null; then - echo "letsencrypt-auto failed to install Python3 when only Python2.6 is present." +if [ -f /opt/rh/rh-python36/enable ]; then + echo "ERROR: certbot-auto installed Python3.6 in a non-interactive shell with --non-interactive flag not set." exit 1 fi -echo "PASSED: Successfully upgraded to Python3 when only Python2.6 is present." -echo "" +echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell with --non-interactive flag not set." -export VENV_PATH=$(mktemp -d) -"$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 -if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1)" != 3 ]; then - echo "Python 3 wasn't used with --no-bootstrap!" - exit 1 -fi -unset VENV_PATH +# now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL +"$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null + +# Following test is exectued in a subshell, to not leak any environment variable +( + # enable SCL rh-python36 + . /opt/rh/rh-python36/enable + + # ensure python 3.6 is installed + if ! python3.6 --version >/dev/null 2>/dev/null; then + echo "ERROR: letsencrypt-auto failed to install Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." + exit 1 + fi + + echo "PASSED: Successfully upgraded to Python3.6 using current letsencrypt-auto when only Python2.6/Python3.4 are present." +) + +# Following test is executed in a subshell, to not leak any environment variable +( + export VENV_PATH=$(mktemp -d) + "$LE_AUTO" -n --no-bootstrap --no-self-upgrade --version >/dev/null 2>&1 + if [ "$($VENV_PATH/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Python 3.6 wasn't used with --no-bootstrap!" + exit 1 + fi +) + +# Following test is exectued in a subshell, to not leak any environment variable +( + # enable SCL rh-python36 + . /opt/rh/rh-python36/enable + + # ensure everything works fine with certbot-auto bootstrap when python 3.6 is already enabled + export VENV_PATH=$(mktemp -d) + if ! "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null; then + echo "ERROR: Certbot-auto broke when Python 3.6 SCL is already enabled." + exit 1 + fi +) # we're going to modify env variables, so do this in a subshell ( -# ensure CentOS6 32bits is not supported anymore, and so certbot -# is not upgraded nor reinstalled. -export UNAME_FAKE_32BITS=true -set -o pipefail -if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then - echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot instance." - exit 1 -fi -set +o pipefail -if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then - echo "On CentOS 6 32 bits, certbot-auto installed certbot again." - exit 1 -fi + # ensure CentOS6 32bits is not supported anymore, and so certbot + # is not upgraded nor reinstalled. + export UNAME_FAKE_32BITS=true + OUTPUT=$("$LE_AUTO" --version 2>&1) + if ! echo "$OUTPUT" | grep -q "Certbot will no longer receive updates."; then + echo "ERROR: certbot-auto failed to run or upgraded pre-existing Certbot instance on 32-bit CentOS 6." + exit 1 + fi + if ! "$LE_AUTO" --install-only 2>&1 | grep -q "Certbot cannot be installed."; then + echo "ERROR: certbot-auto reinstalled Certbot on 32-bit CentOS 6." + exit 1 + fi ) # we're going to modify env variables, so do this in a subshell ( -# Prepare a certbot installation in the old venv path -rm -rf /opt/eff.org -VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null -# fake 32 bits mode -export UNAME_FAKE_32BITS=true -set -o pipefail -if ! "$LE_AUTO" --version 2>&1 | grep -q "Certbot will no longer receive updates."; then - echo "On CentOS 6 32 bits, certbot-auto failed or upgraded installed certbot in the old venv path." - exit 1 -fi -set +o pipefail + # Prepare a certbot installation in the old venv path + rm -rf /opt/eff.org + VENV_PATH=~/.local/share/letsencrypt "$LE_AUTO" --install-only > /dev/null 2> /dev/null + # fake 32 bits mode + export UNAME_FAKE_32BITS=true + OUTPUT=$("$LE_AUTO" --version 2>&1) + if ! echo "$OUTPUT" | grep -q "Certbot will no longer receive updates."; then + echo "ERROR: certbot-auto failed to run or upgraded pre-existing Certbot instance in the old venv path on 32-bit CentOS 6." + exit 1 + fi ) -echo "PASSED: On CentOS 6 32 bits, certbot-auto refused to install/upgrade certbot." +echo "PASSED: certbot-auto refused to install/upgrade certbot on 32-bit CentOS 6." # test using python3 pytest -v -s certbot/letsencrypt-auto-source/tests diff --git a/letsencrypt-auto-source/tests/oraclelinux6_tests.sh b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh new file mode 100644 index 000000000..f3fd952f3 --- /dev/null +++ b/letsencrypt-auto-source/tests/oraclelinux6_tests.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -eo pipefail +# Start by making sure your system is up-to-date: +yum update -y >/dev/null + +LE_AUTO_PY_34="certbot/letsencrypt-auto-source/letsencrypt-auto_py_34" +LE_AUTO="certbot/letsencrypt-auto-source/letsencrypt-auto" + +# Apply installation instructions from official documentation: +# https://certbot.eff.org/lets-encrypt/centosrhel6-other +cp "$LE_AUTO" /usr/local/bin/certbot-auto +chown root /usr/local/bin/certbot-auto +chmod 0755 /usr/local/bin/certbot-auto +LE_AUTO=/usr/local/bin/certbot-auto + +# Last version of certbot-auto that was bootstraping Python 3.4 for CentOS 6 users +INITIAL_CERTBOT_VERSION_PY34="certbot 0.38.0" + +# Check bootstrap from current certbot-auto will fail, because SCL is not enabled. +set +o pipefail +if ! "$LE_AUTO" -n 2>&1 | grep -q "Enable the SCL repository and try running Certbot again."; then + echo "ERROR: Bootstrap was not aborted although SCL was not installed!" + exit 1 +fi +set -o pipefail + +echo "PASSED: Bootstrap was aborted since SCL was not installed." + +# Bootstrap from the old letsencrypt-auto, Python 3.4 will be installed from EPEL. +"$LE_AUTO_PY_34" --no-self-upgrade -n --install-only >/dev/null 2>/dev/null + +# Ensure Python 3.4 is installed +if ! command -v python3.4 &>/dev/null; then + echo "ERROR: old letsencrypt-auto failed to install Python3.4 using letsencrypt-auto < 0.37.0 when only Python2.6 is present." + exit 1 +fi + +echo "PASSED: Bootstrap from old letsencrypt-auto succeeded and installed Python 3.4" + +# Expect certbot-auto to skip rebootstrapping with a warning since SCL is not installed. +if ! "$LE_AUTO" --non-interactive --version 2>&1 | grep -q "This requires manual user intervention"; then + echo "FAILED: Script certbot-auto did not print a warning about needing manual intervention!" + exit 1 +fi + +echo "PASSED: Script certbot-auto did not rebootstrap." + +# NB: Readline has an issue on all Python versions for OL 6, making `certbot --version` +# output an unprintable ASCII character on a new line at the end. +# So we take the second last line of the output. +version=$($LE_AUTO --version 2>/dev/null | tail -2 | head -1) + +if [ "$version" != "$INITIAL_CERTBOT_VERSION_PY34" ]; then + echo "ERROR: Script certbot-auto upgraded certbot in a non-interactive shell while SCL was not enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto did not upgrade certbot but started it successfully while SCL was not enabled." + +# Enable SCL +yum install -y oracle-softwarecollection-release-el6 >/dev/null + +# Expect certbot-auto to bootstrap successfully since SCL is available. +"$LE_AUTO" -n --version &>/dev/null + +if [ "$(/opt/eff.org/certbot/venv/bin/python -V 2>&1 | cut -d" " -f2 | cut -d. -f1-2)" != "3.6" ]; then + echo "ERROR: Script certbot-auto failed to bootstrap and install Python 3.6 while SCL is available." + exit 1 +fi + +if ! /opt/eff.org/certbot/venv/bin/certbot --version > /dev/null 2> /dev/null; then + echo "ERROR: Script certbot-auto did not install certbot correctly while SCL is enabled." + exit 1 +fi + +echo "PASSED: Script certbot-auto correctly bootstraped Certbot using rh-python36 when SCL is available." + +# Expect certbot-auto will be totally silent now that everything has been correctly boostraped. +OUTPUT_LEN=$("$LE_AUTO" --install-only --no-self-upgrade --quiet 2>&1 | wc -c) +if [ "$OUTPUT_LEN" != 0 ]; then + echo certbot-auto produced unexpected output! + exit 1 +fi + +echo "PASSED: Script certbot-auto did not print anything in quiet mode." diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 541f54f6b..fc7632793 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -117,6 +117,8 @@ if ! diff letsencrypt-auto letsencrypt-auto-source/letsencrypt-auto ; then fi if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Add the SCL python release to PATH in order to resolve python3 command + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" if ! command -v python3; then echo "Python3 wasn't properly installed" exit 1 diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index dc024c567..204f55d55 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,8 +1,21 @@ #!/bin/sh -xe cd letsencrypt + +# If we're on a RHEL 6 based system, we can be confident Python is already +# installed because the package manager is written in Python. +if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; then + # RHEL/CentOS 6 will need a special treatment, so we need to detect that environment + RUN_RHEL6_TESTS=1 +fi + letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug +if [ "$RUN_RHEL6_TESTS" = 1 ]; then + # Enable the SCL Python 3.6 installed by letsencrypt-auto bootstrap + PATH="/opt/rh/rh-python36/root/usr/bin:$PATH" +fi + PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) TEMP_DIR=$(mktemp -d) diff --git a/tox.ini b/tox.ini index 5f1a9a426..3a31558d8 100644 --- a/tox.ini +++ b/tox.ini @@ -207,7 +207,17 @@ passenv = DOCKER_* # At the moment, this tests under Python 2.6 only, as only that version is # readily available on the CentOS 6 Docker image. commands = - docker build -f letsencrypt-auto-source/Dockerfile.centos6 -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=centos -t lea letsencrypt-auto-source + docker run --rm -t -i lea +whitelist_externals = + docker +passenv = DOCKER_* + +[testenv:le_auto_oraclelinux6] +# At the moment, this tests under Python 2.6 only, as only that version is +# readily available on the Oracle Linux 6 Docker image. +commands = + docker build -f letsencrypt-auto-source/Dockerfile.redhat6 --build-arg REDHAT_DIST_FLAVOR=oraclelinux -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker -- cgit v1.2.3 From 9800e5d8fc2b03d3fc9124428abfb22922613ce9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:41:32 -0800 Subject: Update changelog for 1.1.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index b3b8ec9d9..8cd893c04 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.1.0 - master +## 1.1.0 - 2020-01-14 ### Added -- cgit v1.2.3 From f512b5eaa2ae08dabdcf04e260b34bc59ea594d1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:03 -0800 Subject: Release 1.1.0 --- acme/setup.py | 2 +- certbot-apache/local-oldest-requirements.txt | 2 +- certbot-apache/setup.py | 4 +- certbot-auto | 331 +++++++++++++++------ certbot-compatibility-test/setup.py | 2 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-cloudflare/setup.py | 4 +- certbot-dns-cloudxns/local-oldest-requirements.txt | 2 +- certbot-dns-cloudxns/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-digitalocean/setup.py | 4 +- certbot-dns-dnsimple/local-oldest-requirements.txt | 2 +- certbot-dns-dnsimple/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-dnsmadeeasy/setup.py | 4 +- certbot-dns-gehirn/local-oldest-requirements.txt | 2 +- certbot-dns-gehirn/setup.py | 4 +- certbot-dns-google/local-oldest-requirements.txt | 2 +- certbot-dns-google/setup.py | 4 +- certbot-dns-linode/local-oldest-requirements.txt | 2 +- certbot-dns-linode/setup.py | 4 +- certbot-dns-luadns/local-oldest-requirements.txt | 2 +- certbot-dns-luadns/setup.py | 4 +- certbot-dns-nsone/local-oldest-requirements.txt | 2 +- certbot-dns-nsone/setup.py | 4 +- certbot-dns-ovh/local-oldest-requirements.txt | 2 +- certbot-dns-ovh/setup.py | 4 +- certbot-dns-rfc2136/local-oldest-requirements.txt | 2 +- certbot-dns-rfc2136/setup.py | 4 +- certbot-dns-route53/local-oldest-requirements.txt | 2 +- certbot-dns-route53/setup.py | 4 +- .../local-oldest-requirements.txt | 2 +- certbot-dns-sakuracloud/setup.py | 4 +- certbot-nginx/local-oldest-requirements.txt | 2 +- certbot-nginx/setup.py | 4 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 2 +- letsencrypt-auto | 331 +++++++++++++++------ letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +- 42 files changed, 555 insertions(+), 277 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6da5fe519..17c321903 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 204d01620..b27c5e50d 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-auto b/certbot-auto index 24c007e03..2d3f4cfef 100755 --- a/certbot-auto +++ b/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="1.0.0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -758,6 +820,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -769,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -870,6 +956,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1160,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1078,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1372,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1617,6 +1748,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1962,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." 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 - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" - - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + 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 + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f26fb0706..8b488129a 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.1.0.dev0' +version = '1.1.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index b3fd81223..7a8f4ae7b 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 288a6d115..00780ead2 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index ba3190567..ce8c8f19f 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 5729bd789..7fa0ed095 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,13 +5,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 6fc756389..3b5f42632 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 7c4da556d..151fa6531 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index a0dc1c386..cb0d664be 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'google-api-python-client>=1.5.5', 'mock', 'oauth2client>=4.0', diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 1829f7eb2..a8bd7449a 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index f772dc26a..5f12c51ae 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 18ba8cacc..f041b59c7 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 3894f01cd..adef7567a 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 2e11550d6..c55e0d570 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 2fccf17c2..aeec4e88f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 47167fa2b..df01f3868 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 3fce6f83b..cf61c15a5 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index b4dcc58c1..40a62b8c5 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 67d4cc53b..1307698d4 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 56c209a90..d13641024 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,12 +4,12 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 37532aabf..cee142934 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==1.0.0 --e certbot[dev] +certbot[dev]==1.1.0 diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 96bf32d3e..1122a56bd 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,13 +4,13 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0.dev0' +version = '1.1.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=1.0.0', - 'certbot>=1.0.0.dev0', + 'certbot>=1.0.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 71c7e4e87..1e78d4345 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.1.0.dev0' +__version__ = '1.1.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index de12cefda..9b463820a 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,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/1.0.0 (certbot(-auto); + "". (default: CertbotACMEClient/1.1.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, diff --git a/letsencrypt-auto b/letsencrypt-auto index 24c007e03..2d3f4cfef 100755 --- a/letsencrypt-auto +++ b/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="1.0.0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -256,20 +256,28 @@ DeprecationBootstrap() { fi } -MIN_PYTHON_VERSION="2.7" -MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//') +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.5" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') # Sets LE_PYTHON to Python version string and PYVER to the first two -# digits of the python version +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. DeterminePythonVersion() { # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python # # If no Python is found, PYVER is set to 0. if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION for LE_PYTHON in "$LE_PYTHON" python3; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break @@ -285,7 +293,7 @@ DeterminePythonVersion() { fi fi - PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') if [ "$PYVER" -lt "$MIN_PYVER" ]; then if [ "$1" != "NOCRASH" ]; then error "You have an ancient version of Python entombed in your operating system..." @@ -368,7 +376,9 @@ BootstrapDebCommon() { # Sets TOOL to the name of the package manager # Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. -# Enables EPEL if applicable and possible. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. InitializeRPMCommonBase() { if type dnf 2>/dev/null then @@ -388,26 +398,6 @@ InitializeRPMCommonBase() { if [ "$QUIET" = 1 ]; then QUIET_FLAG='--quiet' fi - - if ! $TOOL list *virtualenv >/dev/null 2>&1; then - echo "To use Certbot, packages from the EPEL repository need to be installed." - if ! $TOOL list epel-release >/dev/null 2>&1; then - error "Enable the EPEL repository and try running Certbot again." - exit 1 - fi - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Enabling the EPEL repository in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 second..." - sleep 1s - fi - if ! $TOOL install $YES_FLAG $QUIET_FLAG epel-release; then - error "Could not enable EPEL. Aborting bootstrap!" - exit 1 - fi - fi } BootstrapRpmCommonBase() { @@ -488,13 +478,91 @@ BootstrapRpmCommon() { BootstrapRpmCommonBase "$python_pkgs" } +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + # If new packages are installed by BootstrapRpmPython3 below, this version # number must be increased. BOOTSTRAP_RPM_PYTHON3_VERSION=1 BootstrapRpmPython3() { # Tested with: - # - CentOS 6 # - Fedora 29 InitializeRPMCommonBase @@ -505,12 +573,6 @@ BootstrapRpmPython3() { python3-virtualenv python3-devel " - # EPEL uses python34 - elif $TOOL list python34 >/dev/null 2>&1; then - python_pkgs="python34 - python34-devel - python34-tools - " else error "No supported Python package available to install. Aborting bootstrap!" exit 1 @@ -758,6 +820,11 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an # error, RPM_DIST_VERSION is set to "unknown". @@ -769,31 +836,50 @@ elif [ -f /etc/redhat-release ]; then RPM_DIST_VERSION=0 fi - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. - # RHEL 8 also uses python3 by default. - if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then - RPM_USE_PYTHON_3=1 - else - RPM_USE_PYTHON_3=0 - fi + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi - if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { - BootstrapMessage "RedHat-based OSes that will use Python3" - BootstrapRpmPython3 + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy } USE_PYTHON_3=1 - BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL else - Bootstrap() { - BootstrapMessage "RedHat-based OSes" - BootstrapRpmCommon - } - BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi fi LE_PYTHON="$prev_le_python" @@ -870,6 +956,13 @@ if [ "$NO_BOOTSTRAP" = 1 ]; then unset BOOTSTRAP_VERSION fi +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + # Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used # to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set # if it is unknown how OS dependencies were installed on this system. @@ -1067,6 +1160,28 @@ if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + SetPrevBootstrapVersion if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then @@ -1078,8 +1193,15 @@ if [ "$1" = "--le-auto-phase2" ]; then # If the selected Bootstrap function isn't a noop and it differs from the # previously used version if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then - # if non-interactive mode or stdin and stdout are connected to a terminal - if [ \( "$NONINTERACTIVE" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then if [ -d "$VENV_PATH" ]; then rm -rf "$VENV_PATH" fi @@ -1090,12 +1212,21 @@ if [ "$1" = "--le-auto-phase2" ]; then ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. else - error "Skipping upgrade because new OS dependencies may need to be installed." - error - error "To upgrade to a newer version, please run this script again manually so you can" - error "approve changes or with --non-interactive on the command line to automatically" - error "install any required packages." + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi # Set INSTALLED_VERSION to be the same so we don't update the venv INSTALLED_VERSION="$LE_AUTO_VERSION" # Continue to use OLD_VENV_PATH if the new venv doesn't exist @@ -1372,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- @@ -1617,6 +1748,9 @@ UNLIKELY_EOF say "Installation succeeded." fi + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + if [ "$INSTALL_ONLY" = 1 ]; then say "Certbot is installed." exit 0 @@ -1828,30 +1962,35 @@ UNLIKELY_EOF error "WARNING: unable to check for updates." 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 - # dependencies (curl, etc.), for better flow control, and for the option of - # future Windows compatibility. - "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" - - # Install new copy of certbot-auto. - # TODO: Deal with quotes in pathnames. - say "Replacing certbot-auto..." - # Clone permissions with cp. chmod and chown don't have a --reference - # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: - cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" - cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" - # Using mv rather than cp leaves the old file descriptor pointing to the - # original copy so the shell can continue to read it unmolested. mv across - # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the - # cp is unlikely to fail if the rm doesn't. - mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" - fi # A newer version is available. + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + 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 + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi fi # Self-upgrading is allowed. RerunWithArgs --le-auto-phase2 "$@" diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index aea28117c..1a030eb47 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3mmvMACgkQTRfJlc2X -dfKUbQf/aW8ZWRH36WhTHmZjJmBumSUYclFdDAR4c6Ym+MBTeYT0iQq/dqfqTklB -7jPHTcxWbyMJCjOqtMEDRt+aVF0A91OA1bSRt1MJCm7o8Oa1h4XVVPL2UZYCPNlu -46UEBGDOkd6DlrRvD0X2BrQ4EsktLe1d+EoDbDPebwfip9OYnEYMD7EQB9O3N8eo -aYRkaSJMc2HalI5u0oLEhnZGucNw6K7uvuW0LkwmRWpN8Lc8e9ELZ3FOCE6qD9yh -giAkvZNklwhAxkk9spFkEilvEOPVtKgiSS6jZIL5G1NlAhp8n6+vhatY5Aotw8nO -QrqmPvzBd+2Gy2nrrGuSMC146m0x/g== -=3A0n +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl4eDcYACgkQTRfJlc2X +dfIAiQgAufTpgNvnHKoLQLwWf3GbjLQYWc3w1zRbGUMjghS/rS1yuf7RE/IPItET +ocIuIE36ogjvgnRuI0OOu3yJ+jxe41u3ToPb0ehNhINd+3rXsDhzwJDPjFdOiq98 +NoW9wQE9AHSfKEEVprckuZe2XmNLsYbBfa9THFULYIlnqAewtercXXx0eKaMG9+d +aRaD+LZXANx7IV6XnI9jfdKRuldHDvYp1TdvrRWBAVHid8j44c3P0pSvzf0YKGbx +xIty/w0zQFIWCfqPdK7/R2EHbEyR0SdI00a1Va1x7P8JGf7kDyLXl+Y9Yth7/uHA +osivJCpSrtAEbvMXojnL7u7kq3b37Q== +=Une9 -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 78e449444..2d3f4cfef 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.1.0.dev0" +LE_AUTO_VERSION="1.1.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1503,18 +1503,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 705f30e3f..bae77d59b 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index d4bdfd49e..67a33390b 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==1.0.0 \ - --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ - --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a -acme==1.0.0 \ - --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ - --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 -certbot-apache==1.0.0 \ - --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ - --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 -certbot-nginx==1.0.0 \ - --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ - --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 +certbot==1.1.0 \ + --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ + --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 +acme==1.1.0 \ + --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ + --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 +certbot-apache==1.1.0 \ + --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ + --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc +certbot-nginx==1.1.0 \ + --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ + --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 -- cgit v1.2.3 From 60cd920bcb4490e630febff1a934316c5b6de1ca Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:05 -0800 Subject: Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 8cd893c04..8884aeb5d 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.2.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.1.0 - 2020-01-14 ### Added -- cgit v1.2.3 From 619b17753e9907df79b6ebf019600a5b46677f1c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 14 Jan 2020 10:52:05 -0800 Subject: Bump version to 1.2.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 17c321903..2ab4db34e 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b27c5e50d..b676330c6 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 8b488129a..177fd9d31 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.1.0' +version = '1.2.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 7a8f4ae7b..0f9dd70dd 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 00780ead2..d4d40e8b3 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index ce8c8f19f..62fc85603 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 7fa0ed095..69e20c06b 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 3b5f42632..c42573554 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 151fa6531..61ba30c0e 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index cb0d664be..d53ed9723 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 5f12c51ae..1e698c229 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index f041b59c7..0df82b08f 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index adef7567a..b98ec96c5 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index aeec4e88f..1b2e0cad2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index df01f3868..1af30681a 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 40a62b8c5..70e19048b 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index d13641024..2c762bc4a 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 1122a56bd..eb6961729 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.1.0' +version = '1.2.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index 1e78d4345..caae1a041 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.1.0' +__version__ = '1.2.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 2d3f4cfef..17badacc7 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.1.0" +LE_AUTO_VERSION="1.2.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 6e07e8b5c0f3c6da2ae03a2a63adc7ce8e15a202 Mon Sep 17 00:00:00 2001 From: osirisinferi Date: Thu, 16 Jan 2020 20:31:22 +0100 Subject: Add missing directory field (#7687) Fixes #7683. * Add missing directory field to error message * Added change to CHANGELOG.md --- acme/acme/messages.py | 2 +- certbot/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 96a1ed7c0..c824c43cf 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -251,7 +251,7 @@ class Directory(jose.JSONDeSerializable): try: return self._jobj[self._canon_key(name)] except KeyError: - raise KeyError('Directory field not found') + raise KeyError('Directory field "' + self._canon_key(name) + '" not found') def to_partial_json(self): return self._jobj diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 8884aeb5d..dd70000cd 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -10,7 +10,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed -* +* Add directory field to error message when field is missing. ### Fixed -- cgit v1.2.3 From 91ce42ce9c47395f979c7fd72a6fa2b6da1f3ac7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:44:08 -0800 Subject: Do not list the name twice. (#7689) --- acme/acme/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index c824c43cf..1d907e5fc 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -245,7 +245,7 @@ class Directory(jose.JSONDeSerializable): try: return self[name.replace('_', '-')] except KeyError as error: - raise AttributeError(str(error) + ': ' + name) + raise AttributeError(str(error)) def __getitem__(self, name): try: -- cgit v1.2.3 From 9a3186a67ee9ed28299a190aaee70179fb8b0cba Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:47:23 -0800 Subject: Cleanup disabled warnings list in pytest.ini. (#7690) --- pytest.ini | 7 +------ tools/dev_constraints.txt | 18 +++++++++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/pytest.ini b/pytest.ini index 6c2404056..31a294ca6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,15 +4,10 @@ [pytest] # In general, all warnings are treated as errors. Here are the exceptions: # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 -# 2- ignore our own TLS-SNI-01 warning -# 3- ignore warn for importing abstract classes from collections instead of collections.abc, +# 2- ignore warn for importing abstract classes from collections instead of collections.abc, # too much third party dependencies are still relying on this behavior, # but it should be corrected to allow Certbot compatiblity with Python >= 3.8 -# 4- ipdb uses deprecated functionality of IPython. See -# https://github.com/gotcha/ipdb/issues/144. filterwarnings = error ignore:decodestring:DeprecationWarning ignore:.*collections\.abc:DeprecationWarning - ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* - ignore:.*get_systemd_os_info:DeprecationWarning diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d5d78c96a..a16a9d680 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -21,7 +21,7 @@ codecov==2.0.15 configparser==3.7.4 contextlib2==0.6.0.post1 coverage==4.5.4 -decorator==4.1.2 +decorator==4.4.1 dns-lexicon==3.2.1 dnspython==1.15.0 docker==3.7.2 @@ -39,8 +39,8 @@ google-api-python-client==1.5.5 httplib2==0.10.3 imagesize==0.7.1 importlib-metadata==0.23 -ipdb==0.10.2 -ipython==5.5.0 +ipdb==0.12.3 +ipython==5.8.0 ipython-genutils==0.2.0 isort==4.3.21 Jinja2==2.9.6 @@ -59,12 +59,12 @@ ndg-httpsclient==0.3.2 oauth2client==4.0.0 packaging==19.2 paramiko==2.4.2 -pathlib2==2.3.0 +pathlib2==2.3.5 pexpect==4.7.0 -pickleshare==0.7.4 +pickleshare==0.7.5 pkginfo==1.4.2 pluggy==0.13.0 -prompt-toolkit==1.0.15 +prompt-toolkit==1.0.18 ptyprocess==0.6.0 py==1.8.0 pyasn1==0.1.9 @@ -90,7 +90,7 @@ requests-file==1.4.2 requests-toolbelt==0.8.0 rsa==3.4.2 s3transfer==0.1.11 -scandir==1.6 +scandir==1.10.0 simplegeneric==0.8.1 singledispatch==3.4.0.3 snowballstemmer==1.2.1 @@ -102,13 +102,13 @@ tldextract==2.2.0 toml==0.10.0 tox==3.14.0 tqdm==4.19.4 -traitlets==4.3.2 +traitlets==4.3.3 twine==1.11.0 typed-ast==1.4.0 typing==3.6.4 uritemplate==3.0.0 virtualenv==16.6.2 -wcwidth==0.1.7 +wcwidth==0.1.8 websocket-client==0.56.0 wrapt==1.11.2 zipp==0.6.0 -- cgit v1.2.3 From 5f0703cbf161f216c59f6b50888644df741b008c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 13:54:25 -0800 Subject: Fix minimum certbot version in plugins (#7684) Fixes the problem found at https://github.com/certbot/certbot/pull/7682#discussion_r367140415. --- certbot-apache/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index b676330c6..7c74d5869 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'python-augeas', 'setuptools', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 0f9dd70dd..7f4f5137a 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'cloudflare>=1.5.1', 'mock', 'setuptools', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index d4d40e8b3..4e04ae820 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 62fc85603..1f146b678 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'python-digitalocean>=1.11', 'setuptools', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 69e20c06b..c486c37df 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -11,7 +11,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'setuptools', 'zope.interface', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index c42573554..7eb4473aa 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 61ba30c0e..19036b52b 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index d53ed9723..08d9755a1 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'google-api-python-client>=1.5.5', 'mock', 'oauth2client>=4.0', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 1e698c229..27165a09f 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 0df82b08f..ea669dc65 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index b98ec96c5..f90d73e0e 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 1b2e0cad2..6a9281498 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 1af30681a..df391fc65 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dnspython', 'mock', 'setuptools', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 70e19048b..01f9c9ee2 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'boto3', 'mock', 'setuptools', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 2c762bc4a..e81be051d 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -9,7 +9,7 @@ version = '1.2.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index eb6961729..4a5e5eb05 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -10,7 +10,7 @@ version = '1.2.0.dev0' # acme/certbot version. install_requires = [ 'acme>=1.0.0', - 'certbot>=1.0.0', + 'certbot>=1.1.0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? -- cgit v1.2.3 From 702ad99090f1901460c272d4ee481e4a9b4e3fac Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 16 Jan 2020 14:08:38 -0800 Subject: Don't run some tests multiple times. (#7685) --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdb692ac1..33c50b9f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,9 +62,6 @@ matrix: - python: "3.4" env: TOXENV=py34 <<: *not-on-master - - python: "3.7" - env: TOXENV=py37 - <<: *not-on-master - python: "3.8" env: TOXENV=py38 <<: *not-on-master @@ -163,9 +160,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "3.4" - env: TOXENV=py34 - <<: *extended-test-suite - python: "3.5" env: TOXENV=py35 <<: *extended-test-suite @@ -175,9 +169,6 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *extended-test-suite - - python: "3.8" - env: TOXENV=py38 - <<: *extended-test-suite - python: "3.4" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required -- cgit v1.2.3 From fcdeaf48f23f8971692dcb0d1daa84b03e3e8c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 17 Jan 2020 16:42:10 +0200 Subject: Include added/deleted TXT record name in RFC 2136 debug log (#7696) --- certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index ee71c9681..cb4d5addb 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -129,7 +129,7 @@ class _RFC2136Client(object): rcode = response.rcode() if rcode == dns.rcode.NOERROR: - logger.debug('Successfully added TXT record') + logger.debug('Successfully added TXT record %s', record_name) else: raise errors.PluginError('Received response from server: {0}' .format(dns.rcode.to_text(rcode))) @@ -164,7 +164,7 @@ class _RFC2136Client(object): rcode = response.rcode() if rcode == dns.rcode.NOERROR: - logger.debug('Successfully deleted TXT record') + logger.debug('Successfully deleted TXT record %s', record_name) else: raise errors.PluginError('Received response from server: {0}' .format(dns.rcode.to_text(rcode))) -- cgit v1.2.3 From 1702cb90fdc84e0f251df4d023b721a63f553011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 17 Jan 2020 19:55:51 +0200 Subject: Spelling and grammar fixes (#7695) --- .azure-pipelines/INSTALL.md | 4 ++-- acme/acme/client.py | 2 +- acme/acme/messages.py | 2 +- .../certbot_apache/_internal/configurator.py | 2 +- certbot-apache/certbot_apache/_internal/parser.py | 2 +- .../centos6_apache/apache/httpd/conf.d/ssl.conf | 2 +- .../centos6_apache/apache/httpd/conf/httpd.conf | 2 +- .../centos7_apache/apache/httpd/conf.d/ssl.conf | 2 +- .../augeas_vhosts/apache2/mods-available/ssl.conf | 2 +- .../default_vhost/apache2/mods-available/ssl.conf | 2 +- .../multiple_vhosts/apache2/mods-available/ssl.conf | 2 +- .../apache/apache2/modules.d/00_languages.conf | 2 +- .../apache/apache2/modules.d/40_mod_ssl.conf | 2 +- certbot-ci/certbot_integration_tests/conftest.py | 2 +- .../certbot_integration_tests/utils/acme_server.py | 2 +- .../certbot_compatibility_test/validator_test.py | 2 +- certbot-dns-gehirn/README.rst | 2 +- certbot-dns-gehirn/certbot_dns_gehirn/__init__.py | 12 ++++++------ .../certbot_dns_gehirn/_internal/dns_gehirn.py | 20 ++++++++++---------- certbot-dns-gehirn/setup.py | 2 +- certbot/CHANGELOG.md | 6 +++--- certbot/certbot/_internal/cli.py | 2 +- certbot/certbot/_internal/ocsp.py | 2 +- certbot/certbot/_internal/renewal.py | 8 ++++---- certbot/certbot/compat/filesystem.py | 2 +- certbot/certbot/display/ops.py | 4 ++-- certbot/docs/cli-help.txt | 6 +++--- certbot/tests/util_test.py | 2 +- letsencrypt-auto-source/rebuild_dependencies.py | 2 +- letsencrypt-auto-source/tests/centos6_tests.sh | 4 ++-- linter_plugin.py | 2 +- pytest.ini | 2 +- 32 files changed, 56 insertions(+), 56 deletions(-) diff --git a/.azure-pipelines/INSTALL.md b/.azure-pipelines/INSTALL.md index 9c1e4bff7..1a50bcb0c 100644 --- a/.azure-pipelines/INSTALL.md +++ b/.azure-pipelines/INSTALL.md @@ -69,12 +69,12 @@ Access can be defined for all or only selected repositories, which is nice. ``` - Redirected to Azure DevOps, select the account created in _Having an Azure DevOps account_ section. -- Select the organization, and click "Create a new project" (let's name it the same than the targetted github repo) +- Select the organization, and click "Create a new project" (let's name it the same than the targeted github repo) - The Visibility is public, to profit from 10 parallel jobs ``` !!! ACCESS !!! -Azure Pipelines needs access to the GitHub account (in term of beeing able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. +Azure Pipelines needs access to the GitHub account (in term of being able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. ``` _Done. We can move to pipelines configuration._ diff --git a/acme/acme/client.py b/acme/acme/client.py index 527430120..f48ff40b2 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -942,7 +942,7 @@ class ClientNetwork(object): :param messages.RegistrationResource account: Account object. Required if you are planning to use .post() with acme_version=2 for anything other than creating a new account; may be set later after registering. - :param josepy.JWASignature alg: Algoritm to use in signing JWS. + :param josepy.JWASignature alg: Algorithm to use in signing JWS. :param bool verify_ssl: Whether to verify certificates on SSL connections. :param str user_agent: String to send as User-Agent header. :param float timeout: Timeout for requests. diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 1d907e5fc..e82d12890 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -36,7 +36,7 @@ ERROR_CODES = { ' domain'), 'dns': 'There was a problem with a DNS query during identifier validation', 'dnssec': 'The server could not validate a DNSSEC signed domain', - 'incorrectResponse': 'Response recieved didn\'t match the challenge\'s requirements', + 'incorrectResponse': 'Response received didn\'t match the challenge\'s requirements', # deprecate invalidEmail 'invalidEmail': 'The provided email for a registration was invalid', 'invalidContact': 'The provided contact URI was invalid', diff --git a/certbot-apache/certbot_apache/_internal/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py index 20c225e04..84b59d2c7 100644 --- a/certbot-apache/certbot_apache/_internal/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -1817,7 +1817,7 @@ class ApacheConfigurator(common.Installer): ssl_vhost.filep) def _verify_no_matching_http_header(self, ssl_vhost, header_substring): - """Checks to see if an there is an existing Header directive that + """Checks to see if there is an existing Header directive that contains the string header_substring. :param ssl_vhost: vhost to check diff --git a/certbot-apache/certbot_apache/_internal/parser.py b/certbot-apache/certbot_apache/_internal/parser.py index e4073ee16..0703b8fb5 100644 --- a/certbot-apache/certbot_apache/_internal/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -764,7 +764,7 @@ class ApacheParser(object): split_arg = arg.split("/") for idx, split in enumerate(split_arg): if any(char in ApacheParser.fnmatch_chars for char in split): - # Turn it into a augeas regex + # Turn it into an augeas regex # TODO: Can this instead be an augeas glob instead of regex split_arg[idx] = ("* [label()=~regexp('%s')]" % self.fnmatch_to_re(split)) diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf index fb2174af1..abe07dd0c 100644 --- a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf @@ -26,7 +26,7 @@ Listen 443 # Pass Phrase Dialog: # Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal +# The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog builtin diff --git a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf index 579d194ce..eac6143da 100644 --- a/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf +++ b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf @@ -702,7 +702,7 @@ IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t # English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) # Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) # Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) +# Norwegian (no) - Polish (pl) - Portuguese (pt) # Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) # Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) # diff --git a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf index 6e2502e9a..c90fc780f 100644 --- a/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf +++ b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf @@ -13,7 +13,7 @@ Listen 443 https # Pass Phrase Dialog: # Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal +# The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf index e9fcf4f9b..65baec874 100644 --- a/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf +++ b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf @@ -31,7 +31,7 @@ # Pass Phrase Dialog: # Configure the pass phrase gathering process. - # The filtering dialog program (`builtin' is a internal + # The filtering dialog program (`builtin' is an internal # terminal dialog) has to provide the pass phrase on stdout. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf index c429bf94c..10cf3fb54 100644 --- a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf @@ -33,7 +33,7 @@ # English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de) # Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja) # Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn) -# Norwegian (no) - Polish (pl) - Portugese (pt) +# Norwegian (no) - Polish (pl) - Portuguese (pt) # Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv) # Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW) AddLanguage ca .ca diff --git a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf index f51de4641..7f3cef423 100644 --- a/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf +++ b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf @@ -43,7 +43,7 @@ SSLRandomSeed connect builtin ## Pass Phrase Dialog: # Configure the pass phrase gathering process. The filtering dialog program -# (`builtin' is a internal terminal dialog) has to provide the pass phrase on +# (`builtin' is an internal terminal dialog) has to provide the pass phrase on # stdout. SSLPassPhraseDialog builtin diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index 6eb9ee865..bb1d76e57 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -62,7 +62,7 @@ def _setup_primary_node(config): """ Setup the environment for integration tests. Will: - - check runtime compatiblity (Docker, docker-compose, Nginx) + - check runtime compatibility (Docker, docker-compose, Nginx) - create a temporary workspace and the persistent GIT repositories space - configure and start paralleled ACME CA servers using Docker - transfer ACME CA servers configurations to pytest nodes using env variables diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index fbf97fef1..5483251e6 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -189,7 +189,7 @@ class ACMEServer(object): print('=> Finished configuring the HTTP proxy.') def _launch_process(self, command, cwd=os.getcwd(), env=None): - """Launch silently an subprocess OS command""" + """Launch silently a subprocess OS command""" if not env: env = os.environ process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py index 86edbdb55..235ce0e3c 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py @@ -39,7 +39,7 @@ class ValidatorTest(unittest.TestCase): cert, "test.com", "127.0.0.1")) @mock.patch("certbot_compatibility_test.validator.requests.get") - def test_succesful_redirect(self, mock_get_request): + def test_successful_redirect(self, mock_get_request): mock_get_request.return_value = create_response( 301, {"location": "https://test.com"}) self.assertTrue(self.validator.redirect("test.com")) diff --git a/certbot-dns-gehirn/README.rst b/certbot-dns-gehirn/README.rst index 16058eff8..7a825bd7e 100644 --- a/certbot-dns-gehirn/README.rst +++ b/certbot-dns-gehirn/README.rst @@ -1 +1 @@ -Gehirn Infrastracture Service DNS Authenticator plugin for Certbot +Gehirn Infrastructure Service DNS Authenticator plugin for Certbot diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py b/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py index db54154ac..fdcb8cd48 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/__init__.py @@ -1,14 +1,14 @@ """ The `~certbot_dns_gehirn.dns_gehirn` plugin automates the process of completing a ``dns-01`` challenge (`~acme.challenges.DNS01`) by creating, and subsequently -removing, TXT records using the Gehirn Infrastracture Service DNS API. +removing, TXT records using the Gehirn Infrastructure Service DNS API. Named Arguments --------------- ======================================== ===================================== -``--dns-gehirn-credentials`` Gehirn Infrastracture Service +``--dns-gehirn-credentials`` Gehirn Infrastructure Service credentials_ INI file. (Required) ``--dns-gehirn-propagation-seconds`` The number of seconds to wait for DNS @@ -22,15 +22,15 @@ Credentials ----------- Use of this plugin requires a configuration file containing -Gehirn Infrastracture Service DNS API credentials, -obtained from your Gehirn Infrastracture Service +Gehirn Infrastructure Service DNS API credentials, +obtained from your Gehirn Infrastructure Service `dashboard `_. .. code-block:: ini :name: credentials.ini :caption: Example credentials file: - # Gehirn Infrastracture Service API credentials used by Certbot + # Gehirn Infrastructure Service API credentials used by Certbot dns_gehirn_api_token = 00000000-0000-0000-0000-000000000000 dns_gehirn_api_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw @@ -40,7 +40,7 @@ to this file for use during renewal, but does not store the file's contents. .. caution:: You should protect these API credentials as you would the password to your - Gehirn Infrastracture Service account. Users who can read this file can use + Gehirn Infrastructure Service account. Users who can read this file can use these credentials to issue arbitrary API calls on your behalf. Users who can cause Certbot to run using these credentials can complete a ``dns-01`` challenge to acquire new certificates or revoke existing certificates for diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index 18090c95a..76c0ed584 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -1,4 +1,4 @@ -"""DNS Authenticator for Gehirn Infrastracture Service DNS.""" +"""DNS Authenticator for Gehirn Infrastructure Service DNS.""" import logging from lexicon.providers import gehirn @@ -15,14 +15,14 @@ DASHBOARD_URL = "https://gis.gehirn.jp/" @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for Gehirn Infrastracture Service DNS + """DNS Authenticator for Gehirn Infrastructure Service DNS - This Authenticator uses the Gehirn Infrastracture Service API to fulfill + This Authenticator uses the Gehirn Infrastructure Service API to fulfill a dns-01 challenge. """ description = 'Obtain certificates using a DNS TXT record ' + \ - '(if you are using Gehirn Infrastracture Service for DNS).' + '(if you are using Gehirn Infrastructure Service for DNS).' ttl = 60 def __init__(self, *args, **kwargs): @@ -32,20 +32,20 @@ class Authenticator(dns_common.DNSAuthenticator): @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) - add('credentials', help='Gehirn Infrastracture Service credentials file.') + add('credentials', help='Gehirn Infrastructure Service credentials file.') def more_info(self): # pylint: disable=missing-docstring,no-self-use return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Gehirn Infrastracture Service API.' + 'the Gehirn Infrastructure Service API.' def _setup_credentials(self): self.credentials = self._configure_credentials( 'credentials', - 'Gehirn Infrastracture Service credentials file', + 'Gehirn Infrastructure Service credentials file', { - 'api-token': 'API token for Gehirn Infrastracture Service ' + \ + 'api-token': 'API token for Gehirn Infrastructure Service ' + \ 'API obtained from {0}'.format(DASHBOARD_URL), - 'api-secret': 'API secret for Gehirn Infrastracture Service ' + \ + 'api-secret': 'API secret for Gehirn Infrastructure Service ' + \ 'API obtained from {0}'.format(DASHBOARD_URL), } ) @@ -66,7 +66,7 @@ class Authenticator(dns_common.DNSAuthenticator): class _GehirnLexiconClient(dns_common_lexicon.LexiconClient): """ - Encapsulates all communication with the Gehirn Infrastracture Service via Lexicon. + Encapsulates all communication with the Gehirn Infrastructure Service via Lexicon. """ def __init__(self, api_token, api_secret, ttl): diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 19036b52b..0ba0228d1 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -38,7 +38,7 @@ class PyTest(TestCommand): setup( name='certbot-dns-gehirn', version=version, - description="Gehirn Infrastracture Service DNS Authenticator plugin for Certbot", + description="Gehirn Infrastructure Service DNS Authenticator plugin for Certbot", url='https://github.com/certbot/certbot', author="Certbot Project", author_email='client-dev@letsencrypt.org', diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index dd70000cd..84de0bfe5 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -248,7 +248,7 @@ More details about these changes can be found on our GitHub repo. ### Added -* dns_rfc2136 plugin now supports explicitly specifing an authorative +* dns_rfc2136 plugin now supports explicitly specifying an authoritative base domain for cases when the automatic method does not work (e.g. Split horizon DNS) @@ -632,7 +632,7 @@ https://github.com/certbot/certbot/milestone/62?closed=1 * Log warning about TLS-SNI deprecation in Certbot * Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins * OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies -* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. +* Default time the Linode plugin waits for DNS changes to propagate is now 1200 seconds. ### Fixed @@ -751,7 +751,7 @@ https://github.com/certbot/certbot/milestone/58?closed=1 increased over time. The max-age value is not increased to a large value until you've successfully managed to renew your certificate. This enhancement can be requested with the --auto-hsts flag. -* New official DNS plugins have been created for Gehirn Infrastracture Service, +* New official DNS plugins have been created for Gehirn Infrastructure Service, Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub page at https://hub.docker.com/u/certbot and on PyPI. * The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on diff --git a/certbot/certbot/_internal/cli.py b/certbot/certbot/_internal/cli.py index 9853d9b53..d5d498b4d 100644 --- a/certbot/certbot/_internal/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -1414,7 +1414,7 @@ def _plugins_parsing(helpful, plugins): helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true", default=flag_default("dns_gehirn"), help=("Obtain certificates using a DNS TXT record " - "(if you are using Gehirn Infrastracture Service for DNS).")) + "(if you are using Gehirn Infrastructure Service for DNS).")) helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", default=flag_default("dns_google"), help=("Obtain certificates using a DNS TXT record (if you are " diff --git a/certbot/certbot/_internal/ocsp.py b/certbot/certbot/_internal/ocsp.py index 65a6d5c17..2f6543e5d 100644 --- a/certbot/certbot/_internal/ocsp.py +++ b/certbot/certbot/_internal/ocsp.py @@ -192,7 +192,7 @@ def _check_ocsp_cryptography(cert_path, chain_path, url): def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path): - """Verify that the OCSP is valid for serveral criterias""" + """Verify that the OCSP is valid for serveral criteria""" # Assert OCSP response corresponds to the certificate we are talking about if response_ocsp.serial_number != request_ocsp.serial_number: raise AssertionError('the certificate in response does not correspond ' diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 0426b2e2d..930f6c1a9 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -192,7 +192,7 @@ def _restore_pref_challs(unused_name, value): :returns: converted option value to be stored in the runtime config :rtype: `list` of `str` - :raises errors.Error: if value can't be converted to an bool + :raises errors.Error: if value can't be converted to a bool """ # If pref_challs has only one element, configobj saves the value @@ -203,7 +203,7 @@ def _restore_pref_challs(unused_name, value): def _restore_bool(name, value): - """Restores an boolean key-value pair from a renewal config file. + """Restores a boolean key-value pair from a renewal config file. :param str name: option name :param str value: option value @@ -211,7 +211,7 @@ def _restore_bool(name, value): :returns: converted option value to be stored in the runtime config :rtype: bool - :raises errors.Error: if value can't be converted to an bool + :raises errors.Error: if value can't be converted to a bool """ lowercase_value = value.lower() @@ -244,7 +244,7 @@ def _restore_int(name, value): def _restore_str(unused_name, value): - """Restores an string key-value pair from a renewal config file. + """Restores a string key-value pair from a renewal config file. :param str unused_name: option name :param str value: option value diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index ba4a155e8..b49824f8d 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -541,7 +541,7 @@ def _generate_windows_flags(rights_desc): # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS - # substracted of the rights corresponding to POSIX read and POSIX execute. + # subtracted of the rights corresponding to POSIX read and POSIX execute. # # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, # so a "Full Control" on the file. diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index 92b09d6a1..eab9d251d 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -340,7 +340,7 @@ def validated_input(validator, *args, **kwargs): """Like `~certbot.interfaces.IDisplay.input`, but with validation. :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its + supplied input. If the method raises an `errors.Error`, its text will be displayed and the user will be re-prompted. :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`. @@ -355,7 +355,7 @@ def validated_directory(validator, *args, **kwargs): """Like `~certbot.interfaces.IDisplay.directory_select`, but with validation. :param callable validator: A method which will be called on the - supplied input. If the method raises a `errors.Error`, its + supplied input. If the method raises an `errors.Error`, its text will be displayed and the user will be re-prompted. :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`. :param dict `**kwargs`: Arguments to be passed to diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index 9b463820a..51967eb76 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -451,7 +451,7 @@ plugins: --dns-dnsmadeeasy Obtain certificates using a DNS TXT record (if you are using DNS Made Easy for DNS). (default: False) --dns-gehirn Obtain certificates using a DNS TXT record (if you are - using Gehirn Infrastracture Service for DNS). + using Gehirn Infrastructure Service for DNS). (default: False) --dns-google Obtain certificates using a DNS TXT record (if you are using Google Cloud DNS). (default: False) @@ -560,14 +560,14 @@ dns-dnsmadeeasy: dns-gehirn: Obtain certificates using a DNS TXT record (if you are using Gehirn - Infrastracture Service for DNS). + Infrastructure Service for DNS). --dns-gehirn-propagation-seconds DNS_GEHIRN_PROPAGATION_SECONDS The number of seconds to wait for DNS to propagate before asking the ACME server to verify the DNS record. (default: 30) --dns-gehirn-credentials DNS_GEHIRN_CREDENTIALS - Gehirn Infrastracture Service credentials file. + Gehirn Infrastructure Service credentials file. (default: None) dns-google: diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index ae061de65..3ff09a83f 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -579,7 +579,7 @@ class AtexitRegisterTest(unittest.TestCase): with mock.patch('certbot.util.atexit') as mock_atexit: self._call(self.func, *self.args, **self.kwargs) - # _INITAL_PID must be mocked when calling atexit_func + # _INITIAL_PID must be mocked when calling atexit_func self.assertTrue(mock_atexit.register.called) args, kwargs = mock_atexit.register.call_args atexit_func = args[0] diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index a79bdd8aa..eedc604e0 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -63,7 +63,7 @@ CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) # - then this venv is used to consistently construct an empty new venv # - once pipstraped, this new venv pip-installs certbot runtime (including apache/nginx), # without pinned dependencies, and respecting input authoritative requirements -# - `certbot plugins` is called to check we have an healthy environment +# - `certbot plugins` is called to check we have a healthy environment # - finally current set of dependencies is extracted out of the docker using pip freeze SCRIPT = r"""#!/bin/sh set -e diff --git a/letsencrypt-auto-source/tests/centos6_tests.sh b/letsencrypt-auto-source/tests/centos6_tests.sh index 86b307ad2..8bdffec87 100644 --- a/letsencrypt-auto-source/tests/centos6_tests.sh +++ b/letsencrypt-auto-source/tests/centos6_tests.sh @@ -100,7 +100,7 @@ echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell # now bootstrap from current letsencrypt-auto, that will install python3.6 from SCL "$LE_AUTO" --no-self-upgrade -n --version >/dev/null 2>/dev/null -# Following test is exectued in a subshell, to not leak any environment variable +# Following test is executed in a subshell, to not leak any environment variable ( # enable SCL rh-python36 . /opt/rh/rh-python36/enable @@ -124,7 +124,7 @@ echo "PASSED: certbot-auto did not install Python3.6 in a non-interactive shell fi ) -# Following test is exectued in a subshell, to not leak any environment variable +# Following test is executed in a subshell, to not leak any environment variable ( # enable SCL rh-python36 . /opt/rh/rh-python36/enable diff --git a/linter_plugin.py b/linter_plugin.py index 6be8c2414..1754b1a2a 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -16,7 +16,7 @@ WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', class ForbidStandardOsModule(BaseChecker): """ This checker ensures that standard os module (and submodules) is not imported by certbot - modules. Otherwise a 'os-module-forbidden' error will be registered for the faulty lines. + modules. Otherwise an 'os-module-forbidden' error will be registered for the faulty lines. """ __implements__ = IAstroidChecker diff --git a/pytest.ini b/pytest.ini index 31a294ca6..019676292 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,7 +6,7 @@ # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 # 2- ignore warn for importing abstract classes from collections instead of collections.abc, # too much third party dependencies are still relying on this behavior, -# but it should be corrected to allow Certbot compatiblity with Python >= 3.8 +# but it should be corrected to allow Certbot compatibility with Python >= 3.8 filterwarnings = error ignore:decodestring:DeprecationWarning -- cgit v1.2.3 From 07dc2400eb5622e88bf11c9a150afb89c947c937 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 14:53:19 -0800 Subject: Downgrade NSIS and upgrade Python (#7702) * Add --allow-downgrade to chocolatey command. * Upgrade tests to use Python 3.8.1. --- .azure-pipelines/templates/installer-tests.yml | 2 +- windows-installer/construct.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index e3a005334..f1ccd92ed 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -40,7 +40,7 @@ jobs: displayName: Retrieve Windows installer - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S displayName: Install Certbot - - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe -OutFile C:\py3-setup.exe + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe displayName: Get Python - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 displayName: Install Python diff --git a/windows-installer/construct.py b/windows-installer/construct.py index 77ca67e65..f0724f5f4 100644 --- a/windows-installer/construct.py +++ b/windows-installer/construct.py @@ -56,7 +56,7 @@ def _prepare_build_tools(venv_path, venv_python, repo_path): subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')]) subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist']) - subprocess.check_call(['choco', 'upgrade', '-y', 'nsis', '--version', NSIS_VERSION]) + subprocess.check_call(['choco', 'upgrade', '--allow-downgrade', '-y', 'nsis', '--version', NSIS_VERSION]) @contextlib.contextmanager -- cgit v1.2.3 From 7234d8922d61bbbd7eedfb8fb36c0763311eaacd Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Wed, 22 Jan 2020 01:34:34 +0200 Subject: Drop Travis tests for Python 3.4 (#7394) --- .travis.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33c50b9f0..a521da703 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,8 +59,8 @@ matrix: dist: trusty env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' <<: *not-on-master - - python: "3.4" - env: TOXENV=py34 + - python: "3.5" + env: TOXENV=py35 <<: *not-on-master - python: "3.8" env: TOXENV=py38 @@ -169,16 +169,6 @@ matrix: - python: "3.7" env: TOXENV=py37 <<: *extended-test-suite - - python: "3.4" - env: ACME_SERVER=boulder-v1 TOXENV=integration - sudo: required - services: docker - <<: *extended-test-suite - - python: "3.4" - env: ACME_SERVER=boulder-v2 TOXENV=integration - sudo: required - services: docker - <<: *extended-test-suite - python: "3.5" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required -- cgit v1.2.3 From a6772043d6631341b525c4d69b47d6ef2d8b5d02 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 15:53:31 -0800 Subject: Minor release script improvements (#7697) * Do not use git diff. * Add a warning on exit. --- tools/_release.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/_release.sh b/tools/_release.sh index 89f2a3737..1819adad2 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -7,6 +7,24 @@ if [ "$RELEASE_DIR" = "" ]; then exit 1 fi +ExitWarning() { + exit_status="$?" + if [ "$exit_status" != 0 ]; then + # Don't print each command before executing it because it will disrupt + # the desired output. + set +x + echo '******************************' + echo '* *' + echo '* THE RELEASE SCRIPT FAILED! *' + echo '* *' + echo '******************************' + set -x + fi + exit "$exit_status" +} + +trap ExitWarning EXIT + version="$1" echo Releasing production version "$version"... nextversion="$2" @@ -67,7 +85,6 @@ git checkout "$RELEASE_BRANCH" # Update changelog sed -i "s/master/$(date +'%Y-%m-%d')/" certbot/CHANGELOG.md git add certbot/CHANGELOG.md -git diff --cached git commit -m "Update changelog for $version release" for pkg_dir in $SUBPKGS certbot-compatibility-test @@ -230,7 +247,6 @@ cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto git add certbot-auto letsencrypt-auto letsencrypt-auto-source certbot/docs/cli-help.txt -git diff --cached while ! git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"; do echo "Unable to sign the release commit using git." echo "You may have to configure git to use gpg2 by running:" @@ -258,7 +274,6 @@ $body $footer" > certbot/CHANGELOG.md git add certbot/CHANGELOG.md -git diff --cached git commit -m "Add contents to certbot/CHANGELOG.md for next version" echo "New root: $root" @@ -273,6 +288,5 @@ if [ "$RELEASE_BRANCH" = candidate-"$version" ] ; then SetVersion "$nextversion".dev0 letsencrypt-auto-source/build.py git add letsencrypt-auto-source/letsencrypt-auto - git diff git commit -m "Bump version to $nextversion" fi -- cgit v1.2.3 From 4473fd25cbd3a56906e25c0f0d20a7d953a77299 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 23:18:21 -0800 Subject: Don't run Python 3.5 tests twice. (#7704) --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a521da703..733470214 100644 --- a/.travis.yml +++ b/.travis.yml @@ -160,9 +160,6 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "3.5" - env: TOXENV=py35 - <<: *extended-test-suite - python: "3.6" env: TOXENV=py36 <<: *extended-test-suite -- cgit v1.2.3 From 90fd1afc38c7d467da4a70de63313c5263aebe79 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 21 Jan 2020 23:20:52 -0800 Subject: unpin macos (#7705) --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 733470214..70038c150 100644 --- a/.travis.yml +++ b/.travis.yml @@ -225,9 +225,6 @@ matrix: - language: generic env: TOXENV=py27 os: osx - # Using this osx_image is a workaround for - # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. - osx_image: xcode10.2 addons: homebrew: packages: @@ -237,9 +234,6 @@ matrix: - language: generic env: TOXENV=py3 os: osx - # Using this osx_image is a workaround for - # https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798. - osx_image: xcode10.2 addons: homebrew: packages: -- cgit v1.2.3 From a342eb55469f8b92bc9550e4474ba04a2fd3dd58 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 23 Jan 2020 13:58:36 -0500 Subject: fixes #1948 -- MD5 on FIPS systems (#7708) * use MD5 in non-security mode to get around FIPS issue * update CHANGELOG * add myself to AUTHORS * ignore hashlib params --- AUTHORS.md | 1 + certbot/CHANGELOG.md | 1 + certbot/certbot/_internal/account.py | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d24c5be1d..e89cd9d57 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -124,6 +124,7 @@ Authors * [Jonathan Herlin](https://github.com/Jonher937) * [Jon Walsh](https://github.com/code-tree) * [Joona Hoikkala](https://github.com/joohoi) +* [Josh McCullough](https://github.com/JoshMcCullough) * [Josh Soref](https://github.com/jsoref) * [Joubin Jabbari](https://github.com/joubin) * [Juho Juopperi](https://github.com/jkjuopperi) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 84de0bfe5..7d824d714 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -11,6 +11,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Add directory field to error message when field is missing. +* If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) ### Fixed diff --git a/certbot/certbot/_internal/account.py b/certbot/certbot/_internal/account.py index c4ea6ef35..61f63bda6 100644 --- a/certbot/certbot/_internal/account.py +++ b/certbot/certbot/_internal/account.py @@ -56,11 +56,18 @@ class Account(object): tz=pytz.UTC).replace(microsecond=0), creation_host=socket.getfqdn()) if meta is None else meta - self.id = hashlib.md5( - self.key.key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo) - ).hexdigest() + # try MD5, else use MD5 in non-security mode (e.g. for FIPS systems / RHEL) + try: + hasher = hashlib.md5() + except ValueError: + hasher = hashlib.new('md5', usedforsecurity=False) # type: ignore + + hasher.update(self.key.key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + ) + + self.id = hasher.hexdigest() # Implementation note: Email? Multiple accounts can have the # same email address. Registration URI? Assigned by the # server, not guaranteed to be stable over time, nor -- cgit v1.2.3 From 5f315b46e9a4c2a79d15753969c4fbdf89f7f34b Mon Sep 17 00:00:00 2001 From: Amjad Mashaal Date: Fri, 24 Jan 2020 02:35:39 +0200 Subject: Update documentation files to remove claiming support for Python 3.4 (#7395) --- certbot/certbot/compat/filesystem.py | 2 +- certbot/docs/contributing.rst | 2 +- certbot/docs/install.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py index b49824f8d..65bb53f38 100644 --- a/certbot/certbot/compat/filesystem.py +++ b/certbot/certbot/compat/filesystem.py @@ -263,7 +263,7 @@ def replace(src, dst): :param str dst: The new file path. """ if hasattr(os, 'replace'): - # Use replace if possible. On Windows, only Python >= 3.4 is supported + # Use replace if possible. On Windows, only Python >= 3.5 is supported # so we can assume that os.replace() is always available for this platform. getattr(os, 'replace')(src, dst) else: diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index e1289c849..4ac266ed8 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -583,7 +583,7 @@ OS-level dependencies can be installed like so: In general... * ``sudo`` is required as a suggested way of running privileged process -* `Python`_ 2.7 or 3.4+ is required +* `Python`_ 2.7 or 3.5+ is required * `Augeas`_ is required for the Python bindings * ``virtualenv`` is used for managing other Python library dependencies diff --git a/certbot/docs/install.rst b/certbot/docs/install.rst index d21242367..11994776c 100644 --- a/certbot/docs/install.rst +++ b/certbot/docs/install.rst @@ -28,7 +28,7 @@ your system. System Requirements =================== -Certbot currently requires Python 2.7 or 3.4+ running on a UNIX-like operating +Certbot currently requires Python 2.7 or 3.5+ 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 port 80 (if you use the ``standalone`` plugin) and to read and -- cgit v1.2.3 From 2f24726d4cd8fa9b6c58cf8e80876082c960ab73 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 24 Jan 2020 15:13:58 +0200 Subject: Fix collections.abc imports for Python 3.9 (#7707) * Fix collections.abc imports for Python 3.9 * Update AUTHORS.md * No longer ignore collections.abc deprecation warning * Update changelog * Remove outdated comment * Disabling no-name-in-module not needed as linting is on Python 3 --- AUTHORS.md | 1 + certbot/CHANGELOG.md | 2 +- certbot/certbot/_internal/plugins/disco.py | 8 +++++++- pytest.ini | 4 ---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index e89cd9d57..d2d5fc2a7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -100,6 +100,7 @@ Authors * [Harlan Lieberman-Berg](https://github.com/hlieberman) * [Henri Salo](https://github.com/fgeek) * [Henry Chen](https://github.com/henrychen95) +* [Hugo van Kemenade](https://github.com/hugovk) * [Ingolf Becker](https://github.com/watercrossing) * [Jaap Eldering](https://github.com/eldering) * [Jacob Hoffman-Andrews](https://github.com/jsha) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 7d824d714..590bff38a 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -15,7 +15,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fix collections.abc imports for Python 3.9. More details about these changes can be found on our GitHub repo. diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 360597474..d7d6390f7 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -13,6 +13,12 @@ from certbot import errors from certbot import interfaces from certbot._internal import constants +try: + # Python 3.3+ + from collections.abc import Mapping +except ImportError: # pragma: no cover + from collections import Mapping + logger = logging.getLogger(__name__) @@ -178,7 +184,7 @@ class PluginEntryPoint(object): return "\n".join(lines) -class PluginsRegistry(collections.Mapping): +class PluginsRegistry(Mapping): """Plugins registry.""" def __init__(self, plugins): diff --git a/pytest.ini b/pytest.ini index 019676292..e09813e52 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,10 +4,6 @@ [pytest] # In general, all warnings are treated as errors. Here are the exceptions: # 1- decodestring: https://github.com/rthalley/dnspython/issues/338 -# 2- ignore warn for importing abstract classes from collections instead of collections.abc, -# too much third party dependencies are still relying on this behavior, -# but it should be corrected to allow Certbot compatibility with Python >= 3.8 filterwarnings = error ignore:decodestring:DeprecationWarning - ignore:.*collections\.abc:DeprecationWarning -- cgit v1.2.3 From 896c1e0b66817eff447b41f619772a6441b416df Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 24 Jan 2020 10:09:28 -0800 Subject: Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list (#7719) As mentioned in https://github.com/certbot/certbot/pull/7712#discussion_r370419867, it's time to remove this ciphersuite now that Windows 2008 R2 and Windows 7 are EOLed. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list to celebrate Windows 2008 R2 deprecation * Update changelog --- .../certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf | 2 +- .../_internal/tls_configs/options-ssl-nginx-tls12-only.conf | 2 +- .../_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf | 2 +- .../certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf | 2 +- certbot/CHANGELOG.md | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf index 731e38919..a678b0507 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf index 33771a189..1933cbc4f 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf index 91197d2c8..52fdfde24 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -10,4 +10,4 @@ ssl_session_timeout 1440m; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf index 98b1c4ab9..978e6e8ab 100644 --- a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf @@ -11,4 +11,4 @@ ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; -ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 590bff38a..5ddfab472 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,6 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) +* Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed ### Fixed -- cgit v1.2.3 From 1e2f70b17a67658ed5b7a8ab5efdfef8f55623db Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 12:32:07 -0800 Subject: Drop Python 3.4 support (#7721) Fixes #7393. * Remove Python 3.4 classifiers * Remove unneeded typing dependency * Exclude Python 3.4 in python_requires * Remove Python 3.4 deprecation warning * update changelog --- acme/setup.py | 3 +-- certbot-apache/setup.py | 3 +-- certbot-ci/setup.py | 3 +-- certbot-compatibility-test/setup.py | 3 +-- certbot-dns-cloudflare/setup.py | 3 +-- certbot-dns-cloudxns/setup.py | 3 +-- certbot-dns-digitalocean/setup.py | 3 +-- certbot-dns-dnsimple/setup.py | 3 +-- certbot-dns-dnsmadeeasy/setup.py | 3 +-- certbot-dns-gehirn/setup.py | 3 +-- certbot-dns-google/setup.py | 3 +-- certbot-dns-linode/setup.py | 3 +-- certbot-dns-luadns/setup.py | 3 +-- certbot-dns-nsone/setup.py | 3 +-- certbot-dns-ovh/setup.py | 3 +-- certbot-dns-rfc2136/setup.py | 3 +-- certbot-dns-route53/setup.py | 3 +-- certbot-dns-sakuracloud/setup.py | 3 +-- certbot-nginx/setup.py | 3 +-- certbot/CHANGELOG.md | 1 + certbot/certbot/_internal/main.py | 4 ---- certbot/setup.py | 4 +--- letshelp-certbot/setup.py | 3 +-- 23 files changed, 22 insertions(+), 47 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 2ab4db34e..458ca083d 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -61,7 +61,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -70,7 +70,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 7c74d5869..599925929 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -42,7 +42,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -53,7 +53,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index fb82b6ca5..75d2cc96a 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -40,7 +40,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', @@ -49,7 +49,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 177fd9d31..c3443e35e 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -28,7 +28,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', @@ -37,7 +37,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 7f4f5137a..a3e64f07d 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 4e04ae820..a7a0072c4 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 1f146b678..fe5243bc5 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -45,7 +45,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -56,7 +56,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index c486c37df..e31bc5949 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -56,7 +56,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -67,7 +67,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 7eb4473aa..fceb0b518 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 0ba0228d1..974b17d44 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 08d9755a1..7b5583307 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -47,7 +47,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -58,7 +58,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 27165a09f..37b5a4921 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index ea669dc65..4d60ca520 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index f90d73e0e..81324dbc2 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 6a9281498..ee7d9fc69 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index df391fc65..aa2509727 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -44,7 +44,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -55,7 +55,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 01f9c9ee2..df43d90a9 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -39,7 +39,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -50,7 +50,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index e81be051d..4d35dc7ac 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -43,7 +43,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -54,7 +54,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 4a5e5eb05..aad736da5 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -42,7 +42,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', @@ -53,7 +53,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 5ddfab472..b025866e7 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -13,6 +13,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed +* Support for Python 3.4 has been removed. ### Fixed diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index 509b5b981..72fcfca71 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -1337,10 +1337,6 @@ def main(cli_args=None): if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise - if sys.version_info[:2] == (3, 4): - logger.warning("Python 3.4 support will be dropped in the next release " - "of Certbot - please upgrade your Python version to 3.5+.") - set_displayer(config) # Reporter diff --git a/certbot/setup.py b/certbot/setup.py index 0026ef8e9..d19327e5e 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -88,7 +88,6 @@ dev3_extras = [ 'astroid', 'mypy', 'pylint', - 'typing', # for python3.4 ] docs_extras = [ @@ -124,7 +123,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -136,7 +135,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py index af992de16..448c145ce 100644 --- a/letshelp-certbot/setup.py +++ b/letshelp-certbot/setup.py @@ -21,7 +21,7 @@ setup( author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: System Administrators', @@ -31,7 +31,6 @@ setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', -- cgit v1.2.3 From b1a8e7175b1d7ee9d7bd16d5ba9b3ddd055c80db Mon Sep 17 00:00:00 2001 From: ohemorange Date: Fri, 24 Jan 2020 13:37:42 -0800 Subject: Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache (#7712) Part of #7204. Makes the smaller changes described at https://github.com/certbot/certbot/issues/7204#issuecomment-571838185 to disable many old ciphersuites and TLS versions < 1.2. Does not add checks for OpenSSL version or modify session tickets. Since Apache uses TLS protocol blacklisting instead of whitelisting (as in NGINX), we additionally may not need to determine if the server supports TLS1.3 and turn it on or off based on Apache version. * Update SSL versions and ciphersuites based on Mozilla intermediate recommendations for apache * Update constants with hashes of new config files * Update changelog --- .../certbot_apache/_internal/centos-options-ssl-apache.conf | 13 +++---------- certbot-apache/certbot_apache/_internal/constants.py | 2 ++ .../certbot_apache/_internal/options-ssl-apache.conf | 13 +++---------- certbot/CHANGELOG.md | 1 + 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf index 56c946a4e..1a3799628 100644 --- a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf @@ -7,19 +7,12 @@ SSLEngine on # Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +SSLHonorCipherOrder off SSLOptions +StrictRequire # Add vhost name to log entries: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot-apache/certbot_apache/_internal/constants.py b/certbot-apache/certbot_apache/_internal/constants.py index 47e3be856..a37bebac5 100644 --- a/certbot-apache/certbot_apache/_internal/constants.py +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -24,6 +24,8 @@ ALL_SSL_OPTIONS_HASHES = [ '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', + '5cc003edd93fb9cd03d40c7686495f8f058f485f75b5e764b789245a386e6daf', + '007cd497a56a3bb8b6a2c1aeb4997789e7e38992f74e44cc5d13a625a738ac73', ] """SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" diff --git a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf index 8113ee81e..60095faa0 100644 --- a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf @@ -7,9 +7,9 @@ SSLEngine on # Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS -SSLHonorCipherOrder on +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 +SSLHonorCipherOrder off SSLCompression off SSLOptions +StrictRequire @@ -17,10 +17,3 @@ SSLOptions +StrictRequire # Add vhost name to log entries: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common - -#CustomLog /var/log/apache2/access.log vhost_combined -#LogLevel warn -#ErrorLog /var/log/apache2/error.log - -# Always ensure Cookies have "Secure" set (JAH 2012/1) -#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4" diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index b025866e7..1cd4d3f1c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,6 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) +* Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed * Support for Python 3.4 has been removed. -- cgit v1.2.3 From 2072599bd716d9b6afe4b7b66d0ef12a01f36cf7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 14:02:54 -0800 Subject: Unpin Python 3.4 dependencies (#7709) * Unpin dependencies pinned back for py3.4 support. * update pinned packages * run build.py * Update boto3 and deps to work with requests --- letsencrypt-auto-source/letsencrypt-auto | 175 +++++++++++++-------- .../pieces/dependency-requirements.txt | 175 +++++++++++++-------- letsencrypt-auto-source/rebuild_dependencies.py | 6 - tools/dev_constraints.txt | 14 +- 4 files changed, 219 insertions(+), 151 deletions(-) diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 17badacc7..9d2013cd1 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 034fae46d..eec5a9946 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -9,11 +9,11 @@ # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -86,8 +86,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -100,40 +98,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -145,47 +143,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index eedc604e0..6d1ec15ff 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -46,12 +46,6 @@ AUTHORITATIVE_CONSTRAINTS = { # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. 'enum34': '1.1.6 ; python_version < \'3.4\'', - # Newer versions of the packages below dropped support for python 3.4. Once - # Certbot does as well, we should unpin these dependencies. - 'requests': '2.21.0', - 'ConfigArgParse': '0.14.0', - 'zope.hookable': '4.2.0', - 'zope.interface': '4.6.0', } diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index a16a9d680..1204cbf5f 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -13,8 +13,8 @@ backports.functools-lru-cache==1.5 backports.shutil-get-terminal-size==1.0.0 backports.ssl-match-hostname==3.7.0.1 bcrypt==3.1.6 -boto3==1.9.36 -botocore==1.12.36 +boto3==1.11.7 +botocore==1.14.7 cached-property==1.5.1 cloudflare==1.5.1 codecov==2.0.15 @@ -29,11 +29,11 @@ docker-compose==1.25.0 docker-pycreds==0.4.0 dockerpty==0.4.1 docopt==0.6.2 -docutils==0.12 +docutils==0.15.2 execnet==1.5.0 functools32==3.2.3.post2 future==0.16.0 -futures==3.1.1 +futures==3.3.0 filelock==3.0.12 google-api-python-client==1.5.5 httplib2==0.10.3 @@ -44,7 +44,7 @@ ipython==5.8.0 ipython-genutils==0.2.0 isort==4.3.21 Jinja2==2.9.6 -jmespath==0.9.3 +jmespath==0.9.4 josepy==1.1.0 jsonschema==2.6.0 lazy-object-proxy==1.4.3 @@ -81,7 +81,7 @@ pytest-forked==0.2 pytest-xdist==1.22.5 pytest-sugar==0.9.2 pytest-rerunfailures==4.2 -python-dateutil==2.6.1 +python-dateutil==2.8.1 python-digitalocean==1.11 pywin32==227 PyYAML==3.13 @@ -89,7 +89,7 @@ repoze.sphinx.autointerface==0.8 requests-file==1.4.2 requests-toolbelt==0.8.0 rsa==3.4.2 -s3transfer==0.1.11 +s3transfer==0.3.1 scandir==1.10.0 simplegeneric==0.8.1 singledispatch==3.4.0.3 -- cgit v1.2.3 From b8a9dd75eb8c8be7990d3053536de59716c3c585 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 24 Jan 2020 15:02:57 -0800 Subject: Update dns-lexicon version. (#7723) --- tools/dev_constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index 1204cbf5f..d40c6e19a 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -22,7 +22,7 @@ configparser==3.7.4 contextlib2==0.6.0.post1 coverage==4.5.4 decorator==4.4.1 -dns-lexicon==3.2.1 +dns-lexicon==3.3.17 dnspython==1.15.0 docker==3.7.2 docker-compose==1.25.0 -- cgit v1.2.3 From e3c996de102b876aaedfc77517898e42094242da Mon Sep 17 00:00:00 2001 From: Cameron Steel Date: Sat, 25 Jan 2020 10:25:03 +1100 Subject: dns-cloudflare: Implement limited-scope API Tokens (#7583) A while ago Cloudflare added support for limited-scope API Tokens in place of using a global API key, but support for them in cloudflare/python-cloudflare took a while to get through. In summary, this PR: - Implements token functionality through the INI file parameter `dns_cloudflare_api_token` (in addition to the traditional `dns_cloudflare_email` and `dns_cloudflare_api_key`). This needed a more advanced parameter validator than the built in `required_variables` mechanism. - Updates the docs to reflect the new option, needed token permissions, and version details of the `cloudflare` module * Update python-cloudflare version * Add Cloudflare API Token support to certbot-dns-cloudflare * Add token-specific errors to certbot-dns-cloudflare * Tidy up certbot-dns-cloudflare * Implement Cloudflare API Tokens in testing for certbot-dns-cloudflare(needs work) * Further tidying of certbot-dns-cloudflare * Update CHANGELOG with Cloudflare API Tokens implementation * Improve testing of certbot-dns-cloudflare * Improve certbot-dns-cloudflare test formatting * Further improve testing for certbot-dns-cloudflare * Change needed permissions for token * Add documentation regarding python-cloudflare version * Fix changelog, references to python-cloudflare and docs * Fix behaviour when domain does not match cloudflare root domain. Improve error handling. * Improve testing * Improve hints and error handling --- AUTHORS.md | 1 + .../certbot_dns_cloudflare/__init__.py | 35 ++++++++-- .../_internal/dns_cloudflare.py | 75 ++++++++++++++++++---- .../tests/dns_cloudflare_test.py | 68 +++++++++++++++++++- certbot/CHANGELOG.md | 2 +- tools/dev_constraints.txt | 2 +- 6 files changed, 160 insertions(+), 23 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d2d5fc2a7..80a24d3be 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -36,6 +36,7 @@ Authors * [Brad Warren](https://github.com/bmw) * [Brandon Kraft](https://github.com/kraftbj) * [Brandon Kreisel](https://github.com/kraftbj) +* [Cameron Steel](https://github.com/Tugzrida) * [Ceesjan Luiten](https://github.com/quinox) * [Chad Whitacre](https://github.com/whit537) * [Chhatoi Pritam Baral](https://github.com/pritambaral) diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py index b08bc0968..11886ea54 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py @@ -22,17 +22,40 @@ Credentials Use of this plugin requires a configuration file containing Cloudflare API credentials, obtained from your Cloudflare -`account page `_. This plugin -does not currently support Cloudflare's "API Tokens", so please ensure you use -the "Global API Key" for authentication. +`account page `_. + +Previously, Cloudflare's "Global API Key" was used for authentication, however +this key can access the entire Cloudflare API for all domains in your account, +meaning it could cause a lot of damage if leaked. + +Cloudflare's newer API Tokens can be restricted to specific domains and +operations, and are therefore now the recommended authentication option. + +However, due to some shortcomings in Cloudflare's implementation of Tokens, +Tokens created for Certbot currently require ``Zone:Zone:Read`` and ``Zone:DNS:Edit`` +permissions for **all** zones in your account. While this is not ideal, your Token +will still have fewer permission than the Global key, so it's still worth doing. +Hopefully Cloudflare will improve this in the future. + +Using Cloudflare Tokens also requires at least version 2.3.1 of the ``cloudflare`` +python module. If the version that automatically installed with this plugin is +older than that, and you can't upgrade it on your system, you'll have to stick to +the Global key. + +.. code-block:: ini + :name: certbot_cloudflare_token.ini + :caption: Example credentials file using restricted API Token (recommended): + + # Cloudflare API token used by Certbot + dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567 .. code-block:: ini - :name: credentials.ini - :caption: Example credentials file: + :name: certbot_cloudflare_key.ini + :caption: Example credentials file using Global API Key (not recommended): # Cloudflare API credentials used by Certbot dns_cloudflare_email = cloudflare@example.com - dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567 + dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234 The path to this file can be provided interactively or using the ``--dns-cloudflare-credentials`` command-line argument. Certbot records the path diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index 0bbdf703a..22124ac04 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -4,6 +4,10 @@ import logging import CloudFlare import zope.interface +from acme.magic_typing import Any +from acme.magic_typing import Dict +from acme.magic_typing import List + from certbot import errors from certbot import interfaces from certbot.plugins import dns_common @@ -38,14 +42,35 @@ class Authenticator(dns_common.DNSAuthenticator): return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Cloudflare API.' + def _validate_credentials(self, credentials): + token = credentials.conf('api-token') + email = credentials.conf('email') + key = credentials.conf('api-key') + if token: + if email or key: + raise errors.PluginError('{}: dns_cloudflare_email and dns_cloudflare_api_key are ' + 'not needed when using an API Token' + .format(credentials.confobj.filename)) + elif email or key: + if not email: + raise errors.PluginError('{}: dns_cloudflare_email is required when using a Global ' + 'API Key. (should be email address associated with ' + 'Cloudflare account)'.format(credentials.confobj.filename)) + if not key: + raise errors.PluginError('{}: dns_cloudflare_api_key is required when using a ' + 'Global API Key. (see {})' + .format(credentials.confobj.filename, ACCOUNT_URL)) + else: + raise errors.PluginError('{}: Either dns_cloudflare_api_token (recommended), or ' + 'dns_cloudflare_email and dns_cloudflare_api_key are required.' + ' (see {})'.format(credentials.confobj.filename, ACCOUNT_URL)) + def _setup_credentials(self): self.credentials = self._configure_credentials( 'credentials', 'Cloudflare credentials INI file', - { - 'email': 'email address associated with Cloudflare account', - 'api-key': 'API key for Cloudflare account, obtained from {0}'.format(ACCOUNT_URL) - } + None, + self._validate_credentials ) def _perform(self, domain, validation_name, validation): @@ -55,6 +80,8 @@ class Authenticator(dns_common.DNSAuthenticator): self._get_cloudflare_client().del_txt_record(domain, validation_name, validation) def _get_cloudflare_client(self): + if self.credentials.conf('api-token'): + return _CloudflareClient(None, self.credentials.conf('api-token')) return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key')) @@ -88,8 +115,15 @@ class _CloudflareClient(object): logger.debug('Attempting to add record to zone %s: %s', zone_id, data) self.cf.zones.dns_records.post(zone_id, data=data) # zones | pylint: disable=no-member except CloudFlare.exceptions.CloudFlareAPIError as e: + code = int(e) + hint = None + + if code == 9109: + hint = 'Does your API token have "Zone:DNS:Edit" permissions?' + logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e) - raise errors.PluginError('Error communicating with the Cloudflare API: {0}'.format(e)) + raise errors.PluginError('Error communicating with the Cloudflare API: {0}{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) record_id = self._find_txt_record_id(zone_id, record_name, record_content) logger.debug('Successfully added TXT record with record_id: %s', record_id) @@ -139,6 +173,8 @@ class _CloudflareClient(object): """ zone_name_guesses = dns_common.base_domain_name_guesses(domain) + zones = [] # type: List[Dict[str, Any]] + code = msg = None for zone_name in zone_name_guesses: params = {'name': zone_name, @@ -148,16 +184,26 @@ class _CloudflareClient(object): zones = self.cf.zones.get(params=params) # zones | pylint: disable=no-member except CloudFlare.exceptions.CloudFlareAPIError as e: code = int(e) + msg = str(e) hint = None if code == 6003: - hint = 'Did you copy your entire API key?' + hint = ('Did you copy your entire API token/key? To use Cloudflare tokens, ' + 'you\'ll need the python package cloudflare>=2.3.1.{}' + .format(' This certbot is running cloudflare ' + str(CloudFlare.__version__) + if hasattr(CloudFlare, '__version__') else '')) elif code == 9103: - hint = 'Did you enter the correct email address?' - - raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm that ' - 'you have supplied valid Cloudflare API credentials.{2}' - .format(code, e, ' ({0})'.format(hint) if hint else '')) + hint = 'Did you enter the correct email address and Global key?' + elif code == 9109: + hint = 'Did you enter a valid Cloudflare Token?' + + if hint: + raise errors.PluginError('Error determining zone_id: {0} {1}. Please confirm ' + 'that you have supplied valid Cloudflare API credentials. ({2})' + .format(code, msg, hint)) + else: + logger.debug('Unrecognised CloudFlareAPIError while finding zone_id: %d %s. ' + 'Continuing with next zone guess...', e, e) if zones: zone_id = zones[0]['id'] @@ -165,9 +211,10 @@ class _CloudflareClient(object): return zone_id raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. ' - 'Please confirm that the domain name has been entered correctly ' - 'and is already associated with the supplied Cloudflare account.' - .format(domain, zone_name_guesses)) + 'Please confirm that the domain name has been entered correctly ' + 'and is already associated with the supplied Cloudflare account.{2}' + .format(domain, zone_name_guesses, ' The error from Cloudflare was:' + ' {0} {1}'.format(code, msg) if code is not None else '')) def _find_txt_record_id(self, zone_id, record_name, record_content): """ diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py index b24628b0d..d38330191 100644 --- a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -12,6 +12,9 @@ from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '') + +API_TOKEN = 'an-api-token' + API_KEY = 'an-api-key' EMAIL = 'example@example.com' @@ -49,6 +52,50 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)] self.assertEqual(expected, self.mock_client.mock_calls) + def test_api_token(self): + dns_test_common.write({"cloudflare_api_token": API_TOKEN}, + self.config.cloudflare_credentials) + self.auth.perform([self.achall]) + + expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_no_creds(self): + dns_test_common.write({}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_missing_email_or_key(self): + dns_test_common.write({"cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_email": EMAIL}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + def test_email_or_key_with_token(self): + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL}, + self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_api_key": API_KEY}, + self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + + dns_test_common.write({"cloudflare_api_token": API_TOKEN, "cloudflare_email": EMAIL, + "cloudflare_api_key": API_KEY}, self.config.cloudflare_credentials) + self.assertRaises(errors.PluginError, + self.auth.perform, + [self.achall]) + class CloudflareClientTest(unittest.TestCase): record_name = "foo" @@ -83,7 +130,7 @@ class CloudflareClientTest(unittest.TestCase): def test_add_txt_record_error(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.post.side_effect = API_ERROR + self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') self.assertRaises( errors.PluginError, @@ -106,6 +153,25 @@ class CloudflareClientTest(unittest.TestCase): self.cloudflare_client.add_txt_record, DOMAIN, self.record_name, self.record_content, self.record_ttl) + def test_add_txt_record_bad_creds(self): + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(6003, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9103, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + def test_del_txt_record(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 1cd4d3f1c..48a2a554b 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Added -* +* Added support for Cloudflare's limited-scope API Tokens ### Changed diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index d40c6e19a..265d967d8 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -16,7 +16,7 @@ bcrypt==3.1.6 boto3==1.11.7 botocore==1.14.7 cached-property==1.5.1 -cloudflare==1.5.1 +cloudflare==2.3.1 codecov==2.0.15 configparser==3.7.4 contextlib2==0.6.0.post1 -- cgit v1.2.3 From 2338ab36fd70e9d65eeafb8e22cd05b3ac879e3f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 27 Jan 2020 13:13:38 -0800 Subject: Add backwards compatibility docs (#7611) Fixes #7463. * Add backwards compatibility docs. * Exclude certbot-auto --- certbot/docs/compatibility.rst | 39 +++++++++++++++++++++++++++++++++++++++ certbot/docs/index.rst | 1 + 2 files changed, 40 insertions(+) create mode 100644 certbot/docs/compatibility.rst diff --git a/certbot/docs/compatibility.rst b/certbot/docs/compatibility.rst new file mode 100644 index 000000000..a511f36a2 --- /dev/null +++ b/certbot/docs/compatibility.rst @@ -0,0 +1,39 @@ +======================= +Backwards Compatibility +======================= + +All Certbot components including `acme `_, +Certbot, and :ref:`non-third party plugins ` follow `Semantic +Versioning `_ both for its Python :doc:`API ` and for the +application itself. This means that we will not change behavior in a backwards +incompatible way except in a new major version of the project. + +.. note:: None of this applies to the behavior of Certbot distribution + mechanisms such as :ref:`certbot-auto ` or OS packages whose + behavior may change at any time. Semantic versioning only applies to the + common Certbot components that are installed by various distribution + methods. + +For Certbot as an application, the command line interface and non-interactive +behavior can be considered stable with two exceptions. The first is that no +aspects of Certbot's console or log output should be considered stable and it +may change at any time. The second is that Certbot's behavior should only be +considered stable with certain files but not all. Files with which users should +expect Certbot to maintain its current behavior with are: + +* ``/etc/letsencrypt/live//{cert,chain,fullchain,privkey}.pem`` where + ```` is the name given to ``--cert-name``. If ``--cert-name`` is not + set by the user, it is the first domain given to ``--domains``. +* :ref:`CLI configuration files ` +* Hook directories in ``/etc/letsencrypt/renewal-hooks`` + +Certbot's behavior with other files may change at any point. + +Another area where Certbot should not be considered stable is its behavior when +not run in non-interactive mode which also may change at any point. + +In general, if we're making a change that we expect will break some users, we +will bump the major version and will have warned about it in a prior release +when possible. For our Python API, we will issue warnings using Python's +warning module. For application level changes, we will print and log warning +messages. diff --git a/certbot/docs/index.rst b/certbot/docs/index.rst index 17cde1adf..a7fc75c5b 100644 --- a/certbot/docs/index.rst +++ b/certbot/docs/index.rst @@ -10,6 +10,7 @@ Welcome to the Certbot documentation! using contributing packaging + compatibility resources .. toctree:: -- cgit v1.2.3 From 11e402893fc0bff32fb6fb5ab2a307c3dc62a633 Mon Sep 17 00:00:00 2001 From: ohemorange Date: Wed, 29 Jan 2020 15:21:17 -0800 Subject: Remove SSLCompression off line from all config options (#7726) Based on discussion at https://github.com/certbot/certbot/pull/7712#discussion_r371451761. * Remove SSLCompression off line from all config options * Update changelog --- certbot-apache/MANIFEST.in | 1 - .../_internal/centos-options-ssl-apache.conf | 18 ------------------ .../certbot_apache/_internal/options-ssl-apache.conf | 1 - .../certbot_apache/_internal/override_centos.py | 2 +- .../certbot_apache/_internal/override_fedora.py | 2 +- certbot/CHANGELOG.md | 2 +- 6 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index fa15504e7..2316983bb 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,7 +1,6 @@ include LICENSE.txt include README.rst recursive-include tests * -include certbot_apache/_internal/centos-options-ssl-apache.conf include certbot_apache/_internal/options-ssl-apache.conf recursive-include certbot_apache/_internal/augeas_lens *.aug global-exclude __pycache__ diff --git a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf deleted file mode 100644 index 1a3799628..000000000 --- a/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf +++ /dev/null @@ -1,18 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -SSLEngine on - -# Intermediate configuration, tweak to your needs -SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 -SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 -SSLHonorCipherOrder off - -SSLOptions +StrictRequire - -# Add vhost name to log entries: -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined -LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common diff --git a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf index 60095faa0..1a3799628 100644 --- a/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf +++ b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf @@ -10,7 +10,6 @@ SSLEngine on SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder off -SSLCompression off SSLOptions +StrictRequire diff --git a/certbot-apache/certbot_apache/_internal/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py index b3576e083..a3ef2d760 100644 --- a/certbot-apache/certbot_apache/_internal/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -38,7 +38,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def config_test(self): diff --git a/certbot-apache/certbot_apache/_internal/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py index a9607a60f..8197b0dcd 100644 --- a/certbot-apache/certbot_apache/_internal/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -33,7 +33,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( # TODO: eventually newest version of Fedora will need their own config - "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def config_test(self): diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 48a2a554b..86d27143c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -12,7 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Add directory field to error message when field is missing. * If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948) -* Disable old SSL versions and ciphersuites to follow Mozilla recommendations in Apache. +* Disable old SSL versions and ciphersuites and remove `SSLCompression off` setting to follow Mozilla recommendations in Apache. * Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed * Support for Python 3.4 has been removed. -- cgit v1.2.3 From 35fa4c0457827f08134f7ea96e8ebf17cc120c3d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 29 Jan 2020 15:30:51 -0800 Subject: Add space between words. --- certbot-nginx/certbot_nginx/_internal/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index 70d9d87f8..fdead036a 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1008,7 +1008,7 @@ class NginxConfigurator(common.Installer): matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) if not matches: logger.warning("NGINX configured with OpenSSL alternatives is not officially" - "supported by Certbot.") + " supported by Certbot.") return "" return matches[0] -- cgit v1.2.3 From 8d9943cb08be2f12bb167d991069a1c128f5b162 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 30 Jan 2020 11:47:48 -0800 Subject: Update instructions about how to build docs (#7605) --- certbot/docs/contributing.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 4ac266ed8..c13005a9d 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -524,19 +524,22 @@ during the next release. Updating the documentation ========================== -In order to generate the Sphinx documentation, run the following -commands: +Many of the packages in the Certbot repository have documentation in a +``docs/`` directory. This directory is located under the top level directory +for the package. For instance, Certbot's documentation is under +``certbot/docs``. -.. code-block:: shell - - make -C docs clean html man +To build the documentation of a package, make sure you have followed the +instructions to set up a `local copy`_ of Certbot including activating the +virtual environment. After that, ``cd`` to the docs directory you want to build +and run the command: -This should generate documentation in the ``docs/_build/html`` -directory. +.. code-block:: shell -.. note:: If you skipped the "Getting Started" instructions above, - run ``pip install -e "certbot[docs]"`` to install Certbot's docs extras modules. + make clean html +This would generate the HTML documentation in ``_build/html`` in your current +``docs/`` directory. .. _docker-dev: -- cgit v1.2.3 From 174fa0e05ca3f8e6c8549abc7f3bc4bc8c40123a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 30 Jan 2020 13:26:39 -0800 Subject: Turn off Travis notifications in test branches. (#7733) When I want to manually run the full test suite to test something, I've been manually deleting our notification setup from `.travis.yml` to avoid spamming IRC with my personal test failures. This PR sets this behavior up to happen automatically by turning off IRC notifications in test branches. You can see this working by noticing the IRC notification section in the bottom of the config for this PR at https://travis-ci.com/certbot/certbot/builds/146827907/config and the fact that it is absent from a `test-` branch based on this one at https://travis-ci.com/certbot/certbot/jobs/282059094/config. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 70038c150..0ed6b47af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -274,6 +274,7 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' notifications: email: false irc: + if: NOT branch =~ ^test-.*$ channels: # This is set to a secure variable to prevent forks from sending # notifications. This value was created by installing -- cgit v1.2.3 From 6c5959d892339d8921aa302dffddd70aa4d3caad Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 13:46:57 -0800 Subject: Update changelog for 1.2.0 release --- certbot/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 86d27143c..a14cea76c 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,7 +2,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 1.2.0 - master +## 1.2.0 - 2020-02-04 ### Added -- cgit v1.2.3 From 3907b53b4b94e3519c6b3a83ca3396087ec77f33 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:02 -0800 Subject: Release 1.2.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-auto | 201 ++++++++++++--------- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- certbot/docs/cli-help.txt | 2 +- letsencrypt-auto | 201 ++++++++++++--------- letsencrypt-auto-source/certbot-auto.asc | 16 +- letsencrypt-auto-source/letsencrypt-auto | 26 +-- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes .../pieces/certbot-requirements.txt | 24 +-- 26 files changed, 291 insertions(+), 217 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 458ca083d..58d2c12ce 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 599925929..8a887b444 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index 2d3f4cfef..cea58e2cb 100755 --- a/certbot-auto +++ b/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="1.1.0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ @@ -1503,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index c3443e35e..891bd8e1c 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.2.0.dev0' +version = '1.2.0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index a3e64f07d..706113d45 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index a7a0072c4..c290c5599 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index fe5243bc5..fac989ae6 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index e31bc5949..fe29a7137 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index fceb0b518..a36c74f9d 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 974b17d44..1ef15c85f 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 7b5583307..f2b99ba15 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 37b5a4921..eead76cc8 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 4d60ca520..f8484e9f5 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 81324dbc2..7a4e21a8e 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index ee7d9fc69..dbecc3f7f 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index aa2509727..8f2f09f27 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index df43d90a9..1d30e5b40 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 4d35dc7ac..d0403ac87 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index aad736da5..dc50f5cb3 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0.dev0' +version = '1.2.0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index caae1a041..ab972d9e3 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.2.0.dev0' +__version__ = '1.2.0' diff --git a/certbot/docs/cli-help.txt b/certbot/docs/cli-help.txt index 51967eb76..ff49609c4 100644 --- a/certbot/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -113,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/1.1.0 (certbot(-auto); + "". (default: CertbotACMEClient/1.2.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the user agent are: --duplicate, diff --git a/letsencrypt-auto b/letsencrypt-auto index 2d3f4cfef..cea58e2cb 100755 --- a/letsencrypt-auto +++ b/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="1.1.0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1274,11 +1274,11 @@ if [ "$1" = "--le-auto-phase2" ]; then # pip install hashin # hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` -ConfigArgParse==0.14.0 \ - --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -certifi==2019.9.11 \ - --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ - --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +ConfigArgParse==1.0 \ + --hash=sha256:bf378245bc9cdc403a527e5b7406b991680c2a530e7e81af747880b54eb57133 +certifi==2019.11.28 \ + --hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \ + --hash=sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f cffi==1.13.2 \ --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ @@ -1351,8 +1351,6 @@ enum34==1.1.6 \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.18.2 \ - --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c @@ -1365,40 +1363,40 @@ josepy==1.2.0 \ mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb -parsedatetime==2.4 \ - --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ - --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.4.3 \ - --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ - --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 -pyOpenSSL==19.0.0 \ - --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ - --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.4 \ + --hash=sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b \ + --hash=sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 pyRFC3339==1.1 \ --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.4.5 \ - --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ - --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a +pyparsing==2.4.6 \ + --hash=sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f \ + --hash=sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 pytz==2019.3 \ --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be -requests==2.21.0 \ - --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ - --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b +requests==2.22.0 \ + --hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \ + --hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31 requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.13.0 \ - --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ - --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 -urllib3==1.24.3 \ - --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ - --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +six==1.14.0 \ + --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a \ + --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c +urllib3==1.25.8 \ + --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \ + --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc zope.component==4.6 \ --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 zope.deferredimport==4.3.1 \ @@ -1410,47 +1408,86 @@ zope.deprecation==4.4.0 \ zope.event==4.4 \ --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 -zope.hookable==4.2.0 \ - --hash=sha256:22886e421234e7e8cedc21202e1d0ab59960e40a47dd7240e9659a2d82c51370 \ - --hash=sha256:39912f446e45b4e1f1951b5ffa2d5c8b074d25727ec51855ae9eab5408f105ab \ - --hash=sha256:3adb7ea0871dbc56b78f62c4f5c024851fc74299f4f2a95f913025b076cde220 \ - --hash=sha256:3d7c4b96341c02553d8b8d71065a9366ef67e6c6feca714f269894646bb8268b \ - --hash=sha256:4e826a11a529ed0464ffcecf34b0b7bd1b4928dd5848c5c61bedd7833e8f4801 \ - --hash=sha256:700d68cc30728de1c4c62088a981c6daeaefdf20a0d81995d2c0b7f442c5f88c \ - --hash=sha256:77c82a430cedfbf508d1aa406b2f437363c24fa90c73f577ead0fb5295749b83 \ - --hash=sha256:c1df3929a3666fc5a0c80d60a0c1e6f6ef97c7f6ed2f1b7cf49f3e6f3d4dde15 \ - --hash=sha256:dba8b2dd2cd41cb5f37bfa3f3d82721b8ae10e492944e48ddd90a439227f2893 \ - --hash=sha256:f492540305b15b5591bd7195d61f28946bb071de071cee5d68b6b8414da90fd2 -zope.interface==4.6.0 \ - --hash=sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c \ - --hash=sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b \ - --hash=sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02 \ - --hash=sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f \ - --hash=sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5 \ - --hash=sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375 \ - --hash=sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487 \ - --hash=sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2 \ - --hash=sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0 \ - --hash=sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b \ - --hash=sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63 \ - --hash=sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39 \ - --hash=sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745 \ - --hash=sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc \ - --hash=sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2 \ - --hash=sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa \ - --hash=sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1 \ - --hash=sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc \ - --hash=sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98 \ - --hash=sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97 \ - --hash=sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab \ - --hash=sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127 \ - --hash=sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d \ - --hash=sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe \ - --hash=sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891 \ - --hash=sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1 \ - --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ - --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ - --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 +zope.hookable==5.0.0 \ + --hash=sha256:0992a0dd692003c09fb958e1480cebd1a28f2ef32faa4857d864f3ca8e9d6952 \ + --hash=sha256:0f325838dbac827a1e2ed5d482c1f2656b6844dc96aa098f7727e76395fcd694 \ + --hash=sha256:22a317ba00f61bac99eac1a5e330be7cb8c316275a21269ec58aa396b602af0c \ + --hash=sha256:25531cb5e7b35e8a6d1d6eddef624b9a22ce5dcf8f4448ef0f165acfa8c3fc21 \ + --hash=sha256:30890892652766fc80d11f078aca9a5b8150bef6b88aba23799581a53515c404 \ + --hash=sha256:342d682d93937e5b8c232baffb32a87d5eee605d44f74566657c64a239b7f342 \ + --hash=sha256:46b2fddf1f5aeb526e02b91f7e62afbb9fff4ffd7aafc97cdb00a0d717641567 \ + --hash=sha256:523318ff96df9b8d378d997c00c5d4cbfbff68dc48ff5ee5addabdb697d27528 \ + --hash=sha256:53aa02eb8921d4e667c69d76adeed8fe426e43870c101cb08dcd2f3468aff742 \ + --hash=sha256:62e79e8fdde087cb20822d7874758f5acbedbffaf3c0fbe06309eb8a41ee4e06 \ + --hash=sha256:74bf2f757f7385b56dc3548adae508d8b3ef952d600b4b12b88f7d1706b05dcc \ + --hash=sha256:751ee9d89eb96e00c1d7048da9725ce392a708ed43406416dc5ed61e4d199764 \ + --hash=sha256:7b83bc341e682771fe810b360cd5d9c886a948976aea4b979ff214e10b8b523b \ + --hash=sha256:81eeeb27dbb0ddaed8070daee529f0d1bfe4f74c7351cce2aaca3ea287c4cc32 \ + --hash=sha256:856509191e16930335af4d773c0fc31a17bae8991eb6f167a09d5eddf25b56cc \ + --hash=sha256:8853e81fd07b18fa9193b19e070dc0557848d9945b1d2dac3b7782543458c87d \ + --hash=sha256:94506a732da2832029aecdfe6ea07eb1b70ee06d802fff34e1b3618fe7cdf026 \ + --hash=sha256:95ad874a8cc94e786969215d660143817f745225579bfe318c4676e218d3147c \ + --hash=sha256:9758ec9174966ffe5c499b6c3d149f80aa0a9238020006a2b87c6af5963fcf48 \ + --hash=sha256:a169823e331da939aa7178fc152e65699aeb78957e46c6f80ccb50ee4c3616c2 \ + --hash=sha256:a67878a798f6ca292729a28c2226592b3d000dc6ee7825d31887b553686c7ac7 \ + --hash=sha256:a9a6d9eb2319a09905670810e2de971d6c49013843700b4975e2fc0afe96c8db \ + --hash=sha256:b3e118b58a3d2301960e6f5f25736d92f6b9f861728d3b8c26d69f54d8a157d2 \ + --hash=sha256:ca6705c2a1fb5059a4efbe9f5426be4cdf71b3c9564816916fc7aa7902f19ede \ + --hash=sha256:cf711527c9d4ae72085f137caffb4be74fc007ffb17cd103628c7d5ba17e205f \ + --hash=sha256:d087602a6845ebe9d5a1c5a949fedde2c45f372d77fbce4f7fe44b68b28a1d03 \ + --hash=sha256:d1080e1074ddf75ad6662a9b34626650759c19a9093e1a32a503d37e48da135b \ + --hash=sha256:db9c60368aff2b7e6c47115f3ad9bd6e96aa298b12ed5f8cb13f5673b30be565 \ + --hash=sha256:dbeb127a04473f5a989169eb400b67beb921c749599b77650941c21fe39cb8d9 \ + --hash=sha256:dca336ca3682d869d291d7cd18284f6ff6876e4244eb1821430323056b000e2c \ + --hash=sha256:dd69a9be95346d10c853b6233fcafe3c0315b89424b378f2ad45170d8e161568 \ + --hash=sha256:dd79f8fae5894f1ee0a0042214685f2d039341250c994b825c10a4cd075d80f6 \ + --hash=sha256:e647d850aa1286d98910133cee12bd87c354f7b7bb3f3cd816a62ba7fa2f7007 \ + --hash=sha256:f37a210b5c04b2d4e4bac494ab15b70196f219a1e1649ddca78560757d4278fb \ + --hash=sha256:f67820b6d33a705dc3c1c457156e51686f7b350ff57f2112e1a9a4dad38ec268 \ + --hash=sha256:f68969978ccf0e6123902f7365aae5b7a9e99169d4b9105c47cf28e788116894 \ + --hash=sha256:f717a0b34460ae1ac0064e91b267c0588ac2c098ffd695992e72cd5462d97a67 \ + --hash=sha256:f9d58ccec8684ca276d5a4e7b0dfacca028336300a8f715d616d9f0ce9ae8096 \ + --hash=sha256:fcc3513a54e656067cbf7b98bab0d6b9534b9eabc666d1f78aad6acdf0962736 +zope.interface==4.7.1 \ + --hash=sha256:048b16ac882a05bc7ef534e8b9f15c9d7a6c190e24e8938a19b7617af4ed854a \ + --hash=sha256:05816cf8e7407cf62f2ec95c0a5d69ec4fa5741d9ccd10db9f21691916a9a098 \ + --hash=sha256:065d6a1ac89d35445168813bed45048ed4e67a4cdfc5a68fdb626a770378869f \ + --hash=sha256:14157421f4121a57625002cc4f48ac7521ea238d697c4a4459a884b62132b977 \ + --hash=sha256:18dc895945694f397a0be86be760ff664b790f95d8e7752d5bab80284ff9105d \ + --hash=sha256:1962c9f838bd6ae4075d0014f72697510daefc7e1c7e48b2607df0b6e157989c \ + --hash=sha256:1a67408cacd198c7e6274a19920bb4568d56459e659e23c4915528686ac1763a \ + --hash=sha256:21bf781076dd616bd07cf0223f79d61ab4f45176076f90bc2890e18c48195da4 \ + --hash=sha256:21c0a5d98650aebb84efa16ce2c8df1a46bdc4fe8a9e33237d0ca0b23f416ead \ + --hash=sha256:23cfeea25d1e42ff3bf4f9a0c31e9d5950aa9e7c4b12f0c4bd086f378f7b7a71 \ + --hash=sha256:24b6fce1fb71abf9f4093e3259084efcc0ef479f89356757780685bd2b06ef37 \ + --hash=sha256:24f84ce24eb6b5fcdcb38ad9761524f1ae96f7126abb5e597f8a3973d9921409 \ + --hash=sha256:25e0ef4a824017809d6d8b0ce4ab3288594ba283e4d4f94d8cfb81d73ed65114 \ + --hash=sha256:2e8fdd625e9aba31228e7ddbc36bad5c38dc3ee99a86aa420f89a290bd987ce9 \ + --hash=sha256:2f3bc2f49b67b1bea82b942d25bc958d4f4ea6709b411cb2b6b9718adf7914ce \ + --hash=sha256:35d24be9d04d50da3a6f4d61de028c1dd087045385a0ff374d93ef85af61b584 \ + --hash=sha256:35dbe4e8c73003dff40dfaeb15902910a4360699375e7b47d3c909a83ff27cd0 \ + --hash=sha256:3dfce831b824ab5cf446ed0c350b793ac6fa5fe33b984305cb4c966a86a8fb79 \ + --hash=sha256:3f7866365df5a36a7b8de8056cd1c605648f56f9a226d918ed84c85d25e8d55f \ + --hash=sha256:455cc8c01de3bac6f9c223967cea41f4449f58b4c2e724ec8177382ddd183ab4 \ + --hash=sha256:4bb937e998be9d5e345f486693e477ba79e4344674484001a0b646be1d530487 \ + --hash=sha256:52303a20902ca0888dfb83230ca3ee6fbe63c0ad1dd60aa0bba7958ccff454d8 \ + --hash=sha256:6e0a897d4e09859cc80c6a16a29697406ead752292ace17f1805126a4f63c838 \ + --hash=sha256:6e1816e7c10966330d77af45f77501f9a68818c065dec0ad11d22b50a0e212e7 \ + --hash=sha256:73b5921c5c6ce3358c836461b5470bf675601c96d5e5d8f2a446951470614f67 \ + --hash=sha256:8093cd45cdb5f6c8591cfd1af03d32b32965b0f79b94684cd0c9afdf841982bb \ + --hash=sha256:864b4a94b60db301899cf373579fd9ef92edddbf0fb2cd5ae99f53ef423ccc56 \ + --hash=sha256:8a27b4d3ea9c6d086ce8e7cdb3e8d319b6752e2a03238a388ccc83ccbe165f50 \ + --hash=sha256:91b847969d4784abd855165a2d163f72ac1e58e6dce09a5e46c20e58f19cc96d \ + --hash=sha256:b47b1028be4758c3167e474884ccc079b94835f058984b15c145966c4df64d27 \ + --hash=sha256:b68814a322835d8ad671b7acc23a3b2acecba527bb14f4b53fc925f8a27e44d8 \ + --hash=sha256:bcb50a032c3b6ec7fb281b3a83d2b31ab5246c5b119588725b1350d3a1d9f6a3 \ + --hash=sha256:c56db7d10b25ce8918b6aec6b08ac401842b47e6c136773bfb3b590753f7fb67 \ + --hash=sha256:c94b77a13d4f47883e4f97f9fa00f5feadd38af3e6b3c7be45cfdb0a14c7149b \ + --hash=sha256:db381f6fdaef483ad435f778086ccc4890120aff8df2ba5cfeeac24d280b3145 \ + --hash=sha256:e6487d01c8b7ed86af30ea141fcc4f93f8a7dde26f94177c1ad637c353bd5c07 \ + --hash=sha256:e86923fa728dfba39c5bb6046a450bd4eec8ad949ac404eca728cfce320d1732 \ + --hash=sha256:f6ca36dc1e9eeb46d779869c60001b3065fb670b5775c51421c099ea2a77c3c9 \ + --hash=sha256:fb62f2cbe790a50d95593fb40e8cca261c31a2f5637455ea39440d6457c2ba25 zope.proxy==4.3.3 \ --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ @@ -1503,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 1a030eb47..488d0bf2e 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl4eDcYACgkQTRfJlc2X -dfIAiQgAufTpgNvnHKoLQLwWf3GbjLQYWc3w1zRbGUMjghS/rS1yuf7RE/IPItET -ocIuIE36ogjvgnRuI0OOu3yJ+jxe41u3ToPb0ehNhINd+3rXsDhzwJDPjFdOiq98 -NoW9wQE9AHSfKEEVprckuZe2XmNLsYbBfa9THFULYIlnqAewtercXXx0eKaMG9+d -aRaD+LZXANx7IV6XnI9jfdKRuldHDvYp1TdvrRWBAVHid8j44c3P0pSvzf0YKGbx -xIty/w0zQFIWCfqPdK7/R2EHbEyR0SdI00a1Va1x7P8JGf7kDyLXl+Y9Yth7/uHA -osivJCpSrtAEbvMXojnL7u7kq3b37Q== -=Une9 +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl456ZoACgkQTRfJlc2X +dfJx8wf/addMw4kUlwu6poHqLvsifZzHAESgvq+qybgFvl5yTh2U+99PGBgxRYx+ +bENIWBi6+XB+CiVuLzIXWw/VkXh+za99orRkkVK9PI33Xr7jBMZo5Oa3JviYjl3X +PcfjioRQCD+a9Tf9RO25LXQmxn87Ql9x3nxJuk//YeSpuImFmYjIBPE4n/LPEf7z +8WHU4oxxa/bgqGCPgv6O7ZBw7ipd3g+VHcDZcNQMP4tWYb6m7x/nN61yirid7q3M +uqQ1lbitN48ISyru6xPyE6WGTvfl1SIQd21FNRETpcoesx+MTv3ApWT4dqXjZvaX +FeM55IS65e7ci6yLV9qdAbqGKzhX0Q== +=uLcV -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 9d2013cd1..cea58e2cb 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.2.0.dev0" +LE_AUTO_VERSION="1.2.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index bae77d59b..fefc81b37 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 67a33390b..eb9027edb 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==1.1.0 \ - --hash=sha256:66a5cab9267349941604c2c98082bfef85877653c023fc324b1c3869fb16add6 \ - --hash=sha256:46e93661a0db53f416c0f5476d8d2e62bc7259b7660dd983453b85df9ef6e8b8 -acme==1.1.0 \ - --hash=sha256:11b9beba706fb8f652c8910d46dd1939d670cac8169f3c66c18c080ed3353e71 \ - --hash=sha256:c305a20eeb9cb02240347703d497891c13d43a47c794fa100d4dbb479a5370d9 -certbot-apache==1.1.0 \ - --hash=sha256:9c847ff223c2e465e241c78d22f97cee77d5e551df608bed06c55f8627f4cbd2 \ - --hash=sha256:05e84dfe96b72582cde97c490977d8e2d33d440c927a320debb4cf287f6fadcc -certbot-nginx==1.1.0 \ - --hash=sha256:bf06fa2f5059f0fdb7d352c8739e1ed0830db4f0d89e812dab4f081bda6ec7d6 \ - --hash=sha256:0a80ecbd2a30f3757c7652cabfff854ca07873b1cf02ebbe1892786c3b3a5874 +certbot==1.2.0 \ + --hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \ + --hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c +acme==1.2.0 \ + --hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \ + --hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab +certbot-apache==1.2.0 \ + --hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \ + --hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3 +certbot-nginx==1.2.0 \ + --hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \ + --hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db -- cgit v1.2.3 From 97ae63efa6dd24b239e641d3b73a80f7de9b8cf2 Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:03 -0800 Subject: Add contents to certbot/CHANGELOG.md for next version --- certbot/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index a14cea76c..3e3fda49f 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -2,6 +2,22 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). +## 1.3.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + ## 1.2.0 - 2020-02-04 ### Added -- cgit v1.2.3 From 6a4b610269be13dd4227e03ff91c5d00188eb78e Mon Sep 17 00:00:00 2001 From: Erica Portnoy Date: Tue, 4 Feb 2020 14:01:04 -0800 Subject: Bump version to 1.3.0 --- acme/setup.py | 2 +- certbot-apache/setup.py | 2 +- certbot-compatibility-test/setup.py | 2 +- certbot-dns-cloudflare/setup.py | 2 +- certbot-dns-cloudxns/setup.py | 2 +- certbot-dns-digitalocean/setup.py | 2 +- certbot-dns-dnsimple/setup.py | 2 +- certbot-dns-dnsmadeeasy/setup.py | 2 +- certbot-dns-gehirn/setup.py | 2 +- certbot-dns-google/setup.py | 2 +- certbot-dns-linode/setup.py | 2 +- certbot-dns-luadns/setup.py | 2 +- certbot-dns-nsone/setup.py | 2 +- certbot-dns-ovh/setup.py | 2 +- certbot-dns-rfc2136/setup.py | 2 +- certbot-dns-route53/setup.py | 2 +- certbot-dns-sakuracloud/setup.py | 2 +- certbot-nginx/setup.py | 2 +- certbot/certbot/__init__.py | 2 +- letsencrypt-auto-source/letsencrypt-auto | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 58d2c12ce..0e11779ba 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 8a887b444..f9b85008b 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 891bd8e1c..1dbcefa75 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -3,7 +3,7 @@ import sys from setuptools import find_packages from setuptools import setup -version = '1.2.0' +version = '1.3.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index 706113d45..9376bc1c4 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index c290c5599..4e99ff5ff 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index fac989ae6..9c9d1717c 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index fe29a7137..9cde6214c 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index a36c74f9d..adaba6851 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 1ef15c85f..a849cef45 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index f2b99ba15..51d5b8a3f 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index eead76cc8..e7e91b929 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index f8484e9f5..ea64f79a2 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 7a4e21a8e..d6bedca1c 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index dbecc3f7f..8f5b052a2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 8f2f09f27..fa51c2108 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 1d30e5b40..f25e348ff 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index d0403ac87..8df2320ba 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index dc50f5cb3..3b75a3424 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages from setuptools import setup from setuptools.command.test import test as TestCommand -version = '1.2.0' +version = '1.3.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/certbot/__init__.py b/certbot/certbot/__init__.py index ab972d9e3..84ade6b08 100644 --- a/certbot/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '1.2.0' +__version__ = '1.3.0.dev0' diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index cea58e2cb..e2813853b 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/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="1.2.0" +LE_AUTO_VERSION="1.3.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates -- cgit v1.2.3 From 7b35abbcb42845a5c3a889884f20345edf55f640 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 5 Feb 2020 23:12:29 +0100 Subject: Windows installer integration tests (#7724) As discussed in #7539, we need proper tests of the Windows installer itself in order to variety that all the logic contained in a production-grade runtime of Certbot on Windows is correctly setup by each version of the installer, and so for a variety of Windows OSes. This PR handles this requirement. The new `windows_installer_integration_tests` module in `certbot-ci` will: * run the given Windows installer * check that Certbot is properly installed and working * check that the scheduled renew task is set up * check that the scheduled task actually launch the Certbot renew logic The Windows nightly tests are updated accordingly, in order to have the tests run on Windows Server 2012R2, 2016 and 2019. These tests will evolve as we add more logic on the installer. * Configure an integration test testing the windows installer * Write the test module * Configurable installer path, prepare azure pipelines * Fix option * Update test_main.py * Add confirmation for this destructive test * Use regex to validate certbot --version output * Explicit dependency on a log output * Use an exception to ask confirmation * Use --allow-persistent-changes --- .azure-pipelines/templates/installer-tests.yml | 16 +++--- .../__init__.py | 0 .../conftest.py | 38 ++++++++++++++ .../test_main.py | 61 ++++++++++++++++++++++ certbot/certbot/_internal/renewal.py | 3 ++ 5 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 certbot-ci/windows_installer_integration_tests/__init__.py create mode 100644 certbot-ci/windows_installer_integration_tests/conftest.py create mode 100644 certbot-ci/windows_installer_integration_tests/test_main.py diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml index f1ccd92ed..6d5672339 100644 --- a/.azure-pipelines/templates/installer-tests.yml +++ b/.azure-pipelines/templates/installer-tests.yml @@ -33,22 +33,24 @@ jobs: pool: vmImage: $(imageName) steps: + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe + displayName: Get Python + - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 + displayName: Install Python - task: DownloadPipelineArtifact@2 inputs: artifact: windows-installer path: $(Build.SourcesDirectory)/bin displayName: Retrieve Windows installer - - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S - displayName: Install Certbot - - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe - displayName: Get Python - - script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3 - displayName: Install Python - script: | py -3 -m venv venv venv\Scripts\python tools\pip_install.py -e certbot-ci displayName: Prepare Certbot-CI + - script: | + set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% + venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe + displayName: Run windows installer integration tests - script: | set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 - displayName: Run integration tests + displayName: Run certbot integration tests diff --git a/certbot-ci/windows_installer_integration_tests/__init__.py b/certbot-ci/windows_installer_integration_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/certbot-ci/windows_installer_integration_tests/conftest.py b/certbot-ci/windows_installer_integration_tests/conftest.py new file mode 100644 index 000000000..e36654f90 --- /dev/null +++ b/certbot-ci/windows_installer_integration_tests/conftest.py @@ -0,0 +1,38 @@ +""" +General conftest for pytest execution of all integration tests lying +in the window_installer_integration tests package. +As stated by pytest documentation, conftest module is used to set on +for a directory a specific configuration using built-in pytest hooks. + +See https://docs.pytest.org/en/latest/reference.html#hook-reference +""" +from __future__ import print_function +import os + +import pytest + +ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + + +def pytest_addoption(parser): + """ + Standard pytest hook to add options to the pytest parser. + :param parser: current pytest parser that will be used on the CLI + """ + parser.addoption('--installer-path', + default=os.path.join(ROOT_PATH, 'windows-installer', 'build', + 'nsis', 'certbot-beta-installer-win32.exe'), + help='set the path of the windows installer to use, default to ' + 'CERTBOT_ROOT_PATH\\windows-installer\\build\\nsis\\certbot-beta-installer-win32.exe') + parser.addoption('--allow-persistent-changes', action='store_true', + help='needs to be set, and confirm that the test will make persistent changes on this machine') + + +def pytest_configure(config): + """ + Standard pytest hook used to add a configuration logic for each node of a pytest run. + :param config: the current pytest configuration + """ + if not config.option.allow_persistent_changes: + raise RuntimeError('This integration test would install Certbot on your machine. ' + 'Please run it again with the `--allow-persistent-changes` flag set to acknowledge.') diff --git a/certbot-ci/windows_installer_integration_tests/test_main.py b/certbot-ci/windows_installer_integration_tests/test_main.py new file mode 100644 index 000000000..c8c347aa8 --- /dev/null +++ b/certbot-ci/windows_installer_integration_tests/test_main.py @@ -0,0 +1,61 @@ +import os +import time +import unittest +import subprocess +import re + + +@unittest.skipIf(os.name != 'nt', reason='Windows installer tests must be run on Windows.') +def test_it(request): + try: + subprocess.check_call(['certbot', '--version']) + except (subprocess.CalledProcessError, OSError): + pass + else: + raise AssertionError('Expect certbot to not be available in the PATH.') + + try: + # Install certbot + subprocess.check_call([request.config.option.installer_path, '/S']) + + # Assert certbot is installed and runnable + output = subprocess.check_output(['certbot', '--version'], universal_newlines=True) + assert re.match(r'^certbot \d+\.\d+\.\d+.*$', output), 'Flag --version does not output a version.' + + # Assert renew task is installed and ready + output = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', capture_stdout=True) + assert output.strip() == 'Ready' + + # Assert renew task is working + now = time.time() + _ps('Start-ScheduledTask -TaskName "Certbot Renew Task"') + + status = 'Running' + while status != 'Ready': + status = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', capture_stdout=True).strip() + time.sleep(1) + + log_path = os.path.join('C:\\', 'Certbot', 'log', 'letsencrypt.log') + + modification_time = os.path.getmtime(log_path) + assert now < modification_time, 'Certbot log file has not been modified by the renew task.' + + with open(log_path) as file_h: + data = file_h.read() + assert 'no renewal failures' in data, 'Renew task did not execute properly.' + + finally: + # Sadly this command cannot work in non interactive mode: uninstaller will ask explicitly permission in an UAC prompt + # print('Uninstalling Certbot ...') + # uninstall_path = _ps('(gci "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"' + # ' | foreach { gp $_.PSPath }' + # ' | ? { $_ -match "Certbot" }' + # ' | select UninstallString)' + # '.UninstallString', capture_stdout=True) + # subprocess.check_call([uninstall_path, '/S']) + pass + + +def _ps(powershell_str, capture_stdout=False): + fn = subprocess.check_output if capture_stdout else subprocess.check_call + return fn(['powershell.exe', '-c', powershell_str], universal_newlines=True) diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 930f6c1a9..bf30404f5 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -471,4 +471,7 @@ def handle_renewal_request(config): if renew_failures or parse_failures: raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( len(renew_failures), len(parse_failures))) + + # Windows installer integration tests rely on handle_renewal_request behavior here. + # If the text below changes, these tests will need to be updated accordingly. logger.debug("no renewal failures") -- cgit v1.2.3 From cc764b65c1bb3371389ba2ef7c20176d10b58538 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 5 Feb 2020 14:37:39 -0800 Subject: Set recreate = true in tox.ini. (#7746) Fixes #7745. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 3a31558d8..6d9814192 100644 --- a/tox.ini +++ b/tox.ini @@ -67,6 +67,9 @@ passenv = commands = {[base]install_and_test} {[base]all_packages} python tests/lock_test.py +# We always recreate the virtual environment to avoid problems like +# https://github.com/certbot/certbot/issues/7745. +recreate = true setenv = PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:--numprocesses auto} PYTHONHASHSEED = 0 -- cgit v1.2.3 From 7da5196206b33d5593bd15cd1dcce4d790db7e6d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 5 Feb 2020 14:49:01 -0800 Subject: Add triggers for only a single CI system (#7748) * Configure travis-test to only run on Travis. * Configure azure-test to only run on Azure. * Add docs and comments to keep it up-to-date. --- .azure-pipelines/advanced.yml | 3 +++ .travis.yml | 12 +++++++----- certbot/docs/contributing.rst | 10 ++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml index 44cdf5d54..dda7f9bfd 100644 --- a/.azure-pipelines/advanced.yml +++ b/.azure-pipelines/advanced.yml @@ -1,5 +1,8 @@ # Advanced pipeline for isolated checks and release purpose trigger: + # When changing these triggers, please ensure the documentation under + # "Running tests in CI" is still correct. + - azure-test-* - test-* - '*.x' pr: diff --git a/.travis.yml b/.travis.yml index 0ed6b47af..6c5147603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,19 @@ before_script: - export TOX_TESTENV_PASSENV=TRAVIS # Only build pushes to the master branch, PRs, and branches beginning with -# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of -# simultaneous Travis runs, which speeds turnaround time on review since there -# is a cap of on the number of simultaneous runs. +# `test-`, `travis-test-`, or of the form `digit(s).digit(s).x`. This reduces +# the number of simultaneous Travis runs, which speeds turnaround time on +# review since there is a cap of on the number of simultaneous runs. branches: + # When changing these branches, please ensure the documentation under + # "Running tests in CI" is still correct. only: # apache-parser-v2 is a temporary branch for doing work related to # rewriting the parser in the Apache plugin. - apache-parser-v2 - master - /^\d+\.\d+\.x$/ - - /^test-.*$/ + - /^(travis-)?test-.*$/ # Jobs for the main test suite are always executed (including on PRs) except for pushes on master. not-on-master: ¬-on-master @@ -274,7 +276,7 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux' notifications: email: false irc: - if: NOT branch =~ ^test-.*$ + if: NOT branch =~ ^(travis-)?test-.*$ channels: # This is set to a secure variable to prevent forks from sending # notifications. This value was created by installing diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index c13005a9d..25d832761 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -201,6 +201,16 @@ using an HTTP-01 challenge on a machine with Python 3: certbot_test certonly --standalone -d test.example.com # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C +Running tests in CI +~~~~~~~~~~~~~~~~~~~ + +Certbot uses both Azure Pipelines and Travis to run continuous integration +tests. If you are using our Azure and Travis setup, a branch whose name starts +with `test-` will run all Azure and Travis tests on that branch. If the branch +name starts with `azure-test-`, it will run all of our Azure tests and none of +our Travis tests. If the branch stats with `travis-test-`, only our Travis +tests will be run. + Code components and layout ========================== -- cgit v1.2.3